mirror of
https://gitea.com/gitea/go-sdk.git
synced 2024-06-02 18:21:26 +05:30
Compare commits
140 commits
gitea/v0.1
...
main
Author | SHA1 | Date | |
---|---|---|---|
8110db1e6d | |||
83c73e79a5 | |||
6c0938dc15 | |||
0a99bda0cb | |||
36723e11c8 | |||
a991e370c5 | |||
bb25c989a0 | |||
18e5522f90 | |||
bad4de0196 | |||
9ec1b82849 | |||
bf71ec2fd9 | |||
001d5fad51 | |||
5d0143e4e7 | |||
53f735b911 | |||
98b424a8af | |||
2abfdd7ba6 | |||
8f4d3d8916 | |||
bde56fb9bb | |||
228d6931f4 | |||
c8745e1599 | |||
0e0a4691b6 | |||
e23e8aa300 | |||
d5e174e5b5 | |||
0141b499c9 | |||
091528835f | |||
ae1583a7be | |||
93cfc320cf | |||
0fe2ace132 | |||
f4be505bf6 | |||
315cf7aac8 | |||
970776d1c1 | |||
24a404e561 | |||
3c5cad657b | |||
6d1bcd107f | |||
7511c6d3cd | |||
846d53e967 | |||
df1269f18d | |||
2c35c11772 | |||
2d9ee57af1 | |||
1c9c71d84a | |||
8f846bdb9b | |||
aef4e5e2bd | |||
7aeaaa45e1 | |||
e5b5b3447a | |||
5852fcc4a3 | |||
a0127ed0e7 | |||
cc14c63ccc | |||
de34275bb6 | |||
e5f0c189f2 | |||
359c771ce3 | |||
99a9de3172 | |||
23e1316337 | |||
f3ebdb8afe | |||
319a978c6c | |||
adebf1cd11 | |||
8fab37e740 | |||
ce9d46682d | |||
89a4b0be6e | |||
ad3580e44d | |||
559cc2fb2a | |||
468d48c978 | |||
223f0a75e0 | |||
2e8bb53b30 | |||
321bd56d93 | |||
2616d10528 | |||
603e4358f8 | |||
a56a62a4df | |||
f3162e5333 | |||
22f2853429 | |||
f0663b3c13 | |||
29e6eb37fe | |||
36c7f8c8de | |||
73182f46eb | |||
3ff2c60a86 | |||
d19bc07721 | |||
635de1b821 | |||
a87a2c7390 | |||
5d54a04f8d | |||
0fb32ac11f | |||
bf0e0883a8 | |||
4debc6ca4b | |||
747c78e835 | |||
71d2bf01d1 | |||
b81847d03d | |||
63e97e127c | |||
f5cc003900 | |||
dee0475f01 | |||
79f379313c | |||
230fd25196 | |||
13d2d23dfc | |||
cc08994d13 | |||
ddc879e297 | |||
a5ff184297 | |||
fb42ca1c8d | |||
42fed7165c | |||
e11a4f7f3b | |||
00ddb6b116 | |||
4e4d6dd731 | |||
7de882b744 | |||
0599915e88 | |||
120aa6234e | |||
4fac753f78 | |||
61c9b900e1 | |||
6b6fdd91ce | |||
e0cf20a456 | |||
ff82113459 | |||
a968e32ca1 | |||
b0ee740ed0 | |||
32b0722f98 | |||
757f8bdb90 | |||
95ed973c8d | |||
8947cd3b00 | |||
c5a981333c | |||
fd7e38f7b5 | |||
063d97fe74 | |||
ff00c13597 | |||
30e7dc9ccb | |||
97e61e5a8a | |||
e34d140607 | |||
7ddbf1a015 | |||
cd52f0058b | |||
68eec69f47 | |||
fa7edc6f46 | |||
6ea6e887f2 | |||
60293eb2d1 | |||
aa13606bc6 | |||
b00ea89ffe | |||
9c81fa936f | |||
623a9eb4bf | |||
3009babf7a | |||
378e711f7a | |||
8c9d3985b4 | |||
cb66dbb487 | |||
3c8ff9d91c | |||
2afffe1ca9 | |||
61f89c458f | |||
3226ac2e53 | |||
779e9330e9 | |||
302993adb8 | |||
3d7419dabb |
|
@ -10,11 +10,11 @@ base-url: https://gitea.com
|
||||||
|
|
||||||
# Changelog groups and which labeled PRs to add to each group
|
# Changelog groups and which labeled PRs to add to each group
|
||||||
groups:
|
groups:
|
||||||
-
|
-
|
||||||
name: BREAKING
|
name: BREAKING
|
||||||
labels:
|
labels:
|
||||||
- kind/breaking
|
- kind/breaking
|
||||||
-
|
-
|
||||||
name: FEATURES
|
name: FEATURES
|
||||||
labels:
|
labels:
|
||||||
- kind/feature
|
- kind/feature
|
||||||
|
@ -22,7 +22,7 @@ groups:
|
||||||
name: BUGFIXES
|
name: BUGFIXES
|
||||||
labels:
|
labels:
|
||||||
- kind/bug
|
- kind/bug
|
||||||
-
|
-
|
||||||
name: ENHANCEMENTS
|
name: ENHANCEMENTS
|
||||||
labels:
|
labels:
|
||||||
- kind/enhancement
|
- kind/enhancement
|
||||||
|
@ -32,26 +32,26 @@ groups:
|
||||||
name: SECURITY
|
name: SECURITY
|
||||||
labels:
|
labels:
|
||||||
- kind/security
|
- kind/security
|
||||||
-
|
-
|
||||||
name: TESTING
|
name: TESTING
|
||||||
labels:
|
labels:
|
||||||
- kind/testing
|
- kind/testing
|
||||||
-
|
-
|
||||||
name: TRANSLATION
|
name: TRANSLATION
|
||||||
labels:
|
labels:
|
||||||
- kind/translation
|
- kind/translation
|
||||||
-
|
-
|
||||||
name: BUILD
|
name: BUILD
|
||||||
labels:
|
labels:
|
||||||
- kind/build
|
- kind/build
|
||||||
- kind/lint
|
- kind/lint
|
||||||
-
|
-
|
||||||
name: DOCS
|
name: DOCS
|
||||||
labels:
|
labels:
|
||||||
- kind/docs
|
- kind/docs
|
||||||
-
|
-
|
||||||
name: MISC
|
name: MISC
|
||||||
default: true
|
default: true
|
||||||
|
|
||||||
# regex indicating which labels to skip for the changelog
|
# regex indicating which labels to skip for the changelog
|
||||||
skip-labels: skip-changelog|backport\/.+
|
skip-labels: skip-changelog|backport\/.+|has/backport
|
||||||
|
|
69
.drone.yml
69
.drone.yml
|
@ -1,69 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: default
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
workspace:
|
|
||||||
base: /go
|
|
||||||
path: src/code.gitea.io/sdk
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: gitea
|
|
||||||
image: gitea/gitea:latest
|
|
||||||
detach: true
|
|
||||||
commands:
|
|
||||||
- mkdir -p /tmp/conf/
|
|
||||||
- mkdir -p /tmp/data/
|
|
||||||
- echo "[security]" > /tmp/conf/app.ini
|
|
||||||
- echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> /tmp/conf/app.ini
|
|
||||||
- echo "INSTALL_LOCK = true" >> /tmp/conf/app.ini
|
|
||||||
- echo "SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo" >> /tmp/conf/app.ini
|
|
||||||
- echo "PASSWORD_COMPLEXITY = off" >> /tmp/conf/app.ini
|
|
||||||
- echo "[database]" >> /tmp/conf/app.ini
|
|
||||||
- echo "DB_TYPE = sqlite3" >> /tmp/conf/app.ini
|
|
||||||
- echo "[repository]" >> /tmp/conf/app.ini
|
|
||||||
- echo "ROOT = /tmp/data/" >> /tmp/conf/app.ini
|
|
||||||
- echo "[server]" >> /tmp/conf/app.ini
|
|
||||||
- echo "ROOT_URL = http://gitea:3000" >> /tmp/conf/app.ini
|
|
||||||
- gitea migrate -c /tmp/conf/app.ini
|
|
||||||
- gitea admin create-user --username=test01 --password=test01 --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c /tmp/conf/app.ini
|
|
||||||
- gitea web -c /tmp/conf/app.ini
|
|
||||||
|
|
||||||
- name: testing
|
|
||||||
pull: always
|
|
||||||
image: golang:1.14
|
|
||||||
environment:
|
|
||||||
GOPROXY: "https://goproxy.cn"
|
|
||||||
GO111MODULE: "on"
|
|
||||||
HTTP_PROXY: ""
|
|
||||||
GITEA_SDK_TEST_URL: "http://gitea:3000"
|
|
||||||
GITEA_SDK_TEST_USERNAME: "test01"
|
|
||||||
GITEA_SDK_TEST_PASSWORD: "test01"
|
|
||||||
#GITEA_SDK_TEST_RUN_GITEA: "true"
|
|
||||||
commands:
|
|
||||||
- make clean
|
|
||||||
- make vet
|
|
||||||
- make revive
|
|
||||||
- make build
|
|
||||||
- curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance
|
|
||||||
- make test
|
|
||||||
|
|
||||||
- name: discord
|
|
||||||
pull: always
|
|
||||||
image: appleboy/drone-discord:1.0.0
|
|
||||||
environment:
|
|
||||||
DISCORD_WEBHOOK_ID:
|
|
||||||
from_secret: discord_webhook_id
|
|
||||||
DISCORD_WEBHOOK_TOKEN:
|
|
||||||
from_secret: discord_webhook_token
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
- pull_request
|
|
||||||
status:
|
|
||||||
- changed
|
|
||||||
- failure
|
|
52
.gitea/workflows/testing.yml
Normal file
52
.gitea/workflows/testing.yml
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
name: testing
|
||||||
|
|
||||||
|
on:
|
||||||
|
- pull_request
|
||||||
|
- push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
testing:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GOPROXY: "https://goproxy.io"
|
||||||
|
GO111MODULE: "on"
|
||||||
|
HTTP_PROXY: ""
|
||||||
|
GITEA_SDK_TEST_URL: "http://gitea:3000"
|
||||||
|
GITEA_SDK_TEST_USERNAME: "test01"
|
||||||
|
GITEA_SDK_TEST_PASSWORD: "test01"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ">=1.21"
|
||||||
|
check-latest: true
|
||||||
|
- run: make clean
|
||||||
|
- run: make vet
|
||||||
|
- run: make ci-lint
|
||||||
|
- run: make build
|
||||||
|
- run: curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance
|
||||||
|
- run: make test
|
||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
image: gitea/gitea:1.22.0-rc1
|
||||||
|
cmd:
|
||||||
|
- bash
|
||||||
|
- -c
|
||||||
|
- >-
|
||||||
|
mkdir -p /tmp/conf/
|
||||||
|
&& mkdir -p /tmp/data/
|
||||||
|
&& echo "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT = true" > /tmp/conf/app.ini
|
||||||
|
&& echo "[security]" >> /tmp/conf/app.ini
|
||||||
|
&& echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> /tmp/conf/app.ini
|
||||||
|
&& echo "INSTALL_LOCK = true" >> /tmp/conf/app.ini
|
||||||
|
&& echo "SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo" >> /tmp/conf/app.ini
|
||||||
|
&& echo "PASSWORD_COMPLEXITY = off" >> /tmp/conf/app.ini
|
||||||
|
&& echo "[database]" >> /tmp/conf/app.ini
|
||||||
|
&& echo "DB_TYPE = sqlite3" >> /tmp/conf/app.ini
|
||||||
|
&& echo "[repository]" >> /tmp/conf/app.ini
|
||||||
|
&& echo "ROOT = /tmp/data/" >> /tmp/conf/app.ini
|
||||||
|
&& echo "[server]" >> /tmp/conf/app.ini
|
||||||
|
&& echo "ROOT_URL = http://gitea:3000" >> /tmp/conf/app.ini
|
||||||
|
&& gitea migrate -c /tmp/conf/app.ini
|
||||||
|
&& gitea admin user create --username=test01 --password=test01 --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c /tmp/conf/app.ini
|
||||||
|
&& gitea web -c /tmp/conf/app.ini
|
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
|
@ -1,7 +1,7 @@
|
||||||
Please check the following:
|
Please check the following:
|
||||||
|
|
||||||
1. Make sure you are targeting the `master` branch, pull requests on release branches are only allowed for bug fixes.
|
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for bug fixes.
|
||||||
2. Read contributing guidelines: https://gitea.com/gitea/go-sdk/src/branch/master/CONTRIBUTING.md
|
2. Read contributing guidelines: https://gitea.com/gitea/go-sdk/src/branch/main/CONTRIBUTING.md
|
||||||
3. Describe what your pull request does and which issue you're targeting (if any)
|
3. Describe what your pull request does and which issue you're targeting (if any)
|
||||||
|
|
||||||
**You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.**
|
**You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.**
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -18,6 +18,7 @@ _cgo_gotypes.go
|
||||||
_cgo_export.*
|
_cgo_export.*
|
||||||
|
|
||||||
_testmain.go
|
_testmain.go
|
||||||
|
gitea-vet
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
|
|
105
CHANGELOG.md
105
CHANGELOG.md
|
@ -1,6 +1,107 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [v0.13.0](https://gitea.com/gitea/go-sdk/releases/tag/v0.13.0) - 2020-09-15
|
## [v0.15.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.15.1) - 2022-01-04
|
||||||
|
|
||||||
|
* FEATURES
|
||||||
|
* Add ignoreVersion & manuall version set option (#560) (#562)
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix version string for next release (#559)
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.15.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.15.0) - 2021-08-13
|
||||||
|
|
||||||
|
* BREAKING
|
||||||
|
* Introduce NotifySubjectState (#520)
|
||||||
|
* Drop deprecations (#503)
|
||||||
|
* FEATURES
|
||||||
|
* Add Repo Team Management Functions (#537)
|
||||||
|
* Add CreateRepoFromTemplate (#536)
|
||||||
|
* Add GetReviewers & GetAssignees (#534)
|
||||||
|
* Add GetTag, GetAnnotatedTag & CreateTag (#533)
|
||||||
|
* Add GetUserSettings & UpdateUserSettings (#531)
|
||||||
|
* Add ListPullRequestCommits (#530)
|
||||||
|
* Add GetUserByID (#513)
|
||||||
|
* Add GetRepoByID (#511)
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Update List Options (#527)
|
||||||
|
* Update Structs (#524)
|
||||||
|
* ListFunctions: option to disable pagination (#509)
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.14.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.14.1) - 2021-06-30
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix setDefaults (#508) (#510)
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.14.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.14.0) - 2021-03-21
|
||||||
|
|
||||||
|
* BREAKING
|
||||||
|
* Update Structs (#486)
|
||||||
|
* Added repo ListContents and changed GetContents doc to talk about a single file (#485)
|
||||||
|
* Remove & Rename TrackedTimes list functions (#467)
|
||||||
|
* UrlEscape Function Arguments used in UrlPath (#273)
|
||||||
|
* FEATURES
|
||||||
|
* Add Create/Delete ReviewRequests (#493)
|
||||||
|
* Add Un-/DismissPullReview funcs (#489)
|
||||||
|
* Add Repo Un-Star Functions (#483)
|
||||||
|
* introduce Client.GetArchiveReader (#476)
|
||||||
|
* Add DeleteRepoTag function (#461)
|
||||||
|
* Add GetReleaseByTag (#427)
|
||||||
|
* BUGFIXES
|
||||||
|
* Handle Contents Edge-Case (#492)
|
||||||
|
* Fix GetCombinedStatus() (#470)
|
||||||
|
* Use Predefind Versions & Compare Function (#442)
|
||||||
|
* Return resp on NotFound too (#428)
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Add workaround to get head branch sha of pulls with deleted head branch (#498)
|
||||||
|
* GetFile: Use "ref" in-query if posible (#491)
|
||||||
|
* Add DeleteTag & Correct DeleteReleaseByTag (#488)
|
||||||
|
* Add html_url field to Release struct (#477)
|
||||||
|
* Add Ref to Issue structs (#466)
|
||||||
|
* Update Issue Struct (#458)
|
||||||
|
* Use sync.Once for loading ServerVersion (#456)
|
||||||
|
* Add Gitea2Gitea Migration Support (#454)
|
||||||
|
* Add Helper for Optional Values (#448)
|
||||||
|
* Update CreateRepoOption struct (#445)
|
||||||
|
* Update CommitMeta Struct (#434)
|
||||||
|
* Update Struct NotificationSubject (#424)
|
||||||
|
* Add Debug Mode (#422)
|
||||||
|
* DOCS
|
||||||
|
* Make Client thread-safe & add docs (#495)
|
||||||
|
* Improve PullReview docs (#469)
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.13.3](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.3) - 2021-03-22
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* Fix GetCombinedStatus() (#470) (#472)
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Add html_url field to Release struct (#477) (#478)
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.13.2](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.2) - 2020-12-07
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* Use Predefind Versions & Compare Function (#442) (#446)
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Add Gitea2Gitea Migration Support (#454) (#455)
|
||||||
|
* Update CreateRepoOption struct (#445) (#447)
|
||||||
|
* Update CommitMeta Struct (#434) (#437)
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.13.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.1) - 2020-09-29
|
||||||
|
|
||||||
|
* FEATURES
|
||||||
|
* Add GetReleaseByTag (#427) (#430)
|
||||||
|
* BUGFIXES
|
||||||
|
* Return http Response on NotFound too (#428) (#429)
|
||||||
|
* ENHANCEMENTS
|
||||||
|
* Update Struct NotificationSubject (#424) (#425)
|
||||||
|
* Add Debug Mode (#422) (#423)
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.13.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.0) - 2020-09-15
|
||||||
|
|
||||||
* BREAKING
|
* BREAKING
|
||||||
* Check Gitea Version Requirement (#419)
|
* Check Gitea Version Requirement (#419)
|
||||||
|
@ -39,7 +140,7 @@
|
||||||
* File Create/Update/Delete detect DefaultBranch if Branch not set for old Versions (#352)
|
* File Create/Update/Delete detect DefaultBranch if Branch not set for old Versions (#352)
|
||||||
* Improve Error Handling (#351)
|
* Improve Error Handling (#351)
|
||||||
|
|
||||||
## [v0.12.2](https://gitea.com/gitea/go-sdk/releases/tag/v0.12.2) - 2020-09-05
|
## [v0.12.2](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.12.2) - 2020-09-05
|
||||||
|
|
||||||
* ENHANCEMENTS
|
* ENHANCEMENTS
|
||||||
* Extend Notification Functions (#381) (#385)
|
* Extend Notification Functions (#381) (#385)
|
||||||
|
|
30
MAINTAINERS
30
MAINTAINERS
|
@ -1,30 +0,0 @@
|
||||||
Alexey Makhov <amakhov@avito.ru> (@makhov)
|
|
||||||
Andrey Nering <andrey.nering@gmail.com> (@andreynering)
|
|
||||||
Bo-Yi Wu <appleboy.tw@gmail.com> (@appleboy)
|
|
||||||
Ethan Koenig <ethantkoenig@gmail.com> (@ethantkoenig)
|
|
||||||
Kees de Vries <bouwko@gmail.com> (@Bwko)
|
|
||||||
Kim Carlbäcker <kim.carlbacker@gmail.com> (@bkcsoft)
|
|
||||||
LefsFlare <nobody@nobody.tld> (@LefsFlarey)
|
|
||||||
Lunny Xiao <xiaolunwen@gmail.com> (@lunny)
|
|
||||||
Matthias Loibl <mail@matthiasloibl.com> (@metalmatze)
|
|
||||||
Morgan Bazalgette <the@howl.moe> (@thehowl)
|
|
||||||
Rachid Zarouali <nobody@nobody.tld> (@xinity)
|
|
||||||
Rémy Boulanouar <admin@dblk.org> (@DblK)
|
|
||||||
Sandro Santilli <strk@kbt.io> (@strk)
|
|
||||||
Thibault Meyer <meyer.thibault@gmail.com> (@0xbaadf00d)
|
|
||||||
Thomas Boerger <thomas@webhippie.de> (@tboerger)
|
|
||||||
Patrick G <geek1011@outlook.com> (@geek1011)
|
|
||||||
Antoine Girard <sapk@sapk.fr> (@sapk)
|
|
||||||
Lauris Bukšis-Haberkorns <lauris@nix.lv> (@lafriks)
|
|
||||||
Jonas Östanbäck <jonas.ostanback@gmail.com> (@cez81)
|
|
||||||
David Schneiderbauer <dschneiderbauer@gmail.com> (@daviian)
|
|
||||||
Peter Žeby <morlinest@gmail.com> (@morlinest)
|
|
||||||
Matti Ranta <matti@mdranta.net> (@techknowlogick)
|
|
||||||
Jonas Franz <info@jonasfranz.software> (@jonasfranz)
|
|
||||||
Alexey Terentyev <axifnx@gmail.com> (@axifive)
|
|
||||||
Lanre Adelowo <yo@lanre.wtf> (@adelowo)
|
|
||||||
Konrad Langenberg <k@knt.li> (@kolaente)
|
|
||||||
He-Long Zhang <outman99@hotmail.com> (@BetaCat0)
|
|
||||||
Andrew Thornton <art27@cantab.net> (@zeripath)
|
|
||||||
John Olheiser <john.olheiser@gmail.com> (@jolheiser)
|
|
||||||
Richard Mahn <rich.mahn@unfoldingword.org> (@richmahn)
|
|
87
Makefile
87
Makefile
|
@ -6,6 +6,36 @@ GITEA_SDK_TEST_URL ?= http://localhost:3000
|
||||||
GITEA_SDK_TEST_USERNAME ?= test01
|
GITEA_SDK_TEST_USERNAME ?= test01
|
||||||
GITEA_SDK_TEST_PASSWORD ?= test01
|
GITEA_SDK_TEST_PASSWORD ?= test01
|
||||||
|
|
||||||
|
PACKAGE := code.gitea.io/sdk/gitea
|
||||||
|
|
||||||
|
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.4.0
|
||||||
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0
|
||||||
|
GITEA_VET_PACKAGE ?= code.gitea.io/gitea-vet@v0.2.1
|
||||||
|
|
||||||
|
GITEA_VERSION := 1.21.10
|
||||||
|
GITEA_DL := https://dl.gitea.com/gitea/$(GITEA_VERSION)/gitea-$(GITEA_VERSION)-
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
GITEA_DL := $(GITEA_DL)linux-
|
||||||
|
|
||||||
|
UNAME_P := $(shell uname -p)
|
||||||
|
ifeq ($(UNAME_P),unknown)
|
||||||
|
GITEA_DL := $(GITEA_DL)amd64
|
||||||
|
endif
|
||||||
|
ifeq ($(UNAME_P),x86_64)
|
||||||
|
GITEA_DL := $(GITEA_DL)amd64
|
||||||
|
endif
|
||||||
|
ifneq ($(filter %86,$(UNAME_P)),)
|
||||||
|
GITEA_DL := $(GITEA_DL)386
|
||||||
|
endif
|
||||||
|
ifneq ($(filter arm%,$(UNAME_P)),)
|
||||||
|
GITEA_DL := $(GITEA_DL)arm-5
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
GITEA_DL := $(GITEA_DL)darwin-10.12-amd64
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: clean test build
|
all: clean test build
|
||||||
|
|
||||||
|
@ -25,26 +55,36 @@ help:
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -r -f test
|
rm -r -f test
|
||||||
$(GO) clean -i ./...
|
cd gitea && $(GO) clean -i ./...
|
||||||
|
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
fmt:
|
fmt:
|
||||||
find . -name "*.go" -type f ! -path "./vendor/*" ! -path "./benchmark/*" | xargs gofmt -s -w
|
find . -name "*.go" -type f | xargs gofmt -s -w; \
|
||||||
|
$(GO) run $(GOFUMPT_PACKAGE) -extra -w ./gitea
|
||||||
|
|
||||||
.PHONY: vet
|
.PHONY: vet
|
||||||
vet:
|
vet:
|
||||||
cd gitea && $(GO) vet ./...
|
# Default vet
|
||||||
|
cd gitea && $(GO) vet $(PACKAGE)
|
||||||
|
# Custom vet
|
||||||
|
cd gitea && $(GO) get $(GITEA_VET_PACKAGE)
|
||||||
|
cd gitea && $(GO) build code.gitea.io/gitea-vet
|
||||||
|
cd gitea && $(GO) vet -vettool=gitea-vet $(PACKAGE)
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: ci-lint
|
||||||
lint:
|
ci-lint:
|
||||||
@echo 'make lint is depricated. Use "make revive" if you want to use the old lint tool, or "make golangci-lint" to run a complete code check.'
|
@cd gitea/; echo -n "gofumpt ...";\
|
||||||
|
diff=$$($(GO) run $(GOFUMPT_PACKAGE) -extra -l .); \
|
||||||
.PHONY: revive
|
if [ -n "$$diff" ]; then \
|
||||||
revive:
|
echo; echo "Not gofumpt-ed"; \
|
||||||
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
exit 1; \
|
||||||
$(GO) get -u github.com/mgechev/revive; \
|
fi; echo " done"; echo -n "golangci-lint ...";\
|
||||||
fi
|
$(GO) run $(GOLANGCI_LINT_PACKAGE) run --timeout 5m; \
|
||||||
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
|
if [ $$? -eq 1 ]; then \
|
||||||
|
echo; echo "Doesn't pass golangci-lint"; \
|
||||||
|
exit 1; \
|
||||||
|
fi; echo " done"; \
|
||||||
|
cd -; \
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
|
@ -57,8 +97,8 @@ test:
|
||||||
test-instance:
|
test-instance:
|
||||||
rm -f -r ${WORK_DIR}/test 2> /dev/null; \
|
rm -f -r ${WORK_DIR}/test 2> /dev/null; \
|
||||||
mkdir -p ${WORK_DIR}/test/conf/ ${WORK_DIR}/test/data/
|
mkdir -p ${WORK_DIR}/test/conf/ ${WORK_DIR}/test/data/
|
||||||
wget "https://dl.gitea.io/gitea/master/gitea-master-linux-amd64" -O ${WORK_DIR}/test/gitea-master; \
|
wget ${GITEA_DL} -O ${WORK_DIR}/test/gitea-main; \
|
||||||
chmod +x ${WORK_DIR}/test/gitea-master; \
|
chmod +x ${WORK_DIR}/test/gitea-main; \
|
||||||
echo "[security]" > ${WORK_DIR}/test/conf/app.ini; \
|
echo "[security]" > ${WORK_DIR}/test/conf/app.ini; \
|
||||||
echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> ${WORK_DIR}/test/conf/app.ini; \
|
echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||||
echo "INSTALL_LOCK = true" >> ${WORK_DIR}/test/conf/app.ini; \
|
echo "INSTALL_LOCK = true" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||||
|
@ -68,11 +108,11 @@ test-instance:
|
||||||
echo "DB_TYPE = sqlite3" >> ${WORK_DIR}/test/conf/app.ini; \
|
echo "DB_TYPE = sqlite3" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||||
echo "[repository]" >> ${WORK_DIR}/test/conf/app.ini; \
|
echo "[repository]" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||||
echo "ROOT = ${WORK_DIR}/test/data/" >> ${WORK_DIR}/test/conf/app.ini; \
|
echo "ROOT = ${WORK_DIR}/test/data/" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||||
echo "[server]" >> /tmp/conf/app.ini; \
|
echo "[server]" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||||
echo "ROOT_URL = ${GITEA_SDK_TEST_URL}" >> /tmp/conf/app.ini; \
|
echo "ROOT_URL = ${GITEA_SDK_TEST_URL}" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||||
${WORK_DIR}/test/gitea-master migrate -c ${WORK_DIR}/test/conf/app.ini; \
|
${WORK_DIR}/test/gitea-main migrate -c ${WORK_DIR}/test/conf/app.ini; \
|
||||||
${WORK_DIR}/test/gitea-master admin create-user --username=${GITEA_SDK_TEST_USERNAME} --password=${GITEA_SDK_TEST_PASSWORD} --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c ${WORK_DIR}/test/conf/app.ini; \
|
${WORK_DIR}/test/gitea-main admin user create --username=${GITEA_SDK_TEST_USERNAME} --password=${GITEA_SDK_TEST_PASSWORD} --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c ${WORK_DIR}/test/conf/app.ini; \
|
||||||
${WORK_DIR}/test/gitea-master web -c ${WORK_DIR}/test/conf/app.ini
|
${WORK_DIR}/test/gitea-main web -c ${WORK_DIR}/test/conf/app.ini
|
||||||
|
|
||||||
.PHONY: bench
|
.PHONY: bench
|
||||||
bench:
|
bench:
|
||||||
|
@ -82,10 +122,3 @@ bench:
|
||||||
build:
|
build:
|
||||||
cd gitea && $(GO) build
|
cd gitea && $(GO) build
|
||||||
|
|
||||||
.PHONY: golangci-lint
|
|
||||||
golangci-lint:
|
|
||||||
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
export BINARY="golangci-lint"; \
|
|
||||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.22.2; \
|
|
||||||
fi
|
|
||||||
golangci-lint run --timeout 5m
|
|
||||||
|
|
12
README.md
12
README.md
|
@ -1,8 +1,14 @@
|
||||||
# Gitea SDK for Go
|
# Gitea SDK for Go
|
||||||
|
|
||||||
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Release](https://raster.shields.io/badge/dynamic/json.svg?label=release&url=https://gitea.com/api/v1/repos/gitea/go-sdk/releases&query=$[0].tag_name)](https://gitea.com/gitea/go-sdk/releases) [![Build Status](https://drone.gitea.com/api/badges/gitea/go-sdk/status.svg)](https://drone.gitea.com/gitea/go-sdk) [![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/Gitea) [![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](http://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com") [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/sdk)](https://goreportcard.com/report/code.gitea.io/sdk) [![GoDoc](https://godoc.org/code.gitea.io/sdk/gitea?status.svg)](https://godoc.org/code.gitea.io/sdk/gitea)
|
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
|
||||||
|
[![Release](https://raster.shields.io/badge/dynamic/json.svg?label=release&url=https://gitea.com/api/v1/repos/gitea/go-sdk/releases&query=$[0].tag_name)](https://gitea.com/gitea/go-sdk/releases)
|
||||||
|
[![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/Gitea)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/sdk)](https://goreportcard.com/report/code.gitea.io/sdk)
|
||||||
|
[![GoDoc](https://godoc.org/code.gitea.io/sdk/gitea?status.svg)](https://godoc.org/code.gitea.io/sdk/gitea)
|
||||||
|
|
||||||
This project acts as a client SDK implementation written in Go to interact with the Gitea API implementation. For further informations take a look at the current [documentation](https://godoc.org/code.gitea.io/sdk/gitea).
|
This project acts as a client SDK implementation written in Go to interact with the Gitea API implementation. For further informations take a look at the current [documentation](https://pkg.go.dev/code.gitea.io/sdk/gitea).
|
||||||
|
|
||||||
|
Note: function arguments are escaped by the SDK.
|
||||||
|
|
||||||
## Use it
|
## Use it
|
||||||
|
|
||||||
|
@ -12,7 +18,7 @@ import "code.gitea.io/sdk/gitea"
|
||||||
|
|
||||||
## Version Requirements
|
## Version Requirements
|
||||||
* go >= 1.13
|
* go >= 1.13
|
||||||
* gitea >= 1.10
|
* gitea >= 1.11
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
# Migration Guide: v0.11 to v0.12
|
# Migration Guide: v0.11 to v0.12
|
||||||
|
|
||||||
v0.12.0 introduces a number of breaking changes, throu it should not be hard to
|
v0.12.0 introduces a number of breaking changes, through which it should not be difficult to migrate.
|
||||||
migrate.
|
Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue.
|
||||||
Just follow this guid and if issues still ocure ask for help on discord or
|
|
||||||
feel free to create an issue.
|
|
||||||
|
|
||||||
<!-- toc -->
|
<!-- toc -->
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
# Migration Guide: v0.12 to v0.13
|
# Migration Guide: v0.12 to v0.13
|
||||||
|
|
||||||
v0.13.0 introduces a number of breaking changes, throu it should not be hard to migrate.
|
v0.13.0 introduces a number of breaking changes, through which it should not be difficult to migrate.
|
||||||
Just follow this guid and if issues still ocure ask for help on discord or
|
Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue.
|
||||||
feel free to create an issue.
|
|
||||||
|
|
||||||
<!-- toc -->
|
<!-- toc -->
|
||||||
|
|
||||||
|
|
46
docs/migrate-v0.13-to-v0.14.md
Normal file
46
docs/migrate-v0.13-to-v0.14.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Migration Guide: v0.13 to v0.14
|
||||||
|
|
||||||
|
v0.14.0 introduces a number of breaking changes, through which it should not be difficult to migrate.
|
||||||
|
Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue.
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
- [Removed Functions (#467)](#removed-functions)
|
||||||
|
- [Renamed Functions (#467)](#renamed-functions)
|
||||||
|
- [New Optional Fields (#486)](#new-optional-fields)
|
||||||
|
- [Arguemnts are escapted by the SDK iteslve now (#273)](#escape-function-arguments)
|
||||||
|
|
||||||
|
<!-- tocstop -->
|
||||||
|
|
||||||
|
## Removed Functions
|
||||||
|
|
||||||
|
- for **GetUserTrackedTimes** and **GetRepoTrackedTimes** use **ListRepoTrackedTimes** with specific options set
|
||||||
|
|
||||||
|
Pulls:
|
||||||
|
- [#467 Remove GetUserTrackedTimes](https://gitea.com/gitea/go-sdk/pulls/467)
|
||||||
|
|
||||||
|
|
||||||
|
## Renamed Functions
|
||||||
|
|
||||||
|
- **ListTrackedTimes** is now **ListIssueTrackedTimes**
|
||||||
|
|
||||||
|
Pulls:
|
||||||
|
- [#467 Remove & Rename TrackedTimes list functions](https://gitea.com/gitea/go-sdk/pulls/467)
|
||||||
|
|
||||||
|
|
||||||
|
## New Optional Fields
|
||||||
|
|
||||||
|
The `EditUserOption` struct has gained several new Optional fields.
|
||||||
|
For example Email type changed from `string` to `*string`.
|
||||||
|
|
||||||
|
The easiest migration path is, to wrap your options with:
|
||||||
|
**OptionalString()**, **OptionalBool()** and **OptionalInt64()**
|
||||||
|
|
||||||
|
Pulls:
|
||||||
|
- [#486 Update Structs](https://gitea.com/gitea/go-sdk/pulls/486)
|
||||||
|
|
||||||
|
|
||||||
|
## Escape Function Arguments
|
||||||
|
|
||||||
|
String arguments like `user`, `repo`, `tag`, ... are now url/path/query escapted as they need.
|
||||||
|
If you had issues and did escape arguments by yourselve you have to remove this now.
|
20
docs/migrate-v0.14-to-v0.15.md
Normal file
20
docs/migrate-v0.14-to-v0.15.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Migration Guide: v0.14 to v0.15
|
||||||
|
|
||||||
|
v0.15.0 introduces a number of API changes, which should be simple to migrate.
|
||||||
|
Just follow this guide and if you still encounter problems, ask for help on Discord or feel free to create an issue.
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
- [Changed Struct Fields (#503) (#520)](#changed-struct-fields)
|
||||||
|
|
||||||
|
<!-- tocstop -->
|
||||||
|
|
||||||
|
## Changed Struct Fields
|
||||||
|
|
||||||
|
- The `State` field at **NotificationSubject** changed from **StateType** to **NotifySubjectState**, it also contains `"open"`, `"closed"` and add `"merged"`.
|
||||||
|
- In **Issue**, **CreateIssueOption** and **EditIssueOption** structs, `Assignee` got removed. Use `Assignees`.
|
||||||
|
- `Type` field at **CreateHookOption** now use **HookType** instead of pure string.
|
||||||
|
|
||||||
|
Pulls:
|
||||||
|
- [#503 Drop deprecations](https://gitea.com/gitea/go-sdk/pulls/503)
|
||||||
|
- [#520 Introduce NotifySubjectState](https://gitea.com/gitea/go-sdk/pulls/520)
|
28
docs/migrate-v0.15-to-v0.16.md
Normal file
28
docs/migrate-v0.15-to-v0.16.md
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Migration Guide: v0.15 to v0.16
|
||||||
|
|
||||||
|
v0.16.0 introduces a number of API changes, which should be simple to migrate.
|
||||||
|
Just follow this guide and if you still encounter problems, ask for help on Discord or feel free to create an issue.
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
- [Upstream API changes](#upstream-api-changes)
|
||||||
|
- [GetPullRequestDiff: add PullRequestDiffOption parameter (#542)](#getpullrequestdiff)
|
||||||
|
|
||||||
|
<!-- tocstop -->
|
||||||
|
|
||||||
|
## Upstream API changes
|
||||||
|
|
||||||
|
As we aim to track API changes in Gitea 1.16 with this SDK release, you may find this [summary listing of changes](https://gitea.com/gitea/go-sdk/issues/558) helpful.
|
||||||
|
|
||||||
|
## GetPullRequestDiff
|
||||||
|
Added new parameter `opts PullRequestDiffOption`. Gitea 1.16 will default to omit binary file changes in diffs; if you still need that information, set `opts.Binary = true`.
|
||||||
|
Related PRs:
|
||||||
|
- [go-sdk#542](https://gitea.com/gitea/go-sdk/pulls/542)
|
||||||
|
- [gitea#17158](https://github.com/go-gitea/gitea/pull/17158)
|
||||||
|
|
||||||
|
## ReadNotification, ReadNotifications, ReadRepoNotifications
|
||||||
|
The function now has a new return argument. The read notifications will now be returned by Gitea 1.16. If you don't require this information, use a blank identifier for the return variable.
|
||||||
|
|
||||||
|
Related PRs:
|
||||||
|
- [go-sdk#590](https://gitea.com/gitea/go-sdk/pulls/590)
|
||||||
|
- [gitea#17064](https://github.com/go-gitea/gitea/pull/17064)
|
|
@ -1,40 +0,0 @@
|
||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestLabels test label related func
|
|
||||||
func TestLabels(t *testing.T) {
|
|
||||||
log.Println("== TestLabels ==")
|
|
||||||
c := newTestClient()
|
|
||||||
repo, err := createTestRepo(t, "LabelTestsRepo", c)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
createOpts := CreateLabelOption{
|
|
||||||
Name: " ",
|
|
||||||
Description: "",
|
|
||||||
Color: "",
|
|
||||||
}
|
|
||||||
err = createOpts.Validate()
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.EqualValues(t, "invalid color format", err.Error())
|
|
||||||
createOpts.Color = "12345f"
|
|
||||||
err = createOpts.Validate()
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.EqualValues(t, "empty name not allowed", err.Error())
|
|
||||||
createOpts.Name = "label one"
|
|
||||||
|
|
||||||
label1, _, err := c.CreateLabel(repo.Owner.UserName, repo.Name, createOpts)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, createOpts.Name, label1.Name)
|
|
||||||
assert.EqualValues(t, createOpts.Color, label1.Color)
|
|
||||||
|
|
||||||
}
|
|
|
@ -25,7 +25,7 @@ type ListCronTaskOptions struct {
|
||||||
|
|
||||||
// ListCronTasks list available cron tasks
|
// ListCronTasks list available cron tasks
|
||||||
func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response, error) {
|
func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
|
@ -36,7 +36,10 @@ func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response,
|
||||||
|
|
||||||
// RunCronTasks run a cron task
|
// RunCronTasks run a cron task
|
||||||
func (c *Client) RunCronTasks(task string) (*Response, error) {
|
func (c *Client) RunCronTasks(task string) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&task); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil)
|
_, resp, err := c.getResponse("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil)
|
||||||
|
|
|
@ -26,6 +26,9 @@ func (c *Client) AdminListOrgs(opt AdminListOrgsOptions) ([]*Organization, *Resp
|
||||||
|
|
||||||
// AdminCreateOrg create an organization
|
// AdminCreateOrg create an organization
|
||||||
func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, *Response, error) {
|
func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
|
@ -12,6 +12,9 @@ import (
|
||||||
|
|
||||||
// AdminCreateRepo create a repo
|
// AdminCreateRepo create a repo
|
||||||
func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, *Response, error) {
|
func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
|
@ -30,8 +30,10 @@ func TestAdminOrg(t *testing.T) {
|
||||||
|
|
||||||
orgs, _, err := c.AdminListOrgs(AdminListOrgsOptions{})
|
orgs, _, err := c.AdminListOrgs(AdminListOrgsOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, orgs, 1)
|
if assert.True(t, len(orgs) >= 1) {
|
||||||
assert.EqualValues(t, newOrg.ID, orgs[0].ID)
|
orgs = orgs[len(orgs)-1:]
|
||||||
|
assert.EqualValues(t, newOrg.ID, orgs[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = c.DeleteOrg(orgName)
|
_, err = c.DeleteOrg(orgName)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -43,7 +45,7 @@ func TestAdminCronTasks(t *testing.T) {
|
||||||
|
|
||||||
tasks, _, err := c.ListCronTasks(ListCronTaskOptions{})
|
tasks, _, err := c.ListCronTasks(ListCronTaskOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, tasks, 15)
|
assert.True(t, len(tasks) > 15)
|
||||||
_, err = c.RunCronTasks(tasks[0].Name)
|
_, err = c.RunCronTasks(tasks[0].Name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,14 +26,15 @@ func (c *Client) AdminListUsers(opt AdminListUsersOptions) ([]*User, *Response,
|
||||||
|
|
||||||
// CreateUserOption create user options
|
// CreateUserOption create user options
|
||||||
type CreateUserOption struct {
|
type CreateUserOption struct {
|
||||||
SourceID int64 `json:"source_id"`
|
SourceID int64 `json:"source_id"`
|
||||||
LoginName string `json:"login_name"`
|
LoginName string `json:"login_name"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
MustChangePassword *bool `json:"must_change_password"`
|
MustChangePassword *bool `json:"must_change_password"`
|
||||||
SendNotify bool `json:"send_notify"`
|
SendNotify bool `json:"send_notify"`
|
||||||
|
Visibility *VisibleType `json:"visibility"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the CreateUserOption struct
|
// Validate the CreateUserOption struct
|
||||||
|
@ -63,25 +64,31 @@ func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error)
|
||||||
|
|
||||||
// EditUserOption edit user options
|
// EditUserOption edit user options
|
||||||
type EditUserOption struct {
|
type EditUserOption struct {
|
||||||
SourceID int64 `json:"source_id"`
|
SourceID int64 `json:"source_id"`
|
||||||
LoginName string `json:"login_name"`
|
LoginName string `json:"login_name"`
|
||||||
FullName string `json:"full_name"`
|
Email *string `json:"email"`
|
||||||
Email string `json:"email"`
|
FullName *string `json:"full_name"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
MustChangePassword *bool `json:"must_change_password"`
|
Description *string `json:"description"`
|
||||||
Website string `json:"website"`
|
MustChangePassword *bool `json:"must_change_password"`
|
||||||
Location string `json:"location"`
|
Website *string `json:"website"`
|
||||||
Active *bool `json:"active"`
|
Location *string `json:"location"`
|
||||||
Admin *bool `json:"admin"`
|
Active *bool `json:"active"`
|
||||||
AllowGitHook *bool `json:"allow_git_hook"`
|
Admin *bool `json:"admin"`
|
||||||
AllowImportLocal *bool `json:"allow_import_local"`
|
AllowGitHook *bool `json:"allow_git_hook"`
|
||||||
MaxRepoCreation *int `json:"max_repo_creation"`
|
AllowImportLocal *bool `json:"allow_import_local"`
|
||||||
ProhibitLogin *bool `json:"prohibit_login"`
|
MaxRepoCreation *int `json:"max_repo_creation"`
|
||||||
AllowCreateOrganization *bool `json:"allow_create_organization"`
|
ProhibitLogin *bool `json:"prohibit_login"`
|
||||||
|
AllowCreateOrganization *bool `json:"allow_create_organization"`
|
||||||
|
Restricted *bool `json:"restricted"`
|
||||||
|
Visibility *VisibleType `json:"visibility"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminEditUser modify user informations
|
// AdminEditUser modify user informations
|
||||||
func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) {
|
func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -92,12 +99,18 @@ func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, erro
|
||||||
|
|
||||||
// AdminDeleteUser delete one user according name
|
// AdminDeleteUser delete one user according name
|
||||||
func (c *Client) AdminDeleteUser(user string) (*Response, error) {
|
func (c *Client) AdminDeleteUser(user string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s", user), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s", user), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminCreateUserPublicKey adds a public key for the user
|
// AdminCreateUserPublicKey adds a public key for the user
|
||||||
func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*PublicKey, *Response, error) {
|
func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*PublicKey, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -109,6 +122,9 @@ func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*Pu
|
||||||
|
|
||||||
// AdminDeleteUserPublicKey deletes a user's public key
|
// AdminDeleteUserPublicKey deletes a user's public key
|
||||||
func (c *Client) AdminDeleteUserPublicKey(user string, keyID int) (*Response, error) {
|
func (c *Client) AdminDeleteUserPublicKey(user string, keyID int) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s/keys/%d", user, keyID), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s/keys/%d", user, keyID), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
38
gitea/agent.go
Normal file
38
gitea/agent.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hasAgent returns true if the ssh agent is available
|
||||||
|
func hasAgent() bool {
|
||||||
|
if _, err := os.Stat(os.Getenv("SSH_AUTH_SOCK")); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAgent returns a ssh agent
|
||||||
|
func GetAgent() (agent.Agent, error) {
|
||||||
|
if !hasAgent() {
|
||||||
|
return nil, fmt.Errorf("no ssh agent available")
|
||||||
|
}
|
||||||
|
|
||||||
|
sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return agent.NewClient(sshAgent), nil
|
||||||
|
}
|
28
gitea/agent_windows.go
Normal file
28
gitea/agent_windows.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davidmz/go-pageant"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hasAgent returns true if pageant is available
|
||||||
|
func hasAgent() bool {
|
||||||
|
return pageant.Available()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAgent returns a ssh agent
|
||||||
|
func GetAgent() (agent.Agent, error) {
|
||||||
|
if !hasAgent() {
|
||||||
|
return nil, fmt.Errorf("no pageant available")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageant.New(), nil
|
||||||
|
}
|
|
@ -31,6 +31,9 @@ type ListReleaseAttachmentsOptions struct {
|
||||||
|
|
||||||
// ListReleaseAttachments list release's attachments
|
// ListReleaseAttachments list release's attachments
|
||||||
func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt ListReleaseAttachmentsOptions) ([]*Attachment, *Response, error) {
|
func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt ListReleaseAttachmentsOptions) ([]*Attachment, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
attachments := make([]*Attachment, 0, opt.PageSize)
|
attachments := make([]*Attachment, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET",
|
resp, err := c.getParsedResponse("GET",
|
||||||
|
@ -40,7 +43,10 @@ func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt Li
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReleaseAttachment returns the requested attachment
|
// GetReleaseAttachment returns the requested attachment
|
||||||
func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64) (*Attachment, *Response, error) {
|
func (c *Client) GetReleaseAttachment(user, repo string, release, id int64) (*Attachment, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
a := new(Attachment)
|
a := new(Attachment)
|
||||||
resp, err := c.getParsedResponse("GET",
|
resp, err := c.getParsedResponse("GET",
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id),
|
fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id),
|
||||||
|
@ -50,6 +56,9 @@ func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64
|
||||||
|
|
||||||
// CreateReleaseAttachment creates an attachment for the given release
|
// CreateReleaseAttachment creates an attachment for the given release
|
||||||
func (c *Client) CreateReleaseAttachment(user, repo string, release int64, file io.Reader, filename string) (*Attachment, *Response, error) {
|
func (c *Client) CreateReleaseAttachment(user, repo string, release int64, file io.Reader, filename string) (*Attachment, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
// Write file to body
|
// Write file to body
|
||||||
body := new(bytes.Buffer)
|
body := new(bytes.Buffer)
|
||||||
writer := multipart.NewWriter(body)
|
writer := multipart.NewWriter(body)
|
||||||
|
@ -79,7 +88,10 @@ type EditAttachmentOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditReleaseAttachment updates the given attachment with the given options
|
// EditReleaseAttachment updates the given attachment with the given options
|
||||||
func (c *Client) EditReleaseAttachment(user, repo string, release int64, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) {
|
func (c *Client) EditReleaseAttachment(user, repo string, release, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&form)
|
body, err := json.Marshal(&form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -90,7 +102,10 @@ func (c *Client) EditReleaseAttachment(user, repo string, release int64, attachm
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteReleaseAttachment deletes the given attachment including the uploaded file
|
// DeleteReleaseAttachment deletes the given attachment including the uploaded file
|
||||||
func (c *Client) DeleteReleaseAttachment(user, repo string, release int64, id int64) (*Response, error) {
|
func (c *Client) DeleteReleaseAttachment(user, repo string, release, id int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
396
gitea/client.go
396
gitea/client.go
|
@ -6,13 +6,15 @@
|
||||||
package gitea
|
package gitea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -23,41 +25,61 @@ var jsonHeader = http.Header{"content-type": []string{"application/json"}}
|
||||||
|
|
||||||
// Version return the library version
|
// Version return the library version
|
||||||
func Version() string {
|
func Version() string {
|
||||||
return "0.13.0"
|
return "0.16.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client represents a Gitea API client.
|
// Client represents a thread-safe Gitea API client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
url string
|
url string
|
||||||
accessToken string
|
accessToken string
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
otp string
|
otp string
|
||||||
sudo string
|
sudo string
|
||||||
client *http.Client
|
userAgent string
|
||||||
ctx context.Context
|
debug bool
|
||||||
serverVersion *version.Version
|
httpsigner *HTTPSign
|
||||||
versionLock sync.RWMutex
|
client *http.Client
|
||||||
|
ctx context.Context
|
||||||
|
mutex sync.RWMutex
|
||||||
|
serverVersion *version.Version
|
||||||
|
getVersionOnce sync.Once
|
||||||
|
ignoreVersion bool // only set by SetGiteaVersion so don't need a mutex lock
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response represents the gitea response
|
// Response represents the gitea response
|
||||||
type Response struct {
|
type Response struct {
|
||||||
*http.Response
|
*http.Response
|
||||||
|
|
||||||
|
FirstPage int
|
||||||
|
PrevPage int
|
||||||
|
NextPage int
|
||||||
|
LastPage int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientOption are functions used to init a new client
|
||||||
|
type ClientOption func(*Client) error
|
||||||
|
|
||||||
// NewClient initializes and returns a API client.
|
// NewClient initializes and returns a API client.
|
||||||
func NewClient(url string, options ...func(*Client)) (*Client, error) {
|
// Usage of all gitea.Client methods is concurrency-safe.
|
||||||
|
func NewClient(url string, options ...ClientOption) (*Client, error) {
|
||||||
client := &Client{
|
client := &Client{
|
||||||
url: strings.TrimSuffix(url, "/"),
|
url: strings.TrimSuffix(url, "/"),
|
||||||
client: &http.Client{},
|
client: &http.Client{},
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
}
|
}
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
opt(client)
|
if err := opt(client); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := client.CheckServerVersionConstraint(">=1.10"); err != nil {
|
if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||||
|
if errors.Is(err, &ErrUnknownVersion{}) {
|
||||||
|
return client, err
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,90 +91,256 @@ func NewClientWithHTTP(url string, httpClient *http.Client) *Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHTTPClient is an option for NewClient to set custom http client
|
// SetHTTPClient is an option for NewClient to set custom http client
|
||||||
func SetHTTPClient(httpClient *http.Client) func(client *Client) {
|
func SetHTTPClient(httpClient *http.Client) ClientOption {
|
||||||
return func(client *Client) {
|
return func(client *Client) error {
|
||||||
client.client = httpClient
|
client.SetHTTPClient(httpClient)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHTTPClient replaces default http.Client with user given one.
|
||||||
|
func (c *Client) SetHTTPClient(client *http.Client) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
c.client = client
|
||||||
|
c.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// SetToken is an option for NewClient to set token
|
// SetToken is an option for NewClient to set token
|
||||||
func SetToken(token string) func(client *Client) {
|
func SetToken(token string) ClientOption {
|
||||||
return func(client *Client) {
|
return func(client *Client) error {
|
||||||
|
client.mutex.Lock()
|
||||||
client.accessToken = token
|
client.accessToken = token
|
||||||
|
client.mutex.Unlock()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBasicAuth is an option for NewClient to set username and password
|
// SetBasicAuth is an option for NewClient to set username and password
|
||||||
func SetBasicAuth(username, password string) func(client *Client) {
|
func SetBasicAuth(username, password string) ClientOption {
|
||||||
return func(client *Client) {
|
return func(client *Client) error {
|
||||||
client.SetBasicAuth(username, password)
|
client.SetBasicAuth(username, password)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseSSHCert is an option for NewClient to enable SSH certificate authentication via HTTPSign
|
||||||
|
// If you want to auth against the ssh-agent you'll need to set a principal, if you want to
|
||||||
|
// use a file on disk you'll need to specify sshKey.
|
||||||
|
// If you have an encrypted sshKey you'll need to also set the passphrase.
|
||||||
|
func UseSSHCert(principal, sshKey, passphrase string) ClientOption {
|
||||||
|
return func(client *Client) error {
|
||||||
|
if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.mutex.Lock()
|
||||||
|
defer client.mutex.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
client.httpsigner, err = NewHTTPSignWithCert(principal, sshKey, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseSSHPubkey is an option for NewClient to enable SSH pubkey authentication via HTTPSign
|
||||||
|
// If you want to auth against the ssh-agent you'll need to set a fingerprint, if you want to
|
||||||
|
// use a file on disk you'll need to specify sshKey.
|
||||||
|
// If you have an encrypted sshKey you'll need to also set the passphrase.
|
||||||
|
func UseSSHPubkey(fingerprint, sshKey, passphrase string) ClientOption {
|
||||||
|
return func(client *Client) error {
|
||||||
|
if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.mutex.Lock()
|
||||||
|
defer client.mutex.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
client.httpsigner, err = NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBasicAuth sets username and password
|
// SetBasicAuth sets username and password
|
||||||
func (c *Client) SetBasicAuth(username, password string) {
|
func (c *Client) SetBasicAuth(username, password string) {
|
||||||
|
c.mutex.Lock()
|
||||||
c.username, c.password = username, password
|
c.username, c.password = username, password
|
||||||
|
c.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOTP is an option for NewClient to set OTP for 2FA
|
// SetOTP is an option for NewClient to set OTP for 2FA
|
||||||
func SetOTP(otp string) func(client *Client) {
|
func SetOTP(otp string) ClientOption {
|
||||||
return func(client *Client) {
|
return func(client *Client) error {
|
||||||
client.SetOTP(otp)
|
client.SetOTP(otp)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOTP sets OTP for 2FA
|
// SetOTP sets OTP for 2FA
|
||||||
func (c *Client) SetOTP(otp string) {
|
func (c *Client) SetOTP(otp string) {
|
||||||
|
c.mutex.Lock()
|
||||||
c.otp = otp
|
c.otp = otp
|
||||||
|
c.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetContext is an option for NewClient to set context
|
// SetContext is an option for NewClient to set the default context
|
||||||
func SetContext(ctx context.Context) func(client *Client) {
|
func SetContext(ctx context.Context) ClientOption {
|
||||||
return func(client *Client) {
|
return func(client *Client) error {
|
||||||
client.SetContext(ctx)
|
client.SetContext(ctx)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetContext set context witch is used for http requests
|
// SetContext set default context witch is used for http requests
|
||||||
func (c *Client) SetContext(ctx context.Context) {
|
func (c *Client) SetContext(ctx context.Context) {
|
||||||
|
c.mutex.Lock()
|
||||||
c.ctx = ctx
|
c.ctx = ctx
|
||||||
}
|
c.mutex.Unlock()
|
||||||
|
|
||||||
// SetHTTPClient replaces default http.Client with user given one.
|
|
||||||
func (c *Client) SetHTTPClient(client *http.Client) {
|
|
||||||
c.client = client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSudo is an option for NewClient to set sudo header
|
// SetSudo is an option for NewClient to set sudo header
|
||||||
func SetSudo(sudo string) func(client *Client) {
|
func SetSudo(sudo string) ClientOption {
|
||||||
return func(client *Client) {
|
return func(client *Client) error {
|
||||||
client.SetSudo(sudo)
|
client.SetSudo(sudo)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSudo sets username to impersonate.
|
// SetSudo sets username to impersonate.
|
||||||
func (c *Client) SetSudo(sudo string) {
|
func (c *Client) SetSudo(sudo string) {
|
||||||
|
c.mutex.Lock()
|
||||||
c.sudo = sudo
|
c.sudo = sudo
|
||||||
|
c.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAgent is an option for NewClient to set user-agent header
|
||||||
|
func SetUserAgent(userAgent string) ClientOption {
|
||||||
|
return func(client *Client) error {
|
||||||
|
client.SetUserAgent(userAgent)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAgent sets the user-agent to send with every request.
|
||||||
|
func (c *Client) SetUserAgent(userAgent string) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
c.userAgent = userAgent
|
||||||
|
c.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDebugMode is an option for NewClient to enable debug mode
|
||||||
|
func SetDebugMode() ClientOption {
|
||||||
|
return func(client *Client) error {
|
||||||
|
client.mutex.Lock()
|
||||||
|
client.debug = true
|
||||||
|
client.mutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResponse(r *http.Response) *Response {
|
||||||
|
response := &Response{Response: r}
|
||||||
|
response.parseLinkHeader()
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Response) parseLinkHeader() {
|
||||||
|
link := r.Header.Get("Link")
|
||||||
|
if link == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
links := strings.Split(link, ",")
|
||||||
|
for _, l := range links {
|
||||||
|
u, param, ok := strings.Cut(l, ";")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
u = strings.Trim(u, " <>")
|
||||||
|
|
||||||
|
key, value, ok := strings.Cut(strings.TrimSpace(param), "=")
|
||||||
|
if !ok || key != "rel" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value = strings.Trim(value, "\"")
|
||||||
|
|
||||||
|
parsed, err := url.Parse(u)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
page := parsed.Query().Get("page")
|
||||||
|
if page == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value {
|
||||||
|
case "first":
|
||||||
|
r.FirstPage, _ = strconv.Atoi(page)
|
||||||
|
case "prev":
|
||||||
|
r.PrevPage, _ = strconv.Atoi(page)
|
||||||
|
case "next":
|
||||||
|
r.NextPage, _ = strconv.Atoi(page)
|
||||||
|
case "last":
|
||||||
|
r.LastPage, _ = strconv.Atoi(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) {
|
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) {
|
||||||
|
c.mutex.RLock()
|
||||||
|
debug := c.debug
|
||||||
|
if debug {
|
||||||
|
fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body)
|
||||||
|
}
|
||||||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body)
|
req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body)
|
||||||
|
|
||||||
|
client := c.client // client ref can change from this point on so safe it
|
||||||
|
c.mutex.RUnlock()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
resp, err := c.client.Do(req)
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
return data, &Response{resp}, nil
|
if debug {
|
||||||
|
fmt.Printf("Response: %v\n\n", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, newResponse(resp), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
|
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
|
||||||
|
c.mutex.RLock()
|
||||||
|
debug := c.debug
|
||||||
|
if debug {
|
||||||
|
var bodyStr string
|
||||||
|
if body != nil {
|
||||||
|
bs, _ := io.ReadAll(body)
|
||||||
|
body = bytes.NewReader(bs)
|
||||||
|
bodyStr = string(bs)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, bodyStr)
|
||||||
|
}
|
||||||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body)
|
req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
c.mutex.RUnlock()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(c.accessToken) != 0 {
|
if len(c.accessToken) != 0 {
|
||||||
|
@ -167,57 +355,114 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
|
||||||
if len(c.sudo) != 0 {
|
if len(c.sudo) != 0 {
|
||||||
req.Header.Set("Sudo", c.sudo)
|
req.Header.Set("Sudo", c.sudo)
|
||||||
}
|
}
|
||||||
|
if len(c.userAgent) != 0 {
|
||||||
|
req.Header.Set("User-Agent", c.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := c.client // client ref can change from this point on so safe it
|
||||||
|
c.mutex.RUnlock()
|
||||||
|
|
||||||
for k, v := range header {
|
for k, v := range header {
|
||||||
req.Header[k] = v
|
req.Header[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
if c.httpsigner != nil {
|
||||||
|
err = c.SignRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Response{resp}, nil
|
if debug {
|
||||||
|
fmt.Printf("Response: %v\n\n", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newResponse(resp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a response for a HTTP status code indicating an error condition
|
||||||
|
// (non-2XX) to a well-known error value and response body. For non-problematic
|
||||||
|
// (2XX) status codes nil will be returned. Note that on a non-2XX response, the
|
||||||
|
// response body stream will have been read and, hence, is closed on return.
|
||||||
|
func statusCodeToErr(resp *Response) (body []byte, err error) {
|
||||||
|
// no error
|
||||||
|
if resp.StatusCode/100 == 2 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// error: body will be read for details
|
||||||
|
//
|
||||||
|
defer resp.Body.Close()
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("body read on HTTP error %d: %v", resp.StatusCode, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to unmarshal and get an error message
|
||||||
|
errMap := make(map[string]interface{})
|
||||||
|
if err = json.Unmarshal(data, &errMap); err != nil {
|
||||||
|
// when the JSON can't be parsed, data was probably empty or a
|
||||||
|
// plain string, so we try to return a helpful error anyway
|
||||||
|
path := resp.Request.URL.Path
|
||||||
|
method := resp.Request.Method
|
||||||
|
header := resp.Request.Header
|
||||||
|
return data, fmt.Errorf("Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body", resp.StatusCode, path, method, header, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg, ok := errMap["message"]; ok {
|
||||||
|
return data, fmt.Errorf("%v", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no error message, at least give status and data
|
||||||
|
return data, fmt.Errorf("%s: %s", resp.Status, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getResponseReader(method, path string, header http.Header, body io.Reader) (io.ReadCloser, *Response, error) {
|
||||||
|
resp, err := c.doRequest(method, path, header, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for errors
|
||||||
|
data, err := statusCodeToErr(resp)
|
||||||
|
if err != nil {
|
||||||
|
return io.NopCloser(bytes.NewReader(data)), resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Body, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) {
|
func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) {
|
||||||
resp, err := c.doRequest(method, path, header, body)
|
resp, err := c.doRequest(method, path, header, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, resp, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
// check for errors
|
||||||
|
data, err := statusCodeToErr(resp)
|
||||||
|
if err != nil {
|
||||||
|
return data, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// success (2XX), read body
|
||||||
|
data, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, resp, err
|
return nil, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case 403:
|
|
||||||
return data, resp, errors.New("403 Forbidden")
|
|
||||||
case 404:
|
|
||||||
return data, resp, errors.New("404 Not Found")
|
|
||||||
case 409:
|
|
||||||
return data, resp, errors.New("409 Conflict")
|
|
||||||
case 422:
|
|
||||||
return data, resp, fmt.Errorf("422 Unprocessable Entity: %s", string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode/100 != 2 {
|
|
||||||
errMap := make(map[string]interface{})
|
|
||||||
if err = json.Unmarshal(data, &errMap); err != nil {
|
|
||||||
// when the JSON can't be parsed, data was probably empty or a plain string,
|
|
||||||
// so we try to return a helpful error anyway
|
|
||||||
return data, resp, fmt.Errorf("Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body", resp.StatusCode, path, method, header, string(data))
|
|
||||||
}
|
|
||||||
return data, resp, errors.New(errMap["message"].(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, resp, nil
|
return data, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) (*Response, error) {
|
func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) (*Response, error) {
|
||||||
data, resp, err := c.getResponse(method, path, header, body)
|
data, resp, err := c.getResponse(method, path, header, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return resp, err
|
||||||
}
|
}
|
||||||
return resp, json.Unmarshal(data, obj)
|
return resp, json.Unmarshal(data, obj)
|
||||||
}
|
}
|
||||||
|
@ -231,3 +476,24 @@ func (c *Client) getStatusCode(method, path string, header http.Header, body io.
|
||||||
|
|
||||||
return resp.StatusCode, resp, nil
|
return resp.StatusCode, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pathEscapeSegments escapes segments of a path while not escaping forward slash
|
||||||
|
func pathEscapeSegments(path string) string {
|
||||||
|
slice := strings.Split(path, "/")
|
||||||
|
for index := range slice {
|
||||||
|
slice[index] = url.PathEscape(slice[index])
|
||||||
|
}
|
||||||
|
escapedPath := strings.Join(slice, "/")
|
||||||
|
return escapedPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeValidatePathSegments is a help function to validate and encode url path segments
|
||||||
|
func escapeValidatePathSegments(seg ...*string) error {
|
||||||
|
for i := range seg {
|
||||||
|
if seg[i] == nil || len(*seg[i]) == 0 {
|
||||||
|
return fmt.Errorf("path segment [%d] is empty", i)
|
||||||
|
}
|
||||||
|
*seg[i] = url.PathEscape(*seg[i])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
35
gitea/client_test.go
Normal file
35
gitea/client_test.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParsedPaging(t *testing.T) {
|
||||||
|
resp := newResponse(&http.Response{
|
||||||
|
Header: http.Header{
|
||||||
|
"Link": []string{
|
||||||
|
strings.Join(
|
||||||
|
[]string{
|
||||||
|
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=3>; rel="next"`,
|
||||||
|
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=4>; rel="last"`,
|
||||||
|
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=1>; rel="first"`,
|
||||||
|
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=1>; rel="prev"`,
|
||||||
|
}, ",",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, 1, resp.FirstPage)
|
||||||
|
assert.Equal(t, 1, resp.PrevPage)
|
||||||
|
assert.Equal(t, 3, resp.NextPage)
|
||||||
|
assert.Equal(t, 4, resp.LastPage)
|
||||||
|
}
|
|
@ -2,4 +2,8 @@
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package gitea implements a client for the Gitea API.
|
||||||
|
// The version corresponds to the highest supported version
|
||||||
|
// of the gitea API, but backwards-compatibility is mostly
|
||||||
|
// given.
|
||||||
package gitea // import "code.gitea.io/sdk/gitea"
|
package gitea // import "code.gitea.io/sdk/gitea"
|
||||||
|
|
|
@ -16,7 +16,10 @@ type ListForksOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListForks list a repository's forks
|
// ListForks list a repository's forks
|
||||||
func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*Repository, *Response, error) {
|
func (c *Client) ListForks(user, repo string, opt ListForksOptions) ([]*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
forks := make([]*Repository, opt.PageSize)
|
forks := make([]*Repository, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET",
|
resp, err := c.getParsedResponse("GET",
|
||||||
|
@ -29,10 +32,15 @@ func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*R
|
||||||
type CreateForkOption struct {
|
type CreateForkOption struct {
|
||||||
// organization name, if forking into an organization
|
// organization name, if forking into an organization
|
||||||
Organization *string `json:"organization"`
|
Organization *string `json:"organization"`
|
||||||
|
// name of the forked repository
|
||||||
|
Name *string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateFork create a fork of a repository
|
// CreateFork create a fork of a repository
|
||||||
func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) {
|
func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(form)
|
body, err := json.Marshal(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
|
@ -19,6 +19,9 @@ type GitBlobResponse struct {
|
||||||
|
|
||||||
// GetBlob get the blob of a repository file
|
// GetBlob get the blob of a repository file
|
||||||
func (c *Client) GetBlob(user, repo, sha string) (*GitBlobResponse, *Response, error) {
|
func (c *Client) GetBlob(user, repo, sha string) (*GitBlobResponse, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
blob := new(GitBlobResponse)
|
blob := new(GitBlobResponse)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/blobs/%s", user, repo, sha), nil, nil, blob)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/blobs/%s", user, repo, sha), nil, nil, blob)
|
||||||
return blob, resp, err
|
return blob, resp, err
|
||||||
|
|
|
@ -24,6 +24,9 @@ type ListRepoGitHooksOptions struct {
|
||||||
|
|
||||||
// ListRepoGitHooks list all the Git hooks of one repository
|
// ListRepoGitHooks list all the Git hooks of one repository
|
||||||
func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions) ([]*GitHook, *Response, error) {
|
func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions) ([]*GitHook, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
hooks := make([]*GitHook, 0, opt.PageSize)
|
hooks := make([]*GitHook, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
||||||
|
@ -32,6 +35,9 @@ func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions
|
||||||
|
|
||||||
// GetRepoGitHook get a Git hook of a repository
|
// GetRepoGitHook get a Git hook of a repository
|
||||||
func (c *Client) GetRepoGitHook(user, repo, id string) (*GitHook, *Response, error) {
|
func (c *Client) GetRepoGitHook(user, repo, id string) (*GitHook, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
h := new(GitHook)
|
h := new(GitHook)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil, h)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil, h)
|
||||||
return h, resp, err
|
return h, resp, err
|
||||||
|
@ -44,6 +50,9 @@ type EditGitHookOption struct {
|
||||||
|
|
||||||
// EditRepoGitHook modify one Git hook of a repository
|
// EditRepoGitHook modify one Git hook of a repository
|
||||||
func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (*Response, error) {
|
func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -54,6 +63,9 @@ func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (
|
||||||
|
|
||||||
// DeleteRepoGitHook delete one Git hook from a repository
|
// DeleteRepoGitHook delete one Git hook from a repository
|
||||||
func (c *Client) DeleteRepoGitHook(user, repo, id string) (*Response, error) {
|
func (c *Client) DeleteRepoGitHook(user, repo, id string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
16
gitea/go.mod
16
gitea/go.mod
|
@ -1,8 +1,18 @@
|
||||||
module code.gitea.io/sdk/gitea
|
module code.gitea.io/sdk/gitea
|
||||||
|
|
||||||
go 1.12
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/hashicorp/go-version v1.2.0
|
github.com/davidmz/go-pageant v1.0.2
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/go-fed/httpsig v1.1.0
|
||||||
|
github.com/hashicorp/go-version v1.6.0
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
golang.org/x/crypto v0.22.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
)
|
)
|
||||||
|
|
33
gitea/go.sum
33
gitea/go.sum
|
@ -1,13 +1,34 @@
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||||
|
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||||
|
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||||
|
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||||
|
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
20
gitea/helper.go
Normal file
20
gitea/helper.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
// OptionalBool convert a bool to a bool reference
|
||||||
|
func OptionalBool(v bool) *bool {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionalString convert a string to a string reference
|
||||||
|
func OptionalString(v string) *string {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionalInt64 convert a int64 to a int64 reference
|
||||||
|
func OptionalInt64(v int64) *int64 {
|
||||||
|
return &v
|
||||||
|
}
|
117
gitea/hook.go
117
gitea/hook.go
|
@ -24,6 +24,28 @@ type Hook struct {
|
||||||
Created time.Time `json:"created_at"`
|
Created time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HookType represent all webhook types gitea currently offer
|
||||||
|
type HookType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HookTypeDingtalk webhook that dingtalk understand
|
||||||
|
HookTypeDingtalk HookType = "dingtalk"
|
||||||
|
// HookTypeDiscord webhook that discord understand
|
||||||
|
HookTypeDiscord HookType = "discord"
|
||||||
|
// HookTypeGitea webhook that gitea understand
|
||||||
|
HookTypeGitea HookType = "gitea"
|
||||||
|
// HookTypeGogs webhook that gogs understand
|
||||||
|
HookTypeGogs HookType = "gogs"
|
||||||
|
// HookTypeMsteams webhook that msteams understand
|
||||||
|
HookTypeMsteams HookType = "msteams"
|
||||||
|
// HookTypeSlack webhook that slack understand
|
||||||
|
HookTypeSlack HookType = "slack"
|
||||||
|
// HookTypeTelegram webhook that telegram understand
|
||||||
|
HookTypeTelegram HookType = "telegram"
|
||||||
|
// HookTypeFeishu webhook that feishu understand
|
||||||
|
HookTypeFeishu HookType = "feishu"
|
||||||
|
)
|
||||||
|
|
||||||
// ListHooksOptions options for listing hooks
|
// ListHooksOptions options for listing hooks
|
||||||
type ListHooksOptions struct {
|
type ListHooksOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
|
@ -31,14 +53,28 @@ type ListHooksOptions struct {
|
||||||
|
|
||||||
// ListOrgHooks list all the hooks of one organization
|
// ListOrgHooks list all the hooks of one organization
|
||||||
func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) {
|
func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
hooks := make([]*Hook, 0, opt.PageSize)
|
hooks := make([]*Hook, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks?%s", org, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks?%s", org, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
||||||
return hooks, resp, err
|
return hooks, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListMyHooks list all the hooks of the authenticated user
|
||||||
|
func (c *Client) ListMyHooks(opt ListHooksOptions) ([]*Hook, *Response, error) {
|
||||||
|
opt.setDefaults()
|
||||||
|
hooks := make([]*Hook, 0, opt.PageSize)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/hooks?%s", opt.getURLQuery().Encode()), nil, nil, &hooks)
|
||||||
|
return hooks, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
// ListRepoHooks list all the hooks of one repository
|
// ListRepoHooks list all the hooks of one repository
|
||||||
func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) {
|
func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
hooks := make([]*Hook, 0, opt.PageSize)
|
hooks := make([]*Hook, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
||||||
|
@ -47,13 +83,26 @@ func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook
|
||||||
|
|
||||||
// GetOrgHook get a hook of an organization
|
// GetOrgHook get a hook of an organization
|
||||||
func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) {
|
func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
h := new(Hook)
|
h := new(Hook)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h)
|
||||||
return h, resp, err
|
return h, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMyHook get a hook of the authenticated user
|
||||||
|
func (c *Client) GetMyHook(id int64) (*Hook, *Response, error) {
|
||||||
|
h := new(Hook)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/hooks/%d", id), nil, nil, h)
|
||||||
|
return h, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
// GetRepoHook get a hook of a repository
|
// GetRepoHook get a hook of a repository
|
||||||
func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, error) {
|
func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
h := new(Hook)
|
h := new(Hook)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h)
|
||||||
return h, resp, err
|
return h, resp, err
|
||||||
|
@ -61,11 +110,12 @@ func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, err
|
||||||
|
|
||||||
// CreateHookOption options when create a hook
|
// CreateHookOption options when create a hook
|
||||||
type CreateHookOption struct {
|
type CreateHookOption struct {
|
||||||
Type string `json:"type"`
|
Type HookType `json:"type"`
|
||||||
Config map[string]string `json:"config"`
|
Config map[string]string `json:"config"`
|
||||||
Events []string `json:"events"`
|
Events []string `json:"events"`
|
||||||
BranchFilter string `json:"branch_filter"`
|
BranchFilter string `json:"branch_filter"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
|
AuthorizationHeader string `json:"authorization_header"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the CreateHookOption struct
|
// Validate the CreateHookOption struct
|
||||||
|
@ -78,6 +128,9 @@ func (opt CreateHookOption) Validate() error {
|
||||||
|
|
||||||
// CreateOrgHook create one hook for an organization, with options
|
// CreateOrgHook create one hook for an organization, with options
|
||||||
func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Response, error) {
|
func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -90,8 +143,25 @@ func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Respon
|
||||||
return h, resp, err
|
return h, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateMyHook create one hook for the authenticated user, with options
|
||||||
|
func (c *Client) CreateMyHook(opt CreateHookOption) (*Hook, *Response, error) {
|
||||||
|
if err := opt.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(&opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
h := new(Hook)
|
||||||
|
resp, err := c.getParsedResponse("POST", "/user/hooks", jsonHeader, bytes.NewReader(body), h)
|
||||||
|
return h, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
// CreateRepoHook create one hook for a repository, with options
|
// CreateRepoHook create one hook for a repository, with options
|
||||||
func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, *Response, error) {
|
func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -103,14 +173,18 @@ func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook,
|
||||||
|
|
||||||
// EditHookOption options when modify one hook
|
// EditHookOption options when modify one hook
|
||||||
type EditHookOption struct {
|
type EditHookOption struct {
|
||||||
Config map[string]string `json:"config"`
|
Config map[string]string `json:"config"`
|
||||||
Events []string `json:"events"`
|
Events []string `json:"events"`
|
||||||
BranchFilter string `json:"branch_filter"`
|
BranchFilter string `json:"branch_filter"`
|
||||||
Active *bool `json:"active"`
|
Active *bool `json:"active"`
|
||||||
|
AuthorizationHeader string `json:"authorization_header"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditOrgHook modify one hook of an organization, with hook id and options
|
// EditOrgHook modify one hook of an organization, with hook id and options
|
||||||
func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) {
|
func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -119,8 +193,21 @@ func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Respons
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EditMyHook modify one hook of the authenticated user, with hook id and options
|
||||||
|
func (c *Client) EditMyHook(id int64, opt EditHookOption) (*Response, error) {
|
||||||
|
body, err := json.Marshal(&opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/user/hooks/%d", id), jsonHeader, bytes.NewReader(body))
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
// EditRepoHook modify one hook of a repository, with hook id and options
|
// EditRepoHook modify one hook of a repository, with hook id and options
|
||||||
func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (*Response, error) {
|
func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -131,12 +218,24 @@ func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (
|
||||||
|
|
||||||
// DeleteOrgHook delete one hook from an organization, with hook id
|
// DeleteOrgHook delete one hook from an organization, with hook id
|
||||||
func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) {
|
func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteMyHook delete one hook from the authenticated user, with hook id
|
||||||
|
func (c *Client) DeleteMyHook(id int64) (*Response, error) {
|
||||||
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/hooks/%d", id), nil, nil)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteRepoHook delete one hook from a repository, with hook id
|
// DeleteRepoHook delete one hook from a repository, with hook id
|
||||||
func (c *Client) DeleteRepoHook(user, repo string, id int64) (*Response, error) {
|
func (c *Client) DeleteRepoHook(user, repo string, id int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
59
gitea/hook_validate.go
Normal file
59
gitea/hook_validate.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VerifyWebhookSignature verifies that a payload matches the X-Gitea-Signature based on a secret
|
||||||
|
func VerifyWebhookSignature(secret, expected string, payload []byte) (bool, error) {
|
||||||
|
hash := hmac.New(sha256.New, []byte(secret))
|
||||||
|
if _, err := hash.Write(payload); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
expectedSum, err := hex.DecodeString(expected)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return hmac.Equal(hash.Sum(nil), expectedSum), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyWebhookSignatureMiddleware is a http.Handler for verifying X-Gitea-Signature on incoming webhooks
|
||||||
|
func VerifyWebhookSignatureMiddleware(secret string) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
if _, err := io.Copy(&b, r.Body); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := r.Header.Get("X-Gitea-Signature")
|
||||||
|
if expected == "" {
|
||||||
|
http.Error(w, "no signature found", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := VerifyWebhookSignature(secret, expected, b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "invalid payload", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Body = io.NopCloser(&b)
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
125
gitea/hook_validate_test.go
Normal file
125
gitea/hook_validate_test.go
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hashers are based on https://github.com/go-gitea/gitea/blob/0dfc2e55ea258d2b1a3cd86e2b6f27a481e495ff/services/webhook/deliver.go#L105-L116
|
||||||
|
|
||||||
|
func TestVerifyWebhookSignature(t *testing.T) {
|
||||||
|
secret := "s3cr3t"
|
||||||
|
payload := []byte(`{"foo": "bar", "baz": true}`)
|
||||||
|
|
||||||
|
hasher := hmac.New(sha256.New, []byte(secret))
|
||||||
|
hasher.Write(payload)
|
||||||
|
sig := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
Name string
|
||||||
|
Secret string
|
||||||
|
Payload string
|
||||||
|
Succeed bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "Correct secret and payload",
|
||||||
|
Secret: "s3cr3t",
|
||||||
|
Payload: `{"foo": "bar", "baz": true}`,
|
||||||
|
Succeed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Correct secret bad payload",
|
||||||
|
Secret: "s3cr3t",
|
||||||
|
Payload: "{}",
|
||||||
|
Succeed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Incorrect secret good payload",
|
||||||
|
Secret: "secret",
|
||||||
|
Payload: `{"foo": "bar", "baz": true}`,
|
||||||
|
Succeed: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
ok, err := VerifyWebhookSignature(tc.Secret, sig, []byte(tc.Payload))
|
||||||
|
assert.NoError(t, err, "verification should not error")
|
||||||
|
assert.True(t, ok == tc.Succeed, "verification should be %t", tc.Succeed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyWebhookSignatureHandler(t *testing.T) {
|
||||||
|
secret := "s3cr3t"
|
||||||
|
payload := []byte(`{"foo": "bar", "baz": true}`)
|
||||||
|
|
||||||
|
hasher := hmac.New(sha256.New, []byte(secret))
|
||||||
|
hasher.Write(payload)
|
||||||
|
sig := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
Name string
|
||||||
|
Secret string
|
||||||
|
Payload string
|
||||||
|
Signature string
|
||||||
|
Status int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "Correct secret and payload",
|
||||||
|
Secret: "s3cr3t",
|
||||||
|
Payload: `{"foo": "bar", "baz": true}`,
|
||||||
|
Signature: sig,
|
||||||
|
Status: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Correct secret bad payload",
|
||||||
|
Secret: "s3cr3t",
|
||||||
|
Payload: "{}",
|
||||||
|
Signature: sig,
|
||||||
|
Status: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Incorrect secret good payload",
|
||||||
|
Secret: "secret",
|
||||||
|
Payload: `{"foo": "bar", "baz": true}`,
|
||||||
|
Signature: sig,
|
||||||
|
Status: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "No signature",
|
||||||
|
Status: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
server := httptest.NewServer(VerifyWebhookSignatureMiddleware(tc.Secret)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
_, _ = w.Write(nil)
|
||||||
|
})))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, server.URL, strings.NewReader(tc.Payload))
|
||||||
|
assert.NoError(t, err, "should create request")
|
||||||
|
|
||||||
|
if tc.Signature != "" {
|
||||||
|
req.Header.Set("X-Gitea-Signature", tc.Signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
assert.NoError(t, err, "request should be delivered")
|
||||||
|
assert.True(t, resp.StatusCode == tc.Status, "status should be %d, but got %d", tc.Status, resp.StatusCode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
253
gitea/httpsign.go
Normal file
253
gitea/httpsign.go
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-fed/httpsig"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPSign contains the signer used for signing requests
|
||||||
|
type HTTPSign struct {
|
||||||
|
ssh.Signer
|
||||||
|
cert bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPSignConfig contains the configuration for creating a HTTPSign
|
||||||
|
type HTTPSignConfig struct {
|
||||||
|
fingerprint string
|
||||||
|
principal string
|
||||||
|
pubkey bool
|
||||||
|
cert bool
|
||||||
|
sshKey string
|
||||||
|
passphrase string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPSignWithPubkey can be used to create a HTTPSign with a public key
|
||||||
|
// if no fingerprint is specified it returns the first public key found
|
||||||
|
func NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase string) (*HTTPSign, error) {
|
||||||
|
return newHTTPSign(&HTTPSignConfig{
|
||||||
|
fingerprint: fingerprint,
|
||||||
|
pubkey: true,
|
||||||
|
sshKey: sshKey,
|
||||||
|
passphrase: passphrase,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPSignWithCert can be used to create a HTTPSign with a certificate
|
||||||
|
// if no principal is specified it returns the first certificate found
|
||||||
|
func NewHTTPSignWithCert(principal, sshKey, passphrase string) (*HTTPSign, error) {
|
||||||
|
return newHTTPSign(&HTTPSignConfig{
|
||||||
|
principal: principal,
|
||||||
|
cert: true,
|
||||||
|
sshKey: sshKey,
|
||||||
|
passphrase: passphrase,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPSign returns a new HTTPSign
|
||||||
|
// It will check the ssh-agent or a local file is config.sshKey is set.
|
||||||
|
// Depending on the configuration it will either use a certificate or a public key
|
||||||
|
func newHTTPSign(config *HTTPSignConfig) (*HTTPSign, error) {
|
||||||
|
var signer ssh.Signer
|
||||||
|
|
||||||
|
if config.sshKey != "" {
|
||||||
|
priv, err := os.ReadFile(config.sshKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.passphrase == "" {
|
||||||
|
signer, err = ssh.ParsePrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signer, err = ssh.ParsePrivateKeyWithPassphrase(priv, []byte(config.passphrase))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.cert {
|
||||||
|
certbytes, err := os.ReadFile(config.sshKey + "-cert.pub")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, _, _, _, err := ssh.ParseAuthorizedKey(certbytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, ok := pub.(*ssh.Certificate)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to parse certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err = ssh.NewCertSigner(cert, signer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if no sshKey is specified, check if we have a ssh-agent and use it
|
||||||
|
agent, err := GetAgent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signers, err := agent.Signers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(signers) == 0 {
|
||||||
|
return nil, fmt.Errorf("no signers found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.cert {
|
||||||
|
signer = findCertSigner(signers, config.principal)
|
||||||
|
if signer == nil {
|
||||||
|
return nil, fmt.Errorf("no certificate found for %s", config.principal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.pubkey {
|
||||||
|
signer = findPubkeySigner(signers, config.fingerprint)
|
||||||
|
if signer == nil {
|
||||||
|
return nil, fmt.Errorf("no public key found for %s", config.fingerprint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &HTTPSign{
|
||||||
|
Signer: signer,
|
||||||
|
cert: config.cert,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignRequest signs a HTTP request
|
||||||
|
func (c *Client) SignRequest(r *http.Request) error {
|
||||||
|
var contents []byte
|
||||||
|
|
||||||
|
headersToSign := []string{httpsig.RequestTarget, "(created)", "(expires)"}
|
||||||
|
|
||||||
|
if c.httpsigner.cert {
|
||||||
|
// add our certificate to the headers to sign
|
||||||
|
pubkey, _ := ssh.ParsePublicKey(c.httpsigner.Signer.PublicKey().Marshal())
|
||||||
|
if cert, ok := pubkey.(*ssh.Certificate); ok {
|
||||||
|
certString := base64.RawStdEncoding.EncodeToString(cert.Marshal())
|
||||||
|
r.Header.Add("x-ssh-certificate", certString)
|
||||||
|
|
||||||
|
headersToSign = append(headersToSign, "x-ssh-certificate")
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("no ssh certificate found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have a body, the Digest header will be added and we'll include this also in
|
||||||
|
// our signature.
|
||||||
|
if r.Body != nil {
|
||||||
|
body, err := r.GetBody()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getBody() failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err = io.ReadAll(body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed reading body: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headersToSign = append(headersToSign, "Digest")
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a signer for the request and headers, the signature will be valid for 10 seconds
|
||||||
|
signer, _, err := httpsig.NewSSHSigner(c.httpsigner.Signer, httpsig.DigestSha512, headersToSign, httpsig.Signature, 10)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("httpsig.NewSSHSigner failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign the request, use the fingerprint if we don't have a certificate
|
||||||
|
keyID := "gitea"
|
||||||
|
if !c.httpsigner.cert {
|
||||||
|
keyID = ssh.FingerprintSHA256(c.httpsigner.Signer.PublicKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = signer.SignRequest(keyID, r, contents)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("httpsig.Signrequest failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findCertSigner returns the Signer containing a valid certificate
|
||||||
|
// if no principal is specified it returns the first certificate found
|
||||||
|
func findCertSigner(sshsigners []ssh.Signer, principal string) ssh.Signer {
|
||||||
|
for _, s := range sshsigners {
|
||||||
|
// Check if the key is a certificate
|
||||||
|
if !strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the ssh.Signer to a ssh.Certificate
|
||||||
|
mpubkey, _ := ssh.ParsePublicKey(s.PublicKey().Marshal())
|
||||||
|
cryptopub := mpubkey.(crypto.PublicKey)
|
||||||
|
cert := cryptopub.(*ssh.Certificate)
|
||||||
|
t := time.Unix(int64(cert.ValidBefore), 0)
|
||||||
|
|
||||||
|
// make sure the certificate is at least 10 seconds valid
|
||||||
|
if time.Until(t) <= time.Second*10 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if principal == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range cert.ValidPrincipals {
|
||||||
|
if p == principal {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findPubkeySigner returns the Signer containing a valid public key
|
||||||
|
// if no fingerprint is specified it returns the first public key found
|
||||||
|
func findPubkeySigner(sshsigners []ssh.Signer, fingerprint string) ssh.Signer {
|
||||||
|
for _, s := range sshsigners {
|
||||||
|
// Check if the key is a certificate
|
||||||
|
if strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fingerprint == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(string(ssh.MarshalAuthorizedKey(s.PublicKey()))) == fingerprint {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
if ssh.FingerprintSHA256(s.PublicKey()) == fingerprint {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
106
gitea/issue.go
106
gitea/issue.go
|
@ -32,15 +32,16 @@ type RepositoryMeta struct {
|
||||||
type Issue struct {
|
type Issue struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
|
HTMLURL string `json:"html_url"`
|
||||||
Index int64 `json:"number"`
|
Index int64 `json:"number"`
|
||||||
Poster *User `json:"user"`
|
Poster *User `json:"user"`
|
||||||
OriginalAuthor string `json:"original_author"`
|
OriginalAuthor string `json:"original_author"`
|
||||||
OriginalAuthorID int64 `json:"original_author_id"`
|
OriginalAuthorID int64 `json:"original_author_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
|
Ref string `json:"ref"`
|
||||||
Labels []*Label `json:"labels"`
|
Labels []*Label `json:"labels"`
|
||||||
Milestone *Milestone `json:"milestone"`
|
Milestone *Milestone `json:"milestone"`
|
||||||
Assignee *User `json:"assignee"`
|
|
||||||
Assignees []*User `json:"assignees"`
|
Assignees []*User `json:"assignees"`
|
||||||
// Whether the issue is open or closed
|
// Whether the issue is open or closed
|
||||||
State StateType `json:"state"`
|
State StateType `json:"state"`
|
||||||
|
@ -62,6 +63,18 @@ type ListIssueOption struct {
|
||||||
Labels []string
|
Labels []string
|
||||||
Milestones []string
|
Milestones []string
|
||||||
KeyWord string
|
KeyWord string
|
||||||
|
Since time.Time
|
||||||
|
Before time.Time
|
||||||
|
// filter by created by username
|
||||||
|
CreatedBy string
|
||||||
|
// filter by assigned to username
|
||||||
|
AssignedBy string
|
||||||
|
// filter by username mentioned
|
||||||
|
MentionedBy string
|
||||||
|
// filter by owner (only works on ListIssues on User)
|
||||||
|
Owner string
|
||||||
|
// filter by team (requires organization owner parameter to be provided and only works on ListIssues on User)
|
||||||
|
Team string
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateType issue state type
|
// StateType issue state type
|
||||||
|
@ -110,6 +123,29 @@ func (opt *ListIssueOption) QueryEncode() string {
|
||||||
query.Add("milestones", strings.Join(opt.Milestones, ","))
|
query.Add("milestones", strings.Join(opt.Milestones, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !opt.Since.IsZero() {
|
||||||
|
query.Add("since", opt.Since.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
if !opt.Before.IsZero() {
|
||||||
|
query.Add("before", opt.Before.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opt.CreatedBy) > 0 {
|
||||||
|
query.Add("created_by", opt.CreatedBy)
|
||||||
|
}
|
||||||
|
if len(opt.AssignedBy) > 0 {
|
||||||
|
query.Add("assigned_by", opt.AssignedBy)
|
||||||
|
}
|
||||||
|
if len(opt.MentionedBy) > 0 {
|
||||||
|
query.Add("mentioned_by", opt.MentionedBy)
|
||||||
|
}
|
||||||
|
if len(opt.Owner) > 0 {
|
||||||
|
query.Add("owner", opt.Owner)
|
||||||
|
}
|
||||||
|
if len(opt.Team) > 0 {
|
||||||
|
query.Add("team", opt.MentionedBy)
|
||||||
|
}
|
||||||
|
|
||||||
return query.Encode()
|
return query.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,50 +157,62 @@ func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) {
|
||||||
link, _ := url.Parse("/repos/issues/search")
|
link, _ := url.Parse("/repos/issues/search")
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
|
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
|
||||||
if e := c.CheckServerVersionConstraint(">=1.12.0"); e != nil {
|
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil {
|
||||||
for i := 0; i < len(issues); i++ {
|
for i := 0; i < len(issues); i++ {
|
||||||
if issues[i].Repository != nil {
|
if issues[i].Repository != nil {
|
||||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i := range issues {
|
||||||
|
c.issueBackwardsCompatibility(issues[i])
|
||||||
|
}
|
||||||
return issues, resp, err
|
return issues, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRepoIssues returns all issues for a given repository
|
// ListRepoIssues returns all issues for a given repository
|
||||||
func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, *Response, error) {
|
func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
issues := make([]*Issue, 0, opt.PageSize)
|
issues := make([]*Issue, 0, opt.PageSize)
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues", owner, repo))
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues", owner, repo))
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
|
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
|
||||||
if e := c.CheckServerVersionConstraint(">=1.12.0"); e != nil {
|
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil {
|
||||||
for i := 0; i < len(issues); i++ {
|
for i := 0; i < len(issues); i++ {
|
||||||
if issues[i].Repository != nil {
|
if issues[i].Repository != nil {
|
||||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i := range issues {
|
||||||
|
c.issueBackwardsCompatibility(issues[i])
|
||||||
|
}
|
||||||
return issues, resp, err
|
return issues, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIssue returns a single issue for a given repository
|
// GetIssue returns a single issue for a given repository
|
||||||
func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, error) {
|
func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
issue := new(Issue)
|
issue := new(Issue)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue)
|
||||||
if e := c.CheckServerVersionConstraint(">=1.12.0"); e != nil && issue.Repository != nil {
|
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil && issue.Repository != nil {
|
||||||
issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0]
|
issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0]
|
||||||
}
|
}
|
||||||
|
c.issueBackwardsCompatibility(issue)
|
||||||
return issue, resp, err
|
return issue, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateIssueOption options to create one issue
|
// CreateIssueOption options to create one issue
|
||||||
type CreateIssueOption struct {
|
type CreateIssueOption struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
// username of assignee
|
Ref string `json:"ref"`
|
||||||
Assignee string `json:"assignee"`
|
|
||||||
Assignees []string `json:"assignees"`
|
Assignees []string `json:"assignees"`
|
||||||
Deadline *time.Time `json:"due_date"`
|
Deadline *time.Time `json:"due_date"`
|
||||||
// milestone id
|
// milestone id
|
||||||
|
@ -184,6 +232,9 @@ func (opt CreateIssueOption) Validate() error {
|
||||||
|
|
||||||
// CreateIssue create a new issue for a given repository
|
// CreateIssue create a new issue for a given repository
|
||||||
func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, *Response, error) {
|
func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -194,18 +245,20 @@ func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue,
|
||||||
issue := new(Issue)
|
issue := new(Issue)
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo),
|
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo),
|
||||||
jsonHeader, bytes.NewReader(body), issue)
|
jsonHeader, bytes.NewReader(body), issue)
|
||||||
|
c.issueBackwardsCompatibility(issue)
|
||||||
return issue, resp, err
|
return issue, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditIssueOption options for editing an issue
|
// EditIssueOption options for editing an issue
|
||||||
type EditIssueOption struct {
|
type EditIssueOption struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Body *string `json:"body"`
|
Body *string `json:"body"`
|
||||||
Assignee *string `json:"assignee"`
|
Ref *string `json:"ref"`
|
||||||
Assignees []string `json:"assignees"`
|
Assignees []string `json:"assignees"`
|
||||||
Milestone *int64 `json:"milestone"`
|
Milestone *int64 `json:"milestone"`
|
||||||
State *StateType `json:"state"`
|
State *StateType `json:"state"`
|
||||||
Deadline *time.Time `json:"due_date"`
|
Deadline *time.Time `json:"due_date"`
|
||||||
|
RemoveDeadline *bool `json:"unset_due_date"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the EditIssueOption struct
|
// Validate the EditIssueOption struct
|
||||||
|
@ -218,6 +271,9 @@ func (opt EditIssueOption) Validate() error {
|
||||||
|
|
||||||
// EditIssue modify an existing issue for a given repository
|
// EditIssue modify an existing issue for a given repository
|
||||||
func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) (*Issue, *Response, error) {
|
func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) (*Issue, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -229,5 +285,25 @@ func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption)
|
||||||
resp, err := c.getParsedResponse("PATCH",
|
resp, err := c.getParsedResponse("PATCH",
|
||||||
fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index),
|
fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index),
|
||||||
jsonHeader, bytes.NewReader(body), issue)
|
jsonHeader, bytes.NewReader(body), issue)
|
||||||
|
c.issueBackwardsCompatibility(issue)
|
||||||
return issue, resp, err
|
return issue, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteIssue delete a issue from a repository
|
||||||
|
func (c *Client) DeleteIssue(user, repo string, id int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("DELETE",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/issues/%d", user, repo, id),
|
||||||
|
nil, nil)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) issueBackwardsCompatibility(issue *Issue) {
|
||||||
|
if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil {
|
||||||
|
c.mutex.RLock()
|
||||||
|
issue.HTMLURL = fmt.Sprintf("%s/%s/issues/%d", c.url, issue.Repository.FullName, issue.Index)
|
||||||
|
c.mutex.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -47,6 +47,9 @@ func (opt *ListIssueCommentOptions) QueryEncode() string {
|
||||||
|
|
||||||
// ListIssueComments list comments on an issue.
|
// ListIssueComments list comments on an issue.
|
||||||
func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
|
func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index))
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index))
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
|
@ -57,6 +60,9 @@ func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssu
|
||||||
|
|
||||||
// ListRepoIssueComments list comments for a given repo.
|
// ListRepoIssueComments list comments for a given repo.
|
||||||
func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
|
func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo))
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo))
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
|
@ -67,8 +73,11 @@ func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentO
|
||||||
|
|
||||||
// GetIssueComment get a comment for a given repo by id.
|
// GetIssueComment get a comment for a given repo by id.
|
||||||
func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) {
|
func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
comment := new(Comment)
|
comment := new(Comment)
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return comment, nil, err
|
return comment, nil, err
|
||||||
}
|
}
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, id), nil, nil, &comment)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, id), nil, nil, &comment)
|
||||||
|
@ -90,6 +99,9 @@ func (opt CreateIssueCommentOption) Validate() error {
|
||||||
|
|
||||||
// CreateIssueComment create comment on an issue.
|
// CreateIssueComment create comment on an issue.
|
||||||
func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, *Response, error) {
|
func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -117,6 +129,9 @@ func (opt EditIssueCommentOption) Validate() error {
|
||||||
|
|
||||||
// EditIssueComment edits an issue comment.
|
// EditIssueComment edits an issue comment.
|
||||||
func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditIssueCommentOption) (*Comment, *Response, error) {
|
func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditIssueCommentOption) (*Comment, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -131,6 +146,9 @@ func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditI
|
||||||
|
|
||||||
// DeleteIssueComment deletes an issue comment.
|
// DeleteIssueComment deletes an issue comment.
|
||||||
func (c *Client) DeleteIssueComment(owner, repo string, commentID int64) (*Response, error) {
|
func (c *Client) DeleteIssueComment(owner, repo string, commentID int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
package gitea
|
package gitea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"log"
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestIssueComment creat a issue and test comment creation/edit/deletion on it
|
// TestIssueComment creat a issue and test comment creation/edit/deletion on it
|
||||||
|
|
|
@ -29,6 +29,9 @@ type ListLabelsOptions struct {
|
||||||
|
|
||||||
// ListRepoLabels list labels of one repository
|
// ListRepoLabels list labels of one repository
|
||||||
func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*Label, *Response, error) {
|
func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*Label, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
labels := make([]*Label, 0, opt.PageSize)
|
labels := make([]*Label, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels?%s", owner, repo, opt.getURLQuery().Encode()), nil, nil, &labels)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels?%s", owner, repo, opt.getURLQuery().Encode()), nil, nil, &labels)
|
||||||
|
@ -37,6 +40,9 @@ func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*L
|
||||||
|
|
||||||
// GetRepoLabel get one label of repository by repo it
|
// GetRepoLabel get one label of repository by repo it
|
||||||
func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, *Response, error) {
|
func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
label := new(Label)
|
label := new(Label)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label)
|
||||||
return label, resp, err
|
return label, resp, err
|
||||||
|
@ -67,11 +73,14 @@ func (opt CreateLabelOption) Validate() error {
|
||||||
|
|
||||||
// CreateLabel create one label of repository
|
// CreateLabel create one label of repository
|
||||||
func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, *Response, error) {
|
func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if len(opt.Color) == 6 {
|
if len(opt.Color) == 6 {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
opt.Color = "#" + opt.Color
|
opt.Color = "#" + opt.Color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +123,9 @@ func (opt EditLabelOption) Validate() error {
|
||||||
|
|
||||||
// EditLabel modify one label with options
|
// EditLabel modify one label with options
|
||||||
func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, *Response, error) {
|
func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -128,12 +140,18 @@ func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*
|
||||||
|
|
||||||
// DeleteLabel delete one label of repository by id
|
// DeleteLabel delete one label of repository by id
|
||||||
func (c *Client) DeleteLabel(owner, repo string, id int64) (*Response, error) {
|
func (c *Client) DeleteLabel(owner, repo string, id int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIssueLabels get labels of one issue via issue id
|
// GetIssueLabels get labels of one issue via issue id
|
||||||
func (c *Client) GetIssueLabels(owner, repo string, index int64, opts ListLabelsOptions) ([]*Label, *Response, error) {
|
func (c *Client) GetIssueLabels(owner, repo string, index int64, opts ListLabelsOptions) ([]*Label, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
labels := make([]*Label, 0, 5)
|
labels := make([]*Label, 0, 5)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/labels?%s", owner, repo, index, opts.getURLQuery().Encode()), nil, nil, &labels)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/labels?%s", owner, repo, index, opts.getURLQuery().Encode()), nil, nil, &labels)
|
||||||
return labels, resp, err
|
return labels, resp, err
|
||||||
|
@ -147,6 +165,9 @@ type IssueLabelsOption struct {
|
||||||
|
|
||||||
// AddIssueLabels add one or more labels to one issue
|
// AddIssueLabels add one or more labels to one issue
|
||||||
func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
|
func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -158,6 +179,9 @@ func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabels
|
||||||
|
|
||||||
// ReplaceIssueLabels replace old labels of issue with new labels
|
// ReplaceIssueLabels replace old labels of issue with new labels
|
||||||
func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
|
func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -170,12 +194,18 @@ func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLa
|
||||||
// DeleteIssueLabel delete one label of one issue by issue id and label id
|
// DeleteIssueLabel delete one label of one issue by issue id and label id
|
||||||
// TODO: maybe we need delete by label name and issue id
|
// TODO: maybe we need delete by label name and issue id
|
||||||
func (c *Client) DeleteIssueLabel(owner, repo string, index, label int64) (*Response, error) {
|
func (c *Client) DeleteIssueLabel(owner, repo string, index, label int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, label), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, label), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearIssueLabels delete all the labels of one issue.
|
// ClearIssueLabels delete all the labels of one issue.
|
||||||
func (c *Client) ClearIssueLabels(owner, repo string, index int64) (*Response, error) {
|
func (c *Client) ClearIssueLabels(owner, repo string, index int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
121
gitea/issue_label_test.go
Normal file
121
gitea/issue_label_test.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestLabels test label related func
|
||||||
|
func TestLabels(t *testing.T) {
|
||||||
|
log.Println("== TestLabels ==")
|
||||||
|
c := newTestClient()
|
||||||
|
repo, err := createTestRepo(t, "LabelTestsRepo", c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
createOpts := CreateLabelOption{
|
||||||
|
Name: " ",
|
||||||
|
Description: "",
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
err = createOpts.Validate()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualValues(t, "invalid color format", err.Error())
|
||||||
|
createOpts.Color = "12345f"
|
||||||
|
err = createOpts.Validate()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualValues(t, "empty name not allowed", err.Error())
|
||||||
|
createOpts.Name = "label one"
|
||||||
|
|
||||||
|
labelOne, _, err := c.CreateLabel(repo.Owner.UserName, repo.Name, createOpts)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, createOpts.Name, labelOne.Name)
|
||||||
|
assert.EqualValues(t, createOpts.Color, labelOne.Color)
|
||||||
|
|
||||||
|
labelTwo, _, err := c.CreateLabel(repo.Owner.UserName, repo.Name, CreateLabelOption{
|
||||||
|
Name: "blue",
|
||||||
|
Color: "#0000FF",
|
||||||
|
Description: "CMYB(100%, 100%, 0%, 0%)",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, _, err = c.CreateLabel(repo.Owner.UserName, repo.Name, CreateLabelOption{
|
||||||
|
Name: "gray",
|
||||||
|
Color: "808080",
|
||||||
|
Description: "CMYB(0%, 0%, 0%, 50%)",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, _, err = c.CreateLabel(repo.Owner.UserName, repo.Name, CreateLabelOption{
|
||||||
|
Name: "green",
|
||||||
|
Color: "#98F76C",
|
||||||
|
Description: "CMYB(38%, 0%, 56%, 3%)",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
labels, resp, err := c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{ListOptions: ListOptions{PageSize: 3}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, labels, 3)
|
||||||
|
assert.NotNil(t, resp)
|
||||||
|
assert.Contains(t, labels, labelTwo)
|
||||||
|
assert.NotContains(t, labels, labelOne)
|
||||||
|
|
||||||
|
label, _, err := c.GetRepoLabel(repo.Owner.UserName, repo.Name, labelTwo.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, labelTwo, label)
|
||||||
|
|
||||||
|
label, _, err = c.EditLabel(repo.Owner.UserName, repo.Name, labelTwo.ID, EditLabelOption{
|
||||||
|
Color: OptionalString("#0e0175"),
|
||||||
|
Description: OptionalString("blueish"),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, &Label{
|
||||||
|
ID: labelTwo.ID,
|
||||||
|
Name: labelTwo.Name,
|
||||||
|
Color: "0e0175",
|
||||||
|
Description: "blueish",
|
||||||
|
URL: labelTwo.URL,
|
||||||
|
}, label)
|
||||||
|
labels, _, _ = c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{ListOptions: ListOptions{PageSize: 3}})
|
||||||
|
|
||||||
|
createTestIssue(t, c, repo.Name, "test-issue", "", nil, nil, 0, []int64{label.ID}, false, false)
|
||||||
|
issueIndex := int64(1)
|
||||||
|
|
||||||
|
issueLabels, _, err := c.GetIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, ListLabelsOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, issueLabels, 1)
|
||||||
|
assert.EqualValues(t, label, issueLabels[0])
|
||||||
|
|
||||||
|
_, _, err = c.AddIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, IssueLabelsOption{Labels: []int64{labels[0].ID}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
issueLabels, _, err = c.AddIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, IssueLabelsOption{Labels: []int64{labels[1].ID, labels[2].ID}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, issueLabels, 3)
|
||||||
|
assert.EqualValues(t, labels, issueLabels)
|
||||||
|
|
||||||
|
labels, _, _ = c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{})
|
||||||
|
assert.Len(t, labels, 11)
|
||||||
|
|
||||||
|
issueLabels, _, err = c.ReplaceIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, IssueLabelsOption{Labels: []int64{labels[0].ID, labels[1].ID}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, issueLabels, 2)
|
||||||
|
|
||||||
|
_, err = c.DeleteIssueLabel(repo.Owner.UserName, repo.Name, issueIndex, labels[0].ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
issueLabels, _, _ = c.GetIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, ListLabelsOptions{})
|
||||||
|
assert.Len(t, issueLabels, 1)
|
||||||
|
|
||||||
|
_, err = c.ClearIssueLabels(repo.Owner.UserName, repo.Name, issueIndex)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
issueLabels, _, _ = c.GetIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, ListLabelsOptions{})
|
||||||
|
assert.Len(t, issueLabels, 0)
|
||||||
|
|
||||||
|
_, err = c.DeleteLabel(repo.Owner.UserName, repo.Name, labelTwo.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
labels, _, _ = c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{})
|
||||||
|
assert.Len(t, labels, 10)
|
||||||
|
}
|
|
@ -49,6 +49,9 @@ func (opt *ListMilestoneOption) QueryEncode() string {
|
||||||
|
|
||||||
// ListRepoMilestones list all the milestones of one repository
|
// ListRepoMilestones list all the milestones of one repository
|
||||||
func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption) ([]*Milestone, *Response, error) {
|
func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption) ([]*Milestone, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
milestones := make([]*Milestone, 0, opt.PageSize)
|
milestones := make([]*Milestone, 0, opt.PageSize)
|
||||||
|
|
||||||
|
@ -60,18 +63,24 @@ func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption)
|
||||||
|
|
||||||
// GetMilestone get one milestone by repo name and milestone id
|
// GetMilestone get one milestone by repo name and milestone id
|
||||||
func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Response, error) {
|
func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
milestone := new(Milestone)
|
milestone := new(Milestone)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone)
|
||||||
return milestone, resp, err
|
return milestone, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMilestoneByName get one milestone by repo and milestone name
|
// GetMilestoneByName get one milestone by repo and milestone name
|
||||||
func (c *Client) GetMilestoneByName(owner, repo string, name string) (*Milestone, *Response, error) {
|
func (c *Client) GetMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) {
|
||||||
if c.CheckServerVersionConstraint(">=1.13") != nil {
|
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
||||||
// backwards compatibility mode
|
// backwards compatibility mode
|
||||||
m, resp, err := c.resolveMilestoneByName(owner, repo, name)
|
m, resp, err := c.resolveMilestoneByName(owner, repo, name)
|
||||||
return m, resp, err
|
return m, resp, err
|
||||||
}
|
}
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
milestone := new(Milestone)
|
milestone := new(Milestone)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone)
|
||||||
return milestone, resp, err
|
return milestone, resp, err
|
||||||
|
@ -95,6 +104,9 @@ func (opt CreateMilestoneOption) Validate() error {
|
||||||
|
|
||||||
// CreateMilestone create one milestone with options
|
// CreateMilestone create one milestone with options
|
||||||
func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, *Response, error) {
|
func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -135,6 +147,9 @@ func (opt EditMilestoneOption) Validate() error {
|
||||||
|
|
||||||
// EditMilestone modify milestone with options
|
// EditMilestone modify milestone with options
|
||||||
func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, *Response, error) {
|
func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -148,8 +163,8 @@ func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOp
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditMilestoneByName modify milestone with options
|
// EditMilestoneByName modify milestone with options
|
||||||
func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMilestoneOption) (*Milestone, *Response, error) {
|
func (c *Client) EditMilestoneByName(owner, repo, name string, opt EditMilestoneOption) (*Milestone, *Response, error) {
|
||||||
if c.CheckServerVersionConstraint(">=1.13") != nil {
|
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
||||||
// backwards compatibility mode
|
// backwards compatibility mode
|
||||||
m, _, err := c.resolveMilestoneByName(owner, repo, name)
|
m, _, err := c.resolveMilestoneByName(owner, repo, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -157,6 +172,9 @@ func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMi
|
||||||
}
|
}
|
||||||
return c.EditMilestone(owner, repo, m.ID, opt)
|
return c.EditMilestone(owner, repo, m.ID, opt)
|
||||||
}
|
}
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -171,13 +189,16 @@ func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMi
|
||||||
|
|
||||||
// DeleteMilestone delete one milestone by id
|
// DeleteMilestone delete one milestone by id
|
||||||
func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error) {
|
func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMilestoneByName delete one milestone by name
|
// DeleteMilestoneByName delete one milestone by name
|
||||||
func (c *Client) DeleteMilestoneByName(owner, repo string, name string) (*Response, error) {
|
func (c *Client) DeleteMilestoneByName(owner, repo, name string) (*Response, error) {
|
||||||
if c.CheckServerVersionConstraint(">=1.13") != nil {
|
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
||||||
// backwards compatibility mode
|
// backwards compatibility mode
|
||||||
m, _, err := c.resolveMilestoneByName(owner, repo, name)
|
m, _, err := c.resolveMilestoneByName(owner, repo, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -185,6 +206,9 @@ func (c *Client) DeleteMilestoneByName(owner, repo string, name string) (*Respon
|
||||||
}
|
}
|
||||||
return c.DeleteMilestone(owner, repo, m.ID)
|
return c.DeleteMilestone(owner, repo, m.ID)
|
||||||
}
|
}
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
@ -205,7 +229,7 @@ func (c *Client) resolveMilestoneByName(owner, repo, name string) (*Milestone, *
|
||||||
return nil, nil, fmt.Errorf("milestone '%s' do not exist", name)
|
return nil, nil, fmt.Errorf("milestone '%s' do not exist", name)
|
||||||
}
|
}
|
||||||
for _, m := range miles {
|
for _, m := range miles {
|
||||||
if strings.ToLower(strings.TrimSpace(m.Title)) == strings.ToLower(strings.TrimSpace(name)) {
|
if strings.EqualFold(strings.TrimSpace(m.Title), strings.TrimSpace(name)) {
|
||||||
return m, resp, nil
|
return m, resp, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ func TestMilestones(t *testing.T) {
|
||||||
|
|
||||||
repo, _ := createTestRepo(t, "TestMilestones", c)
|
repo, _ := createTestRepo(t, "TestMilestones", c)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
future := time.Unix(1896134400, 0) //2030-02-01
|
future := time.Unix(1896134400, 0) // 2030-02-01
|
||||||
closed := "closed"
|
closed := "closed"
|
||||||
sClosed := StateClosed
|
sClosed := StateClosed
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ func TestMilestones(t *testing.T) {
|
||||||
// ListRepoMilestones
|
// ListRepoMilestones
|
||||||
ml, _, err := c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{})
|
ml, _, err := c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, ml, 2)
|
assert.Len(t, ml, 3)
|
||||||
ml, _, err = c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{State: StateClosed})
|
ml, _, err = c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{State: StateClosed})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, ml, 1)
|
assert.Len(t, ml, 1)
|
||||||
|
@ -59,7 +59,7 @@ func TestMilestones(t *testing.T) {
|
||||||
m, _, err := c.resolveMilestoneByName(repo.Owner.UserName, repo.Name, "V3.0")
|
m, _, err := c.resolveMilestoneByName(repo.Owner.UserName, repo.Name, "V3.0")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, ml[0].ID, m.ID)
|
assert.EqualValues(t, ml[0].ID, m.ID)
|
||||||
m, _, err = c.resolveMilestoneByName(repo.Owner.UserName, repo.Name, "NoEvidenceOfExist")
|
_, _, err = c.resolveMilestoneByName(repo.Owner.UserName, repo.Name, "NoEvidenceOfExist")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.EqualValues(t, "milestone 'NoEvidenceOfExist' do not exist", err.Error())
|
assert.EqualValues(t, "milestone 'NoEvidenceOfExist' do not exist", err.Error())
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Reaction struct {
|
||||||
|
|
||||||
// GetIssueReactions get a list reactions of an issue
|
// GetIssueReactions get a list reactions of an issue
|
||||||
func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction, *Response, error) {
|
func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
reactions := make([]*Reaction, 0, 10)
|
reactions := make([]*Reaction, 0, 10)
|
||||||
|
@ -30,7 +30,7 @@ func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction
|
||||||
|
|
||||||
// GetIssueCommentReactions get a list of reactions from a comment of an issue
|
// GetIssueCommentReactions get a list of reactions from a comment of an issue
|
||||||
func (c *Client) GetIssueCommentReactions(owner, repo string, commentID int64) ([]*Reaction, *Response, error) {
|
func (c *Client) GetIssueCommentReactions(owner, repo string, commentID int64) ([]*Reaction, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
reactions := make([]*Reaction, 0, 10)
|
reactions := make([]*Reaction, 0, 10)
|
||||||
|
@ -45,7 +45,7 @@ type editReactionOption struct {
|
||||||
|
|
||||||
// PostIssueReaction add a reaction to an issue
|
// PostIssueReaction add a reaction to an issue
|
||||||
func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction string) (*Reaction, *Response, error) {
|
func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction string) (*Reaction, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
reactionResponse := new(Reaction)
|
reactionResponse := new(Reaction)
|
||||||
|
@ -61,7 +61,7 @@ func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction str
|
||||||
|
|
||||||
// DeleteIssueReaction remove a reaction from an issue
|
// DeleteIssueReaction remove a reaction from an issue
|
||||||
func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction string) (*Response, error) {
|
func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction string) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
||||||
|
@ -74,7 +74,7 @@ func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction s
|
||||||
|
|
||||||
// PostIssueCommentReaction add a reaction to a comment of an issue
|
// PostIssueCommentReaction add a reaction to a comment of an issue
|
||||||
func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Reaction, *Response, error) {
|
func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Reaction, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
reactionResponse := new(Reaction)
|
reactionResponse := new(Reaction)
|
||||||
|
@ -90,7 +90,7 @@ func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, r
|
||||||
|
|
||||||
// DeleteIssueCommentReaction remove a reaction from a comment of an issue
|
// DeleteIssueCommentReaction remove a reaction from a comment of an issue
|
||||||
func (c *Client) DeleteIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Response, error) {
|
func (c *Client) DeleteIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
||||||
|
|
|
@ -11,8 +11,13 @@ import (
|
||||||
|
|
||||||
// StopWatch represents a running stopwatch of an issue / pr
|
// StopWatch represents a running stopwatch of an issue / pr
|
||||||
type StopWatch struct {
|
type StopWatch struct {
|
||||||
Created time.Time `json:"created"`
|
Created time.Time `json:"created"`
|
||||||
IssueIndex int64 `json:"issue_index"`
|
Seconds int64 `json:"seconds"`
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
IssueIndex int64 `json:"issue_index"`
|
||||||
|
IssueTitle string `json:"issue_title"`
|
||||||
|
RepoOwnerName string `json:"repo_owner_name"`
|
||||||
|
RepoName string `json:"repo_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMyStopwatches list all stopwatches
|
// GetMyStopwatches list all stopwatches
|
||||||
|
@ -24,6 +29,9 @@ func (c *Client) GetMyStopwatches() ([]*StopWatch, *Response, error) {
|
||||||
|
|
||||||
// DeleteIssueStopwatch delete / cancel a specific stopwatch
|
// DeleteIssueStopwatch delete / cancel a specific stopwatch
|
||||||
func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Response, error) {
|
func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/delete", owner, repo, index), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/delete", owner, repo, index), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
@ -31,6 +39,9 @@ func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Respons
|
||||||
// StartIssueStopWatch starts a stopwatch for an existing issue for a given
|
// StartIssueStopWatch starts a stopwatch for an existing issue for a given
|
||||||
// repository
|
// repository
|
||||||
func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response, error) {
|
func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index), nil, nil)
|
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
@ -38,6 +49,9 @@ func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response
|
||||||
// StopIssueStopWatch stops an existing stopwatch for an issue in a given
|
// StopIssueStopWatch stops an existing stopwatch for an issue in a given
|
||||||
// repository
|
// repository
|
||||||
func (c *Client) StopIssueStopWatch(owner, repo string, index int64) (*Response, error) {
|
func (c *Client) StopIssueStopWatch(owner, repo string, index int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index), nil, nil)
|
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
// GetIssueSubscribers get list of users who subscribed on an issue
|
// GetIssueSubscribers get list of users who subscribed on an issue
|
||||||
func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User, *Response, error) {
|
func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
subscribers := make([]*User, 0, 10)
|
subscribers := make([]*User, 0, 10)
|
||||||
|
@ -21,7 +21,7 @@ func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User,
|
||||||
|
|
||||||
// AddIssueSubscription Subscribe user to issue
|
// AddIssueSubscription Subscribe user to issue
|
||||||
func (c *Client) AddIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
|
func (c *Client) AddIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
|
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
|
||||||
|
@ -39,7 +39,7 @@ func (c *Client) AddIssueSubscription(owner, repo string, index int64, user stri
|
||||||
|
|
||||||
// DeleteIssueSubscription unsubscribe user from issue
|
// DeleteIssueSubscription unsubscribe user from issue
|
||||||
func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
|
func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
|
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
|
||||||
|
@ -57,7 +57,10 @@ func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user s
|
||||||
|
|
||||||
// CheckIssueSubscription check if current user is subscribed to an issue
|
// CheckIssueSubscription check if current user is subscribed to an issue
|
||||||
func (c *Client) CheckIssueSubscription(owner, repo string, index int64) (*WatchInfo, *Response, error) {
|
func (c *Client) CheckIssueSubscription(owner, repo string, index int64) (*WatchInfo, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
wi := new(WatchInfo)
|
wi := new(WatchInfo)
|
||||||
|
|
97
gitea/issue_template.go
Normal file
97
gitea/issue_template.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IssueTemplate provides metadata and content on an issue template.
|
||||||
|
// There are two types of issue templates: .Markdown- and .Form-based.
|
||||||
|
type IssueTemplate struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
About string `json:"about"`
|
||||||
|
Filename string `json:"file_name"`
|
||||||
|
IssueTitle string `json:"title"`
|
||||||
|
IssueLabels []string `json:"labels"`
|
||||||
|
IssueRef string `json:"ref"`
|
||||||
|
// If non-nil, this is a form-based template
|
||||||
|
Form []IssueFormElement `json:"body"`
|
||||||
|
// Should only be used when .Form is nil.
|
||||||
|
MarkdownContent string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueFormElement describes a part of a IssueTemplate form
|
||||||
|
type IssueFormElement struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type IssueFormElementType `json:"type"`
|
||||||
|
Attributes IssueFormElementAttributes `json:"attributes"`
|
||||||
|
Validations IssueFormElementValidations `json:"validations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueFormElementAttributes contains the combined set of attributes available on all element types.
|
||||||
|
type IssueFormElementAttributes struct {
|
||||||
|
// required for all element types.
|
||||||
|
// A brief description of the expected user input, which is also displayed in the form.
|
||||||
|
Label string `json:"label"`
|
||||||
|
// required for element types "dropdown", "checkboxes"
|
||||||
|
// for dropdown, contains the available options
|
||||||
|
Options []string `json:"options"`
|
||||||
|
// for element types "markdown", "textarea", "input"
|
||||||
|
// Text that is pre-filled in the input
|
||||||
|
Value string `json:"value"`
|
||||||
|
// for element types "textarea", "input", "dropdown", "checkboxes"
|
||||||
|
// A description of the text area to provide context or guidance, which is displayed in the form.
|
||||||
|
Description string `json:"description"`
|
||||||
|
// for element types "textarea", "input"
|
||||||
|
// A semi-opaque placeholder that renders in the text area when empty.
|
||||||
|
Placeholder string `json:"placeholder"`
|
||||||
|
// for element types "textarea"
|
||||||
|
// A language specifier. If set, the input is rendered as codeblock with syntax highlighting.
|
||||||
|
SyntaxHighlighting string `json:"render"`
|
||||||
|
// for element types "dropdown"
|
||||||
|
Multiple bool `json:"multiple"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueFormElementValidations contains the combined set of validations available on all element types.
|
||||||
|
type IssueFormElementValidations struct {
|
||||||
|
// for all element types
|
||||||
|
Required bool `json:"required"`
|
||||||
|
// for element types "input"
|
||||||
|
IsNumber bool `json:"is_number"`
|
||||||
|
// for element types "input"
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssueFormElementType is an enum
|
||||||
|
type IssueFormElementType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// IssueFormElementMarkdown is markdown rendered to the form for context, but omitted in the resulting issue
|
||||||
|
IssueFormElementMarkdown IssueFormElementType = "markdown"
|
||||||
|
// IssueFormElementTextarea is a multi line input
|
||||||
|
IssueFormElementTextarea IssueFormElementType = "textarea"
|
||||||
|
// IssueFormElementInput is a single line input
|
||||||
|
IssueFormElementInput IssueFormElementType = "input"
|
||||||
|
// IssueFormElementDropdown is a select form
|
||||||
|
IssueFormElementDropdown IssueFormElementType = "dropdown"
|
||||||
|
// IssueFormElementCheckboxes are a multi checkbox input
|
||||||
|
IssueFormElementCheckboxes IssueFormElementType = "checkboxes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetIssueTemplates lists all issue templates of the repository
|
||||||
|
func (c *Client) GetIssueTemplates(owner, repo string) ([]*IssueTemplate, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
templates := new([]*IssueTemplate)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issue_templates", owner, repo), nil, nil, templates)
|
||||||
|
return *templates, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsForm tells if this template is a form instead of a markdown-based template.
|
||||||
|
func (t IssueTemplate) IsForm() bool {
|
||||||
|
return t.Form != nil
|
||||||
|
}
|
|
@ -19,9 +19,10 @@ func TestIssue(t *testing.T) {
|
||||||
|
|
||||||
createIssue(t, c)
|
createIssue(t, c)
|
||||||
// Little sleep in order to give some time for gitea to properly store all information on database. Without this sleep, CI is a bit unstable
|
// Little sleep in order to give some time for gitea to properly store all information on database. Without this sleep, CI is a bit unstable
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
editIssues(t, c)
|
editIssues(t, c)
|
||||||
listIssues(t, c)
|
listIssues(t, c)
|
||||||
|
deleteIssue(t, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createIssue(t *testing.T, c *Client) {
|
func createIssue(t *testing.T, c *Client) {
|
||||||
|
@ -47,24 +48,37 @@ func createIssue(t *testing.T, c *Client) {
|
||||||
createTestIssue(t, c, repo.Name, "", "you never know", nil, nil, mile.ID, nil, true, true)
|
createTestIssue(t, c, repo.Name, "", "you never know", nil, nil, mile.ID, nil, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteIssue(t *testing.T, c *Client) {
|
||||||
|
log.Println("== TestDeleteIssues ==")
|
||||||
|
|
||||||
|
user, _, err := c.GetMyUserInfo()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
repo, _ := createTestRepo(t, "IssueTestsRepo", c)
|
||||||
|
|
||||||
|
issue := createTestIssue(t, c, repo.Name, "Deleteable Issue", "", nil, nil, 0, nil, false, false)
|
||||||
|
_, err = c.DeleteIssue(user.UserName, repo.Name, issue.Index)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func editIssues(t *testing.T, c *Client) {
|
func editIssues(t *testing.T, c *Client) {
|
||||||
log.Println("== TestEditIssues ==")
|
log.Println("== TestEditIssues ==")
|
||||||
il, _, err := c.ListIssues(ListIssueOption{KeyWord: "soon"})
|
il, _, err := c.ListIssues(ListIssueOption{KeyWord: "soon!"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
issue, _, err := c.GetIssue(il[0].Poster.UserName, il[0].Repository.Name, il[0].Index)
|
issue, _, err := c.GetIssue(il[0].Poster.UserName, il[0].Repository.Name, il[0].Index)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
body := "123 test and go"
|
|
||||||
state := StateClosed
|
state := StateClosed
|
||||||
issueNew, _, err := c.EditIssue(issue.Poster.UserName, issue.Repository.Name, issue.Index, EditIssueOption{
|
issueNew, _, err := c.EditIssue(issue.Poster.UserName, issue.Repository.Name, issue.Index, EditIssueOption{
|
||||||
Title: "Edited",
|
Title: "Edited",
|
||||||
Body: &body,
|
Body: OptionalString("123 test and go"),
|
||||||
State: &state,
|
State: &state,
|
||||||
|
Ref: OptionalString("main"),
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, issue.ID, issueNew.ID)
|
assert.EqualValues(t, issue.ID, issueNew.ID)
|
||||||
assert.EqualValues(t, body, issueNew.Body)
|
assert.EqualValues(t, "123 test and go", issueNew.Body)
|
||||||
assert.EqualValues(t, "Edited", issueNew.Title)
|
assert.EqualValues(t, "Edited", issueNew.Title)
|
||||||
|
assert.EqualValues(t, "main", issueNew.Ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listIssues(t *testing.T, c *Client) {
|
func listIssues(t *testing.T, c *Client) {
|
||||||
|
@ -103,7 +117,7 @@ func listIssues(t *testing.T, c *Client) {
|
||||||
assert.Len(t, issues, 3)
|
assert.Len(t, issues, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assignees []string, deadline *time.Time, milestone int64, labels []int64, closed, shouldFail bool) {
|
func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assignees []string, deadline *time.Time, milestone int64, labels []int64, closed, shouldFail bool) *Issue {
|
||||||
user, _, err := c.GetMyUserInfo()
|
user, _, err := c.GetMyUserInfo()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
issue, _, e := c.CreateIssue(user.UserName, repoName, CreateIssueOption{
|
issue, _, e := c.CreateIssue(user.UserName, repoName, CreateIssueOption{
|
||||||
|
@ -117,7 +131,7 @@ func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assi
|
||||||
})
|
})
|
||||||
if shouldFail {
|
if shouldFail {
|
||||||
assert.Error(t, e)
|
assert.Error(t, e)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
assert.NoError(t, e)
|
assert.NoError(t, e)
|
||||||
assert.NotEmpty(t, issue)
|
assert.NotEmpty(t, issue)
|
||||||
|
@ -136,4 +150,5 @@ func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assi
|
||||||
} else {
|
} else {
|
||||||
assert.Empty(t, issue.Closed)
|
assert.Empty(t, issue.Closed)
|
||||||
}
|
}
|
||||||
|
return issue
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,33 +26,50 @@ type TrackedTime struct {
|
||||||
Issue *Issue `json:"issue"`
|
Issue *Issue `json:"issue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserTrackedTimes list tracked times of a user
|
// ListTrackedTimesOptions options for listing repository's tracked times
|
||||||
func (c *Client) GetUserTrackedTimes(owner, repo, user string) ([]*TrackedTime, *Response, error) {
|
type ListTrackedTimesOptions struct {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
ListOptions
|
||||||
return nil, nil, err
|
Since time.Time
|
||||||
}
|
Before time.Time
|
||||||
times := make([]*TrackedTime, 0, 10)
|
// User filter is only used by ListRepoTrackedTimes !!!
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times/%s", owner, repo, user), nil, nil, ×)
|
User string
|
||||||
return times, resp, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepoTrackedTimes list tracked times of a repository
|
// QueryEncode turns options into querystring argument
|
||||||
func (c *Client) GetRepoTrackedTimes(owner, repo string) ([]*TrackedTime, *Response, error) {
|
func (opt *ListTrackedTimesOptions) QueryEncode() string {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
query := opt.getURLQuery()
|
||||||
|
|
||||||
|
if !opt.Since.IsZero() {
|
||||||
|
query.Add("since", opt.Since.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
if !opt.Before.IsZero() {
|
||||||
|
query.Add("before", opt.Before.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opt.User) != 0 {
|
||||||
|
query.Add("user", opt.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRepoTrackedTimes list tracked times of a repository
|
||||||
|
func (c *Client) ListRepoTrackedTimes(owner, repo string, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
times := make([]*TrackedTime, 0, 10)
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/times", owner, repo))
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times", owner, repo), nil, nil, ×)
|
opt.setDefaults()
|
||||||
|
link.RawQuery = opt.QueryEncode()
|
||||||
|
times := make([]*TrackedTime, 0, opt.PageSize)
|
||||||
|
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ×)
|
||||||
return times, resp, err
|
return times, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMyTrackedTimes list tracked times of the current user
|
// GetMyTrackedTimes list tracked times of the current user
|
||||||
func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, *Response, error) {
|
func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
times := make([]*TrackedTime, 0, 10)
|
times := make([]*TrackedTime, 0, 10)
|
||||||
resp, err := c.getParsedResponse("GET", "/user/times", nil, nil, ×)
|
resp, err := c.getParsedResponse("GET", "/user/times", jsonHeader, nil, ×)
|
||||||
return times, resp, err
|
return times, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +93,7 @@ func (opt AddTimeOption) Validate() error {
|
||||||
|
|
||||||
// AddTime adds time to issue with the given index
|
// AddTime adds time to issue with the given index
|
||||||
func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, *Response, error) {
|
func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
|
@ -92,36 +110,33 @@ func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*T
|
||||||
return t, resp, err
|
return t, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTrackedTimesOptions options for listing repository's tracked times
|
// ListIssueTrackedTimes list tracked times of a single issue for a given repository
|
||||||
type ListTrackedTimesOptions struct {
|
func (c *Client) ListIssueTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
|
||||||
ListOptions
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
// ListTrackedTimes list tracked times of a single issue for a given repository
|
|
||||||
func (c *Client) ListTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
|
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index))
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
|
link.RawQuery = opt.QueryEncode()
|
||||||
times := make([]*TrackedTime, 0, opt.PageSize)
|
times := make([]*TrackedTime, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/times?%s", owner, repo, index, opt.getURLQuery().Encode()), nil, nil, ×)
|
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ×)
|
||||||
return times, resp, err
|
return times, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetIssueTime reset tracked time of a single issue for a given repository
|
// ResetIssueTime reset tracked time of a single issue for a given repository
|
||||||
func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, error) {
|
func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), jsonHeader, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTime delete a specific tracked time by id of a single issue for a given repository
|
// DeleteTime delete a specific tracked time by id of a single issue for a given repository
|
||||||
func (c *Client) DeleteTime(owner, repo string, index, timeID int64) (*Response, error) {
|
func (c *Client) DeleteTime(owner, repo string, index, timeID int64) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), jsonHeader, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,13 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultPageSize = 10
|
|
||||||
const maxPageSize = 50
|
|
||||||
|
|
||||||
// ListOptions options for using Gitea's API pagination
|
// ListOptions options for using Gitea's API pagination
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
Page int
|
// Setting Page to -1 disables pagination on endpoints that support it.
|
||||||
|
// Page numbering starts at 1.
|
||||||
|
Page int
|
||||||
|
// The default value depends on the server config DEFAULT_PAGING_NUM
|
||||||
|
// The highest valid value depends on the server config MAX_RESPONSE_ITEMS
|
||||||
PageSize int
|
PageSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,12 +27,14 @@ func (o ListOptions) getURLQuery() url.Values {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o ListOptions) setDefaults() {
|
// setDefaults applies default pagination options.
|
||||||
if o.Page < 1 {
|
// If .Page is set to -1, it will disable pagination.
|
||||||
|
// WARNING: This function is not idempotent, make sure to never call this method twice!
|
||||||
|
func (o *ListOptions) setDefaults() {
|
||||||
|
if o.Page < 0 {
|
||||||
|
o.Page, o.PageSize = 0, 0
|
||||||
|
return
|
||||||
|
} else if o.Page == 0 {
|
||||||
o.Page = 1
|
o.Page = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.PageSize < 0 || o.PageSize > maxPageSize {
|
|
||||||
o.PageSize = defaultPageSize
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,14 +40,16 @@ func enableRunGitea() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestClient() *Client {
|
func newTestClient() *Client {
|
||||||
|
c, _ := NewClient(getGiteaURL(), newTestClientAuth())
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestClientAuth() ClientOption {
|
||||||
token := getGiteaToken()
|
token := getGiteaToken()
|
||||||
if token == "" {
|
if token == "" {
|
||||||
client := NewClientWithHTTP(getGiteaURL(), &http.Client{})
|
return SetBasicAuth(getGiteaUsername(), getGiteaPassword())
|
||||||
client.SetBasicAuth(getGiteaUsername(), getGiteaPassword())
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
c, _ := NewClient(getGiteaURL(), SetToken(getGiteaToken()))
|
return SetToken(getGiteaToken())
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func giteaMasterPath() string {
|
func giteaMasterPath() string {
|
||||||
|
@ -80,7 +82,7 @@ func downGitea() (string, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = os.Chmod(f.Name(), 700); err != nil {
|
if err = os.Chmod(f.Name(), 0o700); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +101,10 @@ func runGitea() (*os.Process, error) {
|
||||||
|
|
||||||
giteaDir := filepath.Dir(p)
|
giteaDir := filepath.Dir(p)
|
||||||
cfgDir := filepath.Join(giteaDir, "custom", "conf")
|
cfgDir := filepath.Join(giteaDir, "custom", "conf")
|
||||||
os.MkdirAll(cfgDir, os.ModePerm)
|
err = os.MkdirAll(cfgDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
cfg, err := os.Create(filepath.Join(cfgDir, "app.ini"))
|
cfg, err := os.Create(filepath.Join(cfgDir, "app.ini"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -148,7 +153,9 @@ func TestMain(m *testing.M) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
p.Kill()
|
if err := p.Kill(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
log.Printf("testing with %v, %v, %v\n", getGiteaURL(), getGiteaUsername(), getGiteaPassword())
|
log.Printf("testing with %v, %v, %v\n", getGiteaURL(), getGiteaUsername(), getGiteaPassword())
|
||||||
|
|
|
@ -23,10 +23,13 @@ type NotificationThread struct {
|
||||||
|
|
||||||
// NotificationSubject contains the notification subject (Issue/Pull/Commit)
|
// NotificationSubject contains the notification subject (Issue/Pull/Commit)
|
||||||
type NotificationSubject struct {
|
type NotificationSubject struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
LatestCommentURL string `json:"latest_comment_url"`
|
HTMLURL string `json:"html_url"`
|
||||||
Type string `json:"type" binding:"In(Issue,Pull,Commit)"`
|
LatestCommentURL string `json:"latest_comment_url"`
|
||||||
|
LatestCommentHTMLURL string `json:"latest_comment_html_url"`
|
||||||
|
Type NotifySubjectType `json:"type"`
|
||||||
|
State NotifySubjectState `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyStatus notification status type
|
// NotifyStatus notification status type
|
||||||
|
@ -41,12 +44,39 @@ const (
|
||||||
NotifyStatusPinned NotifyStatus = "pinned"
|
NotifyStatusPinned NotifyStatus = "pinned"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NotifySubjectType represent type of notification subject
|
||||||
|
type NotifySubjectType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NotifySubjectIssue an issue is subject of an notification
|
||||||
|
NotifySubjectIssue NotifySubjectType = "Issue"
|
||||||
|
// NotifySubjectPull an pull is subject of an notification
|
||||||
|
NotifySubjectPull NotifySubjectType = "Pull"
|
||||||
|
// NotifySubjectCommit an commit is subject of an notification
|
||||||
|
NotifySubjectCommit NotifySubjectType = "Commit"
|
||||||
|
// NotifySubjectRepository an repository is subject of an notification
|
||||||
|
NotifySubjectRepository NotifySubjectType = "Repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotifySubjectState reflect state of notification subject
|
||||||
|
type NotifySubjectState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NotifySubjectOpen if subject is a pull/issue and is open at the moment
|
||||||
|
NotifySubjectOpen NotifySubjectState = "open"
|
||||||
|
// NotifySubjectClosed if subject is a pull/issue and is closed at the moment
|
||||||
|
NotifySubjectClosed NotifySubjectState = "closed"
|
||||||
|
// NotifySubjectMerged if subject is a pull and got merged
|
||||||
|
NotifySubjectMerged NotifySubjectState = "merged"
|
||||||
|
)
|
||||||
|
|
||||||
// ListNotificationOptions represents the filter options
|
// ListNotificationOptions represents the filter options
|
||||||
type ListNotificationOptions struct {
|
type ListNotificationOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
Since time.Time
|
Since time.Time
|
||||||
Before time.Time
|
Before time.Time
|
||||||
Status []NotifyStatus
|
Status []NotifyStatus
|
||||||
|
SubjectTypes []NotifySubjectType
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkNotificationOptions represents the filter & modify options
|
// MarkNotificationOptions represents the filter & modify options
|
||||||
|
@ -68,13 +98,16 @@ func (opt *ListNotificationOptions) QueryEncode() string {
|
||||||
for _, s := range opt.Status {
|
for _, s := range opt.Status {
|
||||||
query.Add("status-types", string(s))
|
query.Add("status-types", string(s))
|
||||||
}
|
}
|
||||||
|
for _, s := range opt.SubjectTypes {
|
||||||
|
query.Add("subject-type", string(s))
|
||||||
|
}
|
||||||
return query.Encode()
|
return query.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the CreateUserOption struct
|
// Validate the CreateUserOption struct
|
||||||
func (opt ListNotificationOptions) Validate(c *Client) error {
|
func (opt ListNotificationOptions) Validate(c *Client) error {
|
||||||
if len(opt.Status) != 0 {
|
if len(opt.Status) != 0 {
|
||||||
return c.CheckServerVersionConstraint(">=1.12.3")
|
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -97,14 +130,14 @@ func (opt *MarkNotificationOptions) QueryEncode() string {
|
||||||
// Validate the CreateUserOption struct
|
// Validate the CreateUserOption struct
|
||||||
func (opt MarkNotificationOptions) Validate(c *Client) error {
|
func (opt MarkNotificationOptions) Validate(c *Client) error {
|
||||||
if len(opt.Status) != 0 || len(opt.ToStatus) != 0 {
|
if len(opt.Status) != 0 || len(opt.ToStatus) != 0 {
|
||||||
return c.CheckServerVersionConstraint(">=1.12.3")
|
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckNotifications list users's notification threads
|
// CheckNotifications list users's notification threads
|
||||||
func (c *Client) CheckNotifications() (int64, *Response, error) {
|
func (c *Client) CheckNotifications() (int64, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
new := struct {
|
new := struct {
|
||||||
|
@ -117,7 +150,7 @@ func (c *Client) CheckNotifications() (int64, *Response, error) {
|
||||||
|
|
||||||
// GetNotification get notification thread by ID
|
// GetNotification get notification thread by ID
|
||||||
func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, error) {
|
func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
thread := new(NotificationThread)
|
thread := new(NotificationThread)
|
||||||
|
@ -127,21 +160,27 @@ func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, erro
|
||||||
|
|
||||||
// ReadNotification mark notification thread as read by ID
|
// ReadNotification mark notification thread as read by ID
|
||||||
// It optionally takes a second argument if status has to be set other than 'read'
|
// It optionally takes a second argument if status has to be set other than 'read'
|
||||||
func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*Response, error) {
|
// The relevant notification will be returned as the first parameter when the Gitea server is 1.16.0 or higher.
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*NotificationThread, *Response, error) {
|
||||||
return nil, err
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
link := fmt.Sprintf("/notifications/threads/%d", id)
|
link := fmt.Sprintf("/notifications/threads/%d", id)
|
||||||
if len(status) != 0 {
|
if len(status) != 0 {
|
||||||
link += fmt.Sprintf("?to-status=%s", status[0])
|
link += fmt.Sprintf("?to-status=%s", status[0])
|
||||||
}
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil {
|
||||||
|
thread := &NotificationThread{}
|
||||||
|
resp, err := c.getParsedResponse("PATCH", link, nil, nil, thread)
|
||||||
|
return thread, resp, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("PATCH", link, nil, nil)
|
_, resp, err := c.getResponse("PATCH", link, nil, nil)
|
||||||
return resp, err
|
return nil, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListNotifications list users's notification threads
|
// ListNotifications list users's notification threads
|
||||||
func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := opt.Validate(c); err != nil {
|
if err := opt.Validate(c); err != nil {
|
||||||
|
@ -155,28 +194,38 @@ func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*Notification
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadNotifications mark notification threads as read
|
// ReadNotifications mark notification threads as read
|
||||||
func (c *Client) ReadNotifications(opt MarkNotificationOptions) (*Response, error) {
|
// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher.
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
func (c *Client) ReadNotifications(opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||||
return nil, err
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := opt.Validate(c); err != nil {
|
if err := opt.Validate(c); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
link, _ := url.Parse("/notifications")
|
link, _ := url.Parse("/notifications")
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
|
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil {
|
||||||
|
threads := make([]*NotificationThread, 0, 10)
|
||||||
|
resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads)
|
||||||
|
return threads, resp, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
|
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
|
||||||
return resp, err
|
return nil, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRepoNotifications list users's notification threads on a specific repo
|
// ListRepoNotifications list users's notification threads on a specific repo
|
||||||
func (c *Client) ListRepoNotifications(owner, reponame string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
func (c *Client) ListRepoNotifications(owner, repo string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := opt.Validate(c); err != nil {
|
if err := opt.Validate(c); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, reponame))
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo))
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
threads := make([]*NotificationThread, 0, 10)
|
threads := make([]*NotificationThread, 0, 10)
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads)
|
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads)
|
||||||
|
@ -184,15 +233,25 @@ func (c *Client) ListRepoNotifications(owner, reponame string, opt ListNotificat
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadRepoNotifications mark notification threads as read on a specific repo
|
// ReadRepoNotifications mark notification threads as read on a specific repo
|
||||||
func (c *Client) ReadRepoNotifications(owner, reponame string, opt MarkNotificationOptions) (*Response, error) {
|
// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher.
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||||
return nil, err
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := opt.Validate(c); err != nil {
|
if err := opt.Validate(c); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, reponame))
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo))
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
|
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil {
|
||||||
|
threads := make([]*NotificationThread, 0, 10)
|
||||||
|
resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads)
|
||||||
|
return threads, resp, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
|
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
|
||||||
return resp, err
|
return nil, resp, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ func TestNotifications(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
user2 := createTestUser(t, "notify2", c)
|
user2 := createTestUser(t, "notify2", c)
|
||||||
|
|
||||||
//create 2 repos
|
// create 2 repos
|
||||||
repoA, err := createTestRepo(t, "TestNotifications_A", c)
|
repoA, err := createTestRepo(t, "TestNotifications_A", c)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -35,8 +35,9 @@ func TestNotifications(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
c.sudo = user2.UserName
|
c.sudo = user2.UserName
|
||||||
_, err = c.ReadNotifications(MarkNotificationOptions{})
|
notifications, _, err := c.ReadNotifications(MarkNotificationOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, notifications, 0)
|
||||||
count, _, err := c.CheckNotifications()
|
count, _, err := c.CheckNotifications()
|
||||||
assert.EqualValues(t, 0, count)
|
assert.EqualValues(t, 0, count)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -45,7 +46,7 @@ func TestNotifications(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
issue, _, err := c.CreateIssue(repoB.Owner.UserName, repoB.Name, CreateIssueOption{Title: "B Issue", Closed: false})
|
issue, _, err := c.CreateIssue(repoB.Owner.UserName, repoB.Name, CreateIssueOption{Title: "B Issue", Closed: false})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 5)
|
||||||
|
|
||||||
// CheckNotifications of user2
|
// CheckNotifications of user2
|
||||||
c.sudo = user2.UserName
|
c.sudo = user2.UserName
|
||||||
|
@ -60,6 +61,8 @@ func TestNotifications(t *testing.T) {
|
||||||
for _, n := range nList {
|
for _, n := range nList {
|
||||||
assert.EqualValues(t, true, n.Unread)
|
assert.EqualValues(t, true, n.Unread)
|
||||||
assert.EqualValues(t, "Issue", n.Subject.Type)
|
assert.EqualValues(t, "Issue", n.Subject.Type)
|
||||||
|
assert.EqualValues(t, NotifySubjectOpen, nList[0].Subject.State)
|
||||||
|
assert.EqualValues(t, NotifySubjectOpen, nList[1].Subject.State)
|
||||||
if n.Subject.Title == "A Issue" {
|
if n.Subject.Title == "A Issue" {
|
||||||
assert.EqualValues(t, repoA.Name, n.Repository.Name)
|
assert.EqualValues(t, repoA.Name, n.Repository.Name)
|
||||||
} else if n.Subject.Title == "B Issue" {
|
} else if n.Subject.Title == "B Issue" {
|
||||||
|
@ -75,8 +78,9 @@ func TestNotifications(t *testing.T) {
|
||||||
assert.Len(t, nList, 1)
|
assert.Len(t, nList, 1)
|
||||||
assert.EqualValues(t, "A Issue", nList[0].Subject.Title)
|
assert.EqualValues(t, "A Issue", nList[0].Subject.Title)
|
||||||
// ReadRepoNotifications
|
// ReadRepoNotifications
|
||||||
_, err = c.ReadRepoNotifications(repoA.Owner.UserName, repoA.Name, MarkNotificationOptions{})
|
notifications, _, err = c.ReadRepoNotifications(repoA.Owner.UserName, repoA.Name, MarkNotificationOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, notifications, 1)
|
||||||
|
|
||||||
// GetThread
|
// GetThread
|
||||||
n, _, err := c.GetNotification(nList[0].ID)
|
n, _, err := c.GetNotification(nList[0].ID)
|
||||||
|
@ -85,8 +89,9 @@ func TestNotifications(t *testing.T) {
|
||||||
assert.EqualValues(t, "A Issue", n.Subject.Title)
|
assert.EqualValues(t, "A Issue", n.Subject.Title)
|
||||||
|
|
||||||
// ReadNotifications
|
// ReadNotifications
|
||||||
_, err = c.ReadNotifications(MarkNotificationOptions{})
|
notifications, _, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, notifications, 1)
|
||||||
nList, _, err = c.ListNotifications(ListNotificationOptions{})
|
nList, _, err = c.ListNotifications(ListNotificationOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, nList, 0)
|
assert.Len(t, nList, 0)
|
||||||
|
@ -96,7 +101,7 @@ func TestNotifications(t *testing.T) {
|
||||||
c.sudo = ""
|
c.sudo = ""
|
||||||
_, _, err = c.EditIssue(repoB.Owner.UserName, repoB.Name, issue.Index, EditIssueOption{State: &iState})
|
_, _, err = c.EditIssue(repoB.Owner.UserName, repoB.Name, issue.Index, EditIssueOption{State: &iState})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 5)
|
||||||
|
|
||||||
c.sudo = user2.UserName
|
c.sudo = user2.UserName
|
||||||
nList, _, err = c.ListNotifications(ListNotificationOptions{})
|
nList, _, err = c.ListNotifications(ListNotificationOptions{})
|
||||||
|
@ -104,24 +109,32 @@ func TestNotifications(t *testing.T) {
|
||||||
count, _, err = c.CheckNotifications()
|
count, _, err = c.CheckNotifications()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 1, count)
|
assert.EqualValues(t, 1, count)
|
||||||
assert.Len(t, nList, 1)
|
if assert.Len(t, nList, 1) {
|
||||||
if len(nList) > 0 {
|
assert.EqualValues(t, NotifySubjectClosed, nList[0].Subject.State)
|
||||||
_, err = c.ReadNotification(nList[0].ID)
|
notification, _, err := c.ReadNotification(nList[0].ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, notification.ID, nList[0].ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.sudo = ""
|
c.sudo = ""
|
||||||
_, err = c.ReadNotifications(MarkNotificationOptions{})
|
notifications, _, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, notifications, 2)
|
||||||
nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusRead}})
|
nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusRead}})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, nList, 4)
|
if assert.Len(t, nList, 2) {
|
||||||
|
notification, _, err := c.ReadNotification(nList[0].ID, NotifyStatusPinned)
|
||||||
|
assert.EqualValues(t, notification.ID, nList[0].ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, err = c.ReadNotification(nList[2].ID, NotifyStatusPinned)
|
notification, _, err = c.ReadNotification(nList[1].ID, NotifyStatusUnread)
|
||||||
assert.NoError(t, err)
|
assert.EqualValues(t, notification.ID, nList[1].ID)
|
||||||
_, err = c.ReadNotification(nList[3].ID, NotifyStatusUnread)
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
}
|
||||||
nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusPinned, NotifyStatusUnread}})
|
nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusPinned, NotifyStatusUnread}})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, nList, 2)
|
if assert.Len(t, nList, 2) {
|
||||||
|
assert.EqualValues(t, NotifySubjectOpen, nList[0].Subject.State)
|
||||||
|
assert.EqualValues(t, NotifySubjectOpen, nList[1].Subject.State)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,13 @@ import (
|
||||||
|
|
||||||
// Oauth2 represents an Oauth2 Application
|
// Oauth2 represents an Oauth2 Application
|
||||||
type Oauth2 struct {
|
type Oauth2 struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
ClientID string `json:"client_id"`
|
ClientID string `json:"client_id"`
|
||||||
ClientSecret string `json:"client_secret"`
|
ClientSecret string `json:"client_secret"`
|
||||||
RedirectURIs []string `json:"redirect_uris"`
|
RedirectURIs []string `json:"redirect_uris"`
|
||||||
Created time.Time `json:"created"`
|
ConfidentialClient bool `json:"confidential_client"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListOauth2Option for listing Oauth2 Applications
|
// ListOauth2Option for listing Oauth2 Applications
|
||||||
|
@ -28,13 +29,14 @@ type ListOauth2Option struct {
|
||||||
|
|
||||||
// CreateOauth2Option required options for creating an Application
|
// CreateOauth2Option required options for creating an Application
|
||||||
type CreateOauth2Option struct {
|
type CreateOauth2Option struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
RedirectURIs []string `json:"redirect_uris"`
|
ConfidentialClient bool `json:"confidential_client"`
|
||||||
|
RedirectURIs []string `json:"redirect_uris"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object.
|
// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object.
|
||||||
func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error) {
|
func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
|
@ -48,7 +50,7 @@ func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error
|
||||||
|
|
||||||
// UpdateOauth2 a specific Oauth2 Application by ID and return a completed Oauth2 object.
|
// UpdateOauth2 a specific Oauth2 Application by ID and return a completed Oauth2 object.
|
||||||
func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2, *Response, error) {
|
func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
|
@ -62,7 +64,7 @@ func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2,
|
||||||
|
|
||||||
// GetOauth2 a specific Oauth2 Application by ID.
|
// GetOauth2 a specific Oauth2 Application by ID.
|
||||||
func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) {
|
func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
oauth2s := &Oauth2{}
|
oauth2s := &Oauth2{}
|
||||||
|
@ -72,7 +74,7 @@ func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) {
|
||||||
|
|
||||||
// ListOauth2 all of your Oauth2 Applications.
|
// ListOauth2 all of your Oauth2 Applications.
|
||||||
func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error) {
|
func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
|
@ -83,7 +85,7 @@ func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error)
|
||||||
|
|
||||||
// DeleteOauth2 delete an Oauth2 application by ID
|
// DeleteOauth2 delete an Oauth2 application by ID
|
||||||
func (c *Client) DeleteOauth2(oauth2id int64) (*Response, error) {
|
func (c *Client) DeleteOauth2(oauth2id int64) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil)
|
||||||
|
|
|
@ -18,27 +18,62 @@ func TestOauth2(t *testing.T) {
|
||||||
user := createTestUser(t, "oauth2_user", c)
|
user := createTestUser(t, "oauth2_user", c)
|
||||||
c.SetSudo(user.UserName)
|
c.SetSudo(user.UserName)
|
||||||
|
|
||||||
newApp, _, err := c.CreateOauth2(CreateOauth2Option{Name: "test", RedirectURIs: []string{"http://test/test"}})
|
type test struct {
|
||||||
assert.NoError(t, err)
|
name string
|
||||||
assert.NotNil(t, newApp)
|
confidentialClient *bool
|
||||||
assert.EqualValues(t, "test", newApp.Name)
|
}
|
||||||
|
boolTrue := true
|
||||||
|
boolFalse := false
|
||||||
|
|
||||||
a, _, err := c.ListOauth2(ListOauth2Option{})
|
testCases := []test{
|
||||||
assert.NoError(t, err)
|
{"ConfidentialClient unset should fallback to false", nil},
|
||||||
assert.Len(t, a, 1)
|
{"ConfidentialClient true", &boolTrue},
|
||||||
assert.EqualValues(t, newApp.Name, a[0].Name)
|
{"ConfidentialClient false", &boolFalse},
|
||||||
|
}
|
||||||
|
|
||||||
b, _, err := c.GetOauth2(newApp.ID)
|
for _, testCase := range testCases {
|
||||||
assert.NoError(t, err)
|
createOptions := CreateOauth2Option{
|
||||||
assert.EqualValues(t, newApp.Name, b.Name)
|
Name: "test",
|
||||||
|
RedirectURIs: []string{"http://test/test"},
|
||||||
|
}
|
||||||
|
if testCase.confidentialClient != nil {
|
||||||
|
createOptions.ConfidentialClient = *testCase.confidentialClient
|
||||||
|
}
|
||||||
|
|
||||||
b, _, err = c.UpdateOauth2(newApp.ID, CreateOauth2Option{Name: newApp.Name, RedirectURIs: []string{"https://test/login"}})
|
newApp, _, err := c.CreateOauth2(createOptions)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err, testCase.name)
|
||||||
assert.EqualValues(t, newApp.Name, b.Name)
|
assert.NotNil(t, newApp, testCase.name)
|
||||||
assert.EqualValues(t, "https://test/login", b.RedirectURIs[0])
|
assert.EqualValues(t, "test", newApp.Name, testCase.name)
|
||||||
assert.EqualValues(t, newApp.ID, b.ID)
|
if testCase.confidentialClient != nil {
|
||||||
assert.NotEqual(t, newApp.ClientSecret, b.ClientSecret)
|
assert.EqualValues(t, *testCase.confidentialClient, newApp.ConfidentialClient, testCase.name)
|
||||||
|
} else {
|
||||||
|
assert.EqualValues(t, false, newApp.ConfidentialClient, testCase.name)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = c.DeleteOauth2(newApp.ID)
|
a, _, err := c.ListOauth2(ListOauth2Option{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err, testCase.name)
|
||||||
|
assert.Len(t, a, 1, testCase.name)
|
||||||
|
assert.EqualValues(t, newApp.Name, a[0].Name, testCase.name)
|
||||||
|
assert.EqualValues(t, newApp.ConfidentialClient, a[0].ConfidentialClient, testCase.name)
|
||||||
|
|
||||||
|
b, _, err := c.GetOauth2(newApp.ID)
|
||||||
|
assert.NoError(t, err, testCase.name)
|
||||||
|
assert.EqualValues(t, newApp.Name, b.Name, testCase.name)
|
||||||
|
assert.EqualValues(t, newApp.ConfidentialClient, b.ConfidentialClient, testCase.name)
|
||||||
|
|
||||||
|
b, _, err = c.UpdateOauth2(newApp.ID, CreateOauth2Option{
|
||||||
|
Name: newApp.Name,
|
||||||
|
ConfidentialClient: !newApp.ConfidentialClient,
|
||||||
|
RedirectURIs: []string{"https://test/login"},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err, testCase.name)
|
||||||
|
assert.EqualValues(t, newApp.Name, b.Name, testCase.name)
|
||||||
|
assert.EqualValues(t, "https://test/login", b.RedirectURIs[0], testCase.name)
|
||||||
|
assert.EqualValues(t, newApp.ID, b.ID, testCase.name)
|
||||||
|
assert.NotEqual(t, newApp.ClientSecret, b.ClientSecret, testCase.name)
|
||||||
|
assert.NotEqual(t, newApp.ConfidentialClient, b.ConfidentialClient, testCase.name)
|
||||||
|
|
||||||
|
_, err = c.DeleteOauth2(newApp.ID)
|
||||||
|
assert.NoError(t, err, testCase.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
29
gitea/org.go
29
gitea/org.go
|
@ -52,6 +52,9 @@ func (c *Client) ListMyOrgs(opt ListOrgsOptions) ([]*Organization, *Response, er
|
||||||
|
|
||||||
// ListUserOrgs list all of some user's organizations
|
// ListUserOrgs list all of some user's organizations
|
||||||
func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) {
|
func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
orgs := make([]*Organization, 0, opt.PageSize)
|
orgs := make([]*Organization, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs?%s", user, opt.getURLQuery().Encode()), nil, nil, &orgs)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs?%s", user, opt.getURLQuery().Encode()), nil, nil, &orgs)
|
||||||
|
@ -60,6 +63,9 @@ func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization
|
||||||
|
|
||||||
// GetOrg get one organization by name
|
// GetOrg get one organization by name
|
||||||
func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) {
|
func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&orgname); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
org := new(Organization)
|
org := new(Organization)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org)
|
||||||
return org, resp, err
|
return org, resp, err
|
||||||
|
@ -67,12 +73,13 @@ func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) {
|
||||||
|
|
||||||
// CreateOrgOption options for creating an organization
|
// CreateOrgOption options for creating an organization
|
||||||
type CreateOrgOption struct {
|
type CreateOrgOption struct {
|
||||||
Name string `json:"username"`
|
Name string `json:"username"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Website string `json:"website"`
|
Website string `json:"website"`
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
Visibility VisibleType `json:"visibility"`
|
Visibility VisibleType `json:"visibility"`
|
||||||
|
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkVisibilityOpt check if mode exist
|
// checkVisibilityOpt check if mode exist
|
||||||
|
@ -86,7 +93,7 @@ func (opt CreateOrgOption) Validate() error {
|
||||||
return fmt.Errorf("empty org name")
|
return fmt.Errorf("empty org name")
|
||||||
}
|
}
|
||||||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
|
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
|
||||||
return fmt.Errorf("infalid bisibility option")
|
return fmt.Errorf("invalid visibility option")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -117,13 +124,16 @@ type EditOrgOption struct {
|
||||||
// Validate the EditOrgOption struct
|
// Validate the EditOrgOption struct
|
||||||
func (opt EditOrgOption) Validate() error {
|
func (opt EditOrgOption) Validate() error {
|
||||||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
|
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
|
||||||
return fmt.Errorf("infalid bisibility option")
|
return fmt.Errorf("invalid visibility option")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditOrg modify one organization via options
|
// EditOrg modify one organization via options
|
||||||
func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) {
|
func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&orgname); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -137,6 +147,9 @@ func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) {
|
||||||
|
|
||||||
// DeleteOrg deletes an organization
|
// DeleteOrg deletes an organization
|
||||||
func (c *Client) DeleteOrg(orgname string) (*Response, error) {
|
func (c *Client) DeleteOrg(orgname string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&orgname); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
87
gitea/org_action.go
Normal file
87
gitea/org_action.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListOrgActionSecretOption list OrgActionSecret options
|
||||||
|
type ListOrgActionSecretOption struct {
|
||||||
|
ListOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListOrgActionSecret list an organization's secrets
|
||||||
|
func (c *Client) ListOrgActionSecret(org string, opt ListOrgActionSecretOption) ([]*Secret, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
opt.setDefaults()
|
||||||
|
secrets := make([]*Secret, 0, opt.PageSize)
|
||||||
|
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/actions/secrets", org))
|
||||||
|
link.RawQuery = opt.getURLQuery().Encode()
|
||||||
|
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets)
|
||||||
|
return secrets, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSecretOption represents the options for creating a secret.
|
||||||
|
type CreateSecretOption struct {
|
||||||
|
Name string `json:"name"` // Name is the name of the secret.
|
||||||
|
Data string `json:"data"` // Data is the data of the secret.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the CreateSecretOption is valid.
|
||||||
|
// It returns an error if any of the validation checks fail.
|
||||||
|
func (opt *CreateSecretOption) Validate() error {
|
||||||
|
if len(opt.Name) == 0 {
|
||||||
|
return fmt.Errorf("name required")
|
||||||
|
}
|
||||||
|
if len(opt.Name) > 30 {
|
||||||
|
return fmt.Errorf("name to long")
|
||||||
|
}
|
||||||
|
if len(opt.Data) == 0 {
|
||||||
|
return fmt.Errorf("data required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrgActionSecret creates a secret for the specified organization in the Gitea Actions.
|
||||||
|
// It takes the organization name and the secret options as parameters.
|
||||||
|
// The function returns the HTTP response and an error, if any.
|
||||||
|
func (c *Client) CreateOrgActionSecret(org string, opt CreateSecretOption) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := (&opt).Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(&opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/actions/secrets/%s", org, opt.Name), jsonHeader, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case http.StatusCreated:
|
||||||
|
return resp, nil
|
||||||
|
case http.StatusNoContent:
|
||||||
|
return resp, nil
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return resp, fmt.Errorf("forbidden")
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
return resp, fmt.Errorf("bad request")
|
||||||
|
default:
|
||||||
|
return resp, fmt.Errorf("unexpected Status: %d", status)
|
||||||
|
}
|
||||||
|
}
|
39
gitea/org_action_test.go
Normal file
39
gitea/org_action_test.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateOrgActionSecret(t *testing.T) {
|
||||||
|
log.Println("== TestCreateOrgActionSecret ==")
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
user := createTestUser(t, "org_action_user", c)
|
||||||
|
c.SetSudo(user.UserName)
|
||||||
|
newOrg, _, err := c.CreateOrg(CreateOrgOption{Name: "ActionOrg"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, newOrg)
|
||||||
|
|
||||||
|
// create secret
|
||||||
|
resp, err := c.CreateOrgActionSecret(newOrg.UserName, CreateSecretOption{Name: "test", Data: "test"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||||
|
|
||||||
|
// update secret
|
||||||
|
resp, err = c.CreateOrgActionSecret(newOrg.UserName, CreateSecretOption{Name: "test", Data: "test2"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
|
||||||
|
|
||||||
|
// list secrets
|
||||||
|
secrets, _, err := c.ListOrgActionSecret(newOrg.UserName, ListOrgActionSecretOption{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, secrets, 1)
|
||||||
|
}
|
|
@ -12,7 +12,10 @@ import (
|
||||||
|
|
||||||
// DeleteOrgMembership remove a member from an organization
|
// DeleteOrgMembership remove a member from an organization
|
||||||
func (c *Client) DeleteOrgMembership(org, user string) (*Response, error) {
|
func (c *Client) DeleteOrgMembership(org, user string) (*Response, error) {
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
|
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,10 +26,13 @@ type ListOrgMembershipOption struct {
|
||||||
|
|
||||||
// ListOrgMembership list an organization's members
|
// ListOrgMembership list an organization's members
|
||||||
func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
|
func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
users := make([]*User, 0, opt.PageSize)
|
users := make([]*User, 0, opt.PageSize)
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", url.PathEscape(org)))
|
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", org))
|
||||||
link.RawQuery = opt.getURLQuery().Encode()
|
link.RawQuery = opt.getURLQuery().Encode()
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
|
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
|
||||||
return users, resp, err
|
return users, resp, err
|
||||||
|
@ -34,10 +40,13 @@ func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*
|
||||||
|
|
||||||
// ListPublicOrgMembership list an organization's members
|
// ListPublicOrgMembership list an organization's members
|
||||||
func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
|
func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
users := make([]*User, 0, opt.PageSize)
|
users := make([]*User, 0, opt.PageSize)
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", url.PathEscape(org)))
|
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", org))
|
||||||
link.RawQuery = opt.getURLQuery().Encode()
|
link.RawQuery = opt.getURLQuery().Encode()
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
|
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
|
||||||
return users, resp, err
|
return users, resp, err
|
||||||
|
@ -45,7 +54,10 @@ func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption
|
||||||
|
|
||||||
// CheckOrgMembership Check if a user is a member of an organization
|
// CheckOrgMembership Check if a user is a member of an organization
|
||||||
func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) {
|
func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) {
|
||||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
|
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, resp, err
|
return false, resp, err
|
||||||
}
|
}
|
||||||
|
@ -61,7 +73,10 @@ func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) {
|
||||||
|
|
||||||
// CheckPublicOrgMembership Check if a user is a member of an organization
|
// CheckPublicOrgMembership Check if a user is a member of an organization
|
||||||
func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, error) {
|
func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, error) {
|
||||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
|
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, resp, err
|
return false, resp, err
|
||||||
}
|
}
|
||||||
|
@ -77,15 +92,18 @@ func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, er
|
||||||
|
|
||||||
// SetPublicOrgMembership publicize/conceal a user's membership
|
// SetPublicOrgMembership publicize/conceal a user's membership
|
||||||
func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) {
|
func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
status int
|
status int
|
||||||
err error
|
err error
|
||||||
resp *Response
|
resp *Response
|
||||||
)
|
)
|
||||||
if visible {
|
if visible {
|
||||||
status, resp, err = c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
|
status, resp, err = c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
|
||||||
} else {
|
} else {
|
||||||
status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil)
|
status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
|
@ -99,3 +117,26 @@ func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Respon
|
||||||
return resp, fmt.Errorf("unexpected Status: %d", status)
|
return resp, fmt.Errorf("unexpected Status: %d", status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OrgPermissions represents the permissions for an user in an organization
|
||||||
|
type OrgPermissions struct {
|
||||||
|
CanCreateRepository bool `json:"can_create_repository"`
|
||||||
|
CanRead bool `json:"can_read"`
|
||||||
|
CanWrite bool `json:"can_write"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
IsOwner bool `json:"is_owner"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrgPermissions returns user permissions for specific organization.
|
||||||
|
func (c *Client) GetOrgPermissions(org, user string) (*OrgPermissions, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
perm := &OrgPermissions{}
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs/%s/permissions", user, org), jsonHeader, nil, &perm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
return perm, resp, nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,11 +11,9 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/*
|
||||||
// DeleteOrgMembership remove a member from an organization
|
// DeleteOrgMembership remove a member from an organization
|
||||||
func (c *Client) DeleteOrgMembership(org, user string) error {}
|
func (c *Client) DeleteOrgMembership(org, user string) error {}
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
func TestOrgMembership(t *testing.T) {
|
func TestOrgMembership(t *testing.T) {
|
||||||
log.Println("== TestOrgMembership ==")
|
log.Println("== TestOrgMembership ==")
|
||||||
|
@ -35,6 +33,11 @@ func TestOrgMembership(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, check)
|
assert.True(t, check)
|
||||||
|
|
||||||
|
perm, _, err := c.GetOrgPermissions(newOrg.UserName, user.UserName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, perm)
|
||||||
|
assert.True(t, perm.IsOwner)
|
||||||
|
|
||||||
_, err = c.SetPublicOrgMembership(newOrg.UserName, user.UserName, true)
|
_, err = c.SetPublicOrgMembership(newOrg.UserName, user.UserName, true)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
check, _, err = c.CheckPublicOrgMembership(newOrg.UserName, user.UserName)
|
check, _, err = c.CheckPublicOrgMembership(newOrg.UserName, user.UserName)
|
||||||
|
|
|
@ -8,21 +8,47 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Team represents a team in an organization
|
// Team represents a team in an organization
|
||||||
type Team struct {
|
type Team struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Organization *Organization `json:"organization"`
|
Organization *Organization `json:"organization"`
|
||||||
Permission AccessMode `json:"permission"`
|
Permission AccessMode `json:"permission"`
|
||||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
Units []RepoUnitType `json:"units"`
|
||||||
Units []string `json:"units"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RepoUnitType represent all unit types of a repo gitea currently offer
|
||||||
|
type RepoUnitType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RepoUnitCode represent file view of a repository
|
||||||
|
RepoUnitCode RepoUnitType = "repo.code"
|
||||||
|
// RepoUnitIssues represent issues of a repository
|
||||||
|
RepoUnitIssues RepoUnitType = "repo.issues"
|
||||||
|
// RepoUnitPulls represent pulls of a repository
|
||||||
|
RepoUnitPulls RepoUnitType = "repo.pulls"
|
||||||
|
// RepoUnitExtIssues represent external issues of a repository
|
||||||
|
RepoUnitExtIssues RepoUnitType = "repo.ext_issues"
|
||||||
|
// RepoUnitWiki represent wiki of a repository
|
||||||
|
RepoUnitWiki RepoUnitType = "repo.wiki"
|
||||||
|
// RepoUnitExtWiki represent external wiki of a repository
|
||||||
|
RepoUnitExtWiki RepoUnitType = "repo.ext_wiki"
|
||||||
|
// RepoUnitReleases represent releases of a repository
|
||||||
|
RepoUnitReleases RepoUnitType = "repo.releases"
|
||||||
|
// RepoUnitProjects represent projects of a repository
|
||||||
|
RepoUnitProjects RepoUnitType = "repo.projects"
|
||||||
|
// RepoUnitPackages represents packages of a repository
|
||||||
|
RepoUnitPackages RepoUnitType = "repo.packages"
|
||||||
|
// RepoUnitActions represents actions of a repository
|
||||||
|
RepoUnitActions RepoUnitType = "repo.actions"
|
||||||
|
)
|
||||||
|
|
||||||
// ListTeamsOptions options for listing teams
|
// ListTeamsOptions options for listing teams
|
||||||
type ListTeamsOptions struct {
|
type ListTeamsOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
|
@ -30,6 +56,9 @@ type ListTeamsOptions struct {
|
||||||
|
|
||||||
// ListOrgTeams lists all teams of an organization
|
// ListOrgTeams lists all teams of an organization
|
||||||
func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) {
|
func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
teams := make([]*Team, 0, opt.PageSize)
|
teams := make([]*Team, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams?%s", org, opt.getURLQuery().Encode()), nil, nil, &teams)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams?%s", org, opt.getURLQuery().Encode()), nil, nil, &teams)
|
||||||
|
@ -51,19 +80,56 @@ func (c *Client) GetTeam(id int64) (*Team, *Response, error) {
|
||||||
return t, resp, err
|
return t, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchTeamsOptions options for searching teams.
|
||||||
|
type SearchTeamsOptions struct {
|
||||||
|
ListOptions
|
||||||
|
Query string
|
||||||
|
IncludeDescription bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o SearchTeamsOptions) getURLQuery() url.Values {
|
||||||
|
query := make(url.Values)
|
||||||
|
query.Add("page", fmt.Sprintf("%d", o.Page))
|
||||||
|
query.Add("limit", fmt.Sprintf("%d", o.PageSize))
|
||||||
|
query.Add("q", o.Query)
|
||||||
|
query.Add("include_desc", fmt.Sprintf("%t", o.IncludeDescription))
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamSearchResults is the JSON struct that is returned from Team search API.
|
||||||
|
type TeamSearchResults struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
Data []*Team `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchOrgTeams search for teams in a org.
|
||||||
|
func (c *Client) SearchOrgTeams(org string, opt *SearchTeamsOptions) ([]*Team, *Response, error) {
|
||||||
|
responseBody := TeamSearchResults{}
|
||||||
|
opt.setDefaults()
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams/search?%s", org, opt.getURLQuery().Encode()), nil, nil, &responseBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
if !responseBody.OK {
|
||||||
|
return nil, resp, fmt.Errorf("gitea error: %v", responseBody.Error)
|
||||||
|
}
|
||||||
|
return responseBody.Data, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
// CreateTeamOption options for creating a team
|
// CreateTeamOption options for creating a team
|
||||||
type CreateTeamOption struct {
|
type CreateTeamOption struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Permission AccessMode `json:"permission"`
|
Permission AccessMode `json:"permission"`
|
||||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
Units []RepoUnitType `json:"units"`
|
||||||
Units []string `json:"units"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the CreateTeamOption struct
|
// Validate the CreateTeamOption struct
|
||||||
func (opt CreateTeamOption) Validate() error {
|
func (opt *CreateTeamOption) Validate() error {
|
||||||
if opt.Permission == AccessModeOwner {
|
if opt.Permission == AccessModeOwner {
|
||||||
opt.Permission = AccessModeAdmin
|
opt.Permission = AccessModeAdmin
|
||||||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
|
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
|
||||||
|
@ -83,7 +149,10 @@ func (opt CreateTeamOption) Validate() error {
|
||||||
|
|
||||||
// CreateTeam creates a team for an organization
|
// CreateTeam creates a team for an organization
|
||||||
func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response, error) {
|
func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response, error) {
|
||||||
if err := opt.Validate(); err != nil {
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := (&opt).Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
|
@ -97,17 +166,16 @@ func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response,
|
||||||
|
|
||||||
// EditTeamOption options for editing a team
|
// EditTeamOption options for editing a team
|
||||||
type EditTeamOption struct {
|
type EditTeamOption struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
Permission AccessMode `json:"permission"`
|
Permission AccessMode `json:"permission"`
|
||||||
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
|
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
|
||||||
IncludesAllRepositories *bool `json:"includes_all_repositories"`
|
IncludesAllRepositories *bool `json:"includes_all_repositories"`
|
||||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
Units []RepoUnitType `json:"units"`
|
||||||
Units []string `json:"units"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the EditTeamOption struct
|
// Validate the EditTeamOption struct
|
||||||
func (opt EditTeamOption) Validate() error {
|
func (opt *EditTeamOption) Validate() error {
|
||||||
if opt.Permission == AccessModeOwner {
|
if opt.Permission == AccessModeOwner {
|
||||||
opt.Permission = AccessModeAdmin
|
opt.Permission = AccessModeAdmin
|
||||||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
|
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
|
||||||
|
@ -127,7 +195,7 @@ func (opt EditTeamOption) Validate() error {
|
||||||
|
|
||||||
// EditTeam edits a team of an organization
|
// EditTeam edits a team of an organization
|
||||||
func (c *Client) EditTeam(id int64, opt EditTeamOption) (*Response, error) {
|
func (c *Client) EditTeam(id int64, opt EditTeamOption) (*Response, error) {
|
||||||
if err := opt.Validate(); err != nil {
|
if err := (&opt).Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
|
@ -159,6 +227,9 @@ func (c *Client) ListTeamMembers(id int64, opt ListTeamMembersOptions) ([]*User,
|
||||||
|
|
||||||
// GetTeamMember gets a member of a team
|
// GetTeamMember gets a member of a team
|
||||||
func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) {
|
func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
m := new(User)
|
m := new(User)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m)
|
||||||
return m, resp, err
|
return m, resp, err
|
||||||
|
@ -166,12 +237,18 @@ func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error)
|
||||||
|
|
||||||
// AddTeamMember adds a member to a team
|
// AddTeamMember adds a member to a team
|
||||||
func (c *Client) AddTeamMember(id int64, user string) (*Response, error) {
|
func (c *Client) AddTeamMember(id int64, user string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
|
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveTeamMember removes a member from a team
|
// RemoveTeamMember removes a member from a team
|
||||||
func (c *Client) RemoveTeamMember(id int64, user string) (*Response, error) {
|
func (c *Client) RemoveTeamMember(id int64, user string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
@ -191,12 +268,18 @@ func (c *Client) ListTeamRepositories(id int64, opt ListTeamRepositoriesOptions)
|
||||||
|
|
||||||
// AddTeamRepository adds a repository to a team
|
// AddTeamRepository adds a repository to a team
|
||||||
func (c *Client) AddTeamRepository(id int64, org, repo string) (*Response, error) {
|
func (c *Client) AddTeamRepository(id int64, org, repo string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
|
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveTeamRepository removes a repository from a team
|
// RemoveTeamRepository removes a repository from a team
|
||||||
func (c *Client) RemoveTeamRepository(id int64, org, repo string) (*Response, error) {
|
func (c *Client) RemoveTeamRepository(id int64, org, repo string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
56
gitea/org_team_test.go
Normal file
56
gitea/org_team_test.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createTestOrgTeams(t *testing.T, c *Client, org, name string, accessMode AccessMode, units []RepoUnitType) (*Team, error) {
|
||||||
|
team, _, e := c.CreateTeam(org, CreateTeamOption{
|
||||||
|
Name: name,
|
||||||
|
Description: name + "'s team desc",
|
||||||
|
Permission: accessMode,
|
||||||
|
CanCreateOrgRepo: false,
|
||||||
|
IncludesAllRepositories: false,
|
||||||
|
Units: units,
|
||||||
|
})
|
||||||
|
assert.NoError(t, e)
|
||||||
|
assert.NotNil(t, team)
|
||||||
|
return team, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTeamSearch(t *testing.T) {
|
||||||
|
log.Println("== TestTeamSearch ==")
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
orgName := "TestTeamsOrg"
|
||||||
|
// prepare for test
|
||||||
|
_, _, err := c.CreateOrg(CreateOrgOption{
|
||||||
|
Name: orgName,
|
||||||
|
Visibility: VisibleTypePublic,
|
||||||
|
RepoAdminChangeTeamAccess: true,
|
||||||
|
})
|
||||||
|
defer func() {
|
||||||
|
_, _ = c.DeleteOrg(orgName)
|
||||||
|
}()
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if _, err = createTestOrgTeams(t, c, orgName, "Admins", AccessModeAdmin, []RepoUnitType{RepoUnitCode, RepoUnitIssues, RepoUnitPulls, RepoUnitReleases}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teams, _, err := c.SearchOrgTeams(orgName, &SearchTeamsOptions{
|
||||||
|
Query: "Admins",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, teams, 1) {
|
||||||
|
assert.Equal(t, "Admins", teams[0].Name)
|
||||||
|
}
|
||||||
|
}
|
49
gitea/org_test.go
Normal file
49
gitea/org_test.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createTestOrgRepo(t *testing.T, c *Client, name string) (func(), *Repository, error) {
|
||||||
|
_, _, err := c.GetOrg(name)
|
||||||
|
if err == nil {
|
||||||
|
_, _ = c.DeleteOrg(name)
|
||||||
|
}
|
||||||
|
_, _, err = c.CreateOrg(CreateOrgOption{
|
||||||
|
Name: name,
|
||||||
|
Visibility: VisibleTypePublic,
|
||||||
|
RepoAdminChangeTeamAccess: true,
|
||||||
|
})
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = c.GetRepo(name, name)
|
||||||
|
if err == nil {
|
||||||
|
_, _ = c.DeleteRepo(name, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, _, err := c.CreateOrgRepo(name, CreateRepoOption{
|
||||||
|
Name: name,
|
||||||
|
Description: "A test Repo: " + name,
|
||||||
|
AutoInit: true,
|
||||||
|
Gitignores: "C,C++",
|
||||||
|
License: "MIT",
|
||||||
|
Readme: "Default",
|
||||||
|
IssueLabels: "Default",
|
||||||
|
Private: false,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, repo)
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
_, _ = c.DeleteRepo(name, name)
|
||||||
|
_, _ = c.DeleteOrg(name)
|
||||||
|
}, repo, err
|
||||||
|
}
|
93
gitea/package.go
Normal file
93
gitea/package.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Package represents a package
|
||||||
|
type Package struct {
|
||||||
|
// the package's id
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
// the package's owner
|
||||||
|
Owner User `json:"owner"`
|
||||||
|
// the repo this package belongs to (if any)
|
||||||
|
Repository *Repository `json:"repository"`
|
||||||
|
// the package's creator
|
||||||
|
Creator User `json:"creator"`
|
||||||
|
// the type of package:
|
||||||
|
Type string `json:"type"`
|
||||||
|
// the name of the package
|
||||||
|
Name string `json:"name"`
|
||||||
|
// the version of the package
|
||||||
|
Version string `json:"version"`
|
||||||
|
// the date the package was uploaded
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackageFile represents a file from a package
|
||||||
|
type PackageFile struct {
|
||||||
|
// the file's ID
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
// the size of the file in bytes
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
// the name of the file
|
||||||
|
Name string `json:"name"`
|
||||||
|
// the md5 hash of the file
|
||||||
|
MD5 string `json:"md5"`
|
||||||
|
// the sha1 hash of the file
|
||||||
|
SHA1 string `json:"sha1"`
|
||||||
|
// the sha256 hash of the file
|
||||||
|
SHA256 string `json:"sha256"`
|
||||||
|
// the sha512 hash of the file
|
||||||
|
SHA512 string `json:"sha512"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPackagesOptions options for listing packages
|
||||||
|
type ListPackagesOptions struct {
|
||||||
|
ListOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPackages lists all the packages owned by a given owner (user, organisation)
|
||||||
|
func (c *Client) ListPackages(owner string, opt ListPackagesOptions) ([]*Package, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
opt.setDefaults()
|
||||||
|
packages := make([]*Package, 0, opt.PageSize)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s?%s", owner, opt.getURLQuery().Encode()), nil, nil, &packages)
|
||||||
|
return packages, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPackage gets the details of a specific package version
|
||||||
|
func (c *Client) GetPackage(owner, packageType, name, version string) (*Package, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
foundPackage := new(Package)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil, foundPackage)
|
||||||
|
return foundPackage, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePackage deletes a specific package version
|
||||||
|
func (c *Client) DeletePackage(owner, packageType, name, version string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPackageFiles lists the files within a package
|
||||||
|
func (c *Client) ListPackageFiles(owner, packageType, name, version string) ([]*PackageFile, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
packageFiles := make([]*PackageFile, 0)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s/files", owner, packageType, name, version), nil, nil, &packageFiles)
|
||||||
|
return packageFiles, resp, err
|
||||||
|
}
|
108
gitea/package_test.go
Normal file
108
gitea/package_test.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// create an org with a single package for testing purposes
|
||||||
|
func createTestPackage(t *testing.T, c *Client) error {
|
||||||
|
_, _ = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1")
|
||||||
|
_, _ = c.DeleteOrg("PackageOrg")
|
||||||
|
_, _, _ = c.CreateOrg(CreateOrgOption{Name: "PackageOrg"})
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: time.Second * 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bytes.NewReader([]byte("Hello world!"))
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/api/packages/PackageOrg/generic/MyPackage/v1/file1.txt", os.Getenv("GITEA_SDK_TEST_URL"))
|
||||||
|
req, err := http.NewRequest(http.MethodPut, url, reader)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetBasicAuth(os.Getenv("GITEA_SDK_TEST_USERNAME"), os.Getenv("GITEA_SDK_TEST_PASSWORD"))
|
||||||
|
response, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListPackages(t *testing.T) {
|
||||||
|
log.Println("== TestListPackages ==")
|
||||||
|
c := newTestClient()
|
||||||
|
err := createTestPackage(t, c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{
|
||||||
|
ListOptions{
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 1000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, packagesList, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPackage(t *testing.T) {
|
||||||
|
log.Println("== TestGetPackage ==")
|
||||||
|
c := newTestClient()
|
||||||
|
err := createTestPackage(t, c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
pkg, _, err := c.GetPackage("PackageOrg", "generic", "MyPackage", "v1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, pkg)
|
||||||
|
assert.True(t, pkg.Name == "MyPackage")
|
||||||
|
assert.True(t, pkg.Version == "v1")
|
||||||
|
assert.NotEmpty(t, pkg.CreatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeletePackage(t *testing.T) {
|
||||||
|
log.Println("== TestDeletePackage ==")
|
||||||
|
c := newTestClient()
|
||||||
|
err := createTestPackage(t, c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// no packages should be listed following deletion
|
||||||
|
packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{
|
||||||
|
ListOptions{
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 1000,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, packagesList, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListPackageFiles(t *testing.T) {
|
||||||
|
log.Println("== TestListPackageFiles ==")
|
||||||
|
c := newTestClient()
|
||||||
|
err := createTestPackage(t, c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
packageFiles, _, err := c.ListPackageFiles("PackageOrg", "generic", "MyPackage", "v1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, packageFiles, 1)
|
||||||
|
assert.True(t, packageFiles[0].Name == "file1.txt")
|
||||||
|
}
|
186
gitea/pull.go
186
gitea/pull.go
|
@ -43,11 +43,12 @@ type PullRequest struct {
|
||||||
DiffURL string `json:"diff_url"`
|
DiffURL string `json:"diff_url"`
|
||||||
PatchURL string `json:"patch_url"`
|
PatchURL string `json:"patch_url"`
|
||||||
|
|
||||||
Mergeable bool `json:"mergeable"`
|
Mergeable bool `json:"mergeable"`
|
||||||
HasMerged bool `json:"merged"`
|
HasMerged bool `json:"merged"`
|
||||||
Merged *time.Time `json:"merged_at"`
|
Merged *time.Time `json:"merged_at"`
|
||||||
MergedCommitID *string `json:"merge_commit_sha"`
|
MergedCommitID *string `json:"merge_commit_sha"`
|
||||||
MergedBy *User `json:"merged_by"`
|
MergedBy *User `json:"merged_by"`
|
||||||
|
AllowMaintainerEdit bool `json:"allow_maintainer_edit"`
|
||||||
|
|
||||||
Base *PRBranchInfo `json:"base"`
|
Base *PRBranchInfo `json:"base"`
|
||||||
Head *PRBranchInfo `json:"head"`
|
Head *PRBranchInfo `json:"head"`
|
||||||
|
@ -59,6 +60,19 @@ type PullRequest struct {
|
||||||
Closed *time.Time `json:"closed_at"`
|
Closed *time.Time `json:"closed_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChangedFile is a changed file in a diff
|
||||||
|
type ChangedFile struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
PreviousFilename string `json:"previous_filename"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Additions int `json:"additions"`
|
||||||
|
Deletions int `json:"deletions"`
|
||||||
|
Changes int `json:"changes"`
|
||||||
|
HTMLURL string `json:"html_url"`
|
||||||
|
ContentsURL string `json:"contents_url"`
|
||||||
|
RawURL string `json:"raw_url"`
|
||||||
|
}
|
||||||
|
|
||||||
// ListPullRequestsOptions options for listing pull requests
|
// ListPullRequestsOptions options for listing pull requests
|
||||||
type ListPullRequestsOptions struct {
|
type ListPullRequestsOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
|
@ -99,19 +113,37 @@ func (opt *ListPullRequestsOptions) QueryEncode() string {
|
||||||
|
|
||||||
// ListRepoPullRequests list PRs of one repository
|
// ListRepoPullRequests list PRs of one repository
|
||||||
func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, *Response, error) {
|
func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
prs := make([]*PullRequest, 0, opt.PageSize)
|
prs := make([]*PullRequest, 0, opt.PageSize)
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", owner, repo))
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", owner, repo))
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &prs)
|
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &prs)
|
||||||
|
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
|
||||||
|
for i := range prs {
|
||||||
|
if err := fixPullHeadSha(c, prs[i]); err != nil {
|
||||||
|
return prs, resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return prs, resp, err
|
return prs, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPullRequest get information of one PR
|
// GetPullRequest get information of one PR
|
||||||
func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, *Response, error) {
|
func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
pr := new(PullRequest)
|
pr := new(PullRequest)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr)
|
||||||
|
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
|
||||||
|
if err := fixPullHeadSha(c, pr); err != nil {
|
||||||
|
return pr, resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
return pr, resp, err
|
return pr, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +162,9 @@ type CreatePullRequestOption struct {
|
||||||
|
|
||||||
// CreatePullRequest create pull request with options
|
// CreatePullRequest create pull request with options
|
||||||
func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, *Response, error) {
|
func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -143,15 +178,17 @@ func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOpti
|
||||||
|
|
||||||
// EditPullRequestOption options when modify pull request
|
// EditPullRequestOption options when modify pull request
|
||||||
type EditPullRequestOption struct {
|
type EditPullRequestOption struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
Base string `json:"base"`
|
Base string `json:"base"`
|
||||||
Assignee string `json:"assignee"`
|
Assignee string `json:"assignee"`
|
||||||
Assignees []string `json:"assignees"`
|
Assignees []string `json:"assignees"`
|
||||||
Milestone int64 `json:"milestone"`
|
Milestone int64 `json:"milestone"`
|
||||||
Labels []int64 `json:"labels"`
|
Labels []int64 `json:"labels"`
|
||||||
State *StateType `json:"state"`
|
State *StateType `json:"state"`
|
||||||
Deadline *time.Time `json:"due_date"`
|
Deadline *time.Time `json:"due_date"`
|
||||||
|
RemoveDeadline *bool `json:"unset_due_date"`
|
||||||
|
AllowMaintainerEdit *bool `json:"allow_maintainer_edit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the EditPullRequestOption struct
|
// Validate the EditPullRequestOption struct
|
||||||
|
@ -160,7 +197,7 @@ func (opt EditPullRequestOption) Validate(c *Client) error {
|
||||||
return fmt.Errorf("title is empty")
|
return fmt.Errorf("title is empty")
|
||||||
}
|
}
|
||||||
if len(opt.Base) != 0 {
|
if len(opt.Base) != 0 {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return fmt.Errorf("can not change base gitea to old")
|
return fmt.Errorf("can not change base gitea to old")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,6 +206,9 @@ func (opt EditPullRequestOption) Validate(c *Client) error {
|
||||||
|
|
||||||
// EditPullRequest modify pull request with PR id and options
|
// EditPullRequest modify pull request with PR id and options
|
||||||
func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRequestOption) (*PullRequest, *Response, error) {
|
func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRequestOption) (*PullRequest, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(c); err != nil {
|
if err := opt.Validate(c); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -185,15 +225,20 @@ func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRe
|
||||||
|
|
||||||
// MergePullRequestOption options when merging a pull request
|
// MergePullRequestOption options when merging a pull request
|
||||||
type MergePullRequestOption struct {
|
type MergePullRequestOption struct {
|
||||||
Style MergeStyle `json:"Do"`
|
Style MergeStyle `json:"Do"`
|
||||||
Title string `json:"MergeTitleField"`
|
MergeCommitID string `json:"MergeCommitID"`
|
||||||
Message string `json:"MergeMessageField"`
|
Title string `json:"MergeTitleField"`
|
||||||
|
Message string `json:"MergeMessageField"`
|
||||||
|
DeleteBranchAfterMerge bool `json:"delete_branch_after_merge"`
|
||||||
|
ForceMerge bool `json:"force_merge"`
|
||||||
|
HeadCommitId string `json:"head_commit_id"`
|
||||||
|
MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the MergePullRequestOption struct
|
// Validate the MergePullRequestOption struct
|
||||||
func (opt MergePullRequestOption) Validate(c *Client) error {
|
func (opt MergePullRequestOption) Validate(c *Client) error {
|
||||||
if opt.Style == MergeStyleSquash {
|
if opt.Style == MergeStyleSquash {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.5"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_5); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,6 +247,9 @@ func (opt MergePullRequestOption) Validate(c *Client) error {
|
||||||
|
|
||||||
// MergePullRequest merge a PR to repository by PR id
|
// MergePullRequest merge a PR to repository by PR id
|
||||||
func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePullRequestOption) (bool, *Response, error) {
|
func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePullRequestOption) (bool, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(c); err != nil {
|
if err := opt.Validate(c); err != nil {
|
||||||
return false, nil, err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
|
@ -218,8 +266,10 @@ func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePull
|
||||||
|
|
||||||
// IsPullRequestMerged test if one PR is merged to one repository
|
// IsPullRequestMerged test if one PR is merged to one repository
|
||||||
func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Response, error) {
|
func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil)
|
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, resp, err
|
return false, resp, err
|
||||||
}
|
}
|
||||||
|
@ -227,9 +277,32 @@ func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Re
|
||||||
return status == 204, resp, nil
|
return status == 204, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PullRequestDiffOptions options for GET /repos/<owner>/<repo>/pulls/<idx>.[diff|patch]
|
||||||
|
type PullRequestDiffOptions struct {
|
||||||
|
// Include binary file changes when requesting a .diff
|
||||||
|
Binary bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryEncode converts the options to a query string
|
||||||
|
func (o PullRequestDiffOptions) QueryEncode() string {
|
||||||
|
query := make(url.Values)
|
||||||
|
query.Add("binary", fmt.Sprintf("%v", o.Binary))
|
||||||
|
return query.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
type pullRequestDiffType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
pullRequestDiffTypeDiff pullRequestDiffType = "diff"
|
||||||
|
pullRequestDiffTypePatch pullRequestDiffType = "patch"
|
||||||
|
)
|
||||||
|
|
||||||
// getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR
|
// getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR
|
||||||
func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64) ([]byte, *Response, error) {
|
func (c *Client) getPullRequestDiffOrPatch(owner, repo string, kind pullRequestDiffType, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||||
r, _, err2 := c.GetRepo(owner, repo)
|
r, _, err2 := c.GetRepo(owner, repo)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -237,17 +310,74 @@ func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64
|
||||||
if r.Private {
|
if r.Private {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return c.getWebResponse("GET", fmt.Sprintf("/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil)
|
url := fmt.Sprintf("/%s/%s/pulls/%d.%s?%s", owner, repo, index, kind, opts.QueryEncode())
|
||||||
|
return c.getWebResponse("GET", url, nil)
|
||||||
}
|
}
|
||||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil)
|
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPullRequestPatch gets the .patch file as bytes for a PR
|
// GetPullRequestPatch gets the git patchset of a PR
|
||||||
func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) {
|
func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) {
|
||||||
return c.getPullRequestDiffOrPatch(owner, repo, "patch", index)
|
return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypePatch, index, PullRequestDiffOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPullRequestDiff gets the .diff file as bytes for a PR
|
// GetPullRequestDiff gets the diff of a PR. For Gitea >= 1.16, you must set includeBinary to get an applicable diff
|
||||||
func (c *Client) GetPullRequestDiff(owner, repo string, index int64) ([]byte, *Response, error) {
|
func (c *Client) GetPullRequestDiff(owner, repo string, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) {
|
||||||
return c.getPullRequestDiffOrPatch(owner, repo, "diff", index)
|
return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypeDiff, index, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPullRequestCommitsOptions options for listing pull requests
|
||||||
|
type ListPullRequestCommitsOptions struct {
|
||||||
|
ListOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPullRequestCommits list commits for a pull request
|
||||||
|
func (c *Client) ListPullRequestCommits(owner, repo string, index int64, opt ListPullRequestCommitsOptions) ([]*Commit, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/commits", owner, repo, index))
|
||||||
|
opt.setDefaults()
|
||||||
|
commits := make([]*Commit, 0, opt.PageSize)
|
||||||
|
link.RawQuery = opt.getURLQuery().Encode()
|
||||||
|
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits)
|
||||||
|
return commits, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675
|
||||||
|
// When no head sha is available, this is because the branch got deleted in the base repo.
|
||||||
|
// pr.Head.Ref points in this case not to the head repo branch name, but the base repo ref,
|
||||||
|
// which stays available to resolve the commit sha. This is fixed for gitea >= 1.14.0
|
||||||
|
func fixPullHeadSha(client *Client, pr *PullRequest) error {
|
||||||
|
if pr.Base != nil && pr.Base.Repository != nil && pr.Base.Repository.Owner != nil &&
|
||||||
|
pr.Head != nil && pr.Head.Ref != "" && pr.Head.Sha == "" {
|
||||||
|
owner := pr.Base.Repository.Owner.UserName
|
||||||
|
repo := pr.Base.Repository.Name
|
||||||
|
refs, _, err := client.GetRepoRefs(owner, repo, pr.Head.Ref)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(refs) == 0 {
|
||||||
|
return fmt.Errorf("unable to resolve PR ref '%s'", pr.Head.Ref)
|
||||||
|
}
|
||||||
|
pr.Head.Sha = refs[0].Object.SHA
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPullRequestFilesOptions options for listing pull request files
|
||||||
|
type ListPullRequestFilesOptions struct {
|
||||||
|
ListOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPullRequestFiles list changed files for a pull request
|
||||||
|
func (c *Client) ListPullRequestFiles(owner, repo string, index int64, opt ListPullRequestFilesOptions) ([]*ChangedFile, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/files", owner, repo, index))
|
||||||
|
opt.setDefaults()
|
||||||
|
files := make([]*ChangedFile, 0, opt.PageSize)
|
||||||
|
link.RawQuery = opt.getURLQuery().Encode()
|
||||||
|
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &files)
|
||||||
|
return files, resp, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,15 +33,19 @@ const (
|
||||||
|
|
||||||
// PullReview represents a pull request review
|
// PullReview represents a pull request review
|
||||||
type PullReview struct {
|
type PullReview struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Reviewer *User `json:"user"`
|
Reviewer *User `json:"user"`
|
||||||
State ReviewStateType `json:"state"`
|
ReviewerTeam *Team `json:"team"`
|
||||||
Body string `json:"body"`
|
State ReviewStateType `json:"state"`
|
||||||
CommitID string `json:"commit_id"`
|
Body string `json:"body"`
|
||||||
Stale bool `json:"stale"`
|
CommitID string `json:"commit_id"`
|
||||||
Official bool `json:"official"`
|
// Stale indicates if the pull has changed since the review
|
||||||
CodeCommentsCount int `json:"comments_count"`
|
Stale bool `json:"stale"`
|
||||||
Submitted time.Time `json:"submitted_at"`
|
// Official indicates if the review counts towards the required approval limit, if PR base is a protected branch
|
||||||
|
Official bool `json:"official"`
|
||||||
|
Dismissed bool `json:"dismissed"`
|
||||||
|
CodeCommentsCount int `json:"comments_count"`
|
||||||
|
Submitted time.Time `json:"submitted_at"`
|
||||||
|
|
||||||
HTMLURL string `json:"html_url"`
|
HTMLURL string `json:"html_url"`
|
||||||
HTMLPullURL string `json:"pull_request_url"`
|
HTMLPullURL string `json:"pull_request_url"`
|
||||||
|
@ -53,6 +57,7 @@ type PullReviewComment struct {
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
Reviewer *User `json:"user"`
|
Reviewer *User `json:"user"`
|
||||||
ReviewID int64 `json:"pull_request_review_id"`
|
ReviewID int64 `json:"pull_request_review_id"`
|
||||||
|
Resolver *User `json:"resolver"`
|
||||||
|
|
||||||
Created time.Time `json:"created_at"`
|
Created time.Time `json:"created_at"`
|
||||||
Updated time.Time `json:"updated_at"`
|
Updated time.Time `json:"updated_at"`
|
||||||
|
@ -93,6 +98,17 @@ type SubmitPullReviewOptions struct {
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DismissPullReviewOptions are options to dismiss a pull review
|
||||||
|
type DismissPullReviewOptions struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullReviewRequestOptions are options to add or remove pull review requests
|
||||||
|
type PullReviewRequestOptions struct {
|
||||||
|
Reviewers []string `json:"reviewers"`
|
||||||
|
TeamReviewers []string `json:"team_reviewers"`
|
||||||
|
}
|
||||||
|
|
||||||
// ListPullReviewsOptions options for listing PullReviews
|
// ListPullReviewsOptions options for listing PullReviews
|
||||||
type ListPullReviewsOptions struct {
|
type ListPullReviewsOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
|
@ -100,7 +116,7 @@ type ListPullReviewsOptions struct {
|
||||||
|
|
||||||
// Validate the CreatePullReviewOptions struct
|
// Validate the CreatePullReviewOptions struct
|
||||||
func (opt CreatePullReviewOptions) Validate() error {
|
func (opt CreatePullReviewOptions) Validate() error {
|
||||||
if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 {
|
if opt.State != ReviewStateApproved && len(opt.Comments) == 0 && len(strings.TrimSpace(opt.Body)) == 0 {
|
||||||
return fmt.Errorf("body is empty")
|
return fmt.Errorf("body is empty")
|
||||||
}
|
}
|
||||||
for i := range opt.Comments {
|
for i := range opt.Comments {
|
||||||
|
@ -132,7 +148,10 @@ func (opt CreatePullReviewComment) Validate() error {
|
||||||
|
|
||||||
// ListPullReviews lists all reviews of a pull request
|
// ListPullReviews lists all reviews of a pull request
|
||||||
func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullReviewsOptions) ([]*PullReview, *Response, error) {
|
func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullReviewsOptions) ([]*PullReview, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
|
@ -147,7 +166,10 @@ func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullRe
|
||||||
|
|
||||||
// GetPullReview gets a specific review of a pull request
|
// GetPullReview gets a specific review of a pull request
|
||||||
func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview, *Response, error) {
|
func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +180,10 @@ func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview
|
||||||
|
|
||||||
// ListPullReviewComments lists all comments of a pull request review
|
// ListPullReviewComments lists all comments of a pull request review
|
||||||
func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]*PullReviewComment, *Response, error) {
|
func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]*PullReviewComment, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
rcl := make([]*PullReviewComment, 0, 4)
|
rcl := make([]*PullReviewComment, 0, 4)
|
||||||
|
@ -170,7 +195,10 @@ func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]
|
||||||
|
|
||||||
// DeletePullReview delete a specific review from a pull request
|
// DeletePullReview delete a specific review from a pull request
|
||||||
func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Response, error) {
|
func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +208,10 @@ func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Respons
|
||||||
|
|
||||||
// CreatePullReview create a review to an pull request
|
// CreatePullReview create a review to an pull request
|
||||||
func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePullReviewOptions) (*PullReview, *Response, error) {
|
func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePullReviewOptions) (*PullReview, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
|
@ -200,7 +231,10 @@ func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePul
|
||||||
|
|
||||||
// SubmitPullReview submit a pending review to an pull request
|
// SubmitPullReview submit a pending review to an pull request
|
||||||
func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt SubmitPullReviewOptions) (*PullReview, *Response, error) {
|
func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt SubmitPullReviewOptions) (*PullReview, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
|
@ -217,3 +251,75 @@ func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt Submi
|
||||||
jsonHeader, bytes.NewReader(body), r)
|
jsonHeader, bytes.NewReader(body), r)
|
||||||
return r, resp, err
|
return r, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateReviewRequests create review requests to an pull request
|
||||||
|
func (c *Client) CreateReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(&opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp, err := c.getResponse("POST",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index),
|
||||||
|
jsonHeader, bytes.NewReader(body))
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteReviewRequests delete review requests to an pull request
|
||||||
|
func (c *Client) DeleteReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(&opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp, err := c.getResponse("DELETE",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index),
|
||||||
|
jsonHeader, bytes.NewReader(body))
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DismissPullReview dismiss a review for a pull request
|
||||||
|
func (c *Client) DismissPullReview(owner, repo string, index, id int64, opt DismissPullReviewOptions) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(&opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp, err := c.getResponse("POST",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/dismissals", owner, repo, index, id),
|
||||||
|
jsonHeader, bytes.NewReader(body))
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnDismissPullReview cancel to dismiss a review for a pull request
|
||||||
|
func (c *Client) UnDismissPullReview(owner, repo string, index, id int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp, err := c.getResponse("POST",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, id),
|
||||||
|
jsonHeader, nil)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
|
@ -15,13 +15,11 @@ func TestPullReview(t *testing.T) {
|
||||||
log.Println("== TestPullReview ==")
|
log.Println("== TestPullReview ==")
|
||||||
c := newTestClient()
|
c := newTestClient()
|
||||||
|
|
||||||
var repoName = "Reviews"
|
repoName := "Reviews"
|
||||||
repo, pull, submitter, reviewer, success := preparePullReviewTest(t, c, repoName)
|
repo, pull, submitter, reviewer, success := preparePullReviewTest(t, c, repoName)
|
||||||
if !success {
|
if !success {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer c.AdminDeleteUser(reviewer.UserName)
|
|
||||||
defer c.AdminDeleteUser(submitter.UserName)
|
|
||||||
|
|
||||||
// CreatePullReview
|
// CreatePullReview
|
||||||
r1, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
r1, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
||||||
|
@ -35,12 +33,12 @@ func TestPullReview(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.SetSudo(submitter.UserName)
|
c.SetSudo(submitter.UserName)
|
||||||
r2, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
_, _, err = c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
||||||
State: ReviewStateApproved,
|
State: ReviewStateApproved,
|
||||||
Body: "lgtm it myself",
|
Body: "lgtm it myself",
|
||||||
})
|
})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
r2, _, err = c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
r2, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
||||||
State: ReviewStateComment,
|
State: ReviewStateComment,
|
||||||
Body: "no seriously please have a look at it",
|
Body: "no seriously please have a look at it",
|
||||||
})
|
})
|
||||||
|
@ -51,11 +49,12 @@ func TestPullReview(t *testing.T) {
|
||||||
r3, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
r3, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
||||||
State: ReviewStateApproved,
|
State: ReviewStateApproved,
|
||||||
Body: "lgtm",
|
Body: "lgtm",
|
||||||
Comments: []CreatePullReviewComment{{
|
Comments: []CreatePullReviewComment{
|
||||||
Path: "WOW-file",
|
{
|
||||||
Body: "no better name - really?",
|
Path: "WOW-file",
|
||||||
NewLineNum: 1,
|
Body: "no better name - really?",
|
||||||
},
|
NewLineNum: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -64,9 +63,7 @@ func TestPullReview(t *testing.T) {
|
||||||
// ListPullReviews
|
// ListPullReviews
|
||||||
c.SetSudo("")
|
c.SetSudo("")
|
||||||
rl, _, err := c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{})
|
rl, _, err := c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{})
|
||||||
if !assert.NoError(t, err) {
|
assert.NoError(t, err)
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.Len(t, rl, 3)
|
assert.Len(t, rl, 3)
|
||||||
for i := range rl {
|
for i := range rl {
|
||||||
assert.EqualValues(t, pull.HTMLURL, rl[i].HTMLPullURL)
|
assert.EqualValues(t, pull.HTMLURL, rl[i].HTMLPullURL)
|
||||||
|
@ -89,23 +86,26 @@ func TestPullReview(t *testing.T) {
|
||||||
|
|
||||||
// SubmitPullReview
|
// SubmitPullReview
|
||||||
c.SetSudo("")
|
c.SetSudo("")
|
||||||
r4, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
r4, resp, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
||||||
Body: "...",
|
Body: "...",
|
||||||
Comments: []CreatePullReviewComment{{
|
Comments: []CreatePullReviewComment{
|
||||||
Path: "WOW-file",
|
{
|
||||||
Body: "its ok",
|
Path: "WOW-file",
|
||||||
NewLineNum: 1,
|
Body: "its ok",
|
||||||
},
|
NewLineNum: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, resp)
|
||||||
r5, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
r5, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
||||||
Body: "...",
|
Body: "...",
|
||||||
Comments: []CreatePullReviewComment{{
|
Comments: []CreatePullReviewComment{
|
||||||
Path: "WOW-file",
|
{
|
||||||
Body: "hehe and here it is",
|
Path: "WOW-file",
|
||||||
NewLineNum: 3,
|
Body: "hehe and here it is",
|
||||||
},
|
NewLineNum: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -133,6 +133,55 @@ func TestPullReview(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r, _, err = c.GetPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, r.Dismissed)
|
||||||
|
|
||||||
|
// DismissPullReview
|
||||||
|
resp, err = c.DismissPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID, DismissPullReviewOptions{Message: "stale"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.NotNil(t, resp) {
|
||||||
|
assert.EqualValues(t, 200, resp.StatusCode)
|
||||||
|
}
|
||||||
|
r, _, _ = c.GetPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID)
|
||||||
|
assert.True(t, r.Dismissed)
|
||||||
|
|
||||||
|
// UnDismissPullReview
|
||||||
|
resp, err = c.UnDismissPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.NotNil(t, resp) {
|
||||||
|
assert.EqualValues(t, 200, resp.StatusCode)
|
||||||
|
}
|
||||||
|
r, _, _ = c.GetPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID)
|
||||||
|
assert.False(t, r.Dismissed)
|
||||||
|
|
||||||
|
rl, _, err = c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, rl, 3)
|
||||||
|
|
||||||
|
c.SetSudo(submitter.UserName)
|
||||||
|
resp, err = c.CreateReviewRequests(repo.Owner.UserName, repo.Name, pull.Index, PullReviewRequestOptions{Reviewers: []string{reviewer.UserName}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, resp)
|
||||||
|
|
||||||
|
rl, _, _ = c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{})
|
||||||
|
if assert.Len(t, rl, 4) {
|
||||||
|
assert.EqualValues(t, ReviewStateRequestReview, rl[3].State)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SetSudo(reviewer.UserName)
|
||||||
|
resp, err = c.DeleteReviewRequests(repo.Owner.UserName, repo.Name, pull.Index, PullReviewRequestOptions{Reviewers: []string{reviewer.UserName}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, resp)
|
||||||
|
|
||||||
|
rl, _, _ = c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{})
|
||||||
|
assert.Len(t, rl, 3)
|
||||||
|
|
||||||
|
c.SetSudo("")
|
||||||
|
_, err = c.AdminDeleteUser(reviewer.UserName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = c.AdminDeleteUser(submitter.UserName)
|
||||||
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func preparePullReviewTest(t *testing.T, c *Client, repoName string) (*Repository, *PullRequest, *User, *User, bool) {
|
func preparePullReviewTest(t *testing.T, c *Client, repoName string) (*Repository, *PullRequest, *User, *User, bool) {
|
||||||
|
@ -154,7 +203,7 @@ func preparePullReviewTest(t *testing.T, c *Client, repoName string) (*Repositor
|
||||||
Content: "QSBuZXcgRmlsZQoKYW5kIHNvbWUgbGluZXMK",
|
Content: "QSBuZXcgRmlsZQoKYW5kIHNvbWUgbGluZXMK",
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "creat a new file",
|
Message: "creat a new file",
|
||||||
BranchName: "master",
|
BranchName: "main",
|
||||||
NewBranchName: "new_file",
|
NewBranchName: "new_file",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -164,7 +213,7 @@ func preparePullReviewTest(t *testing.T, c *Client, repoName string) (*Repositor
|
||||||
}
|
}
|
||||||
|
|
||||||
pull, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
pull, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||||
Base: "master",
|
Base: "main",
|
||||||
Head: "new_file",
|
Head: "new_file",
|
||||||
Title: "Creat a NewFile",
|
Title: "Creat a NewFile",
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,8 +17,8 @@ func TestPull(t *testing.T) {
|
||||||
user, _, err := c.GetMyUserInfo()
|
user, _, err := c.GetMyUserInfo()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
var repoName = "repo_pull_test"
|
repoName := "repo_pull_test"
|
||||||
var forkOrg = "ForkOrg"
|
forkOrg := "ForkOrg"
|
||||||
if !preparePullTest(t, c, repoName, forkOrg) {
|
if !preparePullTest(t, c, repoName, forkOrg) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ func TestPull(t *testing.T) {
|
||||||
assert.Len(t, pulls, 0)
|
assert.Len(t, pulls, 0)
|
||||||
|
|
||||||
pullUpdateFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
pullUpdateFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||||
Base: "master",
|
Base: "main",
|
||||||
Head: forkOrg + ":overwrite_licence",
|
Head: forkOrg + ":overwrite_licence",
|
||||||
Title: "overwrite a file",
|
Title: "overwrite a file",
|
||||||
})
|
})
|
||||||
|
@ -37,7 +37,7 @@ func TestPull(t *testing.T) {
|
||||||
assert.NotNil(t, pullUpdateFile)
|
assert.NotNil(t, pullUpdateFile)
|
||||||
|
|
||||||
pullNewFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
pullNewFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||||
Base: "master",
|
Base: "main",
|
||||||
Head: forkOrg + ":new_file",
|
Head: forkOrg + ":new_file",
|
||||||
Title: "create a file",
|
Title: "create a file",
|
||||||
})
|
})
|
||||||
|
@ -45,7 +45,7 @@ func TestPull(t *testing.T) {
|
||||||
assert.NotNil(t, pullNewFile)
|
assert.NotNil(t, pullNewFile)
|
||||||
|
|
||||||
pullConflict, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
pullConflict, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||||
Base: "master",
|
Base: "main",
|
||||||
Head: forkOrg + ":will_conflict",
|
Head: forkOrg + ":will_conflict",
|
||||||
Title: "this pull will conflict",
|
Title: "this pull will conflict",
|
||||||
})
|
})
|
||||||
|
@ -56,16 +56,35 @@ func TestPull(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, pulls, 3)
|
assert.Len(t, pulls, 3)
|
||||||
|
|
||||||
diff, _, err := c.GetPullRequestDiff(c.username, repoName, pullUpdateFile.Index)
|
diff, _, err := c.GetPullRequestDiff(c.username, repoName, pullUpdateFile.Index, PullRequestDiffOptions{
|
||||||
|
Binary: true,
|
||||||
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, diff, 1310)
|
assert.True(t, len(diff) > 1100 && len(diff) < 1300)
|
||||||
patch, _, err := c.GetPullRequestPatch(c.username, repoName, pullUpdateFile.Index)
|
patch, _, err := c.GetPullRequestPatch(c.username, repoName, pullUpdateFile.Index)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, len(patch) > len(diff))
|
assert.True(t, len(patch) > len(diff))
|
||||||
|
|
||||||
|
commits, _, err := c.ListPullRequestCommits(c.username, repoName, pullUpdateFile.Index, ListPullRequestCommitsOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.Len(t, commits, 1) && assert.Len(t, commits[0].Files, 1) {
|
||||||
|
assert.EqualValues(t, "LICENSE", commits[0].Files[0].Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
files, _, err := c.ListPullRequestFiles(c.username, repoName, pullUpdateFile.Index, ListPullRequestFilesOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, files, 1)
|
||||||
|
file := files[0]
|
||||||
|
assert.EqualValues(t, "LICENSE", file.Filename)
|
||||||
|
assert.EqualValues(t, "changed", file.Status)
|
||||||
|
assert.EqualValues(t, 3, file.Additions)
|
||||||
|
assert.EqualValues(t, 9, file.Deletions)
|
||||||
|
assert.EqualValues(t, 12, file.Changes)
|
||||||
|
|
||||||
// test Update pull
|
// test Update pull
|
||||||
pr, _, err := c.GetPullRequest(user.UserName, repoName, pullUpdateFile.Index)
|
pr, _, err := c.GetPullRequest(user.UserName, repoName, pullUpdateFile.Index)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, pr)
|
||||||
assert.False(t, pullUpdateFile.HasMerged)
|
assert.False(t, pullUpdateFile.HasMerged)
|
||||||
assert.True(t, pullUpdateFile.Mergeable)
|
assert.True(t, pullUpdateFile.Mergeable)
|
||||||
merged, _, err := c.MergePullRequest(user.UserName, repoName, pullUpdateFile.Index, MergePullRequestOption{
|
merged, _, err := c.MergePullRequest(user.UserName, repoName, pullUpdateFile.Index, MergePullRequestOption{
|
||||||
|
@ -89,8 +108,8 @@ func TestPull(t *testing.T) {
|
||||||
// test conflict pull
|
// test conflict pull
|
||||||
pr, _, err = c.GetPullRequest(user.UserName, repoName, pullConflict.Index)
|
pr, _, err = c.GetPullRequest(user.UserName, repoName, pullConflict.Index)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, pullConflict.HasMerged)
|
assert.False(t, pr.HasMerged)
|
||||||
assert.False(t, pullConflict.Mergeable)
|
assert.False(t, pr.Mergeable)
|
||||||
merged, _, err = c.MergePullRequest(user.UserName, repoName, pullConflict.Index, MergePullRequestOption{
|
merged, _, err = c.MergePullRequest(user.UserName, repoName, pullConflict.Index, MergePullRequestOption{
|
||||||
Style: MergeStyleMerge,
|
Style: MergeStyleMerge,
|
||||||
Title: "pullConflict",
|
Title: "pullConflict",
|
||||||
|
@ -137,18 +156,18 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, forkRepo)
|
assert.NotNil(t, forkRepo)
|
||||||
|
|
||||||
masterLicence, _, err := c.GetContents(forkRepo.Owner.UserName, forkRepo.Name, "master", "LICENSE")
|
mainLicense, _, err := c.GetContents(forkRepo.Owner.UserName, forkRepo.Name, "main", "LICENSE")
|
||||||
if !assert.NoError(t, err) || !assert.NotNil(t, masterLicence) {
|
if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedFile, _, err := c.UpdateFile(forkRepo.Owner.UserName, forkRepo.Name, "LICENSE", UpdateFileOptions{
|
updatedFile, _, err := c.UpdateFile(forkRepo.Owner.UserName, forkRepo.Name, "LICENSE", UpdateFileOptions{
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "Overwrite",
|
Message: "Overwrite",
|
||||||
BranchName: "master",
|
BranchName: "main",
|
||||||
NewBranchName: "overwrite_licence",
|
NewBranchName: "overwrite_licence",
|
||||||
},
|
},
|
||||||
SHA: masterLicence.SHA,
|
SHA: mainLicense.SHA,
|
||||||
Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
|
Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
|
||||||
})
|
})
|
||||||
if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) {
|
if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) {
|
||||||
|
@ -159,7 +178,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
||||||
Content: "QSBuZXcgRmlsZQo=",
|
Content: "QSBuZXcgRmlsZQo=",
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "creat a new file",
|
Message: "creat a new file",
|
||||||
BranchName: "master",
|
BranchName: "main",
|
||||||
NewBranchName: "new_file",
|
NewBranchName: "new_file",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -171,7 +190,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
||||||
Content: "U3RhcnQgQ29uZmxpY3QK",
|
Content: "U3RhcnQgQ29uZmxpY3QK",
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "Start Conflict",
|
Message: "Start Conflict",
|
||||||
BranchName: "master",
|
BranchName: "main",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if !assert.NoError(t, err) || !assert.NotNil(t, conflictFile1) {
|
if !assert.NoError(t, err) || !assert.NotNil(t, conflictFile1) {
|
||||||
|
@ -182,7 +201,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
||||||
Content: "V2lsbEhhdmUgQ29uZmxpY3QK",
|
Content: "V2lsbEhhdmUgQ29uZmxpY3QK",
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "creat a new file witch will conflict",
|
Message: "creat a new file witch will conflict",
|
||||||
BranchName: "master",
|
BranchName: "main",
|
||||||
NewBranchName: "will_conflict",
|
NewBranchName: "will_conflict",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
114
gitea/release.go
114
gitea/release.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -20,6 +21,7 @@ type Release struct {
|
||||||
Title string `json:"name"`
|
Title string `json:"name"`
|
||||||
Note string `json:"body"`
|
Note string `json:"body"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
|
HTMLURL string `json:"html_url"`
|
||||||
TarURL string `json:"tarball_url"`
|
TarURL string `json:"tarball_url"`
|
||||||
ZipURL string `json:"zipball_url"`
|
ZipURL string `json:"zipball_url"`
|
||||||
IsDraft bool `json:"draft"`
|
IsDraft bool `json:"draft"`
|
||||||
|
@ -33,23 +35,72 @@ type Release struct {
|
||||||
// ListReleasesOptions options for listing repository's releases
|
// ListReleasesOptions options for listing repository's releases
|
||||||
type ListReleasesOptions struct {
|
type ListReleasesOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
|
IsDraft *bool
|
||||||
|
IsPreRelease *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryEncode turns options into querystring argument
|
||||||
|
func (opt *ListReleasesOptions) QueryEncode() string {
|
||||||
|
query := opt.getURLQuery()
|
||||||
|
|
||||||
|
if opt.IsDraft != nil {
|
||||||
|
query.Add("draft", fmt.Sprintf("%t", *opt.IsDraft))
|
||||||
|
}
|
||||||
|
if opt.IsPreRelease != nil {
|
||||||
|
query.Add("pre-release", fmt.Sprintf("%t", *opt.IsPreRelease))
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListReleases list releases of a repository
|
// ListReleases list releases of a repository
|
||||||
func (c *Client) ListReleases(user, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) {
|
func (c *Client) ListReleases(owner, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
releases := make([]*Release, 0, opt.PageSize)
|
releases := make([]*Release, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET",
|
resp, err := c.getParsedResponse("GET",
|
||||||
fmt.Sprintf("/repos/%s/%s/releases?%s", user, repo, opt.getURLQuery().Encode()),
|
fmt.Sprintf("/repos/%s/%s/releases?%s", owner, repo, opt.QueryEncode()),
|
||||||
nil, nil, &releases)
|
nil, nil, &releases)
|
||||||
return releases, resp, err
|
return releases, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRelease get a release of a repository
|
// GetRelease get a release of a repository by id
|
||||||
func (c *Client) GetRelease(user, repo string, id int64) (*Release, *Response, error) {
|
func (c *Client) GetRelease(owner, repo string, id int64) (*Release, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
r := new(Release)
|
r := new(Release)
|
||||||
resp, err := c.getParsedResponse("GET",
|
resp, err := c.getParsedResponse("GET",
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
|
fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id),
|
||||||
|
jsonHeader, nil, &r)
|
||||||
|
return r, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLatestRelease get the latest release of a repository
|
||||||
|
func (c *Client) GetLatestRelease(owner, repo string) (*Release, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
r := new(Release)
|
||||||
|
resp, err := c.getParsedResponse("GET",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/releases/latest", owner, repo),
|
||||||
|
jsonHeader, nil, &r)
|
||||||
|
return r, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReleaseByTag get a release of a repository by tag
|
||||||
|
func (c *Client) GetReleaseByTag(owner, repo, tag string) (*Release, *Response, error) {
|
||||||
|
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
||||||
|
return c.fallbackGetReleaseByTag(owner, repo, tag)
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo, &tag); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
r := new(Release)
|
||||||
|
resp, err := c.getParsedResponse("GET",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/releases/tags/%s", owner, repo, tag),
|
||||||
nil, nil, &r)
|
nil, nil, &r)
|
||||||
return r, resp, err
|
return r, resp, err
|
||||||
}
|
}
|
||||||
|
@ -73,7 +124,10 @@ func (opt CreateReleaseOption) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRelease create a release
|
// CreateRelease create a release
|
||||||
func (c *Client) CreateRelease(user, repo string, opt CreateReleaseOption) (*Release, *Response, error) {
|
func (c *Client) CreateRelease(owner, repo string, opt CreateReleaseOption) (*Release, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -83,7 +137,7 @@ func (c *Client) CreateRelease(user, repo string, opt CreateReleaseOption) (*Rel
|
||||||
}
|
}
|
||||||
r := new(Release)
|
r := new(Release)
|
||||||
resp, err := c.getParsedResponse("POST",
|
resp, err := c.getParsedResponse("POST",
|
||||||
fmt.Sprintf("/repos/%s/%s/releases", user, repo),
|
fmt.Sprintf("/repos/%s/%s/releases", owner, repo),
|
||||||
jsonHeader, bytes.NewReader(body), r)
|
jsonHeader, bytes.NewReader(body), r)
|
||||||
return r, resp, err
|
return r, resp, err
|
||||||
}
|
}
|
||||||
|
@ -99,22 +153,62 @@ type EditReleaseOption struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditRelease edit a release
|
// EditRelease edit a release
|
||||||
func (c *Client) EditRelease(user, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) {
|
func (c *Client) EditRelease(owner, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(form)
|
body, err := json.Marshal(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
r := new(Release)
|
r := new(Release)
|
||||||
resp, err := c.getParsedResponse("PATCH",
|
resp, err := c.getParsedResponse("PATCH",
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
|
fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id),
|
||||||
jsonHeader, bytes.NewReader(body), r)
|
jsonHeader, bytes.NewReader(body), r)
|
||||||
return r, resp, err
|
return r, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRelease delete a release from a repository
|
// DeleteRelease delete a release from a repository, keeping its tag
|
||||||
func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) {
|
func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE",
|
_, resp, err := c.getResponse("DELETE",
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
|
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
|
||||||
nil, nil)
|
nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteReleaseByTag deletes a release frm a repository by tag
|
||||||
|
func (c *Client) DeleteReleaseByTag(user, repo, tag string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("DELETE",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag),
|
||||||
|
nil, nil)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 )
|
||||||
|
func (c *Client) fallbackGetReleaseByTag(owner, repo, tag string) (*Release, *Response, error) {
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
rl, resp, err := c.ListReleases(owner, repo, ListReleasesOptions{ListOptions: ListOptions{Page: i}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
if len(rl) == 0 {
|
||||||
|
return nil,
|
||||||
|
newResponse(&http.Response{StatusCode: 404}),
|
||||||
|
fmt.Errorf("release with tag '%s' not found", tag)
|
||||||
|
}
|
||||||
|
for _, r := range rl {
|
||||||
|
if r.TagName == tag {
|
||||||
|
return r, resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
122
gitea/release_test.go
Normal file
122
gitea/release_test.go
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRelease(t *testing.T) {
|
||||||
|
log.Println("== TestRelease ==")
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
repo, _ := createTestRepo(t, "ReleaseTests", c)
|
||||||
|
|
||||||
|
// ListReleases
|
||||||
|
rl, _, err := c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, rl, 0)
|
||||||
|
|
||||||
|
// CreateRelease
|
||||||
|
r, _, err := c.CreateRelease(repo.Owner.UserName, repo.Name, CreateReleaseOption{
|
||||||
|
TagName: "awesome",
|
||||||
|
Target: "main",
|
||||||
|
Title: "Release 1",
|
||||||
|
Note: "yes it's awesome",
|
||||||
|
IsDraft: true,
|
||||||
|
IsPrerelease: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, "awesome", r.TagName)
|
||||||
|
assert.EqualValues(t, true, r.IsPrerelease)
|
||||||
|
assert.EqualValues(t, true, r.IsDraft)
|
||||||
|
assert.EqualValues(t, "Release 1", r.Title)
|
||||||
|
assert.EqualValues(t, fmt.Sprintf("%s/api/v1/repos/%s/releases/%d", c.url, repo.FullName, r.ID), r.URL)
|
||||||
|
assert.EqualValues(t, "main", r.Target)
|
||||||
|
assert.EqualValues(t, "yes it's awesome", r.Note)
|
||||||
|
assert.EqualValues(t, c.username, r.Publisher.UserName)
|
||||||
|
rl, _, _ = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{})
|
||||||
|
assert.Len(t, rl, 1)
|
||||||
|
|
||||||
|
// GetRelease
|
||||||
|
r2, _, err := c.GetRelease(repo.Owner.UserName, repo.Name, r.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, r, r2)
|
||||||
|
r2, _, err = c.GetReleaseByTag(repo.Owner.UserName, repo.Name, r.TagName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, r, r2)
|
||||||
|
// ListRelease without pre-releases
|
||||||
|
tr := true
|
||||||
|
rl, _, err = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{
|
||||||
|
IsPreRelease: &tr,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, rl, 1) // created release is a pre-release
|
||||||
|
// test fallback
|
||||||
|
r2, _, err = c.fallbackGetReleaseByTag(repo.Owner.UserName, repo.Name, r.TagName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, r, r2)
|
||||||
|
|
||||||
|
// EditRelease
|
||||||
|
r2, _, err = c.EditRelease(repo.Owner.UserName, repo.Name, r.ID, EditReleaseOption{
|
||||||
|
Title: "Release Awesome",
|
||||||
|
Note: "",
|
||||||
|
IsDraft: OptionalBool(false),
|
||||||
|
IsPrerelease: OptionalBool(false),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, r.Target, r2.Target)
|
||||||
|
assert.EqualValues(t, false, r2.IsDraft)
|
||||||
|
assert.EqualValues(t, false, r2.IsPrerelease)
|
||||||
|
assert.EqualValues(t, r.Note, r2.Note)
|
||||||
|
|
||||||
|
// GetLatestRelease
|
||||||
|
r3, _, err := c.GetLatestRelease(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, r2, r3)
|
||||||
|
|
||||||
|
// DeleteRelease
|
||||||
|
_, err = c.DeleteRelease(repo.Owner.UserName, repo.Name, r.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
rl, _, _ = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{})
|
||||||
|
assert.Len(t, rl, 0)
|
||||||
|
|
||||||
|
// CreateRelease
|
||||||
|
_, _, err = c.CreateRelease(repo.Owner.UserName, repo.Name, CreateReleaseOption{
|
||||||
|
TagName: "aNewReleaseTag",
|
||||||
|
Target: "main",
|
||||||
|
Title: "Title of aNewReleaseTag",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// DeleteReleaseByTag
|
||||||
|
_, err = c.DeleteReleaseByTag(repo.Owner.UserName, repo.Name, "aNewReleaseTag")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
rl, _, _ = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{})
|
||||||
|
assert.Len(t, rl, 0)
|
||||||
|
_, err = c.DeleteReleaseByTag(repo.Owner.UserName, repo.Name, "aNewReleaseTag")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
// Test Response if try to get not existing release
|
||||||
|
_, resp, err := c.GetRelease(repo.Owner.UserName, repo.Name, 1234)
|
||||||
|
assert.Error(t, err)
|
||||||
|
if assert.NotNil(t, resp) {
|
||||||
|
assert.EqualValues(t, 404, resp.StatusCode)
|
||||||
|
}
|
||||||
|
_, resp, err = c.GetReleaseByTag(repo.Owner.UserName, repo.Name, "not_here")
|
||||||
|
assert.Error(t, err)
|
||||||
|
if assert.NotNil(t, resp) {
|
||||||
|
assert.EqualValues(t, 404, resp.StatusCode)
|
||||||
|
}
|
||||||
|
_, resp, err = c.fallbackGetReleaseByTag(repo.Owner.UserName, repo.Name, "not_here")
|
||||||
|
assert.Error(t, err)
|
||||||
|
if assert.NotNil(t, resp) {
|
||||||
|
assert.EqualValues(t, 404, resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
236
gitea/repo.go
236
gitea/repo.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -21,42 +22,82 @@ type Permission struct {
|
||||||
Pull bool `json:"pull"`
|
Pull bool `json:"pull"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InternalTracker represents settings for internal tracker
|
||||||
|
type InternalTracker struct {
|
||||||
|
// Enable time tracking (Built-in issue tracker)
|
||||||
|
EnableTimeTracker bool `json:"enable_time_tracker"`
|
||||||
|
// Let only contributors track time (Built-in issue tracker)
|
||||||
|
AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"`
|
||||||
|
// Enable dependencies for issues and pull requests (Built-in issue tracker)
|
||||||
|
EnableIssueDependencies bool `json:"enable_issue_dependencies"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalTracker represents settings for external tracker
|
||||||
|
type ExternalTracker struct {
|
||||||
|
// URL of external issue tracker.
|
||||||
|
ExternalTrackerURL string `json:"external_tracker_url"`
|
||||||
|
// External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.
|
||||||
|
ExternalTrackerFormat string `json:"external_tracker_format"`
|
||||||
|
// External Issue Tracker Number Format, either `numeric` or `alphanumeric`
|
||||||
|
ExternalTrackerStyle string `json:"external_tracker_style"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalWiki represents setting for external wiki
|
||||||
|
type ExternalWiki struct {
|
||||||
|
// URL of external wiki.
|
||||||
|
ExternalWikiURL string `json:"external_wiki_url"`
|
||||||
|
}
|
||||||
|
|
||||||
// Repository represents a repository
|
// Repository represents a repository
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Owner *User `json:"owner"`
|
Owner *User `json:"owner"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Empty bool `json:"empty"`
|
Empty bool `json:"empty"`
|
||||||
Private bool `json:"private"`
|
Private bool `json:"private"`
|
||||||
Fork bool `json:"fork"`
|
Fork bool `json:"fork"`
|
||||||
Parent *Repository `json:"parent"`
|
Template bool `json:"template"`
|
||||||
Mirror bool `json:"mirror"`
|
Parent *Repository `json:"parent"`
|
||||||
Size int `json:"size"`
|
Mirror bool `json:"mirror"`
|
||||||
HTMLURL string `json:"html_url"`
|
Size int `json:"size"`
|
||||||
SSHURL string `json:"ssh_url"`
|
HTMLURL string `json:"html_url"`
|
||||||
CloneURL string `json:"clone_url"`
|
SSHURL string `json:"ssh_url"`
|
||||||
OriginalURL string `json:"original_url"`
|
CloneURL string `json:"clone_url"`
|
||||||
Website string `json:"website"`
|
OriginalURL string `json:"original_url"`
|
||||||
Stars int `json:"stars_count"`
|
Website string `json:"website"`
|
||||||
Forks int `json:"forks_count"`
|
Stars int `json:"stars_count"`
|
||||||
Watchers int `json:"watchers_count"`
|
Forks int `json:"forks_count"`
|
||||||
OpenIssues int `json:"open_issues_count"`
|
Watchers int `json:"watchers_count"`
|
||||||
DefaultBranch string `json:"default_branch"`
|
OpenIssues int `json:"open_issues_count"`
|
||||||
Archived bool `json:"archived"`
|
OpenPulls int `json:"open_pr_counter"`
|
||||||
Created time.Time `json:"created_at"`
|
Releases int `json:"release_counter"`
|
||||||
Updated time.Time `json:"updated_at"`
|
DefaultBranch string `json:"default_branch"`
|
||||||
Permissions *Permission `json:"permissions,omitempty"`
|
Archived bool `json:"archived"`
|
||||||
HasIssues bool `json:"has_issues"`
|
Created time.Time `json:"created_at"`
|
||||||
HasWiki bool `json:"has_wiki"`
|
Updated time.Time `json:"updated_at"`
|
||||||
HasPullRequests bool `json:"has_pull_requests"`
|
Permissions *Permission `json:"permissions,omitempty"`
|
||||||
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"`
|
HasIssues bool `json:"has_issues"`
|
||||||
AllowMerge bool `json:"allow_merge_commits"`
|
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||||
AllowRebase bool `json:"allow_rebase"`
|
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||||
AllowRebaseMerge bool `json:"allow_rebase_explicit"`
|
HasWiki bool `json:"has_wiki"`
|
||||||
AllowSquash bool `json:"allow_squash_merge"`
|
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||||
AvatarURL string `json:"avatar_url"`
|
HasPullRequests bool `json:"has_pull_requests"`
|
||||||
|
HasProjects bool `json:"has_projects"`
|
||||||
|
HasReleases bool `json:"has_releases,omitempty"`
|
||||||
|
HasPackages bool `json:"has_packages,omitempty"`
|
||||||
|
HasActions bool `json:"has_actions,omitempty"`
|
||||||
|
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"`
|
||||||
|
AllowMerge bool `json:"allow_merge_commits"`
|
||||||
|
AllowRebase bool `json:"allow_rebase"`
|
||||||
|
AllowRebaseMerge bool `json:"allow_rebase_explicit"`
|
||||||
|
AllowSquash bool `json:"allow_squash_merge"`
|
||||||
|
AvatarURL string `json:"avatar_url"`
|
||||||
|
Internal bool `json:"internal"`
|
||||||
|
MirrorInterval string `json:"mirror_interval"`
|
||||||
|
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
|
||||||
|
DefaultMergeStyle MergeStyle `json:"default_merge_style"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoType represent repo type
|
// RepoType represent repo type
|
||||||
|
@ -73,6 +114,20 @@ const (
|
||||||
RepoTypeMirror RepoType = "mirror"
|
RepoTypeMirror RepoType = "mirror"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TrustModel represent how git signatures are handled in a repository
|
||||||
|
type TrustModel string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TrustModelDefault use TM set by global config
|
||||||
|
TrustModelDefault TrustModel = "default"
|
||||||
|
// TrustModelCollaborator gpg signature has to be owned by a repo collaborator
|
||||||
|
TrustModelCollaborator TrustModel = "collaborator"
|
||||||
|
// TrustModelCommitter gpg signature has to match committer
|
||||||
|
TrustModelCommitter TrustModel = "committer"
|
||||||
|
// TrustModelCollaboratorCommitter gpg signature has to match committer and owned by a repo collaborator
|
||||||
|
TrustModelCollaboratorCommitter TrustModel = "collaboratorcommitter"
|
||||||
|
)
|
||||||
|
|
||||||
// ListReposOptions options for listing repositories
|
// ListReposOptions options for listing repositories
|
||||||
type ListReposOptions struct {
|
type ListReposOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
|
@ -88,6 +143,9 @@ func (c *Client) ListMyRepos(opt ListReposOptions) ([]*Repository, *Response, er
|
||||||
|
|
||||||
// ListUserRepos list all repositories of one user by user's name
|
// ListUserRepos list all repositories of one user by user's name
|
||||||
func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) {
|
func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
repos := make([]*Repository, 0, opt.PageSize)
|
repos := make([]*Repository, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos?%s", user, opt.getURLQuery().Encode()), nil, nil, &repos)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos?%s", user, opt.getURLQuery().Encode()), nil, nil, &repos)
|
||||||
|
@ -101,6 +159,9 @@ type ListOrgReposOptions struct {
|
||||||
|
|
||||||
// ListOrgRepos list all repositories of one organization by organization's name
|
// ListOrgRepos list all repositories of one organization by organization's name
|
||||||
func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) {
|
func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
repos := make([]*Repository, 0, opt.PageSize)
|
repos := make([]*Repository, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos?%s", org, opt.getURLQuery().Encode()), nil, nil, &repos)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos?%s", org, opt.getURLQuery().Encode()), nil, nil, &repos)
|
||||||
|
@ -224,12 +285,14 @@ func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, e
|
||||||
} else {
|
} else {
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
// IsPrivate only works on gitea >= 1.12.0
|
// IsPrivate only works on gitea >= 1.12.0
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil && opt.IsPrivate != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil && opt.IsPrivate != nil {
|
||||||
if *opt.IsPrivate {
|
if *opt.IsPrivate {
|
||||||
// private repos only not supported on gitea <= 1.11.x
|
// private repos only not supported on gitea <= 1.11.x
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
link.Query().Add("private", "false")
|
newQuery := link.Query()
|
||||||
|
newQuery.Add("private", "false")
|
||||||
|
link.RawQuery = newQuery.Encode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,6 +312,8 @@ type CreateRepoOption struct {
|
||||||
IssueLabels string `json:"issue_labels"`
|
IssueLabels string `json:"issue_labels"`
|
||||||
// Whether the repository should be auto-intialized?
|
// Whether the repository should be auto-intialized?
|
||||||
AutoInit bool `json:"auto_init"`
|
AutoInit bool `json:"auto_init"`
|
||||||
|
// Whether the repository is template
|
||||||
|
Template bool `json:"template"`
|
||||||
// Gitignores to use
|
// Gitignores to use
|
||||||
Gitignores string `json:"gitignores"`
|
Gitignores string `json:"gitignores"`
|
||||||
// License to use
|
// License to use
|
||||||
|
@ -257,19 +322,35 @@ type CreateRepoOption struct {
|
||||||
Readme string `json:"readme"`
|
Readme string `json:"readme"`
|
||||||
// DefaultBranch of the repository (used when initializes and in template)
|
// DefaultBranch of the repository (used when initializes and in template)
|
||||||
DefaultBranch string `json:"default_branch"`
|
DefaultBranch string `json:"default_branch"`
|
||||||
|
// TrustModel of the repository
|
||||||
|
TrustModel TrustModel `json:"trust_model"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the CreateRepoOption struct
|
// Validate the CreateRepoOption struct
|
||||||
func (opt CreateRepoOption) Validate() error {
|
func (opt CreateRepoOption) Validate(c *Client) error {
|
||||||
if len(strings.TrimSpace(opt.Name)) == 0 {
|
if len(strings.TrimSpace(opt.Name)) == 0 {
|
||||||
return fmt.Errorf("name is empty")
|
return fmt.Errorf("name is empty")
|
||||||
}
|
}
|
||||||
|
if len(opt.Name) > 100 {
|
||||||
|
return fmt.Errorf("name has more than 100 chars")
|
||||||
|
}
|
||||||
|
if len(opt.Description) > 2048 {
|
||||||
|
return fmt.Errorf("description has more than 2048 chars")
|
||||||
|
}
|
||||||
|
if len(opt.DefaultBranch) > 100 {
|
||||||
|
return fmt.Errorf("default branch name has more than 100 chars")
|
||||||
|
}
|
||||||
|
if len(opt.TrustModel) != 0 {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRepo creates a repository for authenticated user.
|
// CreateRepo creates a repository for authenticated user.
|
||||||
func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) {
|
func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) {
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(c); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
|
@ -283,7 +364,10 @@ func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error
|
||||||
|
|
||||||
// CreateOrgRepo creates an organization repository for authenticated user.
|
// CreateOrgRepo creates an organization repository for authenticated user.
|
||||||
func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) {
|
func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) {
|
||||||
if err := opt.Validate(); err != nil {
|
if err := escapeValidatePathSegments(&org); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := opt.Validate(c); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
|
@ -297,11 +381,21 @@ func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *
|
||||||
|
|
||||||
// GetRepo returns information of a repository of given owner.
|
// GetRepo returns information of a repository of given owner.
|
||||||
func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) {
|
func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
repo := new(Repository)
|
repo := new(Repository)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo)
|
||||||
return repo, resp, err
|
return repo, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRepoByID returns information of a repository by a giver repository ID.
|
||||||
|
func (c *Client) GetRepoByID(id int64) (*Repository, *Response, error) {
|
||||||
|
repo := new(Repository)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repositories/%d", id), nil, nil, repo)
|
||||||
|
return repo, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
// EditRepoOption options when editing a repository's properties
|
// EditRepoOption options when editing a repository's properties
|
||||||
type EditRepoOption struct {
|
type EditRepoOption struct {
|
||||||
// name of the repository
|
// name of the repository
|
||||||
|
@ -314,14 +408,30 @@ type EditRepoOption struct {
|
||||||
// Note: you will get a 422 error if the organization restricts changing repository visibility to organization
|
// Note: you will get a 422 error if the organization restricts changing repository visibility to organization
|
||||||
// owners and a non-owner tries to change the value of private.
|
// owners and a non-owner tries to change the value of private.
|
||||||
Private *bool `json:"private,omitempty"`
|
Private *bool `json:"private,omitempty"`
|
||||||
|
// either `true` to make this repository a template or `false` to make it a normal repository
|
||||||
|
Template *bool `json:"template,omitempty"`
|
||||||
// either `true` to enable issues for this repository or `false` to disable them.
|
// either `true` to enable issues for this repository or `false` to disable them.
|
||||||
HasIssues *bool `json:"has_issues,omitempty"`
|
HasIssues *bool `json:"has_issues,omitempty"`
|
||||||
|
// set this structure to configure internal issue tracker (requires has_issues)
|
||||||
|
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||||
|
// set this structure to use external issue tracker (requires has_issues)
|
||||||
|
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||||
// either `true` to enable the wiki for this repository or `false` to disable it.
|
// either `true` to enable the wiki for this repository or `false` to disable it.
|
||||||
HasWiki *bool `json:"has_wiki,omitempty"`
|
HasWiki *bool `json:"has_wiki,omitempty"`
|
||||||
|
// set this structure to use external wiki instead of internal (requires has_wiki)
|
||||||
|
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||||
// sets the default branch for this repository.
|
// sets the default branch for this repository.
|
||||||
DefaultBranch *string `json:"default_branch,omitempty"`
|
DefaultBranch *string `json:"default_branch,omitempty"`
|
||||||
// either `true` to allow pull requests, or `false` to prevent pull request.
|
// either `true` to allow pull requests, or `false` to prevent pull request.
|
||||||
HasPullRequests *bool `json:"has_pull_requests,omitempty"`
|
HasPullRequests *bool `json:"has_pull_requests,omitempty"`
|
||||||
|
// either `true` to enable project unit, or `false` to disable them.
|
||||||
|
HasProjects *bool `json:"has_projects,omitempty"`
|
||||||
|
// either `true` to enable release, or `false` to disable them.
|
||||||
|
HasReleases *bool `json:"has_releases,omitempty"`
|
||||||
|
// either `true` to enable packages, or `false` to disable them.
|
||||||
|
HasPackages *bool `json:"has_packages,omitempty"`
|
||||||
|
// either `true` to enable actions, or `false` to disable them.
|
||||||
|
HasActions *bool `json:"has_actions,omitempty"`
|
||||||
// either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`.
|
// either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`.
|
||||||
IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"`
|
IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"`
|
||||||
// either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`.
|
// either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`.
|
||||||
|
@ -334,10 +444,22 @@ type EditRepoOption struct {
|
||||||
AllowSquash *bool `json:"allow_squash_merge,omitempty"`
|
AllowSquash *bool `json:"allow_squash_merge,omitempty"`
|
||||||
// set to `true` to archive this repository.
|
// set to `true` to archive this repository.
|
||||||
Archived *bool `json:"archived,omitempty"`
|
Archived *bool `json:"archived,omitempty"`
|
||||||
|
// set to a string like `8h30m0s` to set the mirror interval time
|
||||||
|
MirrorInterval *string `json:"mirror_interval,omitempty"`
|
||||||
|
// either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`.
|
||||||
|
AllowManualMerge *bool `json:"allow_manual_merge,omitempty"`
|
||||||
|
// either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur.
|
||||||
|
AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"`
|
||||||
|
// set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". `has_pull_requests` must be `true`.
|
||||||
|
DefaultMergeStyle *MergeStyle `json:"default_merge_style,omitempty"`
|
||||||
|
// set to `true` to archive this repository.
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditRepo edit the properties of a repository
|
// EditRepo edit the properties of a repository
|
||||||
func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) {
|
func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -349,18 +471,27 @@ func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Reposito
|
||||||
|
|
||||||
// DeleteRepo deletes a repository of user or organization.
|
// DeleteRepo deletes a repository of user or organization.
|
||||||
func (c *Client) DeleteRepo(owner, repo string) (*Response, error) {
|
func (c *Client) DeleteRepo(owner, repo string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// MirrorSync adds a mirrored repository to the mirror sync queue.
|
// MirrorSync adds a mirrored repository to the mirror sync queue.
|
||||||
func (c *Client) MirrorSync(owner, repo string) (*Response, error) {
|
func (c *Client) MirrorSync(owner, repo string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil)
|
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepoLanguages return language stats of a repo
|
// GetRepoLanguages return language stats of a repo
|
||||||
func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) {
|
func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
langMap := make(map[string]int64)
|
langMap := make(map[string]int64)
|
||||||
|
|
||||||
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil)
|
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil)
|
||||||
|
@ -386,5 +517,30 @@ const (
|
||||||
// GetArchive get an archive of a repository by git reference
|
// GetArchive get an archive of a repository by git reference
|
||||||
// e.g.: ref -> master, 70b7c74b33, v1.2.1, ...
|
// e.g.: ref -> master, 70b7c74b33, v1.2.1, ...
|
||||||
func (c *Client) GetArchive(owner, repo, ref string, ext ArchiveType) ([]byte, *Response, error) {
|
func (c *Client) GetArchive(owner, repo, ref string, ext ArchiveType) ([]byte, *Response, error) {
|
||||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, url.PathEscape(ref), ext), nil, nil)
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ref = pathEscapeSegments(ref)
|
||||||
|
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArchiveReader gets a `git archive` for a particular tree-ish git reference
|
||||||
|
// such as a branch name (`master`), a commit hash (`70b7c74b33`), a tag
|
||||||
|
// (`v1.2.1`). The archive is returned as a byte stream in a ReadCloser. It is
|
||||||
|
// the responsibility of the client to close the reader.
|
||||||
|
func (c *Client) GetArchiveReader(owner, repo, ref string, ext ArchiveType) (io.ReadCloser, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ref = pathEscapeSegments(ref)
|
||||||
|
resp, err := c.doRequest("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := statusCodeToErr(resp); err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Body, resp, nil
|
||||||
}
|
}
|
||||||
|
|
66
gitea/repo_action.go
Normal file
66
gitea/repo_action.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListRepoActionSecretOption list RepoActionSecret options
|
||||||
|
type ListRepoActionSecretOption struct {
|
||||||
|
ListOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRepoActionSecret list a repository's secrets
|
||||||
|
func (c *Client) ListRepoActionSecret(user, repo string, opt ListRepoActionSecretOption) ([]*Secret, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
opt.setDefaults()
|
||||||
|
secrets := make([]*Secret, 0, opt.PageSize)
|
||||||
|
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/secrets", user, repo))
|
||||||
|
link.RawQuery = opt.getURLQuery().Encode()
|
||||||
|
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets)
|
||||||
|
return secrets, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRepoActionSecret creates a secret for the specified repository in the Gitea Actions.
|
||||||
|
// It takes the organization name and the secret options as parameters.
|
||||||
|
// The function returns the HTTP response and an error, if any.
|
||||||
|
func (c *Client) CreateRepoActionSecret(user, repo string, opt CreateSecretOption) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := (&opt).Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(&opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/actions/secrets/%s", user, repo, opt.Name), jsonHeader, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case http.StatusCreated:
|
||||||
|
return resp, nil
|
||||||
|
case http.StatusNoContent:
|
||||||
|
return resp, nil
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return resp, fmt.Errorf("forbidden")
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
return resp, fmt.Errorf("bad request")
|
||||||
|
default:
|
||||||
|
return resp, fmt.Errorf("unexpected Status: %d", status)
|
||||||
|
}
|
||||||
|
}
|
41
gitea/repo_action_test.go
Normal file
41
gitea/repo_action_test.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateRepoActionSecret(t *testing.T) {
|
||||||
|
log.Println("== TestCreateRepoActionSecret ==")
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
user := createTestUser(t, "repo_action_user", c)
|
||||||
|
c.SetSudo(user.UserName)
|
||||||
|
newRepo, _, err := c.CreateRepo(CreateRepoOption{
|
||||||
|
Name: "test",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, newRepo)
|
||||||
|
|
||||||
|
// create secret
|
||||||
|
resp, err := c.CreateRepoActionSecret(newRepo.Owner.UserName, newRepo.Name, CreateSecretOption{Name: "test", Data: "test"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||||
|
|
||||||
|
// update secret
|
||||||
|
resp, err = c.CreateRepoActionSecret(newRepo.Owner.UserName, newRepo.Name, CreateSecretOption{Name: "test", Data: "test2"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
|
||||||
|
|
||||||
|
// list secrets
|
||||||
|
secrets, _, err := c.ListRepoActionSecret(newRepo.Owner.UserName, newRepo.Name, ListRepoActionSecretOption{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, secrets, 1)
|
||||||
|
}
|
|
@ -20,9 +20,6 @@ type PayloadUser struct {
|
||||||
UserName string `json:"username"`
|
UserName string `json:"username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: consider using same format as API when commits API are added.
|
|
||||||
// applies to PayloadCommit and PayloadCommitVerification
|
|
||||||
|
|
||||||
// PayloadCommit represents a commit
|
// PayloadCommit represents a commit
|
||||||
type PayloadCommit struct {
|
type PayloadCommit struct {
|
||||||
// sha1 hash of the commit
|
// sha1 hash of the commit
|
||||||
|
@ -66,6 +63,9 @@ type ListRepoBranchesOptions struct {
|
||||||
|
|
||||||
// ListRepoBranches list all the branches of one repository
|
// ListRepoBranches list all the branches of one repository
|
||||||
func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions) ([]*Branch, *Response, error) {
|
func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions) ([]*Branch, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
branches := make([]*Branch, 0, opt.PageSize)
|
branches := make([]*Branch, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &branches)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &branches)
|
||||||
|
@ -74,6 +74,9 @@ func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions
|
||||||
|
|
||||||
// GetRepoBranch get one branch's information of one repository
|
// GetRepoBranch get one branch's information of one repository
|
||||||
func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, error) {
|
func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
b := new(Branch)
|
b := new(Branch)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -84,7 +87,10 @@ func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, e
|
||||||
|
|
||||||
// DeleteRepoBranch delete a branch in a repository
|
// DeleteRepoBranch delete a branch in a repository
|
||||||
func (c *Client) DeleteRepoBranch(user, repo, branch string) (bool, *Response, error) {
|
func (c *Client) DeleteRepoBranch(user, repo, branch string) (bool, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return false, nil, err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil)
|
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil)
|
||||||
|
@ -118,7 +124,10 @@ func (opt CreateBranchOption) Validate() error {
|
||||||
|
|
||||||
// CreateBranch creates a branch for a user's repository
|
// CreateBranch creates a branch for a user's repository
|
||||||
func (c *Client) CreateBranch(owner, repo string, opt CreateBranchOption) (*Branch, *Response, error) {
|
func (c *Client) CreateBranch(owner, repo string, opt CreateBranchOption) (*Branch, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(); err != nil {
|
||||||
|
|
|
@ -14,75 +14,83 @@ import (
|
||||||
|
|
||||||
// BranchProtection represents a branch protection for a repository
|
// BranchProtection represents a branch protection for a repository
|
||||||
type BranchProtection struct {
|
type BranchProtection struct {
|
||||||
BranchName string `json:"branch_name"`
|
BranchName string `json:"branch_name"`
|
||||||
EnablePush bool `json:"enable_push"`
|
RuleName string `json:"rule_name"`
|
||||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
EnablePush bool `json:"enable_push"`
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
||||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
||||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
||||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
||||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
||||||
EnableStatusCheck bool `json:"enable_status_check"`
|
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
||||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
EnableStatusCheck bool `json:"enable_status_check"`
|
||||||
RequiredApprovals int64 `json:"required_approvals"`
|
StatusCheckContexts []string `json:"status_check_contexts"`
|
||||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
RequiredApprovals int64 `json:"required_approvals"`
|
||||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
||||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
||||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
||||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
|
||||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
||||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
||||||
Created time.Time `json:"created_at"`
|
RequireSignedCommits bool `json:"require_signed_commits"`
|
||||||
Updated time.Time `json:"updated_at"`
|
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
||||||
|
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
|
||||||
|
Created time.Time `json:"created_at"`
|
||||||
|
Updated time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBranchProtectionOption options for creating a branch protection
|
// CreateBranchProtectionOption options for creating a branch protection
|
||||||
type CreateBranchProtectionOption struct {
|
type CreateBranchProtectionOption struct {
|
||||||
BranchName string `json:"branch_name"`
|
BranchName string `json:"branch_name"`
|
||||||
EnablePush bool `json:"enable_push"`
|
RuleName string `json:"rule_name"`
|
||||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
EnablePush bool `json:"enable_push"`
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
||||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
||||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
||||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
||||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
||||||
EnableStatusCheck bool `json:"enable_status_check"`
|
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
||||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
EnableStatusCheck bool `json:"enable_status_check"`
|
||||||
RequiredApprovals int64 `json:"required_approvals"`
|
StatusCheckContexts []string `json:"status_check_contexts"`
|
||||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
RequiredApprovals int64 `json:"required_approvals"`
|
||||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
||||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
||||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
||||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
|
||||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
||||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
||||||
|
RequireSignedCommits bool `json:"require_signed_commits"`
|
||||||
|
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
||||||
|
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditBranchProtectionOption options for editing a branch protection
|
// EditBranchProtectionOption options for editing a branch protection
|
||||||
type EditBranchProtectionOption struct {
|
type EditBranchProtectionOption struct {
|
||||||
EnablePush *bool `json:"enable_push"`
|
EnablePush *bool `json:"enable_push"`
|
||||||
EnablePushWhitelist *bool `json:"enable_push_whitelist"`
|
EnablePushWhitelist *bool `json:"enable_push_whitelist"`
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
||||||
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"`
|
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"`
|
||||||
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
|
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
|
||||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
||||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
||||||
EnableStatusCheck *bool `json:"enable_status_check"`
|
EnableStatusCheck *bool `json:"enable_status_check"`
|
||||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
StatusCheckContexts []string `json:"status_check_contexts"`
|
||||||
RequiredApprovals *int64 `json:"required_approvals"`
|
RequiredApprovals *int64 `json:"required_approvals"`
|
||||||
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"`
|
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"`
|
||||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
||||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||||
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"`
|
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"`
|
||||||
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
|
BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"`
|
||||||
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
|
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
|
||||||
RequireSignedCommits *bool `json:"require_signed_commits"`
|
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
|
||||||
ProtectedFilePatterns *string `json:"protected_file_patterns"`
|
RequireSignedCommits *bool `json:"require_signed_commits"`
|
||||||
|
ProtectedFilePatterns *string `json:"protected_file_patterns"`
|
||||||
|
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListBranchProtectionsOptions list branch protection options
|
// ListBranchProtectionsOptions list branch protection options
|
||||||
|
@ -92,7 +100,10 @@ type ListBranchProtectionsOptions struct {
|
||||||
|
|
||||||
// ListBranchProtections list branch protections for a repo
|
// ListBranchProtections list branch protections for a repo
|
||||||
func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, *Response, error) {
|
func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
bps := make([]*BranchProtection, 0, opt.PageSize)
|
bps := make([]*BranchProtection, 0, opt.PageSize)
|
||||||
|
@ -104,7 +115,10 @@ func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtect
|
||||||
|
|
||||||
// GetBranchProtection gets a branch protection
|
// GetBranchProtection gets a branch protection
|
||||||
func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, *Response, error) {
|
func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
bp := new(BranchProtection)
|
bp := new(BranchProtection)
|
||||||
|
@ -114,7 +128,10 @@ func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtectio
|
||||||
|
|
||||||
// CreateBranchProtection creates a branch protection for a repo
|
// CreateBranchProtection creates a branch protection for a repo
|
||||||
func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, *Response, error) {
|
func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
bp := new(BranchProtection)
|
bp := new(BranchProtection)
|
||||||
|
@ -128,7 +145,10 @@ func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProt
|
||||||
|
|
||||||
// EditBranchProtection edits a branch protection for a repo
|
// EditBranchProtection edits a branch protection for a repo
|
||||||
func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, *Response, error) {
|
func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
bp := new(BranchProtection)
|
bp := new(BranchProtection)
|
||||||
|
@ -142,7 +162,10 @@ func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchPr
|
||||||
|
|
||||||
// DeleteBranchProtection deletes a branch protection for a repo
|
// DeleteBranchProtection deletes a branch protection for a repo
|
||||||
func (c *Client) DeleteBranchProtection(owner, repo, name string) (*Response, error) {
|
func (c *Client) DeleteBranchProtection(owner, repo, name string) (*Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil)
|
||||||
|
|
|
@ -7,6 +7,7 @@ package gitea
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -14,26 +15,31 @@ import (
|
||||||
func TestRepoBranches(t *testing.T) {
|
func TestRepoBranches(t *testing.T) {
|
||||||
log.Println("== TestRepoBranches ==")
|
log.Println("== TestRepoBranches ==")
|
||||||
c := newTestClient()
|
c := newTestClient()
|
||||||
var repoName = "branches"
|
repoName := "branches"
|
||||||
|
|
||||||
repo := prepareBranchTest(t, c, repoName)
|
repo := prepareBranchTest(t, c, repoName)
|
||||||
if repo == nil {
|
if repo == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
bl, _, err := c.ListRepoBranches(repo.Owner.UserName, repo.Name, ListRepoBranchesOptions{})
|
bl, _, err := c.ListRepoBranches(repo.Owner.UserName, repo.Name, ListRepoBranchesOptions{})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, bl, 3)
|
assert.Len(t, bl, 3)
|
||||||
assert.EqualValues(t, "feature", bl[0].Name)
|
|
||||||
assert.EqualValues(t, "master", bl[1].Name)
|
branchNames := make([]string, len(bl))
|
||||||
assert.EqualValues(t, "update", bl[2].Name)
|
branches := make(map[string]Branch, len(bl))
|
||||||
|
for index, branch := range bl {
|
||||||
|
branchNames[index] = branch.Name
|
||||||
|
branches[branch.Name] = *branch
|
||||||
|
}
|
||||||
|
assert.ElementsMatch(t, []string{"feature", "main", "update"}, branchNames)
|
||||||
|
|
||||||
b, _, err := c.GetRepoBranch(repo.Owner.UserName, repo.Name, "update")
|
b, _, err := c.GetRepoBranch(repo.Owner.UserName, repo.Name, "update")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, bl[2].Commit.ID, b.Commit.ID)
|
assert.EqualValues(t, branches["update"].Commit.ID, b.Commit.ID)
|
||||||
assert.EqualValues(t, bl[2].Commit.Added, b.Commit.Added)
|
assert.EqualValues(t, branches["update"].Commit.Added, b.Commit.Added)
|
||||||
|
|
||||||
s, _, err := c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "master")
|
s, _, err := c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "main")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, s)
|
assert.False(t, s)
|
||||||
s, _, err = c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "feature")
|
s, _, err = c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "feature")
|
||||||
|
@ -59,7 +65,7 @@ func TestRepoBranches(t *testing.T) {
|
||||||
func TestRepoBranchProtection(t *testing.T) {
|
func TestRepoBranchProtection(t *testing.T) {
|
||||||
log.Println("== TestRepoBranchProtection ==")
|
log.Println("== TestRepoBranchProtection ==")
|
||||||
c := newTestClient()
|
c := newTestClient()
|
||||||
var repoName = "BranchProtection"
|
repoName := "BranchProtection"
|
||||||
|
|
||||||
repo := prepareBranchTest(t, c, repoName)
|
repo := prepareBranchTest(t, c, repoName)
|
||||||
if repo == nil {
|
if repo == nil {
|
||||||
|
@ -74,7 +80,7 @@ func TestRepoBranchProtection(t *testing.T) {
|
||||||
|
|
||||||
// CreateBranchProtection
|
// CreateBranchProtection
|
||||||
bp, _, err := c.CreateBranchProtection(repo.Owner.UserName, repo.Name, CreateBranchProtectionOption{
|
bp, _, err := c.CreateBranchProtection(repo.Owner.UserName, repo.Name, CreateBranchProtectionOption{
|
||||||
BranchName: "master",
|
BranchName: "main",
|
||||||
EnablePush: true,
|
EnablePush: true,
|
||||||
EnablePushWhitelist: true,
|
EnablePushWhitelist: true,
|
||||||
PushWhitelistUsernames: []string{"test01"},
|
PushWhitelistUsernames: []string{"test01"},
|
||||||
|
@ -83,7 +89,7 @@ func TestRepoBranchProtection(t *testing.T) {
|
||||||
BlockOnOutdatedBranch: true,
|
BlockOnOutdatedBranch: true,
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, "master", bp.BranchName)
|
assert.EqualValues(t, "main", bp.BranchName)
|
||||||
assert.EqualValues(t, false, bp.EnableStatusCheck)
|
assert.EqualValues(t, false, bp.EnableStatusCheck)
|
||||||
assert.EqualValues(t, true, bp.EnablePush)
|
assert.EqualValues(t, true, bp.EnablePush)
|
||||||
assert.EqualValues(t, true, bp.EnablePushWhitelist)
|
assert.EqualValues(t, true, bp.EnablePushWhitelist)
|
||||||
|
@ -107,17 +113,13 @@ func TestRepoBranchProtection(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, bpl[0], bp)
|
assert.EqualValues(t, bpl[0], bp)
|
||||||
|
|
||||||
optTrue := true
|
|
||||||
optFalse := false
|
|
||||||
one := int64(1)
|
|
||||||
|
|
||||||
// EditBranchProtection
|
// EditBranchProtection
|
||||||
bp, _, err = c.EditBranchProtection(repo.Owner.UserName, repo.Name, bpl[0].BranchName, EditBranchProtectionOption{
|
bp, _, err = c.EditBranchProtection(repo.Owner.UserName, repo.Name, bpl[0].BranchName, EditBranchProtectionOption{
|
||||||
EnablePush: &optFalse,
|
EnablePush: OptionalBool(false),
|
||||||
EnablePushWhitelist: &optFalse,
|
EnablePushWhitelist: OptionalBool(false),
|
||||||
PushWhitelistUsernames: nil,
|
PushWhitelistUsernames: nil,
|
||||||
RequiredApprovals: &one,
|
RequiredApprovals: OptionalInt64(1),
|
||||||
EnableApprovalsWhitelist: &optTrue,
|
EnableApprovalsWhitelist: OptionalBool(true),
|
||||||
ApprovalsWhitelistUsernames: []string{"test01"},
|
ApprovalsWhitelistUsernames: []string{"test01"},
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -140,18 +142,18 @@ func prepareBranchTest(t *testing.T, c *Client, repoName string) *Repository {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
masterLicence, _, err := c.GetContents(origRepo.Owner.UserName, origRepo.Name, "master", "README.md")
|
mainLicense, _, err := c.GetContents(origRepo.Owner.UserName, origRepo.Name, "main", "README.md")
|
||||||
if !assert.NoError(t, err) || !assert.NotNil(t, masterLicence) {
|
if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedFile, _, err := c.UpdateFile(origRepo.Owner.UserName, origRepo.Name, "README.md", UpdateFileOptions{
|
updatedFile, _, err := c.UpdateFile(origRepo.Owner.UserName, origRepo.Name, "README.md", UpdateFileOptions{
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "update it",
|
Message: "update it",
|
||||||
BranchName: "master",
|
BranchName: "main",
|
||||||
NewBranchName: "update",
|
NewBranchName: "update",
|
||||||
},
|
},
|
||||||
SHA: masterLicence.SHA,
|
SHA: mainLicense.SHA,
|
||||||
Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
|
Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
|
||||||
})
|
})
|
||||||
if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) {
|
if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) {
|
||||||
|
@ -162,7 +164,7 @@ func prepareBranchTest(t *testing.T, c *Client, repoName string) *Repository {
|
||||||
Content: "QSBuZXcgRmlsZQo=",
|
Content: "QSBuZXcgRmlsZQo=",
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "creat a new file",
|
Message: "creat a new file",
|
||||||
BranchName: "master",
|
BranchName: "main",
|
||||||
NewBranchName: "feature",
|
NewBranchName: "feature",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
@ -15,8 +16,18 @@ type ListCollaboratorsOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollaboratorPermissionResult result type for CollaboratorPermission
|
||||||
|
type CollaboratorPermissionResult struct {
|
||||||
|
Permission AccessMode `json:"permission"`
|
||||||
|
Role string `json:"role_name"`
|
||||||
|
User *User `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
// ListCollaborators list a repository's collaborators
|
// ListCollaborators list a repository's collaborators
|
||||||
func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) {
|
func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
collaborators := make([]*User, 0, opt.PageSize)
|
collaborators := make([]*User, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET",
|
resp, err := c.getParsedResponse("GET",
|
||||||
|
@ -27,6 +38,9 @@ func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptio
|
||||||
|
|
||||||
// IsCollaborator check if a user is a collaborator of a repository
|
// IsCollaborator check if a user is a collaborator of a repository
|
||||||
func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Response, error) {
|
func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
|
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, resp, err
|
return false, resp, err
|
||||||
|
@ -37,6 +51,26 @@ func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Respons
|
||||||
return false, resp, nil
|
return false, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollaboratorPermission gets collaborator permission of a repository
|
||||||
|
func (c *Client) CollaboratorPermission(user, repo, collaborator string) (*CollaboratorPermissionResult, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
rv := new(CollaboratorPermissionResult)
|
||||||
|
resp, err := c.getParsedResponse("GET",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", user, repo, collaborator),
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
rv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
rv = nil
|
||||||
|
}
|
||||||
|
return rv, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddCollaboratorOption options when adding a user as a collaborator of a repository
|
// AddCollaboratorOption options when adding a user as a collaborator of a repository
|
||||||
type AddCollaboratorOption struct {
|
type AddCollaboratorOption struct {
|
||||||
Permission *AccessMode `json:"permission"`
|
Permission *AccessMode `json:"permission"`
|
||||||
|
@ -59,7 +93,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate the AddCollaboratorOption struct
|
// Validate the AddCollaboratorOption struct
|
||||||
func (opt AddCollaboratorOption) Validate() error {
|
func (opt *AddCollaboratorOption) Validate() error {
|
||||||
if opt.Permission != nil {
|
if opt.Permission != nil {
|
||||||
if *opt.Permission == AccessModeOwner {
|
if *opt.Permission == AccessModeOwner {
|
||||||
*opt.Permission = AccessModeAdmin
|
*opt.Permission = AccessModeAdmin
|
||||||
|
@ -78,7 +112,10 @@ func (opt AddCollaboratorOption) Validate() error {
|
||||||
|
|
||||||
// AddCollaborator add some user as a collaborator of a repository
|
// AddCollaborator add some user as a collaborator of a repository
|
||||||
func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollaboratorOption) (*Response, error) {
|
func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollaboratorOption) (*Response, error) {
|
||||||
if err := opt.Validate(); err != nil {
|
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := (&opt).Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
|
@ -91,7 +128,36 @@ func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollabo
|
||||||
|
|
||||||
// DeleteCollaborator remove a collaborator from a repository
|
// DeleteCollaborator remove a collaborator from a repository
|
||||||
func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response, error) {
|
func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE",
|
_, resp, err := c.getResponse("DELETE",
|
||||||
fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
|
fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetReviewers return all users that can be requested to review in this repo
|
||||||
|
func (c *Client) GetReviewers(user, repo string) ([]*User, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
reviewers := make([]*User, 0, 5)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/reviewers", user, repo), nil, nil, &reviewers)
|
||||||
|
return reviewers, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAssignees return all users that have write access and can be assigned to issues
|
||||||
|
func (c *Client) GetAssignees(user, repo string) ([]*User, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
assignees := make([]*User, 0, 5)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/assignees", user, repo), nil, nil, &assignees)
|
||||||
|
return assignees, resp, err
|
||||||
|
}
|
||||||
|
|
80
gitea/repo_collaborator_test.go
Normal file
80
gitea/repo_collaborator_test.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoCollaborator(t *testing.T) {
|
||||||
|
log.Println("== TestRepoCollaborator ==")
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
repo, _ := createTestRepo(t, "RepoCollaborators", c)
|
||||||
|
createTestUser(t, "ping", c)
|
||||||
|
createTestUser(t, "pong", c)
|
||||||
|
defer func() {
|
||||||
|
_, err := c.AdminDeleteUser("ping")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = c.AdminDeleteUser("pong")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
collaborators, _, err := c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, collaborators, 0)
|
||||||
|
|
||||||
|
mode := AccessModeAdmin
|
||||||
|
resp, err := c.AddCollaborator(repo.Owner.UserName, repo.Name, "ping", AddCollaboratorOption{Permission: &mode})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 204, resp.StatusCode)
|
||||||
|
|
||||||
|
permissonPing, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "ping")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 200, resp.StatusCode)
|
||||||
|
assert.EqualValues(t, AccessModeAdmin, permissonPing.Permission)
|
||||||
|
assert.EqualValues(t, "ping", permissonPing.User.UserName)
|
||||||
|
|
||||||
|
mode = AccessModeRead
|
||||||
|
_, err = c.AddCollaborator(repo.Owner.UserName, repo.Name, "pong", AddCollaboratorOption{Permission: &mode})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
permissonPong, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "pong")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 200, resp.StatusCode)
|
||||||
|
assert.EqualValues(t, AccessModeRead, permissonPong.Permission)
|
||||||
|
assert.EqualValues(t, "pong", permissonPong.User.UserName)
|
||||||
|
|
||||||
|
collaborators, _, err = c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, collaborators, 2)
|
||||||
|
assert.EqualValues(t, []string{"ping", "pong"}, userToStringSlice(collaborators))
|
||||||
|
|
||||||
|
reviewers, _, err := c.GetReviewers(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, reviewers, 3)
|
||||||
|
assert.EqualValues(t, []string{"ping", "pong", "test01"}, userToStringSlice(reviewers))
|
||||||
|
|
||||||
|
assignees, _, err := c.GetAssignees(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, assignees, 2)
|
||||||
|
assert.EqualValues(t, []string{"ping", "test01"}, userToStringSlice(assignees))
|
||||||
|
|
||||||
|
resp, err = c.DeleteCollaborator(repo.Owner.UserName, repo.Name, "ping")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 204, resp.StatusCode)
|
||||||
|
|
||||||
|
collaborators, _, err = c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, collaborators, 1)
|
||||||
|
|
||||||
|
permissonNotExists, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "user_that_not_exists")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualValues(t, 404, resp.StatusCode)
|
||||||
|
assert.Nil(t, permissonNotExists)
|
||||||
|
}
|
|
@ -19,8 +19,9 @@ type Identity struct {
|
||||||
|
|
||||||
// CommitMeta contains meta information of a commit in terms of API.
|
// CommitMeta contains meta information of a commit in terms of API.
|
||||||
type CommitMeta struct {
|
type CommitMeta struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
SHA string `json:"sha"`
|
SHA string `json:"sha"`
|
||||||
|
Created time.Time `json:"created"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitUser contains information of a user in the context of a commit.
|
// CommitUser contains information of a user in the context of a commit.
|
||||||
|
@ -31,21 +32,31 @@ type CommitUser struct {
|
||||||
|
|
||||||
// RepoCommit contains information of a commit in the context of a repository.
|
// RepoCommit contains information of a commit in the context of a repository.
|
||||||
type RepoCommit struct {
|
type RepoCommit struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Author *CommitUser `json:"author"`
|
Author *CommitUser `json:"author"`
|
||||||
Committer *CommitUser `json:"committer"`
|
Committer *CommitUser `json:"committer"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Tree *CommitMeta `json:"tree"`
|
Tree *CommitMeta `json:"tree"`
|
||||||
|
Verification *PayloadCommitVerification `json:"verification"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitStats contains stats from a Git commit
|
||||||
|
type CommitStats struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
Additions int `json:"additions"`
|
||||||
|
Deletions int `json:"deletions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit contains information generated from a Git commit.
|
// Commit contains information generated from a Git commit.
|
||||||
type Commit struct {
|
type Commit struct {
|
||||||
*CommitMeta
|
*CommitMeta
|
||||||
HTMLURL string `json:"html_url"`
|
HTMLURL string `json:"html_url"`
|
||||||
RepoCommit *RepoCommit `json:"commit"`
|
RepoCommit *RepoCommit `json:"commit"`
|
||||||
Author *User `json:"author"`
|
Author *User `json:"author"`
|
||||||
Committer *User `json:"committer"`
|
Committer *User `json:"committer"`
|
||||||
Parents []*CommitMeta `json:"parents"`
|
Parents []*CommitMeta `json:"parents"`
|
||||||
|
Files []*CommitAffectedFiles `json:"files"`
|
||||||
|
Stats *CommitStats `json:"stats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
|
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
|
||||||
|
@ -54,8 +65,16 @@ type CommitDateOptions struct {
|
||||||
Committer time.Time `json:"committer"`
|
Committer time.Time `json:"committer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommitAffectedFiles store information about files affected by the commit
|
||||||
|
type CommitAffectedFiles struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
}
|
||||||
|
|
||||||
// GetSingleCommit returns a single commit
|
// GetSingleCommit returns a single commit
|
||||||
func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Response, error) {
|
func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &commitID); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
commit := new(Commit)
|
commit := new(Commit)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s", user, repo, commitID), nil, nil, &commit)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s", user, repo, commitID), nil, nil, &commit)
|
||||||
return commit, resp, err
|
return commit, resp, err
|
||||||
|
@ -64,21 +83,29 @@ func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Respons
|
||||||
// ListCommitOptions list commit options
|
// ListCommitOptions list commit options
|
||||||
type ListCommitOptions struct {
|
type ListCommitOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
//SHA or branch to start listing commits from (usually 'master')
|
// SHA or branch to start listing commits from (usually 'master')
|
||||||
SHA string
|
SHA string
|
||||||
|
// Path indicates that only commits that include the path's file/dir should be returned.
|
||||||
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
// QueryEncode turns options into querystring argument
|
||||||
func (opt *ListCommitOptions) QueryEncode() string {
|
func (opt *ListCommitOptions) QueryEncode() string {
|
||||||
query := opt.ListOptions.getURLQuery()
|
query := opt.getURLQuery()
|
||||||
if opt.SHA != "" {
|
if opt.SHA != "" {
|
||||||
query.Add("sha", opt.SHA)
|
query.Add("sha", opt.SHA)
|
||||||
}
|
}
|
||||||
|
if opt.Path != "" {
|
||||||
|
query.Add("path", opt.Path)
|
||||||
|
}
|
||||||
return query.Encode()
|
return query.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRepoCommits return list of commits from a repo
|
// ListRepoCommits return list of commits from a repo
|
||||||
func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) {
|
func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo))
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo))
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
commits := make([]*Commit, 0, opt.PageSize)
|
commits := make([]*Commit, 0, opt.PageSize)
|
||||||
|
@ -86,3 +113,29 @@ func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*C
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits)
|
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits)
|
||||||
return commits, resp, err
|
return commits, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCommitDiff returns the commit's raw diff.
|
||||||
|
func (c *Client) GetCommitDiff(user, repo, commitID string) ([]byte, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypeDiff), nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommitPatch returns the commit's raw patch.
|
||||||
|
func (c *Client) GetCommitPatch(user, repo, commitID string) ([]byte, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypePatch), nil, nil)
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package gitea
|
package gitea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"log"
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -22,4 +23,38 @@ func TestListRepoCommits(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, l, 1)
|
assert.Len(t, l, 1)
|
||||||
assert.EqualValues(t, "Initial commit\n", l[0].RepoCommit.Message)
|
assert.EqualValues(t, "Initial commit\n", l[0].RepoCommit.Message)
|
||||||
|
assert.EqualValues(t, "gpg.error.not_signed_commit", l[0].RepoCommit.Verification.Reason)
|
||||||
|
assert.EqualValues(t, 100, l[0].Stats.Additions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCommitDiffOrPatch(t *testing.T) {
|
||||||
|
log.Println("== TestGetCommitDiffOrPatch ==")
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
repo, err := createTestRepo(t, "TestGetCommitDiffOrPatch", c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Add a new simple small commit to the repository.
|
||||||
|
fileResponse, _, err := c.CreateFile(repo.Owner.UserName, repo.Name, "NOT_A_LICENSE", CreateFileOptions{
|
||||||
|
Content: base64.StdEncoding.EncodeToString([]byte("But is it?\n")),
|
||||||
|
FileOptions: FileOptions{
|
||||||
|
Message: "Ensure people know it's not a license!",
|
||||||
|
Committer: Identity{
|
||||||
|
Name: "Sup3rCookie",
|
||||||
|
Email: "Sup3rCookie@example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Test the diff output.
|
||||||
|
diffOutput, _, err := c.GetCommitDiff(repo.Owner.UserName, repo.Name, fileResponse.Commit.SHA)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, "diff --git a/NOT_A_LICENSE b/NOT_A_LICENSE\nnew file mode 100644\nindex 0000000..f27a20a\n--- /dev/null\n+++ b/NOT_A_LICENSE\n@@ -0,0 +1 @@\n+But is it?\n", string(diffOutput))
|
||||||
|
|
||||||
|
// Test the patch output.
|
||||||
|
patchOutput, _, err := c.GetCommitPatch(repo.Owner.UserName, repo.Name, fileResponse.Commit.SHA)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Use contains, because we cannot include the first part, because of dates + non-static CommitID..
|
||||||
|
assert.Contains(t, string(patchOutput), "Subject: [PATCH] Ensure people know it's not a license!\n\n---\n NOT_A_LICENSE | 1 +\n 1 file changed, 1 insertion(+)\n create mode 100644 NOT_A_LICENSE\n\ndiff --git a/NOT_A_LICENSE b/NOT_A_LICENSE\nnew file mode 100644\nindex 0000000..f27a20a\n--- /dev/null\n+++ b/NOT_A_LICENSE\n@@ -0,0 +1 @@\n+But is it?\n")
|
||||||
}
|
}
|
||||||
|
|
33
gitea/repo_compare.go
Normal file
33
gitea/repo_compare.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Compare represents a comparison between two commits.
|
||||||
|
type Compare struct {
|
||||||
|
TotalCommits int `json:"total_commits"` // Total number of commits in the comparison.
|
||||||
|
Commits []*Commit `json:"commits"` // List of commits in the comparison.
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareCommits compares two commits in a repository.
|
||||||
|
func (c *Client) CompareCommits(user, repo, prev, current string) (*Compare, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &prev, ¤t); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
basehead := fmt.Sprintf("%s...%s", prev, current)
|
||||||
|
|
||||||
|
apiResp := new(Compare)
|
||||||
|
resp, err := c.getParsedResponse(
|
||||||
|
"GET",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/compare/%s", user, repo, basehead),
|
||||||
|
nil, nil, apiResp,
|
||||||
|
)
|
||||||
|
return apiResp, resp, err
|
||||||
|
}
|
|
@ -9,6 +9,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileOptions options for all file APIs
|
// FileOptions options for all file APIs
|
||||||
|
@ -23,6 +26,8 @@ type FileOptions struct {
|
||||||
Author Identity `json:"author"`
|
Author Identity `json:"author"`
|
||||||
Committer Identity `json:"committer"`
|
Committer Identity `json:"committer"`
|
||||||
Dates CommitDateOptions `json:"dates"`
|
Dates CommitDateOptions `json:"dates"`
|
||||||
|
// Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||||
|
Signoff bool `json:"signoff"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateFileOptions options for creating files
|
// CreateFileOptions options for creating files
|
||||||
|
@ -113,25 +118,94 @@ type FileDeleteResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFile downloads a file of repository, ref can be branch/tag/commit.
|
// GetFile downloads a file of repository, ref can be branch/tag/commit.
|
||||||
// e.g.: ref -> master, tree -> macaron.go(no leading slash)
|
// it optional can resolve lfs pointers and server the file instead
|
||||||
func (c *Client) GetFile(user, repo, ref, tree string) ([]byte, *Response, error) {
|
// e.g.: ref -> master, filepath -> README.md (no leading slash)
|
||||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", user, repo, ref, tree), nil, nil)
|
func (c *Client) GetFile(owner, repo, ref, filepath string, resolveLFS ...bool) ([]byte, *Response, error) {
|
||||||
|
reader, resp, err := c.GetFileReader(owner, repo, ref, filepath, resolveLFS...)
|
||||||
|
if reader == nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
data, err2 := io.ReadAll(reader)
|
||||||
|
if err2 != nil {
|
||||||
|
return nil, resp, err2
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContents get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
|
// GetFileReader return reader for download a file of repository, ref can be branch/tag/commit.
|
||||||
|
// it optional can resolve lfs pointers and server the file instead
|
||||||
|
// e.g.: ref -> master, filepath -> README.md (no leading slash)
|
||||||
|
func (c *Client) GetFileReader(owner, repo, ref, filepath string, resolveLFS ...bool) (io.ReadCloser, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve lfs
|
||||||
|
if len(resolveLFS) != 0 && resolveLFS[0] {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/media/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// normal get
|
||||||
|
filepath = pathEscapeSegments(filepath)
|
||||||
|
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
|
||||||
|
ref = pathEscapeSegments(ref)
|
||||||
|
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", owner, repo, ref, filepath), nil, nil)
|
||||||
|
}
|
||||||
|
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContents get the metadata and contents of a file in a repository
|
||||||
// ref is optional
|
// ref is optional
|
||||||
func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) {
|
func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) {
|
||||||
|
data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
cr := new(ContentsResponse)
|
cr := new(ContentsResponse)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, ref), jsonHeader, nil, cr)
|
if json.Unmarshal(data, &cr) != nil {
|
||||||
|
return nil, resp, fmt.Errorf("expect file, got directory")
|
||||||
|
}
|
||||||
return cr, resp, err
|
return cr, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListContents gets a list of entries in a dir
|
||||||
|
// ref is optional
|
||||||
|
func (c *Client) ListContents(owner, repo, ref, filepath string) ([]*ContentsResponse, *Response, error) {
|
||||||
|
data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
crl := make([]*ContentsResponse, 0)
|
||||||
|
if json.Unmarshal(data, &crl) != nil {
|
||||||
|
return nil, resp, fmt.Errorf("expect directory, got file")
|
||||||
|
}
|
||||||
|
return crl, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) getDirOrFileContents(owner, repo, ref, filepath string) ([]byte, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
filepath = pathEscapeSegments(strings.TrimPrefix(filepath, "/"))
|
||||||
|
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), jsonHeader, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// CreateFile create a file in a repository
|
// CreateFile create a file in a repository
|
||||||
func (c *Client) CreateFile(owner, repo, filepath string, opt CreateFileOptions) (*FileResponse, *Response, error) {
|
func (c *Client) CreateFile(owner, repo, filepath string, opt CreateFileOptions) (*FileResponse, *Response, error) {
|
||||||
var err error
|
var err error
|
||||||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
|
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
filepath = pathEscapeSegments(filepath)
|
||||||
|
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -149,6 +223,11 @@ func (c *Client) UpdateFile(owner, repo, filepath string, opt UpdateFileOptions)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
filepath = pathEscapeSegments(filepath)
|
||||||
|
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -164,6 +243,10 @@ func (c *Client) DeleteFile(owner, repo, filepath string, opt DeleteFileOptions)
|
||||||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
|
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
filepath = pathEscapeSegments(filepath)
|
||||||
|
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -182,7 +265,7 @@ func (c *Client) DeleteFile(owner, repo, filepath string, opt DeleteFileOptions)
|
||||||
func (c *Client) setDefaultBranchForOldVersions(owner, repo, branch string) (string, error) {
|
func (c *Client) setDefaultBranchForOldVersions(owner, repo, branch string) (string, error) {
|
||||||
if len(branch) == 0 {
|
if len(branch) == 0 {
|
||||||
// Gitea >= 1.12.0 Use DefaultBranch on "", mimic this for older versions
|
// Gitea >= 1.12.0 Use DefaultBranch on "", mimic this for older versions
|
||||||
if c.CheckServerVersionConstraint(">=1.12.0") != nil {
|
if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil {
|
||||||
r, _, err := c.GetRepo(owner, repo)
|
r, _, err := c.GetRepo(owner, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package gitea
|
package gitea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"log"
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -20,21 +21,22 @@ func TestFileCreateUpdateGet(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, repo)
|
assert.NotNil(t, repo)
|
||||||
|
|
||||||
raw, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "master", "README.md")
|
raw, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "main", "README.md")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, "IyBDaGFuZ2VGaWxlcwoKQSB0ZXN0IFJlcG86IENoYW5nZUZpbGVz", base64.StdEncoding.EncodeToString(raw))
|
assert.EqualValues(t, "IyBDaGFuZ2VGaWxlcwoKQSB0ZXN0IFJlcG86IENoYW5nZUZpbGVz", base64.StdEncoding.EncodeToString(raw))
|
||||||
|
|
||||||
newFile, _, err := c.CreateFile(repo.Owner.UserName, repo.Name, "A", CreateFileOptions{
|
testFileName := "A+#&ä"
|
||||||
|
newFile, _, err := c.CreateFile(repo.Owner.UserName, repo.Name, testFileName, CreateFileOptions{
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "create file A",
|
Message: "create file " + testFileName,
|
||||||
},
|
},
|
||||||
Content: "ZmlsZUEK",
|
Content: "ZmlsZUEK",
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
raw, _, _ = c.GetFile(repo.Owner.UserName, repo.Name, "master", "A")
|
raw, _, _ = c.GetFile(repo.Owner.UserName, repo.Name, "main", testFileName)
|
||||||
assert.EqualValues(t, "ZmlsZUEK", base64.StdEncoding.EncodeToString(raw))
|
assert.EqualValues(t, "ZmlsZUEK", base64.StdEncoding.EncodeToString(raw))
|
||||||
|
|
||||||
updatedFile, _, err := c.UpdateFile(repo.Owner.UserName, repo.Name, "A", UpdateFileOptions{
|
updatedFile, _, err := c.UpdateFile(repo.Owner.UserName, repo.Name, testFileName, UpdateFileOptions{
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "add a new line",
|
Message: "add a new line",
|
||||||
},
|
},
|
||||||
|
@ -44,18 +46,73 @@ func TestFileCreateUpdateGet(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, updatedFile)
|
assert.NotNil(t, updatedFile)
|
||||||
|
|
||||||
file, _, err := c.GetContents(repo.Owner.UserName, repo.Name, "master", "A")
|
file, _, err := c.GetContents(repo.Owner.UserName, repo.Name, "main", testFileName)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, updatedFile.Content.SHA, file.SHA)
|
assert.EqualValues(t, updatedFile.Content.SHA, file.SHA)
|
||||||
assert.EqualValues(t, &updatedFile.Content.Content, &file.Content)
|
assert.EqualValues(t, &updatedFile.Content.Content, &file.Content)
|
||||||
|
|
||||||
_, err = c.DeleteFile(repo.Owner.UserName, repo.Name, "A", DeleteFileOptions{
|
_, err = c.DeleteFile(repo.Owner.UserName, repo.Name, testFileName, DeleteFileOptions{
|
||||||
FileOptions: FileOptions{
|
FileOptions: FileOptions{
|
||||||
Message: "Delete File A",
|
Message: "Delete File " + testFileName,
|
||||||
},
|
},
|
||||||
SHA: updatedFile.Content.SHA,
|
SHA: updatedFile.Content.SHA,
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, _, err = c.GetFile(repo.Owner.UserName, repo.Name, "master", "A")
|
_, resp, err := c.GetFile(repo.Owner.UserName, repo.Name, "main", testFileName)
|
||||||
assert.EqualValues(t, "404 Not Found", err.Error())
|
assert.Error(t, err)
|
||||||
|
assert.EqualValues(t, "The target couldn't be found.", err.Error())
|
||||||
|
assert.EqualValues(t, 404, resp.StatusCode)
|
||||||
|
|
||||||
|
licence, _, err := c.GetContents(repo.Owner.UserName, repo.Name, "", "LICENSE")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
licenceRaw, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "", "LICENSE")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
testContent := "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo="
|
||||||
|
updatedFile, _, err = c.UpdateFile(repo.Owner.UserName, repo.Name, "LICENSE", UpdateFileOptions{
|
||||||
|
FileOptions: FileOptions{
|
||||||
|
Message: "Overwrite",
|
||||||
|
BranchName: "main",
|
||||||
|
NewBranchName: "overwrite-a+/&licence",
|
||||||
|
},
|
||||||
|
SHA: licence.SHA,
|
||||||
|
Content: testContent,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, updatedFile)
|
||||||
|
licenceRawNew, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "overwrite-a+/&licence", "LICENSE")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, licence)
|
||||||
|
assert.False(t, bytes.Equal(licenceRaw, licenceRawNew))
|
||||||
|
assert.EqualValues(t, testContent, base64.StdEncoding.EncodeToString(licenceRawNew))
|
||||||
|
|
||||||
|
// ListContents in root dir of default branch
|
||||||
|
dir, resp, err := c.ListContents(repo.Owner.UserName, repo.Name, "", "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, dir, 3)
|
||||||
|
assert.NotNil(t, resp)
|
||||||
|
|
||||||
|
// ListContents in not existing dir of default branch
|
||||||
|
_, resp, err = c.ListContents(repo.Owner.UserName, repo.Name, "", "/hehe/")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualValues(t, 404, resp.StatusCode)
|
||||||
|
// ListContents in root dir of not existing branch
|
||||||
|
_, resp, err = c.ListContents(repo.Owner.UserName, repo.Name, "no-ref-at-all", "")
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualValues(t, 404, resp.StatusCode)
|
||||||
|
|
||||||
|
// ListContents try to get file as dir
|
||||||
|
dir, resp, err = c.ListContents(repo.Owner.UserName, repo.Name, "", "LICENSE")
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.EqualValues(t, "expect directory, got file", err.Error())
|
||||||
|
}
|
||||||
|
assert.Nil(t, dir)
|
||||||
|
assert.EqualValues(t, 200, resp.StatusCode)
|
||||||
|
|
||||||
|
// GetContents try to get dir as file
|
||||||
|
file, resp, err = c.GetContents(repo.Owner.UserName, repo.Name, "", "")
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.EqualValues(t, "expect file, got directory", err.Error())
|
||||||
|
}
|
||||||
|
assert.Nil(t, file)
|
||||||
|
assert.EqualValues(t, 200, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,9 @@ func (opt *ListDeployKeysOptions) QueryEncode() string {
|
||||||
|
|
||||||
// ListDeployKeys list all the deploy keys of one repository
|
// ListDeployKeys list all the deploy keys of one repository
|
||||||
func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([]*DeployKey, *Response, error) {
|
func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([]*DeployKey, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/keys", user, repo))
|
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/keys", user, repo))
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
link.RawQuery = opt.QueryEncode()
|
link.RawQuery = opt.QueryEncode()
|
||||||
|
@ -56,6 +59,9 @@ func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([
|
||||||
|
|
||||||
// GetDeployKey get one deploy key with key id
|
// GetDeployKey get one deploy key with key id
|
||||||
func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Response, error) {
|
func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
key := new(DeployKey)
|
key := new(DeployKey)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key)
|
||||||
return key, resp, err
|
return key, resp, err
|
||||||
|
@ -63,6 +69,9 @@ func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Resp
|
||||||
|
|
||||||
// CreateDeployKey options when create one deploy key
|
// CreateDeployKey options when create one deploy key
|
||||||
func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, *Response, error) {
|
func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -74,6 +83,9 @@ func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*Deplo
|
||||||
|
|
||||||
// DeleteDeployKey delete deploy key with key id
|
// DeleteDeployKey delete deploy key with key id
|
||||||
func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) (*Response, error) {
|
func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,14 @@ type GitServiceType string
|
||||||
const (
|
const (
|
||||||
// GitServicePlain represents a plain git service
|
// GitServicePlain represents a plain git service
|
||||||
GitServicePlain GitServiceType = "git"
|
GitServicePlain GitServiceType = "git"
|
||||||
//GitServiceGithub represents github.com
|
// GitServiceGithub represents github.com
|
||||||
GitServiceGithub GitServiceType = "github"
|
GitServiceGithub GitServiceType = "github"
|
||||||
// GitServiceGitlab represents a gitlab service
|
// GitServiceGitlab represents a gitlab service
|
||||||
GitServiceGitlab GitServiceType = "gitlab"
|
GitServiceGitlab GitServiceType = "gitlab"
|
||||||
|
// GitServiceGitea represents a gitea service
|
||||||
// Not supported jet
|
GitServiceGitea GitServiceType = "gitea"
|
||||||
// // GitServiceGitea represents a gitea service
|
// GitServiceGogs represents a gogs service
|
||||||
// GitServiceGitea GitServiceType = "gitea"
|
GitServiceGogs GitServiceType = "gogs"
|
||||||
// // GitServiceGogs represents a gogs service
|
|
||||||
// GitServiceGogs GitServiceType = "gogs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MigrateRepoOption options for migrating a repository from an external service
|
// MigrateRepoOption options for migrating a repository from an external service
|
||||||
|
@ -33,25 +31,28 @@ type MigrateRepoOption struct {
|
||||||
RepoName string `json:"repo_name"`
|
RepoName string `json:"repo_name"`
|
||||||
RepoOwner string `json:"repo_owner"`
|
RepoOwner string `json:"repo_owner"`
|
||||||
// deprecated use RepoOwner
|
// deprecated use RepoOwner
|
||||||
RepoOwnerID int64 `json:"uid"`
|
RepoOwnerID int64 `json:"uid"`
|
||||||
CloneAddr string `json:"clone_addr"`
|
CloneAddr string `json:"clone_addr"`
|
||||||
Service GitServiceType `json:"service"`
|
Service GitServiceType `json:"service"`
|
||||||
AuthUsername string `json:"auth_username"`
|
AuthUsername string `json:"auth_username"`
|
||||||
AuthPassword string `json:"auth_password"`
|
AuthPassword string `json:"auth_password"`
|
||||||
AuthToken string `json:"auth_token"`
|
AuthToken string `json:"auth_token"`
|
||||||
Mirror bool `json:"mirror"`
|
Mirror bool `json:"mirror"`
|
||||||
Private bool `json:"private"`
|
Private bool `json:"private"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Wiki bool `json:"wiki"`
|
Wiki bool `json:"wiki"`
|
||||||
Milestones bool `json:"milestones"`
|
Milestones bool `json:"milestones"`
|
||||||
Labels bool `json:"labels"`
|
Labels bool `json:"labels"`
|
||||||
Issues bool `json:"issues"`
|
Issues bool `json:"issues"`
|
||||||
PullRequests bool `json:"pull_requests"`
|
PullRequests bool `json:"pull_requests"`
|
||||||
Releases bool `json:"releases"`
|
Releases bool `json:"releases"`
|
||||||
|
MirrorInterval string `json:"mirror_interval"`
|
||||||
|
LFS bool `json:"lfs"`
|
||||||
|
LFSEndpoint string `json:"lfs_endpoint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the MigrateRepoOption struct
|
// Validate the MigrateRepoOption struct
|
||||||
func (opt *MigrateRepoOption) Validate() error {
|
func (opt *MigrateRepoOption) Validate(c *Client) error {
|
||||||
// check user options
|
// check user options
|
||||||
if len(opt.CloneAddr) == 0 {
|
if len(opt.CloneAddr) == 0 {
|
||||||
return fmt.Errorf("CloneAddr required")
|
return fmt.Errorf("CloneAddr required")
|
||||||
|
@ -61,13 +62,29 @@ func (opt *MigrateRepoOption) Validate() error {
|
||||||
} else if len(opt.RepoName) > 100 {
|
} else if len(opt.RepoName) > 100 {
|
||||||
return fmt.Errorf("RepoName to long")
|
return fmt.Errorf("RepoName to long")
|
||||||
}
|
}
|
||||||
if len(opt.Description) > 255 {
|
if len(opt.Description) > 2048 {
|
||||||
return fmt.Errorf("Description to long")
|
return fmt.Errorf("Description to long")
|
||||||
}
|
}
|
||||||
switch opt.Service {
|
switch opt.Service {
|
||||||
case GitServiceGithub:
|
case GitServiceGithub:
|
||||||
if len(opt.AuthToken) == 0 {
|
if len(opt.AuthToken) == 0 {
|
||||||
return fmt.Errorf("github require token authentication")
|
return fmt.Errorf("github requires token authentication")
|
||||||
|
}
|
||||||
|
case GitServiceGitlab, GitServiceGitea:
|
||||||
|
if len(opt.AuthToken) == 0 {
|
||||||
|
return fmt.Errorf("%s requires token authentication", opt.Service)
|
||||||
|
}
|
||||||
|
// Gitlab is supported since 1.12.0 but api cant handle it until 1.13.0
|
||||||
|
// https://github.com/go-gitea/gitea/pull/12672
|
||||||
|
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
||||||
|
return fmt.Errorf("migrate from service %s need gitea >= 1.13.0", opt.Service)
|
||||||
|
}
|
||||||
|
case GitServiceGogs:
|
||||||
|
if len(opt.AuthToken) == 0 {
|
||||||
|
return fmt.Errorf("gogs requires token authentication")
|
||||||
|
}
|
||||||
|
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
|
||||||
|
return fmt.Errorf("migrate from service gogs need gitea >= 1.14.0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -78,11 +95,11 @@ func (opt *MigrateRepoOption) Validate() error {
|
||||||
// To migrate a repository for a organization, the authenticated user must be a
|
// To migrate a repository for a organization, the authenticated user must be a
|
||||||
// owner of the specified organization.
|
// owner of the specified organization.
|
||||||
func (c *Client) MigrateRepo(opt MigrateRepoOption) (*Repository, *Response, error) {
|
func (c *Client) MigrateRepo(opt MigrateRepoOption) (*Repository, *Response, error) {
|
||||||
if err := opt.Validate(); err != nil {
|
if err := opt.Validate(c); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||||
if len(opt.AuthToken) != 0 {
|
if len(opt.AuthToken) != 0 {
|
||||||
// gitea <= 1.12 dont understand AuthToken
|
// gitea <= 1.12 dont understand AuthToken
|
||||||
opt.AuthUsername = opt.AuthToken
|
opt.AuthUsername = opt.AuthToken
|
||||||
|
|
45
gitea/repo_mirror.go
Normal file
45
gitea/repo_mirror.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreatePushMirrorOption struct {
|
||||||
|
Interval string `json:"interval"`
|
||||||
|
RemoteAddress string `json:"remote_address"`
|
||||||
|
RemotePassword string `json:"remote_password"`
|
||||||
|
RemoteUsername string `json:"remote_username"`
|
||||||
|
SyncONCommit bool `json:"sync_on_commit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushMirrorResponse returns a git push mirror
|
||||||
|
type PushMirrorResponse struct {
|
||||||
|
Created string `json:"created"`
|
||||||
|
Interval string `json:"interval"`
|
||||||
|
LastError string `json:"last_error"`
|
||||||
|
LastUpdate string `json:"last_update"`
|
||||||
|
RemoteAddress string `json:"remote_address"`
|
||||||
|
RemoteName string `json:"remote_name"`
|
||||||
|
RepoName string `json:"repo_name"`
|
||||||
|
SyncONCommit bool `json:"sync_on_commit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushMirrors add a push mirror to the repository
|
||||||
|
func (c *Client) PushMirrors(user, repo string, opt CreatePushMirrorOption) (*PushMirrorResponse, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
pm := new(PushMirrorResponse)
|
||||||
|
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/push_mirrors", user, repo), jsonHeader, bytes.NewReader(body), &pm)
|
||||||
|
return pm, resp, err
|
||||||
|
}
|
|
@ -27,7 +27,11 @@ type GitObject struct {
|
||||||
|
|
||||||
// GetRepoRef get one ref's information of one repository
|
// GetRepoRef get one ref's information of one repository
|
||||||
func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, error) {
|
func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
ref = strings.TrimPrefix(ref, "refs/")
|
ref = strings.TrimPrefix(ref, "refs/")
|
||||||
|
ref = pathEscapeSegments(ref)
|
||||||
r := new(Reference)
|
r := new(Reference)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil, &r)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil, &r)
|
||||||
if _, ok := err.(*json.UnmarshalTypeError); ok {
|
if _, ok := err.(*json.UnmarshalTypeError); ok {
|
||||||
|
@ -42,7 +46,12 @@ func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, erro
|
||||||
|
|
||||||
// GetRepoRefs get list of ref's information of one repository
|
// GetRepoRefs get list of ref's information of one repository
|
||||||
func (c *Client) GetRepoRefs(user, repo, ref string) ([]*Reference, *Response, error) {
|
func (c *Client) GetRepoRefs(user, repo, ref string) ([]*Reference, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
ref = strings.TrimPrefix(ref, "refs/")
|
ref = strings.TrimPrefix(ref, "refs/")
|
||||||
|
ref = pathEscapeSegments(ref)
|
||||||
|
|
||||||
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil)
|
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, resp, err
|
return nil, resp, err
|
||||||
|
|
96
gitea/repo_stars.go
Normal file
96
gitea/repo_stars.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListStargazersOptions options for listing a repository's stargazers
|
||||||
|
type ListStargazersOptions struct {
|
||||||
|
ListOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRepoStargazers list a repository's stargazers
|
||||||
|
func (c *Client) ListRepoStargazers(user, repo string, opt ListStargazersOptions) ([]*User, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
opt.setDefaults()
|
||||||
|
stargazers := make([]*User, 0, opt.PageSize)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/stargazers?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &stargazers)
|
||||||
|
return stargazers, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStarredRepos returns the repos that the given user has starred
|
||||||
|
func (c *Client) GetStarredRepos(user string) ([]*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
repos := make([]*Repository, 0, 10)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/starred", user), jsonHeader, nil, &repos)
|
||||||
|
return repos, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMyStarredRepos returns the repos that the authenticated user has starred
|
||||||
|
func (c *Client) GetMyStarredRepos() ([]*Repository, *Response, error) {
|
||||||
|
repos := make([]*Repository, 0, 10)
|
||||||
|
resp, err := c.getParsedResponse("GET", "/user/starred", jsonHeader, nil, &repos)
|
||||||
|
return repos, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRepoStarring returns whether the authenticated user has starred the repo or not
|
||||||
|
func (c *Client) IsRepoStarring(user, repo string) (bool, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("GET", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
|
||||||
|
if resp != nil {
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return false, resp, nil
|
||||||
|
case http.StatusNoContent:
|
||||||
|
return true, resp, nil
|
||||||
|
default:
|
||||||
|
return false, resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StarRepo star specified repo as the authenticated user
|
||||||
|
func (c *Client) StarRepo(user, repo string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
|
||||||
|
if resp != nil {
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusNoContent:
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnStarRepo remove star to specified repo as the authenticated user
|
||||||
|
func (c *Client) UnStarRepo(user, repo string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
|
||||||
|
if resp != nil {
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusNoContent:
|
||||||
|
return resp, nil
|
||||||
|
default:
|
||||||
|
return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
72
gitea/repo_stars_test.go
Normal file
72
gitea/repo_stars_test.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoStaring(t *testing.T) {
|
||||||
|
log.Println("== TestRepoStaring ==")
|
||||||
|
|
||||||
|
// init user2
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
user1, _, err := c.GetMyUserInfo()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
userA := createTestUser(t, "stargazer_a", c)
|
||||||
|
userB := createTestUser(t, "stargazer_b", c)
|
||||||
|
|
||||||
|
repo, _ := createTestRepo(t, "toStar", c)
|
||||||
|
if repo == nil {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
is, _, err := c.IsRepoStarring(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.False(t, is)
|
||||||
|
|
||||||
|
repos, _, err := c.GetMyStarredRepos()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, repos, 0)
|
||||||
|
|
||||||
|
_, err = c.StarRepo(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
c.SetSudo(userA.UserName)
|
||||||
|
_, err = c.StarRepo(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
c.SetSudo(userB.UserName)
|
||||||
|
_, err = c.StarRepo(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
users, _, err := c.ListRepoStargazers(repo.Owner.UserName, repo.Name, ListStargazersOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 3)
|
||||||
|
assert.EqualValues(t, user1.UserName, users[0].UserName)
|
||||||
|
|
||||||
|
_, err = c.UnStarRepo(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = c.UnStarRepo(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
c.SetSudo("")
|
||||||
|
|
||||||
|
users, _, err = c.ListRepoStargazers(repo.Owner.UserName, repo.Name, ListStargazersOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, users, 2)
|
||||||
|
|
||||||
|
repos, _, err = c.GetMyStarredRepos()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, repos, 1)
|
||||||
|
|
||||||
|
reposNew, _, err := c.GetStarredRepos(user1.UserName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, repos, 1)
|
||||||
|
assert.EqualValues(t, repos, reposNew)
|
||||||
|
}
|
|
@ -5,18 +5,39 @@
|
||||||
package gitea
|
package gitea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tag represents a repository tag
|
// Tag represents a repository tag
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Message string `json:"message"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Commit *CommitMeta `json:"commit"`
|
Commit *CommitMeta `json:"commit"`
|
||||||
ZipballURL string `json:"zipball_url"`
|
ZipballURL string `json:"zipball_url"`
|
||||||
TarballURL string `json:"tarball_url"`
|
TarballURL string `json:"tarball_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnnotatedTag represents an annotated tag
|
||||||
|
type AnnotatedTag struct {
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
SHA string `json:"sha"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Tagger *CommitUser `json:"tagger"`
|
||||||
|
Object *AnnotatedTagObject `json:"object"`
|
||||||
|
Verification *PayloadCommitVerification `json:"verification"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnotatedTagObject contains meta information of the tag object
|
||||||
|
type AnnotatedTagObject struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
SHA string `json:"sha"`
|
||||||
|
}
|
||||||
|
|
||||||
// ListRepoTagsOptions options for listing a repository's tags
|
// ListRepoTagsOptions options for listing a repository's tags
|
||||||
type ListRepoTagsOptions struct {
|
type ListRepoTagsOptions struct {
|
||||||
ListOptions
|
ListOptions
|
||||||
|
@ -24,8 +45,86 @@ type ListRepoTagsOptions struct {
|
||||||
|
|
||||||
// ListRepoTags list all the branches of one repository
|
// ListRepoTags list all the branches of one repository
|
||||||
func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Tag, *Response, error) {
|
func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Tag, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
tags := make([]*Tag, 0, opt.PageSize)
|
tags := make([]*Tag, 0, opt.PageSize)
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &tags)
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &tags)
|
||||||
return tags, resp, err
|
return tags, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTag get the tag of a repository
|
||||||
|
func (c *Client) GetTag(user, repo, tag string) (*Tag, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
t := new(Tag)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag), nil, nil, &t)
|
||||||
|
return t, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAnnotatedTag get the tag object of an annotated tag (not lightweight tags) of a repository
|
||||||
|
func (c *Client) GetAnnotatedTag(user, repo, sha string) (*AnnotatedTag, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
t := new(AnnotatedTag)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/tags/%s", user, repo, sha), nil, nil, &t)
|
||||||
|
return t, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTagOption options when creating a tag
|
||||||
|
type CreateTagOption struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Target string `json:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates CreateTagOption
|
||||||
|
func (opt CreateTagOption) Validate() error {
|
||||||
|
if len(opt.TagName) == 0 {
|
||||||
|
return fmt.Errorf("TagName is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTag create a new git tag in a repository
|
||||||
|
func (c *Client) CreateTag(user, repo string, opt CreateTagOption) (*Tag, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := opt.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
t := new(Tag)
|
||||||
|
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/tags", user, repo), jsonHeader, bytes.NewReader(body), &t)
|
||||||
|
return t, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTag deletes a tag from a repository, if no release refers to it
|
||||||
|
func (c *Client) DeleteTag(user, repo, tag string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("DELETE",
|
||||||
|
fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag),
|
||||||
|
nil, nil)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
58
gitea/repo_tag_test.go
Normal file
58
gitea/repo_tag_test.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTags(t *testing.T) {
|
||||||
|
log.Println("== TestTags ==")
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
repo, _ := createTestRepo(t, "TestTags", c)
|
||||||
|
|
||||||
|
// Create Tags
|
||||||
|
cTagMSG := "A tag message.\n\n:)"
|
||||||
|
cTag, resp, err := c.CreateTag(repo.Owner.UserName, repo.Name, CreateTagOption{
|
||||||
|
TagName: "tag1",
|
||||||
|
Message: cTagMSG,
|
||||||
|
Target: "main",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 201, resp.StatusCode)
|
||||||
|
assert.EqualValues(t, cTagMSG, cTag.Message)
|
||||||
|
assert.EqualValues(t, fmt.Sprintf("%s/%s/TestTags/archive/tag1.zip", c.url, c.username), cTag.ZipballURL)
|
||||||
|
|
||||||
|
tags, _, err := c.ListRepoTags(repo.Owner.UserName, repo.Name, ListRepoTagsOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, tags, 1)
|
||||||
|
assert.EqualValues(t, cTag, tags[0])
|
||||||
|
|
||||||
|
// get tag
|
||||||
|
gTag, _, err := c.GetTag(repo.Owner.UserName, repo.Name, cTag.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, cTag, gTag)
|
||||||
|
|
||||||
|
aTag, _, err := c.GetAnnotatedTag(repo.Owner.UserName, repo.Name, cTag.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, cTag.Name, aTag.Tag)
|
||||||
|
assert.EqualValues(t, cTag.ID, aTag.SHA)
|
||||||
|
assert.EqualValues(t, fmt.Sprintf("%s/api/v1/repos/%s/TestTags/git/tags/%s", c.url, c.username, cTag.ID), aTag.URL)
|
||||||
|
assert.EqualValues(t, cTag.Message+"\n", aTag.Message)
|
||||||
|
assert.EqualValues(t, "commit", aTag.Object.Type)
|
||||||
|
|
||||||
|
// DeleteReleaseTag
|
||||||
|
resp, err = c.DeleteTag(repo.Owner.UserName, repo.Name, "tag1")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 204, resp.StatusCode)
|
||||||
|
tags, _, err = c.ListRepoTags(repo.Owner.UserName, repo.Name, ListRepoTagsOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, tags, 0)
|
||||||
|
}
|
65
gitea/repo_team.go
Normal file
65
gitea/repo_team.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetRepoTeams return teams from a repository
|
||||||
|
func (c *Client) GetRepoTeams(user, repo string) ([]*Team, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
teams := make([]*Team, 0, 5)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams", user, repo), nil, nil, &teams)
|
||||||
|
return teams, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRepoTeam add a team to a repository
|
||||||
|
func (c *Client) AddRepoTeam(user, repo, team string) (*Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &team); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRepoTeam delete a team from a repository
|
||||||
|
func (c *Client) RemoveRepoTeam(user, repo, team string) (*Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &team); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckRepoTeam check if team is assigned to repo by name and return it.
|
||||||
|
// If not assigned, it will return nil.
|
||||||
|
func (c *Client) CheckRepoTeam(user, repo, team string) (*Team, *Response, error) {
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &team); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
t := new(Team)
|
||||||
|
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil, &t)
|
||||||
|
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||||
|
// if not found it's not an error, it indicates it's not assigned
|
||||||
|
return nil, resp, nil
|
||||||
|
}
|
||||||
|
return t, resp, err
|
||||||
|
}
|
74
gitea/repo_team_test.go
Normal file
74
gitea/repo_team_test.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoTeamManagement(t *testing.T) {
|
||||||
|
log.Println("== TestRepoTeamManagement ==")
|
||||||
|
c := newTestClient()
|
||||||
|
|
||||||
|
// prepare for test
|
||||||
|
clean, repo, err := createTestOrgRepo(t, c, "RepoTeamManagement")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer clean()
|
||||||
|
if _, err = createTestOrgTeams(t, c, repo.Owner.UserName, "Admins", AccessModeAdmin, []RepoUnitType{RepoUnitCode, RepoUnitIssues, RepoUnitPulls, RepoUnitReleases}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = createTestOrgTeams(t, c, repo.Owner.UserName, "CodeManager", AccessModeWrite, []RepoUnitType{RepoUnitCode}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = createTestOrgTeams(t, c, repo.Owner.UserName, "IssueManager", AccessModeWrite, []RepoUnitType{RepoUnitIssues, RepoUnitPulls}); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// test
|
||||||
|
teams, _, err := c.GetRepoTeams(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if !assert.Len(t, teams, 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.EqualValues(t, AccessModeOwner, teams[0].Permission)
|
||||||
|
|
||||||
|
team, _, err := c.CheckRepoTeam(repo.Owner.UserName, repo.Name, "Admins")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, team)
|
||||||
|
|
||||||
|
resp, err := c.AddRepoTeam(repo.Owner.UserName, repo.Name, "Admins")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 204, resp.StatusCode)
|
||||||
|
resp, err = c.AddRepoTeam(repo.Owner.UserName, repo.Name, "CodeManager")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 204, resp.StatusCode)
|
||||||
|
resp, err = c.AddRepoTeam(repo.Owner.UserName, repo.Name, "IssueManager")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 204, resp.StatusCode)
|
||||||
|
|
||||||
|
team, _, err = c.CheckRepoTeam(repo.Owner.UserName, repo.Name, "Admins")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.NotNil(t, team) {
|
||||||
|
assert.EqualValues(t, "Admins", team.Name)
|
||||||
|
assert.EqualValues(t, AccessModeAdmin, team.Permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
teams, _, err = c.GetRepoTeams(repo.Owner.UserName, repo.Name)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, teams, 4)
|
||||||
|
|
||||||
|
resp, err = c.RemoveRepoTeam(repo.Owner.UserName, repo.Name, "IssueManager")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 204, resp.StatusCode)
|
||||||
|
|
||||||
|
team, _, err = c.CheckRepoTeam(repo.Owner.UserName, repo.Name, "IssueManager")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, team)
|
||||||
|
}
|
65
gitea/repo_template.go
Normal file
65
gitea/repo_template.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateRepoFromTemplateOption options when creating repository using a template
|
||||||
|
type CreateRepoFromTemplateOption struct {
|
||||||
|
// Owner is the organization or person who will own the new repository
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
// Name of the repository to create
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Description of the repository to create
|
||||||
|
Description string `json:"description"`
|
||||||
|
// Private is whether the repository is private
|
||||||
|
Private bool `json:"private"`
|
||||||
|
// GitContent include git content of default branch in template repo
|
||||||
|
GitContent bool `json:"git_content"`
|
||||||
|
// Topics include topics of template repo
|
||||||
|
Topics bool `json:"topics"`
|
||||||
|
// GitHooks include git hooks of template repo
|
||||||
|
GitHooks bool `json:"git_hooks"`
|
||||||
|
// Webhooks include webhooks of template repo
|
||||||
|
Webhooks bool `json:"webhooks"`
|
||||||
|
// Avatar include avatar of the template repo
|
||||||
|
Avatar bool `json:"avatar"`
|
||||||
|
// Labels include labels of template repo
|
||||||
|
Labels bool `json:"labels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates CreateRepoFromTemplateOption
|
||||||
|
func (opt CreateRepoFromTemplateOption) Validate() error {
|
||||||
|
if len(opt.Owner) == 0 {
|
||||||
|
return fmt.Errorf("field Owner is required")
|
||||||
|
}
|
||||||
|
if len(opt.Name) == 0 {
|
||||||
|
return fmt.Errorf("field Name is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateRepoFromTemplate create a repository using a template
|
||||||
|
func (c *Client) CreateRepoFromTemplate(templateOwner, templateRepo string, opt CreateRepoFromTemplateOption) (*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&templateOwner, &templateRepo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := opt.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(&opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := new(Repository)
|
||||||
|
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/generate", templateOwner, templateRepo), jsonHeader, bytes.NewReader(body), &repo)
|
||||||
|
return repo, resp, err
|
||||||
|
}
|
44
gitea/repo_template_test.go
Normal file
44
gitea/repo_template_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoFromTemplate(t *testing.T) {
|
||||||
|
log.Println("== TestRepoFromTemplate ==")
|
||||||
|
c := newTestClient()
|
||||||
|
repo, err := createTestRepo(t, "TemplateRepo", c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
repo, _, err = c.EditRepo(repo.Owner.UserName, repo.Name, EditRepoOption{Template: OptionalBool(true)})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = c.SetRepoTopics(repo.Owner.UserName, repo.Name, []string{"abc", "def", "ghi"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
newRepo, resp, err := c.CreateRepoFromTemplate(repo.Owner.UserName, repo.Name, CreateRepoFromTemplateOption{
|
||||||
|
Owner: repo.Owner.UserName,
|
||||||
|
Name: "repoFromTemplate",
|
||||||
|
Description: "",
|
||||||
|
Topics: true,
|
||||||
|
Labels: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.EqualValues(t, 201, resp.StatusCode)
|
||||||
|
assert.False(t, newRepo.Template)
|
||||||
|
|
||||||
|
labels, _, err := c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, labels, 7)
|
||||||
|
|
||||||
|
topics, _, _ := c.ListRepoTopics(repo.Owner.UserName, repo.Name, ListRepoTopicsOptions{})
|
||||||
|
assert.EqualValues(t, []string{"abc", "def", "ghi"}, topics)
|
||||||
|
|
||||||
|
_, err = c.DeleteRepo(repo.Owner.UserName, "repoFromTemplate")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
|
@ -5,6 +5,8 @@
|
||||||
package gitea
|
package gitea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -18,7 +20,7 @@ func TestCreateRepo(t *testing.T) {
|
||||||
user, _, err := c.GetMyUserInfo()
|
user, _, err := c.GetMyUserInfo()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
var repoName = "test1"
|
repoName := "test1"
|
||||||
_, _, err = c.GetRepo(user.UserName, repoName)
|
_, _, err = c.GetRepo(user.UserName, repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
repo, _, err := c.CreateRepo(CreateRepoOption{
|
repo, _, err := c.CreateRepo(CreateRepoOption{
|
||||||
|
@ -55,18 +57,20 @@ func TestRepoMigrateAndLanguages(t *testing.T) {
|
||||||
repoG, _, err := c.GetRepo(repoM.Owner.UserName, repoM.Name)
|
repoG, _, err := c.GetRepo(repoM.Owner.UserName, repoM.Name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, repoM.ID, repoG.ID)
|
assert.EqualValues(t, repoM.ID, repoG.ID)
|
||||||
assert.EqualValues(t, "master", repoG.DefaultBranch)
|
assert.EqualValues(t, "main", repoG.DefaultBranch)
|
||||||
assert.True(t, repoG.Mirror)
|
assert.True(t, repoG.Mirror)
|
||||||
assert.False(t, repoG.Empty)
|
assert.False(t, repoG.Empty)
|
||||||
assert.EqualValues(t, 1, repoG.Watchers)
|
assert.EqualValues(t, 1, repoG.Watchers)
|
||||||
|
var zeroTime time.Time
|
||||||
|
assert.NotEqual(t, zeroTime, repoG.MirrorUpdated)
|
||||||
|
|
||||||
log.Println("== TestRepoLanguages ==")
|
log.Println("== TestRepoLanguages ==")
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second * 2)
|
||||||
lang, _, err := c.GetRepoLanguages(repoM.Owner.UserName, repoM.Name)
|
lang, _, err := c.GetRepoLanguages(repoM.Owner.UserName, repoM.Name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, lang, 2)
|
assert.Len(t, lang, 2)
|
||||||
assert.True(t, 217441 < lang["Go"])
|
assert.True(t, 217441 < lang["Go"])
|
||||||
assert.EqualValues(t, 3578, lang["Makefile"])
|
assert.True(t, 3614 < lang["Makefile"] && 6000 > lang["Makefile"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchRepo(t *testing.T) {
|
func TestSearchRepo(t *testing.T) {
|
||||||
|
@ -134,20 +138,54 @@ func TestGetArchive(t *testing.T) {
|
||||||
c := newTestClient()
|
c := newTestClient()
|
||||||
repo, _ := createTestRepo(t, "ToDownload", c)
|
repo, _ := createTestRepo(t, "ToDownload", c)
|
||||||
time.Sleep(time.Second / 2)
|
time.Sleep(time.Second / 2)
|
||||||
archive, _, err := c.GetArchive(repo.Owner.UserName, repo.Name, "master", ZipArchive)
|
archive, _, err := c.GetArchive(repo.Owner.UserName, repo.Name, "main", ZipArchive)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, len(archive) > 1500 && len(archive) < 1700)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetArchiveReader(t *testing.T) {
|
||||||
|
log.Println("== TestGetArchiveReader ==")
|
||||||
|
c := newTestClient()
|
||||||
|
repo, _ := createTestRepo(t, "ToDownload", c)
|
||||||
|
time.Sleep(time.Second / 2)
|
||||||
|
r, _, err := c.GetArchiveReader(repo.Owner.UserName, repo.Name, "main", ZipArchive)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
archive := bytes.NewBuffer(nil)
|
||||||
|
nBytes, err := io.Copy(archive, r)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, nBytes > 1500)
|
||||||
|
assert.EqualValues(t, nBytes, len(archive.Bytes()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRepoByID(t *testing.T) {
|
||||||
|
log.Println("== TestGetRepoByID ==")
|
||||||
|
c := newTestClient()
|
||||||
|
testrepo, _ := createTestRepo(t, "TestGetRepoByID", c)
|
||||||
|
|
||||||
|
repo, _, err := c.GetRepoByID(testrepo.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, repo)
|
||||||
|
assert.EqualValues(t, testrepo.ID, repo.ID)
|
||||||
|
|
||||||
|
_, err = c.DeleteRepo(repo.Owner.UserName, repo.Name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, 1620, len(archive))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// standard func to create a init repo for test routines
|
// standard func to create a init repo for test routines
|
||||||
func createTestRepo(t *testing.T, name string, c *Client) (*Repository, error) {
|
func createTestRepo(t *testing.T, name string, c *Client) (*Repository, error) {
|
||||||
user, _, uErr := c.GetMyUserInfo()
|
user, _, uErr := c.GetMyUserInfo()
|
||||||
assert.NoError(t, uErr)
|
assert.NoError(t, uErr)
|
||||||
_, _, err := c.GetRepo(user.UserName, name)
|
repo, _, err := c.GetRepo(user.UserName, name)
|
||||||
if err == nil {
|
// We need to check that the received repo is not a
|
||||||
|
// redirected one, it could be the case that gitea redirect us
|
||||||
|
// to a new repo(because it e.g. was transferred or renamed).
|
||||||
|
if err == nil && repo.Owner.UserName == user.UserName {
|
||||||
_, _ = c.DeleteRepo(user.UserName, name)
|
_, _ = c.DeleteRepo(user.UserName, name)
|
||||||
}
|
}
|
||||||
repo, _, err := c.CreateRepo(CreateRepoOption{
|
|
||||||
|
repo, _, err = c.CreateRepo(CreateRepoOption{
|
||||||
Name: name,
|
Name: name,
|
||||||
Description: "A test Repo: " + name,
|
Description: "A test Repo: " + name,
|
||||||
AutoInit: true,
|
AutoInit: true,
|
||||||
|
|
|
@ -22,6 +22,9 @@ type topicsList struct {
|
||||||
|
|
||||||
// ListRepoTopics list all repository's topics
|
// ListRepoTopics list all repository's topics
|
||||||
func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([]string, *Response, error) {
|
func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([]string, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
opt.setDefaults()
|
opt.setDefaults()
|
||||||
|
|
||||||
list := new(topicsList)
|
list := new(topicsList)
|
||||||
|
@ -34,9 +37,10 @@ func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([
|
||||||
|
|
||||||
// SetRepoTopics replaces the list of repo's topics
|
// SetRepoTopics replaces the list of repo's topics
|
||||||
func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, error) {
|
func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
l := topicsList{Topics: list}
|
l := topicsList{Topics: list}
|
||||||
|
|
||||||
body, err := json.Marshal(&l)
|
body, err := json.Marshal(&l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -47,12 +51,18 @@ func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, err
|
||||||
|
|
||||||
// AddRepoTopic adds a topic to a repo's topics list
|
// AddRepoTopic adds a topic to a repo's topics list
|
||||||
func (c *Client) AddRepoTopic(user, repo, topic string) (*Response, error) {
|
func (c *Client) AddRepoTopic(user, repo, topic string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
|
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRepoTopic deletes a topic from repo's topics list
|
// DeleteRepoTopic deletes a topic from repo's topics list
|
||||||
func (c *Client) DeleteRepoTopic(user, repo, topic string) (*Response, error) {
|
func (c *Client) DeleteRepoTopic(user, repo, topic string) (*Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
|
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,10 @@ type TransferRepoOption struct {
|
||||||
|
|
||||||
// TransferRepo transfers the ownership of a repository
|
// TransferRepo transfers the ownership of a repository
|
||||||
func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*Repository, *Response, error) {
|
func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*Repository, *Response, error) {
|
||||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(&opt)
|
body, err := json.Marshal(&opt)
|
||||||
|
@ -31,3 +34,29 @@ func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer", owner, reponame), jsonHeader, bytes.NewReader(body), repo)
|
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer", owner, reponame), jsonHeader, bytes.NewReader(body), repo)
|
||||||
return repo, resp, err
|
return repo, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AcceptRepoTransfer accepts a repo transfer.
|
||||||
|
func (c *Client) AcceptRepoTransfer(owner, reponame string) (*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
repo := new(Repository)
|
||||||
|
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/accept", owner, reponame), jsonHeader, nil, repo)
|
||||||
|
return repo, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RejectRepoTransfer rejects a repo transfer.
|
||||||
|
func (c *Client) RejectRepoTransfer(owner, reponame string) (*Repository, *Response, error) {
|
||||||
|
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
repo := new(Repository)
|
||||||
|
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/reject", owner, reponame), jsonHeader, nil, repo)
|
||||||
|
return repo, resp, err
|
||||||
|
}
|
||||||
|
|
|
@ -20,13 +20,15 @@ func TestRepoTransfer(t *testing.T) {
|
||||||
repo, err := createTestRepo(t, "ToMove", c)
|
repo, err := createTestRepo(t, "ToMove", c)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
newRepo, _, err := c.TransferRepo(repo.Owner.UserName, repo.Name, TransferRepoOption{NewOwner: org.UserName})
|
newRepo, _, err := c.TransferRepo(c.username, repo.Name, TransferRepoOption{NewOwner: org.UserName})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err) // admin transfer repository will execute immediately but not set as pendding.
|
||||||
assert.NotNil(t, newRepo)
|
assert.NotNil(t, newRepo)
|
||||||
|
assert.EqualValues(t, "ToMove", newRepo.Name)
|
||||||
|
|
||||||
repo, err = createTestRepo(t, "ToMove", c)
|
repo, err = createTestRepo(t, "ToMove", c)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, _, err = c.TransferRepo(repo.Owner.UserName, repo.Name, TransferRepoOption{NewOwner: org.UserName})
|
_, resp, err := c.TransferRepo(c.username, repo.Name, TransferRepoOption{NewOwner: org.UserName})
|
||||||
|
assert.EqualValues(t, 422, resp.StatusCode)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
||||||
_, err = c.DeleteRepo(repo.Owner.UserName, repo.Name)
|
_, err = c.DeleteRepo(repo.Owner.UserName, repo.Name)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue