diff --git a/README.md b/README.md index 10600ca66..17672c5df 100644 --- a/README.md +++ b/README.md @@ -110,3 +110,16 @@ http://localhost:3000/ - 里程碑 ![](docs/figs/milestone.png) + +### API +- [API](api_document.md) + +## 贡献代码 + +1. Fork 项目 +2. 创建本地分支(git checkout -b my-new-feature) +3. 提交更改 (git commit -am 'Add some feature') +4. 推送到分支 (git push origin my-new-feature) +5. 向源项目的 **develop** 分支发起 Pull Request + +## License diff --git a/api_document.md b/api_document.md index 23a7c022b..50a45573c 100644 --- a/api_document.md +++ b/api_document.md @@ -7,7 +7,7 @@ ### 开发API服务地址: -**https://testgitea.trustie.net/** +**** 响应状态说明: @@ -26,7 +26,7 @@ POST accounts/remote_register ``` *示例* -``` +```bash curl -X POST \ -d "email=2456233122@qq.com" \ -d "password=djs_D_00001" \ @@ -54,7 +54,7 @@ http://localhost:3000/api/accounts/remote_register | jq 返回值 -``` +```json { "status": 0, "message": "success", @@ -71,7 +71,7 @@ http://localhost:3000/api/accounts/remote_register | jq GET api/users/me ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/users/me | jq ``` @@ -87,7 +87,7 @@ curl -X GET http://localhost:3000/api/users/me | jq 返回值 -``` +```json { "username": "18816895620", "login": "18816895620", @@ -103,7 +103,7 @@ curl -X GET http://localhost:3000/api/users/me | jq GET api/users/list ``` *示例* -``` +```bash curl -X GET \ -d "limit=10" \ -d "search=18816895620" @@ -130,7 +130,7 @@ http://localhost:3000/api/users/list | jq |-- image_url |string|用户头像| 返回值 -``` +```json { "total_count": 1, "users": [ @@ -150,7 +150,7 @@ http://localhost:3000/api/users/list | jq GET api/project_categories ``` *示例* -``` +```bash curl -X GET \ -d "name=大数据" \ http://localhost:3000/api/project_categories/ | jq @@ -172,7 +172,7 @@ http://localhost:3000/api/project_categories/ | jq 返回值 -``` +```json { "project_categories": [ { @@ -189,7 +189,7 @@ http://localhost:3000/api/project_categories/ | jq GET api/project_languages ``` *示例* -``` +```bash curl -X GET \ -d "name=Ruby" \ http://localhost:3000/api/project_languages/ | jq @@ -211,7 +211,7 @@ http://localhost:3000/api/project_languages/ | jq 返回值 -``` +```json { "project_languages": [ { @@ -228,7 +228,7 @@ http://localhost:3000/api/project_languages/ | jq GET api/ignores ``` *示例* -``` +```bash curl -X GET \ -d "name=Ada" \ http://localhost:3000/api/ignores/ | jq @@ -250,7 +250,7 @@ http://localhost:3000/api/ignores/ | jq 返回值 -``` +```json { "ignores": [ { @@ -267,7 +267,7 @@ http://localhost:3000/api/ignores/ | jq GET api/licenses ``` *示例* -``` +```bash curl -X GET \ -d "name=AFL" \ http://localhost:3000/api/licenses/ | jq @@ -289,7 +289,7 @@ http://localhost:3000/api/licenses/ | jq 返回值 -``` +```json { "licenses": [ { @@ -322,7 +322,7 @@ http://localhost:3000/api/licenses/ | jq POST api/projects ``` *示例* -``` +```bash curl -X POST \ -d "user_id=36401" \ -d "name=hnfl_demo" \ @@ -358,7 +358,7 @@ http://localhost:3000/api/projects/ | jq 返回值 -``` +```json { "id": 3240, "name": "好项目" @@ -371,7 +371,7 @@ http://localhost:3000/api/projects/ | jq POST api/projects/migrate ``` *示例* -``` +```bash curl -X POST \ -d "user_id=36408" \ -d "clone_addr=https://gitea.com/mx8090alex/golden.git" \ @@ -407,7 +407,7 @@ http://localhost:3000/api/projects/migrate.json | jq 返回值 -``` +```json { "id": 3263, "name": "ni项目" @@ -420,7 +420,7 @@ http://localhost:3000/api/projects/migrate.json | jq POST api/repositories/:id/sync_mirror ``` *示例* -``` +```bash curl -X POST http://localhost:3000/api/repositories/1244/sync_mirror | jq ``` *请求参数说明:* @@ -439,7 +439,7 @@ curl -X POST http://localhost:3000/api/repositories/1244/sync_mirror | jq 返回值 -``` +```json { "status": 0, "message": "success" @@ -453,7 +453,7 @@ curl -X POST http://localhost:3000/api/repositories/1244/sync_mirror | jq GET /api/:namespace_id/:id ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/jasder/jasder_test | jq ``` *请求参数说明:* @@ -478,7 +478,7 @@ curl -X GET http://localhost:3000/api/jasder/jasder_test | jq 返回值 -``` +```json { "name": "ni项目", "identifier": "mirror_demo", @@ -495,7 +495,7 @@ curl -X GET http://localhost:3000/api/jasder/jasder_test | jq GET /api/:namespace_id/:id/simple ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/jasder/jasder_test/simple | jq ``` *请求参数说明:* @@ -519,7 +519,7 @@ curl -X GET http://localhost:3000/api/jasder/jasder_test/simple | jq 返回值 -``` +```json { "identifier": "jasder_test", "name": "jasder的测试项目", @@ -539,7 +539,7 @@ curl -X GET http://localhost:3000/api/jasder/jasder_test/simple | jq GET /api/repositories/:id/edit.json ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/repositories/:id/edit.json | jq ``` @@ -558,7 +558,7 @@ curl -X GET http://localhost:3000/api/repositories/:id/edit.json | jq 返回值 -``` +```json { "identifier": "mirror_demo", "project_id": 3263, @@ -577,7 +577,7 @@ curl -X GET http://localhost:3000/api/repositories/:id/edit.json | jq PATCH api/projects/:id ``` *示例* -``` +```bash curl -X PATCH \ -d "name=hnfl_demo" \ -d "description=my first project" \ @@ -613,7 +613,7 @@ http://localhost:3000/api/projects/3263.json | jq 返回值 -``` +```json { "id": 3263, "identifier": "mirror_demo", @@ -631,7 +631,7 @@ http://localhost:3000/api/projects/3263.json | jq DELETE api/projects/:id ``` *示例* -``` +```bash curl -X DELETE http://localhost:3000/api/projects/3263.json | jq ``` @@ -652,7 +652,7 @@ curl -X DELETE http://localhost:3000/api/projects/3263.json | jq 返回值 -``` +```json { "status": 0, "message": "success" @@ -665,7 +665,7 @@ curl -X DELETE http://localhost:3000/api/projects/3263.json | jq POST api/projects/:id/members ``` *示例* -``` +```bash curl -X POST \ -d "user_id=36406" \ http://localhost:3000/api/projects/3297/members | jq @@ -687,7 +687,7 @@ http://localhost:3000/api/projects/3297/members | jq 返回值 -``` +```json { "status": 0, "message": "success" @@ -700,7 +700,7 @@ http://localhost:3000/api/projects/3297/members | jq DELETE api/projects/:id/members/remove ``` *示例* -``` +```bash curl -X DELETE \ -d "user_id=36400" \ http://localhost:3000/api/projects/3263/members/remove | jq @@ -722,7 +722,7 @@ http://localhost:3000/api/projects/3263/members/remove | jq 返回值 -``` +```json { "status": 0, "message": "success" @@ -735,7 +735,7 @@ http://localhost:3000/api/projects/3263/members/remove | jq PUT api/projects/:id/members/change_role ``` *示例* -``` +```bash curl -X PUT \ -d "user_id=36400" \ -d "role=Developer" \ @@ -759,7 +759,7 @@ http://localhost:3000/api/projects/3263/members/change_role | jq 返回值 -``` +```json { "status": 0, "message": "success" @@ -773,7 +773,7 @@ http://localhost:3000/api/projects/3263/members/change_role | jq GET api/projects/:id/members ``` *示例* -``` +```bash curl -X GET \ -d "page=1" \ -d "limit=5" \ @@ -803,7 +803,7 @@ http://localhost:3000/api/projects/3263/members | jq 返回值 -``` +```json { "total_count": 2, "members": [ @@ -833,7 +833,7 @@ http://localhost:3000/api/projects/3263/members | jq POST /api/projects/:project_id/forks ``` *示例* -``` +```bash curl -X POST http://localhost:3000/api/projects/3297/forks | jq ``` *请求参数说明:* @@ -852,7 +852,7 @@ curl -X POST http://localhost:3000/api/projects/3297/forks | jq 返回值 -``` +```json { "id": 3290, "identifier": "newadm" @@ -865,7 +865,7 @@ curl -X POST http://localhost:3000/api/projects/3297/forks | jq POST /api/:namespace_id/:project_id/repository/entries ``` *示例* -``` +```bash curl -X GET \ -d "ref=develop" \ http://localhost:3000//api/jasder/jasder_test/repository/entries | jq @@ -960,7 +960,7 @@ http://localhost:3000//api/jasder/jasder_test/repository/entries | jq GET /api/repositories/:id/sub_entries ``` *示例* -``` +```bash curl -X GET \ -d "ref=master" \ -d "filepath=test1_create_file.rb" \ @@ -1039,7 +1039,7 @@ http://localhost:3000/api/repositories/87/sub_entries.json | jq GET api/project_categories/group_list ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/project_categories/group_list | jq ``` @@ -1053,7 +1053,7 @@ curl -X GET http://localhost:3000/api/project_categories/group_list | jq 返回值 -``` +```json [ { "id": 1, @@ -1079,7 +1079,7 @@ curl -X GET http://localhost:3000/api/project_categories/group_list | jq GET api/projects/group_type_list ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/projects/group_type_list | jq ``` @@ -1093,7 +1093,7 @@ curl -X GET http://localhost:3000/api/projects/group_type_list | jq 返回值 -``` +```json [ { "project_type": "common", @@ -1114,7 +1114,7 @@ curl -X GET http://localhost:3000/api/projects/group_type_list | jq GET api/projects ``` *示例* -``` +```bash curl -X GET \ -d "page=1" \ -d "limit=5" \ @@ -1159,7 +1159,7 @@ http://localhost:3000/api/projects | jq 返回值 -``` +```json { "total_count": 3096, "projects": [ @@ -1288,7 +1288,7 @@ http://localhost:3000/api/projects | jq GET api/projects/recommend ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/projects/recommend | jq ``` @@ -1318,7 +1318,7 @@ http://localhost:3000/api/projects/recommend | jq 返回值 -``` +```json [ { "id": 20, @@ -1348,7 +1348,7 @@ GET api/:namespace_id/:id/about ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/:jason/forgeplus/about | jq ``` @@ -1371,7 +1371,7 @@ http://localhost:3000/api/:jason/forgeplus/about | jq 返回值 -``` +```json { "content": "", "identifier": "forgeplus", @@ -1389,7 +1389,7 @@ POST api/:namespace_id/:id/about ``` *示例* -``` +```bash curl -X POST \ -d "content=内容" \ -d "attachment_ids=[1, 2, 2]" \ @@ -1415,7 +1415,7 @@ http://localhost:3000/api/:jasder/forgeplus/about | jq |-- name |string|用户名,也是用户标识| 返回值 -``` +```json { "content": "", "identifier": "forgeplus", @@ -1432,7 +1432,7 @@ http://localhost:3000/api/:jasder/forgeplus/about | jq GET /api/:namespace_id/:id/branches ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/jasder/jasder_test/branches | jq ``` *请求参数说明:* @@ -1464,7 +1464,7 @@ curl -X GET http://localhost:3000/api/jasder/jasder_test/branches | jq 返回值 -``` +```json [ { "name": "develop", @@ -1513,7 +1513,7 @@ curl -X GET http://localhost:3000/api/jasder/jasder_test/branches | jq GET /api/repositories/:id/tags ``` *示例* -``` +```bash curl -X GET \ -d "limit=20" \ -d "page=1" \ @@ -1550,7 +1550,7 @@ http://localhost:3000/api/repositories/5836/tags.json | jq 返回值 -``` +```json [ { "name": "develop", @@ -1599,7 +1599,7 @@ http://localhost:3000/api/repositories/5836/tags.json | jq GET /api/:namespace_id/:project_id/repository ``` *示例* -``` +```bash curl -X GET \ http://192.168.2.230:3000/api/jasder/forgeplus/repository | jq ``` @@ -1645,7 +1645,7 @@ http://192.168.2.230:3000/api/jasder/forgeplus/repository | jq 返回值 -``` +```json { "identifier": "mirror_demo", "project_id": 3263, @@ -1683,7 +1683,7 @@ http://192.168.2.230:3000/api/jasder/forgeplus/repository | jq GET /api/repositories/:id/commits ``` *示例* -``` +```bash curl -X GET \ -d "sha=develop" \ -d "page=1" \ @@ -1714,7 +1714,7 @@ http://localhost:3000/api/repositories/89/commits.json | jq 返回值 -``` +```json { "total_count": 63, "commits": [ @@ -1748,7 +1748,7 @@ http://localhost:3000/api/repositories/89/commits.json | jq GET /api/:owner/:repo/commits/:sha ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/jasder/repo/commits/b0c4a4a1487d53acebf2addc544b29938cad12df.json | jq ``` @@ -1847,7 +1847,7 @@ http://localhost:3000/api/jasder/repo/commits/b0c4a4a1487d53acebf2addc544b29938c POST /api/projects/:id/praise_tread/like ``` *示例* -``` +```bash curl -X POST http://localhost:3000/api/projects/3263/praise_tread/like | jq ``` *请求参数说明:* @@ -1864,7 +1864,7 @@ curl -X POST http://localhost:3000/api/projects/3263/praise_tread/like | jq 返回值 -``` +```json { "status": 0, "message": "success" @@ -1877,7 +1877,7 @@ curl -X POST http://localhost:3000/api/projects/3263/praise_tread/like | jq DELETE /api/projects/:id/praise_tread/unlike ``` *示例* -``` +```bash curl -X DELETE http://localhost:3000/api/projects/3263/praise_tread/unlike | jq ``` *请求参数说明:* @@ -1895,7 +1895,7 @@ curl -X DELETE http://localhost:3000/api/projects/3263/praise_tread/unlike | jq 返回值 -``` +```json { "status": 0, "message": "success" @@ -1908,7 +1908,7 @@ curl -X DELETE http://localhost:3000/api/projects/3263/praise_tread/unlike | jq GET /api/projects/:id/praise_tread/check_like ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/projects/3263/praise_tread/check_like | jq ``` *请求参数说明:* @@ -1926,7 +1926,7 @@ curl -X GET http://localhost:3000/api/projects/3263/praise_tread/check_like | jq 返回值 -``` +```json { "status": 0, "message": "success" @@ -1939,7 +1939,7 @@ curl -X GET http://localhost:3000/api/projects/3263/praise_tread/check_like | jq GET /api/projects/:id/praise_tread ``` *示例* -``` +```bash curl -X GET \ -d "page=1" \ -d "limit=5" \ @@ -1967,7 +1967,7 @@ http://localhost:3000/api/projects/3263/praise_tread | jq 返回值 -``` +```json { "total_count": 1, "praises": [ @@ -1986,7 +1986,7 @@ http://localhost:3000/api/projects/3263/praise_tread | jq POST /api/projects/:id/watchers/follow ``` *示例* -``` +```bash curl -X POST http://localhost:3000/api/projects/3263/watchers/follow | jq ``` *请求参数说明:* @@ -2004,7 +2004,7 @@ curl -X POST http://localhost:3000/api/projects/3263/watchers/follow | jq 返回值 -``` +```json { "status": 0, "message": "响应成功" @@ -2017,7 +2017,7 @@ curl -X POST http://localhost:3000/api/projects/3263/watchers/follow | jq DELETE /api/projects/:id/watchers/unfollow ``` *示例* -``` +```bash curl -X DELETE http://localhost:3000//api/projects/3263/watchers/unfollow | jq ``` *请求参数说明:* @@ -2035,7 +2035,7 @@ curl -X DELETE http://localhost:3000//api/projects/3263/watchers/unfollow | jq 返回值 -``` +```json { "status": 0, "message": "响应成功" @@ -2048,7 +2048,7 @@ curl -X DELETE http://localhost:3000//api/projects/3263/watchers/unfollow | jq GET /api/projects/:id/watchers/check_watch ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/projects/3263/watchers/check_watch | jq ``` *请求参数说明:* @@ -2066,7 +2066,7 @@ curl -X GET http://localhost:3000/api/projects/3263/watchers/check_watch | jq 返回值 -``` +```json { "status": 0, "message": "success" @@ -2079,7 +2079,7 @@ curl -X GET http://localhost:3000/api/projects/3263/watchers/check_watch | jq GET /api/projects/:id/watchers ``` *示例* -``` +```bash curl -X GET \ -d "page=1" \ -d "limit=5" \ @@ -2106,7 +2106,7 @@ http://localhost:3000//api/projects/3263/watchers | jq 返回值 -``` +```json { "total_count": 1, "watchers": [ @@ -2125,7 +2125,7 @@ http://localhost:3000//api/projects/3263/watchers | jq POST /api/repositories/:id/create_file ``` *示例* -``` +```bash curl -X POST \ -d 'filepath=test1_create_file1.rb' \ -d 'branch=master' \ @@ -2164,7 +2164,7 @@ http://localhost:3000/api/18816895620/mirror_demo/contents.json | jq 返回值 -``` +```json { "name": "test1_create_file12.rb", "sha": "7b70509105b587e71f5692b9e8ab70851e321f64", @@ -2193,7 +2193,7 @@ http://localhost:3000/api/18816895620/mirror_demo/contents.json | jq PUT /api/repositories/:id/update_file.json ``` *示例* -``` +```bash curl -X PUT \ -d 'filepath=text1.rb' \ -d 'branch=master' \ @@ -2235,7 +2235,7 @@ http://localhost:3000/api/repositories/3938/update_file.json | jq 返回值 -``` +```json { "name": "test1_create_file6.rb", "sha": "57426eb21e4ceabdf4b206f022257e08077e0040", @@ -2264,7 +2264,7 @@ http://localhost:3000/api/repositories/3938/update_file.json | jq DELETE /api/repositories/:id/delete_file ``` *示例* -``` +```bash curl -X DELETE \ -d 'filepath=test1_create_file12.rb' \ -d 'test delete file' \ @@ -2321,7 +2321,7 @@ http://localhost:3000/api//api/repositories/3868/delete_file | jq GET /api/:owner/:repo/pulls/:id/files.json ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/Jason/test-txt/pulls/1/files.json | jq ``` @@ -2439,7 +2439,7 @@ http://localhost:3000/api/Jason/test-txt/pulls/1/files.json | jq GET /api/:owner/:repo/pulls/:id/commits.json ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/Jason/repo/1/commits.json | jq ``` @@ -2504,7 +2504,7 @@ http://localhost:3000/api/Jason/repo/1/commits.json | jq GET /api/:owner/:repo/compare/{base}...{head}.json ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/Jason/test-txt/compare/master...develop | jq @@ -2650,6 +2650,417 @@ http://localhost:3000/api/ysfns/test-txt/compare/master...Jason/test-txt:develop ``` --- +### 创建保护分支 +``` +POST /api/:owner/:repo/protected_branches +``` +*示例* +```bash +curl -X POST \ +-d 'branch_name=master' \ +-d 'enable_push=true' \ +-d 'enable_push_whitelist=true' \ +-d 'enable_push_whitelist=["demo1", "demo1"]' \ +-d 'enable_merge_whitelist=true' \ +http://localhost:3000/api/trustie/truesite/protected_branches.json | jq +``` +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|owner |是|string |项目拥有者登录名 | +|repo |否|boolean |仓库名称 | +|branch_name |是|string |保护分支名称 | +|enable_push |否|boolean |是否启用推送, true: 启用; false: 不启用, 默认为false | +|enable_push_whitelist |否|boolean |是否启用白名单推送, true: 启用; false: 不启用, 默认为false, 该参数与enable_push参数为单选项,只能选择| +|push_whitelist_usernames |否|array |推送白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_push_whitelist参数配合使用 | +|enable_merge_whitelist |否|boolean |是否启用合并白名单, true: 启用, false: 不启用, 默认为false | +|merge_whitelist_usernames |否|array |合并白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_merge_whitelist配合使用 | +|enable_status_check |否|boolean |是否启用状态检查, true: 启用; false: 不启用, 默认为false | +|required_approvals |否|int |所需的批准数, 默认为0 | +|enable_approvals_whitelist |否|boolean |是否启用批准仅限列入白名单的用户或团队, true: 启用, false: 不启用, 默认为false | +|approvals_whitelist_usernames |否|array |审查者白名单(即具有写操作的项目成员名称的数组), 该参数与enable_approvals_whitelist配合使用 | +|block_on_rejected_reviews |否|boolean |是否启用拒绝审核阻止合并功能, true: 启用, false: 不启用, 默认为false | +|dismiss_stale_approvals |否|boolean |是否启用取消过时的批准, true: 启用, false: 不启用, 默认为false | +|require_signed_commits |否|boolean |是否需要签名提交, true: 是, false: 否, 默认为false | +|block_on_outdated_branch |否|boolean |如果拉取请求已经过时,是否阻止合并, true: 是, false: 否, 默认为false | + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|branch_name |string |保护分支名称 | +|enable_push |boolean |是否启用推送, true: 启用; false: 不启用, 默认为false | +|enable_push_whitelist |boolean |是否启用白名单推送, true: 启用; false: 不启用, 默认为false, 该参数与enable_push参数为单选项,只能选择| +|push_whitelist_usernames |array |推送白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_push_whitelist参数配合使用 | +|enable_merge_whitelist |boolean |是否启用合并白名单, true: 启用, false: 不启用, 默认为false | +|merge_whitelist_usernames |array |合并白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_merge_whitelist配合使用 | +|enable_status_check |boolean |是否启用状态检查, true: 启用; false: 不启用, 默认为false | +|required_approvals |int |所需的批准数, 默认为0 | +|enable_approvals_whitelist |boolean |是否启用批准仅限列入白名单的用户或团队, true: 启用, false: 不启用, 默认为false | +|approvals_whitelist_usernames |array |审查者白名单(即具有写操作的项目成员名称的数组), 该参数与enable_approvals_whitelist配合使用 | +|block_on_rejected_reviews |boolean |是否启用拒绝审核阻止合并功能, true: 启用, false: 不启用, 默认为false | +|dismiss_stale_approvals |boolean |是否启用取消过时的批准, true: 启用, false: 不启用, 默认为false | +|require_signed_commits |boolean |是否需要签名提交, true: 是, false: 否, 默认为false | +|block_on_outdated_branch |boolean |如果拉取请求已经过时,是否阻止合并, true: 是, false: 否, 默认为false | +|created_at |string|创建时间| +|updated_at |string|更新时间| + + +返回值 +``` +{ + "branch_name": "develop", + "enable_push": true, + "required_approvals": 0, + "enable_status_check": true, + "enable_push_whitelist": true, + "enable_merge_whitelist": true, + "enable_approvals_whitelist": false, + "dismiss_stale_approvals": false, + "block_on_rejected_reviews": false, + "block_on_outdated_branch": false, + "require_signed_commits": false, + "merge_whitelist_usernames": [ + "jasder" + ], + "push_whitelist_usernames": [ + "jasder" + ], + "approvals_whitelist_usernames": [], + "created_at": "2020-12-02 17:40", + "updated_at": "2020-12-03 11:29" +} +``` +--- + +### 编辑保护分支参数 +``` +GET /api/:owner/:repo/protected_branches/:branch_name/edit +``` +*示例* +```bash +curl -X GET \ +http://localhost:3000/api/trustie/truesite/protected_branches/master/edit.json | jq +``` +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|owner |是|string |项目拥有者登录名 | +|repo |否|boolean |仓库名称 | +|branch_name |是|string |保护分支名称 | + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|protected |boolean |是否为保护分支, true: 是; false: 不是 | +|branch_name |string |保护分支名称 | +|enable_push |boolean |是否启用推送, true: 启用; false: 不启用, 默认为false | +|enable_push_whitelist |boolean |是否启用白名单推送, true: 启用; false: 不启用, 默认为false, 该参数与enable_push参数为单选项,只能选择| +|push_whitelist_usernames |array |推送白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_push_whitelist参数配合使用 | +|enable_merge_whitelist |boolean |是否启用合并白名单, true: 启用, false: 不启用, 默认为false | +|merge_whitelist_usernames |array |合并白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_merge_whitelist配合使用 | +|enable_status_check |boolean |是否启用状态检查, true: 启用; false: 不启用, 默认为false | +|required_approvals |int |所需的批准数, 默认为0 | +|enable_approvals_whitelist |boolean |是否启用批准仅限列入白名单的用户或团队, true: 启用, false: 不启用, 默认为false | +|approvals_whitelist_usernames |array |审查者白名单(即具有写操作的项目成员名称的数组), 该参数与enable_approvals_whitelist配合使用 | +|block_on_rejected_reviews |boolean |是否启用拒绝审核阻止合并功能, true: 启用, false: 不启用, 默认为false | +|dismiss_stale_approvals |boolean |是否启用取消过时的批准, true: 启用, false: 不启用, 默认为false | +|require_signed_commits |boolean |是否需要签名提交, true: 是, false: 否, 默认为false | +|block_on_outdated_branch |boolean |如果拉取请求已经过时,是否阻止合并, true: 是, false: 否, 默认为false | +|created_at |string|创建时间| +|updated_at |string|更新时间| + + +返回值 +```json +{ + "branch_name": "master", + "protected": true, + "protected_branch": { + "branch_name": "master", + "enable_push": false, + "required_approvals": 0, + "enable_status_check": true, + "enable_push_whitelist": false, + "enable_merge_whitelist": true, + "enable_approvals_whitelist": false, + "dismiss_stale_approvals": false, + "block_on_rejected_reviews": false, + "block_on_outdated_branch": false, + "require_signed_commits": false, + "merge_whitelist_usernames": [ + "jasder" + ], + "push_whitelist_usernames": [], + "approvals_whitelist_usernames": [], + "created_at": "2020-12-03 12:00", + "updated_at": "2020-12-04 10:50" + } +} +``` +--- + +### 修改保护分支参数 +``` +PATCH /api/:owner/:repo/protected_branches/:branch_name +``` +*示例* +```bash +curl -X PATCH \ +-d 'branch_name=master' \ +-d 'enable_push=true' \ +-d 'enable_push_whitelist=true' \ +-d 'enable_push_whitelist=["demo1", "demo1"]' \ +-d 'enable_merge_whitelist=true' \ +http://localhost:3000/api/trustie/truesite/protected_branches/master.json | jq +``` +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|owner |是|string |项目拥有者登录名 | +|repo |否|boolean |仓库名称 | +|branch_name |是|string |保护分支名称 | +|enable_push |否|boolean |是否启用推送, true: 启用; false: 不启用, 默认为false | +|enable_push_whitelist |否|boolean |是否启用白名单推送, true: 启用; false: 不启用, 默认为false, 该参数与enable_push参数为单选项,只能选择| +|push_whitelist_usernames |否|array |推送白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_push_whitelist参数配合使用 | +|enable_merge_whitelist |否|boolean |是否启用合并白名单, true: 启用, false: 不启用, 默认为false | +|merge_whitelist_usernames |否|array |合并白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_merge_whitelist配合使用 | +|enable_status_check |否|boolean |是否启用状态检查, true: 启用; false: 不启用, 默认为false | +|required_approvals |否|int |所需的批准数, 默认为0 | +|enable_approvals_whitelist |否|boolean |是否启用批准仅限列入白名单的用户或团队, true: 启用, false: 不启用, 默认为false | +|approvals_whitelist_usernames |否|array |审查者白名单(即具有写操作的项目成员名称的数组), 该参数与enable_approvals_whitelist配合使用 | +|block_on_rejected_reviews |否|boolean |是否启用拒绝审核阻止合并功能, true: 启用, false: 不启用, 默认为false | +|dismiss_stale_approvals |否|boolean |是否启用取消过时的批准, true: 启用, false: 不启用, 默认为false | +|require_signed_commits |否|boolean |是否需要签名提交, true: 是, false: 否, 默认为false | +|block_on_outdated_branch |否|boolean |如果拉取请求已经过时,是否阻止合并, true: 是, false: 否, 默认为false | + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|branch_name |string |保护分支名称 | +|enable_push |boolean |是否启用推送, true: 启用; false: 不启用, 默认为false | +|enable_push_whitelist |boolean |是否启用白名单推送, true: 启用; false: 不启用, 默认为false, 该参数与enable_push参数为单选项,只能选择| +|push_whitelist_usernames |array |推送白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_push_whitelist参数配合使用 | +|enable_merge_whitelist |boolean |是否启用合并白名单, true: 启用, false: 不启用, 默认为false | +|merge_whitelist_usernames |array |合并白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_merge_whitelist配合使用 | +|enable_status_check |boolean |是否启用状态检查, true: 启用; false: 不启用, 默认为false | +|required_approvals |int |所需的批准数, 默认为0 | +|enable_approvals_whitelist |boolean |是否启用批准仅限列入白名单的用户或团队, true: 启用, false: 不启用, 默认为false | +|approvals_whitelist_usernames |array |审查者白名单(即具有写操作的项目成员名称的数组), 该参数与enable_approvals_whitelist配合使用 | +|block_on_rejected_reviews |boolean |是否启用拒绝审核阻止合并功能, true: 启用, false: 不启用, 默认为false | +|dismiss_stale_approvals |boolean |是否启用取消过时的批准, true: 启用, false: 不启用, 默认为false | +|require_signed_commits |boolean |是否需要签名提交, true: 是, false: 否, 默认为false | +|block_on_outdated_branch |boolean |如果拉取请求已经过时,是否阻止合并, true: 是, false: 否, 默认为false | +|created_at |string|创建时间| +|updated_at |string|更新时间| + + +返回值 +```json +{ + "branch_name": "develop", + "enable_push": true, + "required_approvals": 0, + "enable_status_check": true, + "enable_push_whitelist": true, + "enable_merge_whitelist": true, + "enable_approvals_whitelist": false, + "dismiss_stale_approvals": false, + "block_on_rejected_reviews": false, + "block_on_outdated_branch": false, + "require_signed_commits": false, + "merge_whitelist_usernames": [ + "jasder" + ], + "push_whitelist_usernames": [ + "jasder" + ], + "approvals_whitelist_usernames": [], + "created_at": "2020-12-02 17:40", + "updated_at": "2020-12-03 11:29" +} +``` +--- + +### 删除保护分支 +``` +DELETE /api/:owner/:repo/protected_branches/:branch_name +``` +*示例* +```bash +curl -X DELETE \ +http://localhost:3000/api/trustie/truesite/protected_branches/master.json | jq +``` +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|owner |是|string |项目拥有者登录名 | +|repo |否|boolean |仓库名称 | +|branch_name |是|string |保护分支名称 | + + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|status |int|状态值,0: 请求成功; -1: 请求失败| +|message |string|信息说明| + +返回值 +```json +{ + "status": 0, + "message": "success" +} +``` +--- + +### 获取保护分支列表 +``` +GET /api/:owner/:repo/protected_branches/ +``` +*示例* +```bash +curl -X GET \ +-d "page=1" \ +-d "limit=5" \ +http://localhost:3000/api/trustie/truesite/protected_branches.json | jq +``` +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|owner |是|string |项目拥有者登录名 | +|repo |否|boolean |仓库名称 | +|page |否|string |页数,第几页 | +|limit |否|string |每页多少条数据,默认15条 | + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|total_count |int | 总记录数 | +|branch_name |string |保护分支名称 | +|enable_push |boolean |是否启用推送, true: 启用; false: 不启用, 默认为false | +|enable_push_whitelist |boolean |是否启用白名单推送, true: 启用; false: 不启用, 默认为false, 该参数与enable_push参数为单选项,只能选择| +|push_whitelist_usernames |array |推送白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_push_whitelist参数配合使用 | +|enable_merge_whitelist |boolean |是否启用合并白名单, true: 启用, false: 不启用, 默认为false | +|merge_whitelist_usernames |array |合并白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_merge_whitelist配合使用 | +|enable_status_check |boolean |是否启用状态检查, true: 启用; false: 不启用, 默认为false | +|required_approvals |int |所需的批准数, 默认为0 | +|enable_approvals_whitelist |boolean |是否启用批准仅限列入白名单的用户或团队, true: 启用, false: 不启用, 默认为false | +|approvals_whitelist_usernames |array |审查者白名单(即具有写操作的项目成员名称的数组), 该参数与enable_approvals_whitelist配合使用 | +|block_on_rejected_reviews |boolean |是否启用拒绝审核阻止合并功能, true: 启用, false: 不启用, 默认为false | +|dismiss_stale_approvals |boolean |是否启用取消过时的批准, true: 启用, false: 不启用, 默认为false | +|require_signed_commits |boolean |是否需要签名提交, true: 是, false: 否, 默认为false | +|block_on_outdated_branch |boolean |如果拉取请求已经过时,是否阻止合并, true: 是, false: 否, 默认为false | +|created_at |string|创建时间| +|updated_at |string|更新时间| + + +返回值 +``` +{ + "total_count": 1, + "protected_branches": [ + { + "branch_name": "develop", + "enable_push": true, + "required_approvals": 0, + "enable_status_check": true, + "enable_push_whitelist": true, + "enable_merge_whitelist": true, + "enable_approvals_whitelist": false, + "dismiss_stale_approvals": false, + "block_on_rejected_reviews": false, + "block_on_outdated_branch": false, + "require_signed_commits": false, + "merge_whitelist_usernames": [ + "jasder" + ], + "push_whitelist_usernames": [ + "jasder" + ], + "approvals_whitelist_usernames": [], + "created_at": "2020-12-02 17:40", + "updated_at": "2020-12-03 11:29" + } + ] +} +``` +--- + +### 获取某个具体的保护分支 +``` +GET /api/:owner/:repo/protected_branches/:branch_name +``` +*示例* +```bash +curl -X GET \ +http://localhost:3000/api/trustie/truesite/protected_branches/master.json | jq +``` +*请求参数说明:* + +|参数名|必选|类型|说明| +|-|-|-|-| +|owner |是|string |项目拥有者登录名 | +|repo |否|boolean |仓库名称 | +|branch_name |是|string |保护分支名称 | + + +*返回参数说明:* + +|参数名|类型|说明| +|-|-|-| +|branch_name |string |保护分支名称 | +|enable_push |boolean |是否启用推送, true: 启用; false: 不启用, 默认为false | +|enable_push_whitelist |boolean |是否启用白名单推送, true: 启用; false: 不启用, 默认为false, 该参数与enable_push参数为单选项,只能选择| +|push_whitelist_usernames |array |推送白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_push_whitelist参数配合使用 | +|enable_merge_whitelist |boolean |是否启用合并白名单, true: 启用, false: 不启用, 默认为false | +|merge_whitelist_usernames |array |合并白名单用户(即具有写操作的项目成员名称的数组), 该参数与enable_merge_whitelist配合使用 | +|enable_status_check |boolean |是否启用状态检查, true: 启用; false: 不启用, 默认为false | +|required_approvals |int |所需的批准数, 默认为0 | +|enable_approvals_whitelist |boolean |是否启用批准仅限列入白名单的用户或团队, true: 启用, false: 不启用, 默认为false | +|approvals_whitelist_usernames |array |审查者白名单(即具有写操作的项目成员名称的数组), 该参数与enable_approvals_whitelist配合使用 | +|block_on_rejected_reviews |boolean |是否启用拒绝审核阻止合并功能, true: 启用, false: 不启用, 默认为false | +|dismiss_stale_approvals |boolean |是否启用取消过时的批准, true: 启用, false: 不启用, 默认为false | +|require_signed_commits |boolean |是否需要签名提交, true: 是, false: 否, 默认为false | +|block_on_outdated_branch |boolean |如果拉取请求已经过时,是否阻止合并, true: 是, false: 否, 默认为false | +|created_at |string|创建时间| +|updated_at |string|更新时间| + +返回值 +```json +{ + "branch_name": "develop", + "enable_push": true, + "required_approvals": 0, + "enable_status_check": true, + "enable_push_whitelist": true, + "enable_merge_whitelist": true, + "enable_approvals_whitelist": false, + "dismiss_stale_approvals": false, + "block_on_rejected_reviews": false, + "block_on_outdated_branch": false, + "require_signed_commits": false, + "merge_whitelist_usernames": [ + "jasder" + ], + "push_whitelist_usernames": [ + "jasder" + ], + "approvals_whitelist_usernames": [], + "created_at": "2020-12-02 17:40", + "updated_at": "2020-12-03 11:29" +} +``` +--- ### DevOps相关api --- @@ -2660,7 +3071,7 @@ GET /api/:owner/:repo/ci_authorize ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/jasder/forgeplus/ci_authorize.json | jq ``` @@ -2704,7 +3115,7 @@ POST /api/:owner/:repo/cloud_accounts ``` *示例* -``` +```bash curl -X POST \ -d "account=xx" \ -d "secret=xxx" \ @@ -2745,7 +3156,7 @@ https://localhost:3000/api/jasder/forgeplus/cloud_accounts.json | jq GET /api/users/ci/oauth_grant ``` *示例* -``` +```bash curl -X GET \ -d "password=123456" \ http://localhost:3000/api/users/ci/oauth_grant.json | jq @@ -2762,7 +3173,8 @@ http://localhost:3000/api/users/ci/oauth_grant.json | jq |-|-|-| |status |int|0:成功, -1: 失败| -``` +返回值 +```json { "status": 0, "message": "success" @@ -2775,7 +3187,7 @@ http://localhost:3000/api/users/ci/oauth_grant.json | jq POST /api/:owner/:repo/activate ``` *示例* -``` +```bash curl -X POST \ http://localhost:3000/api/jasder/forgeplus/activate.json | jq ``` @@ -2792,7 +3204,8 @@ http://localhost:3000/api/jasder/forgeplus/activate.json | jq |-|-|-| |status |int|0:成功, -1: 失败| -``` +返回值 +```json { "status": 0, "message": "success" @@ -2805,7 +3218,7 @@ http://localhost:3000/api/jasder/forgeplus/activate.json | jq DELETE /api/:owner/:repo/deactivate ``` *示例* -``` +```bash curl -X POST \ http://localhost:3000/api/jasder/forgeplus/deactivate.json | jq ``` @@ -2822,7 +3235,8 @@ http://localhost:3000/api/jasder/forgeplus/deactivate.json | jq |-|-|-| |status |int|0:成功, -1: 失败| -``` +返回值 +```json { "status": 0, "message": "success" @@ -2835,7 +3249,7 @@ http://localhost:3000/api/jasder/forgeplus/deactivate.json | jq GET /api/:owner/:repo/get_trustie_pipeline ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/jasder/forge/get_trustie_pipeline.json | jq ``` @@ -2856,7 +3270,8 @@ http://localhost:3000/api/jasder/forge/get_trustie_pipeline.json | jq |path |string|文件夹或文件相对路径| |content |string|文件内容,| -``` +返回值 +```json { "name": ".trustie-pipeline.yml", "path": ".trustie-pipeline.yml", @@ -2871,7 +3286,7 @@ http://localhost:3000/api/jasder/forge/get_trustie_pipeline.json | jq PUT /api/:owner/:repo/update_trustie_pipeline ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/jasder/forge/update_trustie_pipeline.json | jq ``` @@ -2913,7 +3328,7 @@ GET /api/ci/languages ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/ci/languages.json | jq ``` @@ -2945,7 +3360,7 @@ GET /api/ci/languages/common ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/ci/languages/common.json | jq ``` @@ -2977,7 +3392,7 @@ GET /api/ci/languages/:id ``` *示例* -``` +```bash curl -X GET http://localhost:3000/api/ci/languages/114.json | jq ``` @@ -3015,7 +3430,7 @@ GET /api/:owner/:repo/builds ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/Jason/forge/builds | jq ``` @@ -3081,7 +3496,7 @@ GET /api/:owner/:repo/builds/:build ``` *示例* -``` +```bash curl -X GET \ http://ocalhost:3000/api/jasder/forge/builds/1 | jq ``` @@ -3187,7 +3602,7 @@ POST /api/:owner/:repo/builds/:build/restart ``` *示例* -``` +```bash curl -X POST \ http://localhost:3000/api/jasder/forgeplus/builds/1 | jq ``` @@ -3245,7 +3660,7 @@ DELETE /api/:owner/:repo/builds/:build/stop ``` *示例* -``` +```bash curl -X DELETE \ http://localhost:3000/api/jaser/forge/builds/2 | jq ``` @@ -3351,7 +3766,7 @@ GET /api/:owner/:repo/builds/:build/logs/:stage/:step ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/dev_ops/builds/2/logs/1/1 | jq ``` @@ -3425,7 +3840,7 @@ GET /api/users/ci/cloud_account ``` *示例* -``` +```bash curl -X GET \ http://localhost:3000/api/users/ci/cloud_account | jq ``` @@ -3458,7 +3873,7 @@ POST /api/users/ci/cloud_account/bind ``` *示例* -``` +```bash curl -X POST \ -d "account=xx" \ -d "secret=xxx" \ @@ -3501,7 +3916,7 @@ DELETE /api/users/ci/cloud_account/unbind ``` *示例* -``` +```bash curl -X DELETE \ http://localhost:3000/api/users/ci/cloud_account/unbind.json | jq ``` @@ -3528,7 +3943,7 @@ GET /api/users/:login/projects ``` *示例* -``` +```bash curl -X GET \ -d "page=1" \ -d "limit=20" \ @@ -3568,7 +3983,7 @@ http://localhost:3000/api/users/Jason/projects.json | jq 返回值 -``` +```json { "total_count": 3096, "projects": [ diff --git a/app/controllers/protected_branches_controller.rb b/app/controllers/protected_branches_controller.rb new file mode 100644 index 000000000..364433d78 --- /dev/null +++ b/app/controllers/protected_branches_controller.rb @@ -0,0 +1,43 @@ +class ProtectedBranchesController < ApplicationController + include OperateProjectAbilityAble + + before_action :require_login + before_action :load_repository + before_action :authorizate_user_can_edit_project! + + def index + scope = ProtectedBranch.all + @total_count = scope.size + @protected_branches = paginate(scope) + end + + def create + @protected_branch = ProtectedBranches::CreateService.call(@repository, @owner, params) + + render_protected_branch_json + end + + def update + @protected_branch = ProtectedBranches::UpdateService.call(@repository, @owner, params) + end + + def destroy + ProtectedBranches::DestroyService.call(@repository, @owner, params[:branch_name]) + + render_ok + end + + def show + @protected_branch = ProtectedBranches::GetService.call(@repository, @owner, params) + end + + def edit + @branch, @protected_branch = ProtectedBranches::EditService.call(@repository, @owner, params[:branch_name]) + end + + private + def render_protected_branch_json + @protected_branch.persisted? ? @protected_branch : render_error('创建失败!') + end + +end diff --git a/app/forms/protected_branches/create_form.rb b/app/forms/protected_branches/create_form.rb new file mode 100644 index 000000000..ec4d87717 --- /dev/null +++ b/app/forms/protected_branches/create_form.rb @@ -0,0 +1,19 @@ +class ProtectedBranches::CreateForm < BaseForm + attr_accessor :repository, :branch_name, :can_push, :enable_whitelist, :whitelist_user_i_ds, + :whitelist_team_i_ds, :enable_merge_whitelist, :whitelist_deploy_keys, :merge_whitelist_user_i_ds, + :merge_whitelist_team_i_ds, :enable_status_check, :status_check_contexts, :approvals_whitelist_user_i_ds, + :approvals_whitelist_team_i_ds, :required_approvals, :enable_approvals_whitelist, :block_on_rejected_reviews, + :dismiss_stale_approvals, :require_signed_commits, :protected_file_patterns, :block_on_outdated_branch + + validates :repo_id, :branch_name, presence: true + + validate do + check_branch_name! + end + + + def check_branch_name! + protected_branch_exists = repository.protected_branches.exists?(branch_name) + raise "Protected branch '#{branch_name}' already exists" if protected_branch_exists + end +end diff --git a/app/helpers/protected_branches_helper.rb b/app/helpers/protected_branches_helper.rb new file mode 100644 index 000000000..dce9506fa --- /dev/null +++ b/app/helpers/protected_branches_helper.rb @@ -0,0 +1,2 @@ +module ProtectedBranchesHelper +end diff --git a/app/interactors/repositories/protected_branches/delete_interactor.rb b/app/interactors/repositories/protected_branches/delete_interactor.rb new file mode 100644 index 000000000..990440b50 --- /dev/null +++ b/app/interactors/repositories/protected_branches/delete_interactor.rb @@ -0,0 +1,44 @@ +module Repositories::ProtectedBranches + class DeleteInteractor + def self.call(user, identifier, filepath, **args) + interactor = new(user, identifier, filepath, **args) + interactor.run + interactor + end + + attr_reader :error, :result + + def initialize(user, identifier, filepath, **args) + @user = user + @identifier = identifier + @filepath = filepath + @args = args + end + + def success? + @error.nil? + end + + def result + end + + def run + + rescue Exception => exception + fail!(exception.message) + end + + + private + + attr_reader :user, :identifier, :filepath, :args + + def fail!(error) + @error = error + end + + def render_result(response) + @result = response + end + end +end diff --git a/app/models/concerns/project_operable.rb b/app/models/concerns/project_operable.rb index bba65e74c..f05ed164e 100644 --- a/app/models/concerns/project_operable.rb +++ b/app/models/concerns/project_operable.rb @@ -7,6 +7,7 @@ module ProjectOperable has_many :managers, -> { joins(:roles).where(roles: { name: 'Manager' }) }, class_name: 'Member' has_many :developers, -> { joins(:roles).where(roles: { name: 'Developer' }) }, class_name: 'Member' has_many :reporters, -> { joins(:roles).where(roles: { name: 'Reporter' }) }, class_name: 'Member' + has_many :writable_members, -> { joins(:roles).where.not(roles: {name: 'Reporter'}) }, class_name: 'Member' end def add_member!(user_id, role_name='Developer') diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb new file mode 100644 index 000000000..da35f6e77 --- /dev/null +++ b/app/models/protected_branch.rb @@ -0,0 +1,65 @@ +# == Schema Information +# +# Table name: protected_branches +# +# id :integer not null, primary key +# repo_id :integer +# branch_name :string(255) default("") +# can_push :boolean default("0"), not null +# enable_whitelist :boolean default("0") +# whitelist_user_i_ds :text(65535) +# whitelist_team_i_ds :text(65535) +# enable_merge_whitelist :boolean default("0"), not null +# whitelist_deploy_keys :boolean default("0"), not null +# merge_whitelist_user_i_ds :text(65535) +# merge_whitelist_team_i_ds :text(65535) +# enable_status_check :boolean default("0"), not null +# status_check_contexts :text(65535) +# approvals_whitelist_user_i_ds :text(65535) +# approvals_whitelist_team_i_ds :text(65535) +# required_approvals :integer default("0") +# enable_approvals_whitelist :boolean default("0"), not null +# block_on_rejected_reviews :boolean default("0"), not null +# dismiss_stale_approvals :boolean default("0"), not null +# require_signed_commits :boolean default("0"), not null +# protected_file_patterns :text(65535) +# block_on_outdated_branch :boolean default("0"), not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_protected_branches_on_repo_id (repo_id) +# + +class ProtectedBranch < ApplicationRecord + serialize :whitelist_user_i_ds, Array + serialize :merge_whitelist_user_i_ds, Array + serialize :approvals_whitelist_user_i_ds, Array + + belongs_to :repo, class_name: 'Repository', foreign_key: :repo_id + validates :branch_name, presence: true + validates :repo, presence: true + + def to_param + self.branch_name.parameterize + end + + + def push_whitelist_usernames + get_logins_by_ids(whitelist_user_i_ds) + end + + def merge_whitelist_usernames + get_logins_by_ids(merge_whitelist_user_i_ds) + end + + def approvals_whitelist_usernames + get_logins_by_ids(approvals_whitelist_user_i_ds) + end + + def get_logins_by_ids(ids) + User.where(id: ids).map(&:login) + end + +end diff --git a/app/models/repository.rb b/app/models/repository.rb index 65b4316ed..be6afcb10 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -37,6 +37,7 @@ class Repository < ApplicationRecord has_one :mirror, foreign_key: :repo_id has_one :ci_cloud_account, class_name: 'Ci::CloudAccount', foreign_key: :repo_id has_many :version_releases, dependent: :destroy + has_many :protected_branches, class_name: 'ProtectedBranch', foreign_key: :repo_id, dependent: :destroy validates :identifier, presence: true diff --git a/app/services/application_service.rb b/app/services/application_service.rb index 81c48de95..2fa59ed29 100644 --- a/app/services/application_service.rb +++ b/app/services/application_service.rb @@ -14,4 +14,8 @@ class ApplicationService def strip(str) str.to_s.strip.presence end -end \ No newline at end of file + + def str_to_boolean str + ActiveModel::Type::Boolean.new.cast str + end +end diff --git a/app/services/gitea/client_service.rb b/app/services/gitea/client_service.rb index 1ccd33508..bb75fde62 100644 --- a/app/services/gitea/client_service.rb +++ b/app/services/gitea/client_service.rb @@ -21,11 +21,10 @@ class Gitea::ClientService < ApplicationService def post(url, params={}) puts "[gitea] request params: #{params}" auth_token = authen_params(params[:token]) - response = conn(auth_token).post do |req| + conn(auth_token).post do |req| req.url full_url(url) req.body = params[:data].to_json end - render_status(response) end def get(url, params={}) @@ -164,4 +163,102 @@ class Gitea::ClientService < ApplicationService nil end end + + def render_response(response) + status = response.status + body = response&.body + + log_error(status, body) + + body, message = get_body_by_status(status, body) + + [status, message, body] + end + + def get_body_by_status(status, body) + body, message = + case status + when 404 then [nil, "404"] + when 403 then [nil, "403"] + else + if body.present? + body = JSON.parse(body) + fix_body(body) + else + nil + end + end + + [body, message] + end + + def log_error(status, body) + puts "[gitea] status: #{status}" + puts "[gitea] body: #{body}" + end + + def fix_body(body) + return [body, nil] if body.is_a? Array + + body['message'].blank? ? [body, nil] : [nil, body['message']] + end + + def render_json_data(status, message, body, success=true) + if success + success(body) + else + error(message, status) + end + end + + def error(message, http_status = nil) + result = { + message: message, + status: :error + } + + result[:http_status] = http_status if http_status + result + end + + def success(body=nil) + { + status: :success, + body: body + } + end + + def render_body(body) + success(body)[:body] + end + + def render_200_response(response) + extract_statuses(response) + end + + def render_200_no_body(response) + response.status + case response.status + when 200 + {status: 200} + else + end + end + + def render_201_response(response) + extract_statuses(response) + end + + def render_202_response(response) + extract_statuses(response) + end + + def extract_statuses(response) + success_statuses = [200, 201, 202, 204] + status, message, body = render_response(response) + + return error(message, status) unless success_statuses.include? status + + render_body(body) + end end diff --git a/app/services/gitea/hooks/create_service.rb b/app/services/gitea/hooks/create_service.rb index 711427e27..6b65a6860 100644 --- a/app/services/gitea/hooks/create_service.rb +++ b/app/services/gitea/hooks/create_service.rb @@ -24,7 +24,8 @@ class Gitea::Hooks::CreateService < Gitea::ClientService end def call - post(url, params) + response = post(url, params) + render_201_response(response) end private diff --git a/app/services/gitea/oauth2/create_service.rb b/app/services/gitea/oauth2/create_service.rb index ba87e960c..1fa93b7a8 100644 --- a/app/services/gitea/oauth2/create_service.rb +++ b/app/services/gitea/oauth2/create_service.rb @@ -27,7 +27,8 @@ class Gitea::Oauth2::CreateService < Gitea::ClientService end def call - post(url, request_params) + response = post(url, request_params) + render_201_response(response) end private diff --git a/app/services/gitea/repository/branches/get_service.rb b/app/services/gitea/repository/branches/get_service.rb index d833b86bb..0e84e15d1 100644 --- a/app/services/gitea/repository/branches/get_service.rb +++ b/app/services/gitea/repository/branches/get_service.rb @@ -13,7 +13,7 @@ class Gitea::Repository::Branches::GetService < Gitea::ClientService def call response = get(url, params) - render_data(response) + render_200_response(response) end private diff --git a/app/services/gitea/repository/branches/list_service.rb b/app/services/gitea/repository/branches/list_service.rb index 17702259c..f42965098 100644 --- a/app/services/gitea/repository/branches/list_service.rb +++ b/app/services/gitea/repository/branches/list_service.rb @@ -8,7 +8,7 @@ class Gitea::Repository::Branches::ListService < Gitea::ClientService def call response = get(url, params) - render_data(response) + render_200_response(response) end private diff --git a/app/services/gitea/repository/create_service.rb b/app/services/gitea/repository/create_service.rb index 80d8b440d..168aaab24 100644 --- a/app/services/gitea/repository/create_service.rb +++ b/app/services/gitea/repository/create_service.rb @@ -18,7 +18,8 @@ class Gitea::Repository::CreateService < Gitea::ClientService end def call - post(url, request_params) + response = post(url, request_params) + render_201_response(response) end private diff --git a/app/services/gitea/repository/entries/create_service.rb b/app/services/gitea/repository/entries/create_service.rb index 7f1a6b529..9f5abad2a 100644 --- a/app/services/gitea/repository/entries/create_service.rb +++ b/app/services/gitea/repository/entries/create_service.rb @@ -29,7 +29,9 @@ class Gitea::Repository::Entries::CreateService < Gitea::ClientService end def call - post(url, params) + response = post(url, params) + + render_201_response(response) end private diff --git a/app/services/gitea/repository/fork_service.rb b/app/services/gitea/repository/fork_service.rb index dce925b8a..c43a9ddf2 100644 --- a/app/services/gitea/repository/fork_service.rb +++ b/app/services/gitea/repository/fork_service.rb @@ -14,7 +14,9 @@ class Gitea::Repository::ForkService < Gitea::ClientService end def call - post(url, request_params) + response = post(url, request_params) + + render_202_response(response) end private diff --git a/app/services/gitea/repository/migrate_service.rb b/app/services/gitea/repository/migrate_service.rb index 568e4d5c5..508812087 100644 --- a/app/services/gitea/repository/migrate_service.rb +++ b/app/services/gitea/repository/migrate_service.rb @@ -31,7 +31,9 @@ class Gitea::Repository::MigrateService < Gitea::ClientService end def call - post(url, request_params) + response = post(url, request_params) + + render_201_response(response) end private diff --git a/app/services/gitea/repository/protected_branches/create_service.rb b/app/services/gitea/repository/protected_branches/create_service.rb new file mode 100644 index 000000000..79415e09c --- /dev/null +++ b/app/services/gitea/repository/protected_branches/create_service.rb @@ -0,0 +1,76 @@ +# Create a branch protections for a repository + +class Gitea::Repository::ProtectedBranches::CreateService < Gitea::ClientService + attr_reader :owner, :repo, :body, :token + + # owner: owner of the repo + # repo: name of the repo + # body: + # { + # "approvals_whitelist_teams": [ + # "string" + # ], + # "approvals_whitelist_username": [ + # "string" + # ], + # "block_on_outdated_branch": true, + # "block_on_rejected_reviews": true, + # "branch_name": "string", + # "dismiss_stale_approvals": true, + # "enable_approvals_whitelist": true, + # "enable_merge_whitelist": true, + # "enable_push": true, + # "enable_push_whitelist": true, + # "enable_status_check": true, + # "merge_whitelist_teams": [ + # "string" + # ], + # "merge_whitelist_usernames": [ + # "string" + # ], + # "protected_file_patterns": "string", + # "push_whitelist_deploy_keys": true, + # "push_whitelist_teams": [ + # "string" + # ], + # "push_whitelist_usernames": [ + # "string" + # ], + # "require_signed_commits": true, + # "required_approvals": 0, + # "status_check_contexts": [ + # "string" + # ] + # } + + def initialize(owner, repo, body={}, token=nil) + @owner = owner + @repo = repo + @body = body + @token = token + end + + def call + response = post(url, params) + status, message, body = render_response(response) + json_format(status, message, body) + end + + private + def params + Hash.new.merge(token: token, data: body) + end + + def url + "/repos/#{owner}/#{repo}/branch_protections".freeze + end + + def json_format(status, message, body) + case status + when 201 then success(body) + else + error(message, status) + end + end + +end diff --git a/app/services/gitea/repository/protected_branches/destroy_service.rb b/app/services/gitea/repository/protected_branches/destroy_service.rb new file mode 100644 index 000000000..b0e5f0cf2 --- /dev/null +++ b/app/services/gitea/repository/protected_branches/destroy_service.rb @@ -0,0 +1,39 @@ +# Delete a specific branch protection for the repository + +class Gitea::Repository::ProtectedBranches::DestroyService < Gitea::ClientService + attr_reader :owner, :repo, :name, :token + + # owner: owner of the repo + # repo: name of the repo + # name: name of protected branch + # eg: + # Gitea::Repository::ProtectedBranches::DestroyService.call(user.login, repo.identifier, branch_name, user.gitea_token) + def initialize(owner, repo, name, token=nil) + @owner = owner + @repo = repo + @name = name + @token = token + end + + def call + response = delete(url, params) + status, message = render_response(response) + json_format(status, message) + end + + private + def params + Hash.new.merge(token: token, data: name) + end + + def url + "/repos/#{owner}/#{repo}/branch_protections/#{name}".freeze + end + + def json_format(status, message) + case status + when 204 then success + when 404 then error(message, 404) + end + end +end diff --git a/app/services/gitea/repository/protected_branches/update_service.rb b/app/services/gitea/repository/protected_branches/update_service.rb new file mode 100644 index 000000000..2f25d9a23 --- /dev/null +++ b/app/services/gitea/repository/protected_branches/update_service.rb @@ -0,0 +1,78 @@ +# Edit a branch protections for a repository. Only fields that are set will be changed + +class Gitea::Repository::ProtectedBranches::UpdateService < Gitea::ClientService + attr_reader :owner, :repo, :name, :body, :token + + # owner: owner of the repo + # repo: name of the repo + # nmae: name of protected branch + # body: + # { + # "approvals_whitelist_teams": [ + # "string" + # ], + # "approvals_whitelist_username": [ + # "string" + # ], + # "block_on_outdated_branch": true, + # "block_on_rejected_reviews": true, + # "branch_name": "string", + # "dismiss_stale_approvals": true, + # "enable_approvals_whitelist": true, + # "enable_merge_whitelist": true, + # "enable_push": true, + # "enable_push_whitelist": true, + # "enable_status_check": true, + # "merge_whitelist_teams": [ + # "string" + # ], + # "merge_whitelist_usernames": [ + # "string" + # ], + # "protected_file_patterns": "string", + # "push_whitelist_deploy_keys": true, + # "push_whitelist_teams": [ + # "string" + # ], + # "push_whitelist_usernames": [ + # "string" + # ], + # "require_signed_commits": true, + # "required_approvals": 0, + # "status_check_contexts": [ + # "string" + # ] + # } + # eq: + # Gitea::Repository::ProtectedBranches::UpdateService.call(user.login, repo.identifier, branch_name, body, user.gitea_token) + def initialize(owner, repo, name, body, token=nil) + @owner = owner + @repo = repo + @name = name + @body = body + @token = token + end + + def call + response = patch(url, params) + status, message, body = render_response(response) + json_format(status, message, body) + end + + private + def params + Hash.new.merge(token: token, data: body) + end + + def url + "/repos/#{owner}/#{repo}/branch_protections/#{name}".freeze + end + + def json_format(status, message, body) + case status + when 200 then success(body) + else + error(message, status) + end + end +end diff --git a/app/services/gitea/repository/sync_mirrored_service.rb b/app/services/gitea/repository/sync_mirrored_service.rb index 6918a6ec2..8d1673143 100644 --- a/app/services/gitea/repository/sync_mirrored_service.rb +++ b/app/services/gitea/repository/sync_mirrored_service.rb @@ -15,7 +15,9 @@ class Gitea::Repository::SyncMirroredService < Gitea::ClientService end def call - post(url, request_params) + response = post(url, request_params) + + render_200_no_body(response) end private diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb new file mode 100644 index 000000000..bfa3a432a --- /dev/null +++ b/app/services/protected_branches/base_service.rb @@ -0,0 +1,276 @@ +module ProtectedBranches + class BaseService < ApplicationService + Error = Class.new(StandardError) + attr_accessor :repository, :owner, :params + + def initialize(repository, user = nil, params = {}) + @repository, @owner, @params = repository, user, params.dup + end + + # delegate :repository, to: :project + + def protected_branch_params + # { + # "approvals_whitelist_teams": [ + # "string" + # ], //批准团队(或审查团队)白名单 + # "approvals_whitelist_username": [ + # "string" + # ], // 批准用户(或审查者)白名单 + # "block_on_outdated_branch": true, // 如果拉取过时,阻止合并 + # "block_on_rejected_reviews": true, // 拒绝审核,阻止合并请求 + # "branch_name": "string", //分支名称 + # "dismiss_stale_approvals": true, // 取消过时的批准 + # "enable_approvals_whitelist": true, //是否批准仅限列入白名单的用户或者团队, 主要用户pr的审核批准计数功能 + # "enable_merge_whitelist": true, // 是否启用合并请求白名单 + # "enable_push": true, //启用、禁止推送 + # "enable_push_whitelist": true, // 是否启动推送白名单 + # "enable_status_check": true, //是否启用状态检查 + # "merge_whitelist_teams": [ + # "string" + # ], // 合并请求团队白名单 + # "merge_whitelist_usernames": [ + # "string" + # ], // 合并请求用户白名单 + # "protected_file_patterns": "string", //保护文件模式 + # "push_whitelist_deploy_keys": true, // 具有推送权限的部署密钥白名单 + # "push_whitelist_teams": [ + # "string" + # ], //推送团队白名单 + # "push_whitelist_usernames": [ + # "string" + # ], //推送用户白名单 + # "require_signed_commits": true, //是否需要签名提交 + # "required_approvals": 0, // 所需批准数 + # "status_check_contexts": [ + # "string" + # ] // 状态检查规则 + # } + + + # branch_name :string(255) default("") + # can_push :boolean default("0"), not null + # enable_whitelist :boolean default("0") + # whitelist_user_i_ds :text(65535) + # whitelist_team_i_ds :text(65535) + # enable_merge_whitelist :boolean default("0"), not null + # whitelist_deploy_keys :boolean default("0"), not null + # merge_whitelist_user_i_ds :text(65535) + # merge_whitelist_team_i_ds :text(65535) + # enable_status_check :boolean default("0"), not null + # status_check_contexts :text(65535) + # approvals_whitelist_user_i_ds :text(65535) + # approvals_whitelist_team_i_ds :text(65535) + # required_approvals :integer default("0") + # enable_approvals_whitelist :boolean default("0"), not null + # block_on_rejected_reviews :boolean default("0"), not null + # dismiss_stale_approvals :boolean default("0"), not null + # require_signed_commits :boolean default("0"), not null + # protected_file_patterns :text(65535) + # block_on_outdated_branch :boolean default("0"), not null + + { + branch_name: params[:branch_name], + can_push: can_push_params, + enable_whitelist: enable_whitelist_params, + whitelist_user_i_ds: whitelist_user_i_ds_params, + # whitelist_team_i_ds: whitelist_team_i_ds_params, + enable_merge_whitelist: enable_merge_whitelist_params, + merge_whitelist_user_i_ds: merge_whitelist_user_i_ds_params, + # merge_whitelist_team_i_ds: merge_whitelist_team_i_ds_params, + enable_status_check: enable_status_check_params, + required_approvals: params[:required_approvals] || 0, + enable_approvals_whitelist: enable_approvals_whitelist_params, + approvals_whitelist_user_i_ds: approvals_whitelist_user_i_ds_params, + # approvals_whitelist_team_i_ds: approvals_whitelist_team_i_ds_params, + block_on_rejected_reviews: block_on_rejected_reviews_params, + dismiss_stale_approvals: dismiss_stale_approvals_params, + require_signed_commits: require_signed_commits_params, + block_on_outdated_branch: block_on_outdated_branch_params + } + end + + def enable_status_check_params + str_to_boolean(params[:enable_status_check] || false) + end + + def enable_approvals_whitelist_params + str_to_boolean(params[:enable_approvals_whitelist] || false) + end + def block_on_rejected_reviews_params + str_to_boolean(params[:block_on_rejected_reviews] || false) + end + + def dismiss_stale_approvals_params + str_to_boolean(params[:dismiss_stale_approvals] || false) + end + + def require_signed_commits_params + str_to_boolean(params[:require_signed_commits] || false) + end + + def block_on_outdated_branch_params + str_to_boolean(params[:block_on_outdated_branch] || false) + end + + def can_push_params + return false if !can_push? + return true if enable_whitelist? + str_to_boolean(params[:enable_push]) + end + + def enable_whitelist_params + return false if !can_push? + str_to_boolean(params[:enable_push_whitelist]) + end + + def whitelist_user_i_ds_params + return [] if !can_push? + user_ids(get_push_whitelist_usernames) + end + + def whitelist_team_i_ds_params + # params[:push_whitelist_usernames] + end + + def enable_merge_whitelist_params + str_to_boolean(params[:enable_merge_whitelist] || false) + end + + def merge_whitelist_user_i_ds_params + returtn [] if !enable_merge_whitelist? + user_ids(get_merge_whitelist_usernames) + end + + def merge_whitelist_team_i_ds_params + params[:merge_whitelist_teams] + end + + def approvals_whitelist_user_i_ds_params + return [] if !enable_approvals_whitelist? + user_ids(get_approvals_whitelist_usernames) + end + + def approvals_whitelist_team_i_ds_params + params[:approvals_whitelist_teams] + end + + def user_ids(names) + member_ids & names_by_params(names) + end + + def member_ids + @repository.project.writable_members.map(&:user_id) + end + + def names_by_params(names) + User.where(login: names.to_a).ids + end + + def get_push_whitelist_usernames + return [] if !can_push? || !enable_whitelist? + filter_empty_element Array(params[:push_whitelist_usernames]) + end + + def get_merge_whitelist_usernames + return [] if !enable_merge_whitelist? + filter_empty_element Array(params[:merge_whitelist_usernames]) + end + + def get_approvals_whitelist_usernames + return [] if !enable_approvals_whitelist? + filter_empty_element Array(params[:approvals_whitelist_usernames]) + end + + def check_users!(names) + names.each {|name| + check_user!(name) + break + } + end + + def check_user!(name) + user_exist = User.exists?(login: name) + raise Error, "user '#{name}' does not exist" if !user_exist + end + + def can_push? + str_to_boolean(params[:enable_push]) === true + end + + def enable_whitelist? + str_to_boolean(params[:enable_push_whitelist]) === true + end + + def enable_merge_whitelist? + str_to_boolean(params[:enable_merge_whitelist]) === true + end + + def enable_approvals_whitelist? + str_to_boolean(params[:enable_approvals_whitelist]) === true + end + + def filter_empty_element(array) + array.reject { |e| e.to_s.empty? } + end + + def save_gitea_protected_branch! + @gitea_protected_branch ||= Gitea::Repository::ProtectedBranches::CreateService.call(@owner.login, + @repository.identifier,gitea_protected_branch_params, @owner.gitea_token) + + raise Error, @gitea_protected_branch[:message] if @gitea_protected_branch[:status] != :success + end + + def gitea_protected_branch_saved? + @gitea_protected_branch[:status] === success + end + + def gitea_protected_branch + @gitea_protected_branch[:body] + end + + def gitea_protected_branch_params + { + approvals_whitelist_username: get_approvals_whitelist_usernames, + branch_name: params[:branch_name], + enable_approvals_whitelist: enable_approvals_whitelist_params, + enable_merge_whitelist: enable_merge_whitelist_params, + enable_push: can_push_params, + enable_push_whitelist: enable_whitelist_params, + enable_status_check: enable_status_check_params, + # merge_whitelist_teams: [], + merge_whitelist_usernames: get_merge_whitelist_usernames, + # protected_file_patterns: string, + # push_whitelist_deploy_keys: true, + # push_whitelist_teams: [], + push_whitelist_usernames: get_push_whitelist_usernames, + block_on_rejected_reviews: block_on_rejected_reviews_params, + dismiss_stale_approvals: dismiss_stale_approvals_params, + require_signed_commits: require_signed_commits_params, + block_on_outdated_branch: block_on_outdated_branch_params + + } + end + + def validate! + protected_branch_exists = repository.protected_branches.exists?(params[:branch_name]) + raise Error, "Protected branch '#{branch_name}' already exists" if protected_branch_exists + + check_users!(get_push_whitelist_usernames) if get_push_whitelist_usernames.any? + check_users!(get_merge_whitelist_usernames) if get_merge_whitelist_usernames.any? + check_users!(get_approvals_whitelist_usernames) if get_approvals_whitelist_usernames.any? + + raise Error, '分支名称不能为空' if params[:branch_name].blank? + + end + end + + def error(errors, award: nil, status: nil) + errors = Array.wrap(errors) + + super(errors.to_sentence.presence, status).merge({ + award: award, + errors: errors + }) + end +end diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb new file mode 100644 index 000000000..02112a188 --- /dev/null +++ b/app/services/protected_branches/create_service.rb @@ -0,0 +1,23 @@ +module ProtectedBranches + class CreateService < ProtectedBranches::BaseService + def call + validate! + + ProtectedBranch.transaction do + save_gitea_protected_branch! + save_protected_branch! + end + + protected_branch + end + + private + def protected_branch + @protected_branch ||= repository.protected_branches.new(protected_branch_params) + end + + def save_protected_branch! + protected_branch.save + end + end +end diff --git a/app/services/protected_branches/destroy_service.rb b/app/services/protected_branches/destroy_service.rb new file mode 100644 index 000000000..503adf9ae --- /dev/null +++ b/app/services/protected_branches/destroy_service.rb @@ -0,0 +1,26 @@ +module ProtectedBranches + class DestroyService < ProtectedBranches::BaseService + def call + protected_branch.destroy! if success? + rescue ActiveRecord::RecordNotFound + raise Error, '404' + rescue => ex + Rails.logger.info ex + raise Error, ex + end + + private + def protected_branch + @protected_branch ||= @repository.protected_branches.find_by!(branch_name: @params) + end + + def success? + result = Gitea::Repository::ProtectedBranches::DestroyService.call(@owner.login, + @repository.identifier, protected_branch.branch_name, @owner.gitea_token) + + return true if result[:status] === :success + raise Error, result[:message] + end + + end +end diff --git a/app/services/protected_branches/edit_service.rb b/app/services/protected_branches/edit_service.rb new file mode 100644 index 000000000..a6373a86a --- /dev/null +++ b/app/services/protected_branches/edit_service.rb @@ -0,0 +1,35 @@ +module ProtectedBranches + class EditService < ProtectedBranches::BaseService + def call + validate_branch_name! + + protected_branch + rescue => ex + Rails.logger.info ex + raise Error, ex + end + + private + def protected_branch + branch = get_common_branch + protected_branch ||= @repository.protected_branches.find_by(branch_name: branch_name) + [branch, protected_branch] + end + + def get_common_branch + result = Gitea::Repository::Branches::GetService.call(@owner.login, + @repository.identifier, branch_name, @owner.gitea_token) + + raise Error, '404' if result[:status] == :error + result + end + + def validate_branch_name! + raise Error, '分支名称不能为空' if branch_name.blank? + end + + def branch_name + params + end + end +end diff --git a/app/services/protected_branches/get_service.rb b/app/services/protected_branches/get_service.rb new file mode 100644 index 000000000..7dcfe9a65 --- /dev/null +++ b/app/services/protected_branches/get_service.rb @@ -0,0 +1,23 @@ +module ProtectedBranches + class GetService < ProtectedBranches::BaseService + def call + validate_branch_name! + + protected_branch + rescue ActiveRecord::RecordNotFound + raise Error, '404' + rescue => ex + Rails.logger.info ex + raise Error, ex + end + + private + def protected_branch + @protected_branch ||= @repository.protected_branches.find_by!(branch_name: params[:branch_name]) + end + + def validate_branch_name! + raise Error, '分支名称不能为空' if params[:branch_name].blank? + end + end +end diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb new file mode 100644 index 000000000..53cc56b55 --- /dev/null +++ b/app/services/protected_branches/update_service.rb @@ -0,0 +1,31 @@ +module ProtectedBranches + class UpdateService < ProtectedBranches::BaseService + def call + validate! + protected_branch.update(protected_branch_params) if success? + + protected_branch + + rescue ActiveRecord::RecordNotFound + raise Error, '404' + rescue => ex + Rails.logger.info ex + raise Error, ex + end + + private + def protected_branch + @protected_branch ||= @repository.protected_branches.find_by!(branch_name: params[:branch_name]) + end + + def success? + result = Gitea::Repository::ProtectedBranches::UpdateService.call(@owner.login, @repository.identifier, + protected_branch.branch_name, gitea_protected_branch_params, @owner.gitea_token) + + + return true if result[:status] === :success + raise Error, result[:message] + end + + end +end diff --git a/app/views/protected_branches/_protected_branch.json.jbuilder b/app/views/protected_branches/_protected_branch.json.jbuilder new file mode 100644 index 000000000..5f1f0428a --- /dev/null +++ b/app/views/protected_branches/_protected_branch.json.jbuilder @@ -0,0 +1,16 @@ +json.branch_name protected_branch.branch_name +json.enable_push protected_branch.can_push +json.required_approvals protected_branch.required_approvals +json.enable_status_check protected_branch.enable_status_check +json.enable_push_whitelist protected_branch.enable_whitelist +json.enable_merge_whitelist protected_branch.enable_merge_whitelist +json.enable_approvals_whitelist protected_branch.enable_approvals_whitelist +json.dismiss_stale_approvals protected_branch.dismiss_stale_approvals +json.block_on_rejected_reviews protected_branch.block_on_rejected_reviews +json.block_on_outdated_branch protected_branch.block_on_outdated_branch +json.require_signed_commits protected_branch.require_signed_commits +json.merge_whitelist_usernames protected_branch.merge_whitelist_usernames +json.push_whitelist_usernames protected_branch.push_whitelist_usernames +json.approvals_whitelist_usernames protected_branch.approvals_whitelist_usernames +json.created_at protected_branch.created_at.strftime("%Y-%m-%d %H:%M") +json.updated_at protected_branch.updated_at.strftime("%Y-%m-%d %H:%M") diff --git a/app/views/protected_branches/create.json.jbuilder b/app/views/protected_branches/create.json.jbuilder new file mode 100644 index 000000000..5354db7c3 --- /dev/null +++ b/app/views/protected_branches/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! @protected_branch, as: :protected_branch diff --git a/app/views/protected_branches/edit.json.jbuilder b/app/views/protected_branches/edit.json.jbuilder new file mode 100644 index 000000000..fc5e137d9 --- /dev/null +++ b/app/views/protected_branches/edit.json.jbuilder @@ -0,0 +1,9 @@ +json.branch_name @branch['name'] +json.protected @branch['protected'] +json.protected_branch do + if @protected_branch + json.partial! @protected_branch, as: :protected_branch + else + json.nil! + end +end diff --git a/app/views/protected_branches/index.json.jbuilder b/app/views/protected_branches/index.json.jbuilder new file mode 100644 index 000000000..eed1eb5a6 --- /dev/null +++ b/app/views/protected_branches/index.json.jbuilder @@ -0,0 +1,2 @@ +json.total_count @total_count +json.protected_branches @protected_branches, partial: 'protected_branches/protected_branch', as: :protected_branch diff --git a/app/views/protected_branches/show.json.jbuilder b/app/views/protected_branches/show.json.jbuilder new file mode 100644 index 000000000..5354db7c3 --- /dev/null +++ b/app/views/protected_branches/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! @protected_branch, as: :protected_branch diff --git a/app/views/protected_branches/update.json.jbuilder b/app/views/protected_branches/update.json.jbuilder new file mode 100644 index 000000000..5354db7c3 --- /dev/null +++ b/app/views/protected_branches/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! @protected_branch, as: :protected_branch diff --git a/config/application.rb b/config/application.rb index fece76ebb..a09b08def 100644 --- a/config/application.rb +++ b/config/application.rb @@ -19,6 +19,8 @@ module Educoderplus # # config.educoder = config_for(:configuration) + # Custom directories with classes and modules you want to be autoloadable. + config.active_record.default_timezone = :utc config.time_zone = 'Beijing' diff --git a/config/routes.rb b/config/routes.rb index 163eb2d2e..145c20a7d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -323,6 +323,34 @@ Rails.application.routes.draw do end end + # protected_branches + scope do + get( + '/protected_branches/', + to: 'protected_branches#index' + ) + get( + '/protected_branches/:branch_name', + to: 'protected_branches#show' + ) + get( + '/protected_branches/:branch_name/edit', + to: 'protected_branches#edit' + ) + delete( + '/protected_branches/:branch_name', + to: 'protected_branches#destroy' + ) + post( + '/protected_branches', + to: 'protected_branches#create' + ) + patch( + '/protected_branches/:branch_name', + to: 'protected_branches#update' + ) + end + resources :issues do collection do get :commit_issues diff --git a/db/migrate/20201130081039_create_protected_branches.rb b/db/migrate/20201130081039_create_protected_branches.rb new file mode 100644 index 000000000..26ce2b1b1 --- /dev/null +++ b/db/migrate/20201130081039_create_protected_branches.rb @@ -0,0 +1,31 @@ +class CreateProtectedBranches < ActiveRecord::Migration[5.2] + def change + create_table :protected_branches do |t| + t.integer :repo_id + t.string :branch_name, default: "" + t.boolean :can_push, default: false, null: false + t.boolean :enable_whitelist, default: false + t.text :whitelist_user_i_ds + t.text :whitelist_team_i_ds + t.boolean :enable_merge_whitelist, default: false, null: false + t.boolean :whitelist_deploy_keys, default: false, null: false + t.text :merge_whitelist_user_i_ds + t.text :merge_whitelist_team_i_ds + t.boolean :enable_status_check, default: false, null: false + t.text :status_check_contexts + t.text :approvals_whitelist_user_i_ds + t.text :approvals_whitelist_team_i_ds + t.integer :required_approvals, default: 0 + t.boolean :enable_approvals_whitelist, default: false, null: false + t.boolean :block_on_rejected_reviews, default: false, null: false + t.boolean :dismiss_stale_approvals, default: false, null: false + t.boolean :require_signed_commits, default: false, null: false + t.text :protected_file_patterns + t.boolean :block_on_outdated_branch, default: false, null: false + + t.timestamps + end + + add_index :protected_branches, :repo_id + end +end diff --git a/spec/controllers/protected_branches_controller_spec.rb b/spec/controllers/protected_branches_controller_spec.rb new file mode 100644 index 000000000..086b746b0 --- /dev/null +++ b/spec/controllers/protected_branches_controller_spec.rb @@ -0,0 +1,40 @@ +require 'rails_helper' + +RSpec.describe ProtectedBranchesController, type: :controller do + + describe "GET #index" do + it "returns http success" do + get :index + expect(response).to have_http_status(:success) + end + end + + describe "GET #create" do + it "returns http success" do + get :create + expect(response).to have_http_status(:success) + end + end + + describe "GET #edit" do + it "returns http success" do + get :edit + expect(response).to have_http_status(:success) + end + end + + describe "GET #update" do + it "returns http success" do + get :update + expect(response).to have_http_status(:success) + end + end + + describe "GET #destroy" do + it "returns http success" do + get :destroy + expect(response).to have_http_status(:success) + end + end + +end diff --git a/spec/helpers/protected_branches_helper_spec.rb b/spec/helpers/protected_branches_helper_spec.rb new file mode 100644 index 000000000..54c22c2f3 --- /dev/null +++ b/spec/helpers/protected_branches_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the ProtectedBranchesHelper. For example: +# +# describe ProtectedBranchesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe ProtectedBranchesHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb new file mode 100644 index 000000000..751a60c2e --- /dev/null +++ b/spec/models/protected_branch_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe ProtectedBranch, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/protected_branches/create.html.erb_spec.rb b/spec/views/protected_branches/create.html.erb_spec.rb new file mode 100644 index 000000000..f3c375336 --- /dev/null +++ b/spec/views/protected_branches/create.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "protected_branches/create.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/protected_branches/destroy.html.erb_spec.rb b/spec/views/protected_branches/destroy.html.erb_spec.rb new file mode 100644 index 000000000..c6570af3d --- /dev/null +++ b/spec/views/protected_branches/destroy.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "protected_branches/destroy.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/protected_branches/edit.html.erb_spec.rb b/spec/views/protected_branches/edit.html.erb_spec.rb new file mode 100644 index 000000000..c4d008460 --- /dev/null +++ b/spec/views/protected_branches/edit.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "protected_branches/edit.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/protected_branches/index.html.erb_spec.rb b/spec/views/protected_branches/index.html.erb_spec.rb new file mode 100644 index 000000000..e99ac1f03 --- /dev/null +++ b/spec/views/protected_branches/index.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "protected_branches/index.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/protected_branches/update.html.erb_spec.rb b/spec/views/protected_branches/update.html.erb_spec.rb new file mode 100644 index 000000000..80aa6baae --- /dev/null +++ b/spec/views/protected_branches/update.html.erb_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe "protected_branches/update.html.erb", type: :view do + pending "add some examples to (or delete) #{__FILE__}" +end