mirror of https://gitea.com/gitea/go-sdk.git
Compare commits
140 Commits
gitea/v0.1
...
main
Author | SHA1 | Date |
---|---|---|
appleboy | 8110db1e6d | |
Bo-Yi Wu | 83c73e79a5 | |
Bo-Yi Wu | 6c0938dc15 | |
tobiasbp | 0a99bda0cb | |
Bo-Yi Wu | 36723e11c8 | |
appleboy | a991e370c5 | |
appleboy | bb25c989a0 | |
appleboy | 18e5522f90 | |
Lunny Xiao | bad4de0196 | |
rjhaverkamp | 9ec1b82849 | |
6543 | bf71ec2fd9 | |
Martijn van der Kleijn | 001d5fad51 | |
diogo464 | 5d0143e4e7 | |
Simon Ser | 53f735b911 | |
Christian Groschupp | 98b424a8af | |
Bo-Yi Wu | 2abfdd7ba6 | |
Andreas Wachter | 8f4d3d8916 | |
ysicing | bde56fb9bb | |
ysicing | 228d6931f4 | |
appleboy | c8745e1599 | |
Lauris BH | 0e0a4691b6 | |
crapStone | e23e8aa300 | |
venc0r | d5e174e5b5 | |
appleboy | 0141b499c9 | |
Bo-Yi Wu | 091528835f | |
infinoid | ae1583a7be | |
John Olheiser | 93cfc320cf | |
b398f0fcac | 0fe2ace132 | |
Alessandro De Blasis | f4be505bf6 | |
JohnWalkerx | 315cf7aac8 | |
Zettat123 | 970776d1c1 | |
Norwin | 24a404e561 | |
jolheiser | 3c5cad657b | |
6543 | 6d1bcd107f | |
John Olheiser | 7511c6d3cd | |
Dmitry Afanasiev | 846d53e967 | |
John Olheiser | df1269f18d | |
John Olheiser | 2c35c11772 | |
edieruby | 2d9ee57af1 | |
qwerty287 | 1c9c71d84a | |
6543 | 8f846bdb9b | |
earl-warren | aef4e5e2bd | |
6543 | 7aeaaa45e1 | |
Norwin | e5b5b3447a | |
6543 | 5852fcc4a3 | |
6543 | a0127ed0e7 | |
6543 | cc14c63ccc | |
lafriks | de34275bb6 | |
Wim | e5f0c189f2 | |
6543 | 359c771ce3 | |
Gusted | 99a9de3172 | |
6543 | 23e1316337 | |
Gusted | f3ebdb8afe | |
arkamar | 319a978c6c | |
Gusted | adebf1cd11 | |
Gusted | 8fab37e740 | |
John Olheiser | ce9d46682d | |
John Olheiser | 89a4b0be6e | |
Gusted | ad3580e44d | |
Gusted | 559cc2fb2a | |
Gusted | 468d48c978 | |
Norwin | 223f0a75e0 | |
Gusted | 2e8bb53b30 | |
John Olheiser | 321bd56d93 | |
John Olheiser | 2616d10528 | |
Gusted | 603e4358f8 | |
Gusted | a56a62a4df | |
Gusted | f3162e5333 | |
Lunny Xiao | 22f2853429 | |
petergardfjall | f0663b3c13 | |
qwerty287 | 29e6eb37fe | |
Norwin | 36c7f8c8de | |
takirala | 73182f46eb | |
Norwin | 3ff2c60a86 | |
6543 | d19bc07721 | |
6543 | 635de1b821 | |
6543 | a87a2c7390 | |
spawn2kill | 5d54a04f8d | |
Norwin | 0fb32ac11f | |
Lunny Xiao | bf0e0883a8 | |
6543 | 4debc6ca4b | |
6543 | 747c78e835 | |
6543 | 71d2bf01d1 | |
6543 | b81847d03d | |
6543 | 63e97e127c | |
6543 | f5cc003900 | |
6543 | dee0475f01 | |
6543 | 79f379313c | |
6543 | 230fd25196 | |
6543 | 13d2d23dfc | |
6543 | cc08994d13 | |
6543 | ddc879e297 | |
6543 | a5ff184297 | |
6543 | fb42ca1c8d | |
6543 | 42fed7165c | |
J0Nes90 | e11a4f7f3b | |
J0Nes90 | 00ddb6b116 | |
J0Nes90 | 4e4d6dd731 | |
6543 | 7de882b744 | |
6543 | 0599915e88 | |
6543 | 120aa6234e | |
6543 | 4fac753f78 | |
6543 | 61c9b900e1 | |
6543 | 6b6fdd91ce | |
6543 | e0cf20a456 | |
Norwin | ff82113459 | |
6543 | a968e32ca1 | |
6543 | b0ee740ed0 | |
6543 | 32b0722f98 | |
cameronbraid | 757f8bdb90 | |
6543 | 95ed973c8d | |
6543 | 8947cd3b00 | |
6543 | c5a981333c | |
6543 | fd7e38f7b5 | |
6543 | 063d97fe74 | |
petergardfjall | ff00c13597 | |
cameronbraid | 30e7dc9ccb | |
Norwin | 97e61e5a8a | |
6543 | e34d140607 | |
Norwin | 7ddbf1a015 | |
6543 | cd52f0058b | |
Norwin | 68eec69f47 | |
6543 | fa7edc6f46 | |
6543 | 6ea6e887f2 | |
6543 | 60293eb2d1 | |
6543 | aa13606bc6 | |
6543 | b00ea89ffe | |
6543 | 9c81fa936f | |
6543 | 623a9eb4bf | |
6543 | 3009babf7a | |
6543 | 378e711f7a | |
6543 | 8c9d3985b4 | |
6543 | cb66dbb487 | |
6543 | 3c8ff9d91c | |
6543 | 2afffe1ca9 | |
6543 | 61f89c458f | |
6543 | 3226ac2e53 | |
6543 | 779e9330e9 | |
6543 | 302993adb8 | |
6543 | 3d7419dabb |
|
@ -10,11 +10,11 @@ base-url: https://gitea.com
|
|||
|
||||
# Changelog groups and which labeled PRs to add to each group
|
||||
groups:
|
||||
-
|
||||
-
|
||||
name: BREAKING
|
||||
labels:
|
||||
- kind/breaking
|
||||
-
|
||||
-
|
||||
name: FEATURES
|
||||
labels:
|
||||
- kind/feature
|
||||
|
@ -22,7 +22,7 @@ groups:
|
|||
name: BUGFIXES
|
||||
labels:
|
||||
- kind/bug
|
||||
-
|
||||
-
|
||||
name: ENHANCEMENTS
|
||||
labels:
|
||||
- kind/enhancement
|
||||
|
@ -32,26 +32,26 @@ groups:
|
|||
name: SECURITY
|
||||
labels:
|
||||
- kind/security
|
||||
-
|
||||
-
|
||||
name: TESTING
|
||||
labels:
|
||||
- kind/testing
|
||||
-
|
||||
-
|
||||
name: TRANSLATION
|
||||
labels:
|
||||
- kind/translation
|
||||
-
|
||||
-
|
||||
name: BUILD
|
||||
labels:
|
||||
- kind/build
|
||||
- kind/lint
|
||||
-
|
||||
-
|
||||
name: DOCS
|
||||
labels:
|
||||
- kind/docs
|
||||
-
|
||||
-
|
||||
name: MISC
|
||||
default: true
|
||||
|
||||
# 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
|
|
@ -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
|
|
@ -1,7 +1,7 @@
|
|||
Please check the following:
|
||||
|
||||
1. Make sure you are targeting the `master` 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
|
||||
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/main/CONTRIBUTING.md
|
||||
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.**
|
||||
|
|
|
@ -18,6 +18,7 @@ _cgo_gotypes.go
|
|||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
gitea-vet
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
|
|
105
CHANGELOG.md
105
CHANGELOG.md
|
@ -1,6 +1,107 @@
|
|||
# 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
|
||||
* Check Gitea Version Requirement (#419)
|
||||
|
@ -39,7 +140,7 @@
|
|||
* File Create/Update/Delete detect DefaultBranch if Branch not set for old Versions (#352)
|
||||
* 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
|
||||
* 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_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
|
||||
all: clean test build
|
||||
|
||||
|
@ -25,26 +55,36 @@ help:
|
|||
.PHONY: clean
|
||||
clean:
|
||||
rm -r -f test
|
||||
$(GO) clean -i ./...
|
||||
cd gitea && $(GO) clean -i ./...
|
||||
|
||||
.PHONY: 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
|
||||
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
|
||||
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.'
|
||||
|
||||
.PHONY: revive
|
||||
revive:
|
||||
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/mgechev/revive; \
|
||||
fi
|
||||
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
|
||||
.PHONY: ci-lint
|
||||
ci-lint:
|
||||
@cd gitea/; echo -n "gofumpt ...";\
|
||||
diff=$$($(GO) run $(GOFUMPT_PACKAGE) -extra -l .); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo; echo "Not gofumpt-ed"; \
|
||||
exit 1; \
|
||||
fi; echo " done"; echo -n "golangci-lint ...";\
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run --timeout 5m; \
|
||||
if [ $$? -eq 1 ]; then \
|
||||
echo; echo "Doesn't pass golangci-lint"; \
|
||||
exit 1; \
|
||||
fi; echo " done"; \
|
||||
cd -; \
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
@ -57,8 +97,8 @@ test:
|
|||
test-instance:
|
||||
rm -f -r ${WORK_DIR}/test 2> /dev/null; \
|
||||
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; \
|
||||
chmod +x ${WORK_DIR}/test/gitea-master; \
|
||||
wget ${GITEA_DL} -O ${WORK_DIR}/test/gitea-main; \
|
||||
chmod +x ${WORK_DIR}/test/gitea-main; \
|
||||
echo "[security]" > ${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; \
|
||||
|
@ -68,11 +108,11 @@ test-instance:
|
|||
echo "DB_TYPE = sqlite3" >> ${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 "[server]" >> /tmp/conf/app.ini; \
|
||||
echo "ROOT_URL = ${GITEA_SDK_TEST_URL}" >> /tmp/conf/app.ini; \
|
||||
${WORK_DIR}/test/gitea-master 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-master web -c ${WORK_DIR}/test/conf/app.ini
|
||||
echo "[server]" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||
echo "ROOT_URL = ${GITEA_SDK_TEST_URL}" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||
${WORK_DIR}/test/gitea-main migrate -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-main web -c ${WORK_DIR}/test/conf/app.ini
|
||||
|
||||
.PHONY: bench
|
||||
bench:
|
||||
|
@ -82,10 +122,3 @@ bench:
|
|||
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
|
||||
|
||||
[![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
|
||||
|
||||
|
@ -12,7 +18,7 @@ import "code.gitea.io/sdk/gitea"
|
|||
|
||||
## Version Requirements
|
||||
* go >= 1.13
|
||||
* gitea >= 1.10
|
||||
* gitea >= 1.11
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# Migration Guide: v0.11 to v0.12
|
||||
|
||||
v0.12.0 introduces a number of breaking changes, throu it should not be hard to
|
||||
migrate.
|
||||
Just follow this guid and if issues still ocure ask for help on discord or
|
||||
feel free to create an issue.
|
||||
v0.12.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 -->
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Migration Guide: v0.12 to v0.13
|
||||
|
||||
v0.13.0 introduces a number of breaking changes, throu it should not be hard to migrate.
|
||||
Just follow this guid and if issues still ocure ask for help on discord or
|
||||
feel free to create an issue.
|
||||
v0.13.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 -->
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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)
|
|
@ -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
|
||||
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
|
||||
}
|
||||
opt.setDefaults()
|
||||
|
@ -36,7 +36,10 @@ func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response,
|
|||
|
||||
// RunCronTasks run a cron task
|
||||
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
|
||||
}
|
||||
_, 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
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
@ -12,6 +12,9 @@ import (
|
|||
|
||||
// AdminCreateRepo create a repo
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
@ -30,8 +30,10 @@ func TestAdminOrg(t *testing.T) {
|
|||
|
||||
orgs, _, err := c.AdminListOrgs(AdminListOrgsOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, orgs, 1)
|
||||
assert.EqualValues(t, newOrg.ID, orgs[0].ID)
|
||||
if assert.True(t, len(orgs) >= 1) {
|
||||
orgs = orgs[len(orgs)-1:]
|
||||
assert.EqualValues(t, newOrg.ID, orgs[0].ID)
|
||||
}
|
||||
|
||||
_, err = c.DeleteOrg(orgName)
|
||||
assert.NoError(t, err)
|
||||
|
@ -43,7 +45,7 @@ func TestAdminCronTasks(t *testing.T) {
|
|||
|
||||
tasks, _, err := c.ListCronTasks(ListCronTaskOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, tasks, 15)
|
||||
assert.True(t, len(tasks) > 15)
|
||||
_, err = c.RunCronTasks(tasks[0].Name)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -26,14 +26,15 @@ func (c *Client) AdminListUsers(opt AdminListUsersOptions) ([]*User, *Response,
|
|||
|
||||
// CreateUserOption create user options
|
||||
type CreateUserOption struct {
|
||||
SourceID int64 `json:"source_id"`
|
||||
LoginName string `json:"login_name"`
|
||||
Username string `json:"username"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
MustChangePassword *bool `json:"must_change_password"`
|
||||
SendNotify bool `json:"send_notify"`
|
||||
SourceID int64 `json:"source_id"`
|
||||
LoginName string `json:"login_name"`
|
||||
Username string `json:"username"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
MustChangePassword *bool `json:"must_change_password"`
|
||||
SendNotify bool `json:"send_notify"`
|
||||
Visibility *VisibleType `json:"visibility"`
|
||||
}
|
||||
|
||||
// Validate the CreateUserOption struct
|
||||
|
@ -63,25 +64,31 @@ func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error)
|
|||
|
||||
// EditUserOption edit user options
|
||||
type EditUserOption struct {
|
||||
SourceID int64 `json:"source_id"`
|
||||
LoginName string `json:"login_name"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
MustChangePassword *bool `json:"must_change_password"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
Active *bool `json:"active"`
|
||||
Admin *bool `json:"admin"`
|
||||
AllowGitHook *bool `json:"allow_git_hook"`
|
||||
AllowImportLocal *bool `json:"allow_import_local"`
|
||||
MaxRepoCreation *int `json:"max_repo_creation"`
|
||||
ProhibitLogin *bool `json:"prohibit_login"`
|
||||
AllowCreateOrganization *bool `json:"allow_create_organization"`
|
||||
SourceID int64 `json:"source_id"`
|
||||
LoginName string `json:"login_name"`
|
||||
Email *string `json:"email"`
|
||||
FullName *string `json:"full_name"`
|
||||
Password string `json:"password"`
|
||||
Description *string `json:"description"`
|
||||
MustChangePassword *bool `json:"must_change_password"`
|
||||
Website *string `json:"website"`
|
||||
Location *string `json:"location"`
|
||||
Active *bool `json:"active"`
|
||||
Admin *bool `json:"admin"`
|
||||
AllowGitHook *bool `json:"allow_git_hook"`
|
||||
AllowImportLocal *bool `json:"allow_import_local"`
|
||||
MaxRepoCreation *int `json:"max_repo_creation"`
|
||||
ProhibitLogin *bool `json:"prohibit_login"`
|
||||
AllowCreateOrganization *bool `json:"allow_create_organization"`
|
||||
Restricted *bool `json:"restricted"`
|
||||
Visibility *VisibleType `json:"visibility"`
|
||||
}
|
||||
|
||||
// AdminEditUser modify user informations
|
||||
func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -92,12 +99,18 @@ func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, erro
|
|||
|
||||
// AdminDeleteUser delete one user according name
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// AdminCreateUserPublicKey adds a public key for the user
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -109,6 +122,9 @@ func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*Pu
|
|||
|
||||
// AdminDeleteUserPublicKey deletes a user's public key
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
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()
|
||||
attachments := make([]*Attachment, 0, opt.PageSize)
|
||||
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
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET",
|
||||
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
|
||||
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
|
||||
body := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(body)
|
||||
|
@ -79,7 +88,10 @@ type EditAttachmentOptions struct {
|
|||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
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
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
396
gitea/client.go
396
gitea/client.go
|
@ -6,13 +6,15 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -23,41 +25,61 @@ var jsonHeader = http.Header{"content-type": []string{"application/json"}}
|
|||
|
||||
// Version return the library version
|
||||
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 {
|
||||
url string
|
||||
accessToken string
|
||||
username string
|
||||
password string
|
||||
otp string
|
||||
sudo string
|
||||
client *http.Client
|
||||
ctx context.Context
|
||||
serverVersion *version.Version
|
||||
versionLock sync.RWMutex
|
||||
url string
|
||||
accessToken string
|
||||
username string
|
||||
password string
|
||||
otp string
|
||||
sudo string
|
||||
userAgent string
|
||||
debug bool
|
||||
httpsigner *HTTPSign
|
||||
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
|
||||
type Response struct {
|
||||
*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.
|
||||
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{
|
||||
url: strings.TrimSuffix(url, "/"),
|
||||
client: &http.Client{},
|
||||
ctx: context.Background(),
|
||||
}
|
||||
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 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
|
||||
func SetHTTPClient(httpClient *http.Client) func(client *Client) {
|
||||
return func(client *Client) {
|
||||
client.client = httpClient
|
||||
func SetHTTPClient(httpClient *http.Client) ClientOption {
|
||||
return func(client *Client) error {
|
||||
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
|
||||
func SetToken(token string) func(client *Client) {
|
||||
return func(client *Client) {
|
||||
func SetToken(token string) ClientOption {
|
||||
return func(client *Client) error {
|
||||
client.mutex.Lock()
|
||||
client.accessToken = token
|
||||
client.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetBasicAuth is an option for NewClient to set username and password
|
||||
func SetBasicAuth(username, password string) func(client *Client) {
|
||||
return func(client *Client) {
|
||||
func SetBasicAuth(username, password string) ClientOption {
|
||||
return func(client *Client) error {
|
||||
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
|
||||
func (c *Client) SetBasicAuth(username, password string) {
|
||||
c.mutex.Lock()
|
||||
c.username, c.password = username, password
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetOTP is an option for NewClient to set OTP for 2FA
|
||||
func SetOTP(otp string) func(client *Client) {
|
||||
return func(client *Client) {
|
||||
func SetOTP(otp string) ClientOption {
|
||||
return func(client *Client) error {
|
||||
client.SetOTP(otp)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetOTP sets OTP for 2FA
|
||||
func (c *Client) SetOTP(otp string) {
|
||||
c.mutex.Lock()
|
||||
c.otp = otp
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetContext is an option for NewClient to set context
|
||||
func SetContext(ctx context.Context) func(client *Client) {
|
||||
return func(client *Client) {
|
||||
// SetContext is an option for NewClient to set the default context
|
||||
func SetContext(ctx context.Context) ClientOption {
|
||||
return func(client *Client) error {
|
||||
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) {
|
||||
c.mutex.Lock()
|
||||
c.ctx = ctx
|
||||
}
|
||||
|
||||
// SetHTTPClient replaces default http.Client with user given one.
|
||||
func (c *Client) SetHTTPClient(client *http.Client) {
|
||||
c.client = client
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetSudo is an option for NewClient to set sudo header
|
||||
func SetSudo(sudo string) func(client *Client) {
|
||||
return func(client *Client) {
|
||||
func SetSudo(sudo string) ClientOption {
|
||||
return func(client *Client) error {
|
||||
client.SetSudo(sudo)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetSudo sets username to impersonate.
|
||||
func (c *Client) SetSudo(sudo string) {
|
||||
c.mutex.Lock()
|
||||
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) {
|
||||
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)
|
||||
|
||||
client := c.client // client ref can change from this point on so safe it
|
||||
c.mutex.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
return data, &Response{resp}, nil
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
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) {
|
||||
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)
|
||||
if err != nil {
|
||||
c.mutex.RUnlock()
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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) {
|
||||
resp, err := c.doRequest(method, path, header, body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, resp, err
|
||||
}
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return resp, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// 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"
|
||||
|
|
|
@ -16,7 +16,10 @@ type ListForksOptions struct {
|
|||
}
|
||||
|
||||
// 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()
|
||||
forks := make([]*Repository, opt.PageSize)
|
||||
resp, err := c.getParsedResponse("GET",
|
||||
|
@ -29,10 +32,15 @@ func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*R
|
|||
type CreateForkOption struct {
|
||||
// organization name, if forking into an organization
|
||||
Organization *string `json:"organization"`
|
||||
// name of the forked repository
|
||||
Name *string `json:"name"`
|
||||
}
|
||||
|
||||
// CreateFork create a fork of a repository
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
@ -19,6 +19,9 @@ type GitBlobResponse struct {
|
|||
|
||||
// GetBlob get the blob of a repository file
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/blobs/%s", user, repo, sha), nil, nil, blob)
|
||||
return blob, resp, err
|
||||
|
|
|
@ -24,6 +24,9 @@ type ListRepoGitHooksOptions struct {
|
|||
|
||||
// ListRepoGitHooks list all the Git hooks of one repository
|
||||
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()
|
||||
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)
|
||||
|
@ -32,6 +35,9 @@ func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions
|
|||
|
||||
// GetRepoGitHook get a Git hook of a repository
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil, h)
|
||||
return h, resp, err
|
||||
|
@ -44,6 +50,9 @@ type EditGitHookOption struct {
|
|||
|
||||
// EditRepoGitHook modify one Git hook of a repository
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
16
gitea/go.mod
16
gitea/go.mod
|
@ -1,8 +1,18 @@
|
|||
module code.gitea.io/sdk/gitea
|
||||
|
||||
go 1.12
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/davidmz/go-pageant v1.0.2
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
||||
// 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
|
||||
type ListHooksOptions struct {
|
||||
ListOptions
|
||||
|
@ -31,14 +53,28 @@ type ListHooksOptions struct {
|
|||
|
||||
// ListOrgHooks list all the hooks of one organization
|
||||
func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
hooks := make([]*Hook, 0, opt.PageSize)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks?%s", org, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
||||
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
|
||||
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()
|
||||
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)
|
||||
|
@ -47,13 +83,26 @@ func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook
|
|||
|
||||
// GetOrgHook get a hook of an organization
|
||||
func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
h := new(Hook)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h)
|
||||
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
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h)
|
||||
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
|
||||
type CreateHookOption struct {
|
||||
Type string `json:"type"`
|
||||
Config map[string]string `json:"config"`
|
||||
Events []string `json:"events"`
|
||||
BranchFilter string `json:"branch_filter"`
|
||||
Active bool `json:"active"`
|
||||
Type HookType `json:"type"`
|
||||
Config map[string]string `json:"config"`
|
||||
Events []string `json:"events"`
|
||||
BranchFilter string `json:"branch_filter"`
|
||||
Active bool `json:"active"`
|
||||
AuthorizationHeader string `json:"authorization_header"`
|
||||
}
|
||||
|
||||
// Validate the CreateHookOption struct
|
||||
|
@ -78,6 +128,9 @@ func (opt CreateHookOption) Validate() error {
|
|||
|
||||
// CreateOrgHook create one hook for an organization, with options
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -90,8 +143,25 @@ func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Respon
|
|||
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
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -103,14 +173,18 @@ func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook,
|
|||
|
||||
// EditHookOption options when modify one hook
|
||||
type EditHookOption struct {
|
||||
Config map[string]string `json:"config"`
|
||||
Events []string `json:"events"`
|
||||
BranchFilter string `json:"branch_filter"`
|
||||
Active *bool `json:"active"`
|
||||
Config map[string]string `json:"config"`
|
||||
Events []string `json:"events"`
|
||||
BranchFilter string `json:"branch_filter"`
|
||||
Active *bool `json:"active"`
|
||||
AuthorizationHeader string `json:"authorization_header"`
|
||||
}
|
||||
|
||||
// EditOrgHook modify one hook of an organization, with hook id and options
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -119,8 +193,21 @@ func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Respons
|
|||
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
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
ID int64 `json:"id"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Index int64 `json:"number"`
|
||||
Poster *User `json:"user"`
|
||||
OriginalAuthor string `json:"original_author"`
|
||||
OriginalAuthorID int64 `json:"original_author_id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Ref string `json:"ref"`
|
||||
Labels []*Label `json:"labels"`
|
||||
Milestone *Milestone `json:"milestone"`
|
||||
Assignee *User `json:"assignee"`
|
||||
Assignees []*User `json:"assignees"`
|
||||
// Whether the issue is open or closed
|
||||
State StateType `json:"state"`
|
||||
|
@ -62,6 +63,18 @@ type ListIssueOption struct {
|
|||
Labels []string
|
||||
Milestones []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
|
||||
|
@ -110,6 +123,29 @@ func (opt *ListIssueOption) QueryEncode() string {
|
|||
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()
|
||||
}
|
||||
|
||||
|
@ -121,50 +157,62 @@ func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) {
|
|||
link, _ := url.Parse("/repos/issues/search")
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
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++ {
|
||||
if issues[i].Repository != nil {
|
||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range issues {
|
||||
c.issueBackwardsCompatibility(issues[i])
|
||||
}
|
||||
return issues, resp, err
|
||||
}
|
||||
|
||||
// ListRepoIssues returns all issues for a given repository
|
||||
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()
|
||||
issues := make([]*Issue, 0, opt.PageSize)
|
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues", owner, repo))
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
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++ {
|
||||
if issues[i].Repository != nil {
|
||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range issues {
|
||||
c.issueBackwardsCompatibility(issues[i])
|
||||
}
|
||||
return issues, resp, err
|
||||
}
|
||||
|
||||
// GetIssue returns a single issue for a given repository
|
||||
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)
|
||||
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]
|
||||
}
|
||||
c.issueBackwardsCompatibility(issue)
|
||||
return issue, resp, err
|
||||
}
|
||||
|
||||
// CreateIssueOption options to create one issue
|
||||
type CreateIssueOption struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
// username of assignee
|
||||
Assignee string `json:"assignee"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Ref string `json:"ref"`
|
||||
Assignees []string `json:"assignees"`
|
||||
Deadline *time.Time `json:"due_date"`
|
||||
// milestone id
|
||||
|
@ -184,6 +232,9 @@ func (opt CreateIssueOption) Validate() error {
|
|||
|
||||
// CreateIssue create a new issue for a given repository
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -194,18 +245,20 @@ func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue,
|
|||
issue := new(Issue)
|
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo),
|
||||
jsonHeader, bytes.NewReader(body), issue)
|
||||
c.issueBackwardsCompatibility(issue)
|
||||
return issue, resp, err
|
||||
}
|
||||
|
||||
// EditIssueOption options for editing an issue
|
||||
type EditIssueOption struct {
|
||||
Title string `json:"title"`
|
||||
Body *string `json:"body"`
|
||||
Assignee *string `json:"assignee"`
|
||||
Assignees []string `json:"assignees"`
|
||||
Milestone *int64 `json:"milestone"`
|
||||
State *StateType `json:"state"`
|
||||
Deadline *time.Time `json:"due_date"`
|
||||
Title string `json:"title"`
|
||||
Body *string `json:"body"`
|
||||
Ref *string `json:"ref"`
|
||||
Assignees []string `json:"assignees"`
|
||||
Milestone *int64 `json:"milestone"`
|
||||
State *StateType `json:"state"`
|
||||
Deadline *time.Time `json:"due_date"`
|
||||
RemoveDeadline *bool `json:"unset_due_date"`
|
||||
}
|
||||
|
||||
// Validate the EditIssueOption struct
|
||||
|
@ -218,6 +271,9 @@ func (opt EditIssueOption) Validate() error {
|
|||
|
||||
// EditIssue modify an existing issue for a given repository
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -229,5 +285,25 @@ func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption)
|
|||
resp, err := c.getParsedResponse("PATCH",
|
||||
fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index),
|
||||
jsonHeader, bytes.NewReader(body), issue)
|
||||
c.issueBackwardsCompatibility(issue)
|
||||
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.
|
||||
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()
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index))
|
||||
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.
|
||||
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()
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo))
|
||||
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.
|
||||
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)
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return comment, nil, err
|
||||
}
|
||||
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.
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -117,6 +129,9 @@ func (opt EditIssueCommentOption) Validate() error {
|
|||
|
||||
// EditIssueComment edits an issue comment.
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -131,6 +146,9 @@ func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditI
|
|||
|
||||
// DeleteIssueComment deletes an issue comment.
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// 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
|
||||
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()
|
||||
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)
|
||||
|
@ -37,6 +40,9 @@ func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*L
|
|||
|
||||
// GetRepoLabel get one label of repository by repo it
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label)
|
||||
return label, resp, err
|
||||
|
@ -67,11 +73,14 @@ func (opt CreateLabelOption) Validate() error {
|
|||
|
||||
// CreateLabel create one label of repository
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +123,9 @@ func (opt EditLabelOption) Validate() error {
|
|||
|
||||
// EditLabel modify one label with options
|
||||
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 {
|
||||
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
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GetIssueLabels get labels of one issue via issue id
|
||||
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)
|
||||
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
|
||||
|
@ -147,6 +165,9 @@ type IssueLabelsOption struct {
|
|||
|
||||
// AddIssueLabels add one or more labels to one issue
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
// TODO: maybe we need delete by label name and issue id
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// ClearIssueLabels delete all the labels of one issue.
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
@ -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
|
||||
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()
|
||||
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
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone)
|
||||
return milestone, resp, err
|
||||
}
|
||||
|
||||
// GetMilestoneByName get one milestone by repo and milestone name
|
||||
func (c *Client) GetMilestoneByName(owner, repo string, name string) (*Milestone, *Response, error) {
|
||||
if c.CheckServerVersionConstraint(">=1.13") != nil {
|
||||
func (c *Client) GetMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) {
|
||||
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
||||
// backwards compatibility mode
|
||||
m, resp, err := c.resolveMilestoneByName(owner, repo, name)
|
||||
return m, resp, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
milestone := new(Milestone)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone)
|
||||
return milestone, resp, err
|
||||
|
@ -95,6 +104,9 @@ func (opt CreateMilestoneOption) Validate() error {
|
|||
|
||||
// CreateMilestone create one milestone with options
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -135,6 +147,9 @@ func (opt EditMilestoneOption) Validate() error {
|
|||
|
||||
// EditMilestone modify milestone with options
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -148,8 +163,8 @@ func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOp
|
|||
}
|
||||
|
||||
// EditMilestoneByName modify milestone with options
|
||||
func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMilestoneOption) (*Milestone, *Response, error) {
|
||||
if c.CheckServerVersionConstraint(">=1.13") != nil {
|
||||
func (c *Client) EditMilestoneByName(owner, repo, name string, opt EditMilestoneOption) (*Milestone, *Response, error) {
|
||||
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
||||
// backwards compatibility mode
|
||||
m, _, err := c.resolveMilestoneByName(owner, repo, name)
|
||||
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)
|
||||
}
|
||||
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(); err != nil {
|
||||
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
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// DeleteMilestoneByName delete one milestone by name
|
||||
func (c *Client) DeleteMilestoneByName(owner, repo string, name string) (*Response, error) {
|
||||
if c.CheckServerVersionConstraint(">=1.13") != nil {
|
||||
func (c *Client) DeleteMilestoneByName(owner, repo, name string) (*Response, error) {
|
||||
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
||||
// backwards compatibility mode
|
||||
m, _, err := c.resolveMilestoneByName(owner, repo, name)
|
||||
if err != nil {
|
||||
|
@ -185,6 +206,9 @@ func (c *Client) DeleteMilestoneByName(owner, repo string, name string) (*Respon
|
|||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestMilestones(t *testing.T) {
|
|||
|
||||
repo, _ := createTestRepo(t, "TestMilestones", c)
|
||||
now := time.Now()
|
||||
future := time.Unix(1896134400, 0) //2030-02-01
|
||||
future := time.Unix(1896134400, 0) // 2030-02-01
|
||||
closed := "closed"
|
||||
sClosed := StateClosed
|
||||
|
||||
|
@ -43,7 +43,7 @@ func TestMilestones(t *testing.T) {
|
|||
// ListRepoMilestones
|
||||
ml, _, err := c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{})
|
||||
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})
|
||||
assert.NoError(t, err)
|
||||
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")
|
||||
assert.NoError(t, err)
|
||||
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.EqualValues(t, "milestone 'NoEvidenceOfExist' do not exist", err.Error())
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ type Reaction struct {
|
|||
|
||||
// GetIssueReactions get a list reactions of an issue
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
reactions := make([]*Reaction, 0, 10)
|
||||
|
@ -45,7 +45,7 @@ type editReactionOption struct {
|
|||
|
||||
// PostIssueReaction add a reaction to an issue
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
||||
|
|
|
@ -11,8 +11,13 @@ import (
|
|||
|
||||
// StopWatch represents a running stopwatch of an issue / pr
|
||||
type StopWatch struct {
|
||||
Created time.Time `json:"created"`
|
||||
IssueIndex int64 `json:"issue_index"`
|
||||
Created time.Time `json:"created"`
|
||||
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
|
||||
|
@ -24,6 +29,9 @@ func (c *Client) GetMyStopwatches() ([]*StopWatch, *Response, error) {
|
|||
|
||||
// DeleteIssueStopwatch delete / cancel a specific stopwatch
|
||||
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)
|
||||
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
|
||||
// repository
|
||||
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)
|
||||
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
|
||||
// repository
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
// GetIssueSubscribers get list of users who subscribed on an issue
|
||||
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
|
||||
}
|
||||
subscribers := make([]*User, 0, 10)
|
||||
|
@ -21,7 +21,7 @@ func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User,
|
|||
|
||||
// AddIssueSubscription Subscribe user to issue
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
wi := new(WatchInfo)
|
||||
|
|
|
@ -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)
|
||||
// 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)
|
||||
listIssues(t, c)
|
||||
deleteIssue(t, c)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
log.Println("== TestEditIssues ==")
|
||||
il, _, err := c.ListIssues(ListIssueOption{KeyWord: "soon"})
|
||||
il, _, err := c.ListIssues(ListIssueOption{KeyWord: "soon!"})
|
||||
assert.NoError(t, err)
|
||||
issue, _, err := c.GetIssue(il[0].Poster.UserName, il[0].Repository.Name, il[0].Index)
|
||||
assert.NoError(t, err)
|
||||
|
||||
body := "123 test and go"
|
||||
state := StateClosed
|
||||
issueNew, _, err := c.EditIssue(issue.Poster.UserName, issue.Repository.Name, issue.Index, EditIssueOption{
|
||||
Title: "Edited",
|
||||
Body: &body,
|
||||
Body: OptionalString("123 test and go"),
|
||||
State: &state,
|
||||
Ref: OptionalString("main"),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
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, "main", issueNew.Ref)
|
||||
}
|
||||
|
||||
func listIssues(t *testing.T, c *Client) {
|
||||
|
@ -103,7 +117,7 @@ func listIssues(t *testing.T, c *Client) {
|
|||
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()
|
||||
assert.NoError(t, err)
|
||||
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 {
|
||||
assert.Error(t, e)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
assert.NoError(t, e)
|
||||
assert.NotEmpty(t, issue)
|
||||
|
@ -136,4 +150,5 @@ func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assi
|
|||
} else {
|
||||
assert.Empty(t, issue.Closed)
|
||||
}
|
||||
return issue
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -25,33 +26,50 @@ type TrackedTime struct {
|
|||
Issue *Issue `json:"issue"`
|
||||
}
|
||||
|
||||
// GetUserTrackedTimes list tracked times of a user
|
||||
func (c *Client) GetUserTrackedTimes(owner, repo, user string) ([]*TrackedTime, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
times := make([]*TrackedTime, 0, 10)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times/%s", owner, repo, user), nil, nil, ×)
|
||||
return times, resp, err
|
||||
// ListTrackedTimesOptions options for listing repository's tracked times
|
||||
type ListTrackedTimesOptions struct {
|
||||
ListOptions
|
||||
Since time.Time
|
||||
Before time.Time
|
||||
// User filter is only used by ListRepoTrackedTimes !!!
|
||||
User string
|
||||
}
|
||||
|
||||
// GetRepoTrackedTimes list tracked times of a repository
|
||||
func (c *Client) GetRepoTrackedTimes(owner, repo string) ([]*TrackedTime, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *ListTrackedTimesOptions) QueryEncode() string {
|
||||
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
|
||||
}
|
||||
times := make([]*TrackedTime, 0, 10)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times", owner, repo), nil, nil, ×)
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/times", owner, repo))
|
||||
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
|
||||
}
|
||||
|
||||
// GetMyTrackedTimes list tracked times of the current user
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET", "/user/times", nil, nil, ×)
|
||||
resp, err := c.getParsedResponse("GET", "/user/times", jsonHeader, nil, ×)
|
||||
return times, resp, err
|
||||
}
|
||||
|
||||
|
@ -75,7 +93,7 @@ func (opt AddTimeOption) Validate() error {
|
|||
|
||||
// AddTime adds time to issue with the given index
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// ListTrackedTimesOptions options for listing repository's tracked times
|
||||
type ListTrackedTimesOptions struct {
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// ListIssueTrackedTimes list tracked times of a single issue for a given repository
|
||||
func (c *Client) ListIssueTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index))
|
||||
opt.setDefaults()
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
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
|
||||
}
|
||||
|
||||
// ResetIssueTime reset tracked time of a single issue for a given repository
|
||||
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
|
||||
}
|
||||
_, 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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.0"); err != nil {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -9,12 +9,13 @@ import (
|
|||
"net/url"
|
||||
)
|
||||
|
||||
const defaultPageSize = 10
|
||||
const maxPageSize = 50
|
||||
|
||||
// ListOptions options for using Gitea's API pagination
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -26,12 +27,14 @@ func (o ListOptions) getURLQuery() url.Values {
|
|||
return query
|
||||
}
|
||||
|
||||
func (o ListOptions) setDefaults() {
|
||||
if o.Page < 1 {
|
||||
// setDefaults applies default pagination options.
|
||||
// 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
|
||||
}
|
||||
|
||||
if o.PageSize < 0 || o.PageSize > maxPageSize {
|
||||
o.PageSize = defaultPageSize
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,14 +40,16 @@ func enableRunGitea() bool {
|
|||
}
|
||||
|
||||
func newTestClient() *Client {
|
||||
c, _ := NewClient(getGiteaURL(), newTestClientAuth())
|
||||
return c
|
||||
}
|
||||
|
||||
func newTestClientAuth() ClientOption {
|
||||
token := getGiteaToken()
|
||||
if token == "" {
|
||||
client := NewClientWithHTTP(getGiteaURL(), &http.Client{})
|
||||
client.SetBasicAuth(getGiteaUsername(), getGiteaPassword())
|
||||
return client
|
||||
return SetBasicAuth(getGiteaUsername(), getGiteaPassword())
|
||||
}
|
||||
c, _ := NewClient(getGiteaURL(), SetToken(getGiteaToken()))
|
||||
return c
|
||||
return SetToken(getGiteaToken())
|
||||
}
|
||||
|
||||
func giteaMasterPath() string {
|
||||
|
@ -80,7 +82,7 @@ func downGitea() (string, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
if err = os.Chmod(f.Name(), 700); err != nil {
|
||||
if err = os.Chmod(f.Name(), 0o700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
@ -99,7 +101,10 @@ func runGitea() (*os.Process, error) {
|
|||
|
||||
giteaDir := filepath.Dir(p)
|
||||
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"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -148,7 +153,9 @@ func TestMain(m *testing.M) {
|
|||
return
|
||||
}
|
||||
defer func() {
|
||||
p.Kill()
|
||||
if err := p.Kill(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
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)
|
||||
type NotificationSubject struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
LatestCommentURL string `json:"latest_comment_url"`
|
||||
Type string `json:"type" binding:"In(Issue,Pull,Commit)"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
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
|
||||
|
@ -41,12 +44,39 @@ const (
|
|||
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
|
||||
type ListNotificationOptions struct {
|
||||
ListOptions
|
||||
Since time.Time
|
||||
Before time.Time
|
||||
Status []NotifyStatus
|
||||
Since time.Time
|
||||
Before time.Time
|
||||
Status []NotifyStatus
|
||||
SubjectTypes []NotifySubjectType
|
||||
}
|
||||
|
||||
// MarkNotificationOptions represents the filter & modify options
|
||||
|
@ -68,13 +98,16 @@ func (opt *ListNotificationOptions) QueryEncode() string {
|
|||
for _, s := range opt.Status {
|
||||
query.Add("status-types", string(s))
|
||||
}
|
||||
for _, s := range opt.SubjectTypes {
|
||||
query.Add("subject-type", string(s))
|
||||
}
|
||||
return query.Encode()
|
||||
}
|
||||
|
||||
// Validate the CreateUserOption struct
|
||||
func (opt ListNotificationOptions) Validate(c *Client) error {
|
||||
if len(opt.Status) != 0 {
|
||||
return c.CheckServerVersionConstraint(">=1.12.3")
|
||||
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -97,14 +130,14 @@ func (opt *MarkNotificationOptions) QueryEncode() string {
|
|||
// Validate the CreateUserOption struct
|
||||
func (opt MarkNotificationOptions) Validate(c *Client) error {
|
||||
if len(opt.Status) != 0 || len(opt.ToStatus) != 0 {
|
||||
return c.CheckServerVersionConstraint(">=1.12.3")
|
||||
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckNotifications list users's notification threads
|
||||
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
|
||||
}
|
||||
new := struct {
|
||||
|
@ -117,7 +150,7 @@ func (c *Client) CheckNotifications() (int64, *Response, error) {
|
|||
|
||||
// GetNotification get notification thread by ID
|
||||
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
|
||||
}
|
||||
thread := new(NotificationThread)
|
||||
|
@ -127,21 +160,27 @@ func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, erro
|
|||
|
||||
// ReadNotification mark notification thread as read by ID
|
||||
// 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) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
return nil, err
|
||||
// The relevant notification will be returned as the first parameter when the Gitea server is 1.16.0 or higher.
|
||||
func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*NotificationThread, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
link := fmt.Sprintf("/notifications/threads/%d", id)
|
||||
if len(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)
|
||||
return resp, err
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
// ListNotifications list users's notification threads
|
||||
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
|
||||
}
|
||||
if err := opt.Validate(c); err != nil {
|
||||
|
@ -155,28 +194,38 @@ func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*Notification
|
|||
}
|
||||
|
||||
// ReadNotifications mark notification threads as read
|
||||
func (c *Client) ReadNotifications(opt MarkNotificationOptions) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
return nil, err
|
||||
// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher.
|
||||
func (c *Client) ReadNotifications(opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(c); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
link, _ := url.Parse("/notifications")
|
||||
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)
|
||||
return resp, err
|
||||
return nil, resp, err
|
||||
}
|
||||
|
||||
// ListRepoNotifications list users's notification threads on a specific repo
|
||||
func (c *Client) ListRepoNotifications(owner, reponame string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
func (c *Client) ListRepoNotifications(owner, repo string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||
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 {
|
||||
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()
|
||||
threads := make([]*NotificationThread, 0, 10)
|
||||
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
|
||||
func (c *Client) ReadRepoNotifications(owner, reponame string, opt MarkNotificationOptions) (*Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil {
|
||||
return nil, err
|
||||
// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher.
|
||||
func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) {
|
||||
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 {
|
||||
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()
|
||||
|
||||
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)
|
||||
return resp, err
|
||||
return nil, resp, err
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ func TestNotifications(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
user2 := createTestUser(t, "notify2", c)
|
||||
|
||||
//create 2 repos
|
||||
// create 2 repos
|
||||
repoA, err := createTestRepo(t, "TestNotifications_A", c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -35,8 +35,9 @@ func TestNotifications(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
c.sudo = user2.UserName
|
||||
_, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
notifications, _, err := c.ReadNotifications(MarkNotificationOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, notifications, 0)
|
||||
count, _, err := c.CheckNotifications()
|
||||
assert.EqualValues(t, 0, count)
|
||||
assert.NoError(t, err)
|
||||
|
@ -45,7 +46,7 @@ func TestNotifications(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
issue, _, err := c.CreateIssue(repoB.Owner.UserName, repoB.Name, CreateIssueOption{Title: "B Issue", Closed: false})
|
||||
assert.NoError(t, err)
|
||||
time.Sleep(time.Second * 1)
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
// CheckNotifications of user2
|
||||
c.sudo = user2.UserName
|
||||
|
@ -60,6 +61,8 @@ func TestNotifications(t *testing.T) {
|
|||
for _, n := range nList {
|
||||
assert.EqualValues(t, true, n.Unread)
|
||||
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" {
|
||||
assert.EqualValues(t, repoA.Name, n.Repository.Name)
|
||||
} else if n.Subject.Title == "B Issue" {
|
||||
|
@ -75,8 +78,9 @@ func TestNotifications(t *testing.T) {
|
|||
assert.Len(t, nList, 1)
|
||||
assert.EqualValues(t, "A Issue", nList[0].Subject.Title)
|
||||
// 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.Len(t, notifications, 1)
|
||||
|
||||
// GetThread
|
||||
n, _, err := c.GetNotification(nList[0].ID)
|
||||
|
@ -85,8 +89,9 @@ func TestNotifications(t *testing.T) {
|
|||
assert.EqualValues(t, "A Issue", n.Subject.Title)
|
||||
|
||||
// ReadNotifications
|
||||
_, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
notifications, _, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, notifications, 1)
|
||||
nList, _, err = c.ListNotifications(ListNotificationOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, nList, 0)
|
||||
|
@ -96,7 +101,7 @@ func TestNotifications(t *testing.T) {
|
|||
c.sudo = ""
|
||||
_, _, err = c.EditIssue(repoB.Owner.UserName, repoB.Name, issue.Index, EditIssueOption{State: &iState})
|
||||
assert.NoError(t, err)
|
||||
time.Sleep(time.Second * 1)
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
c.sudo = user2.UserName
|
||||
nList, _, err = c.ListNotifications(ListNotificationOptions{})
|
||||
|
@ -104,24 +109,32 @@ func TestNotifications(t *testing.T) {
|
|||
count, _, err = c.CheckNotifications()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, count)
|
||||
assert.Len(t, nList, 1)
|
||||
if len(nList) > 0 {
|
||||
_, err = c.ReadNotification(nList[0].ID)
|
||||
if assert.Len(t, nList, 1) {
|
||||
assert.EqualValues(t, NotifySubjectClosed, nList[0].Subject.State)
|
||||
notification, _, err := c.ReadNotification(nList[0].ID)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, notification.ID, nList[0].ID)
|
||||
}
|
||||
|
||||
c.sudo = ""
|
||||
_, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
notifications, _, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, notifications, 2)
|
||||
nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusRead}})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
_, err = c.ReadNotification(nList[3].ID, NotifyStatusUnread)
|
||||
assert.NoError(t, err)
|
||||
notification, _, err = c.ReadNotification(nList[1].ID, NotifyStatusUnread)
|
||||
assert.EqualValues(t, notification.ID, nList[1].ID)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusPinned, NotifyStatusUnread}})
|
||||
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
|
||||
type Oauth2 struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
Created time.Time `json:"created"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
ConfidentialClient bool `json:"confidential_client"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
// ListOauth2Option for listing Oauth2 Applications
|
||||
|
@ -28,13 +29,14 @@ type ListOauth2Option struct {
|
|||
|
||||
// CreateOauth2Option required options for creating an Application
|
||||
type CreateOauth2Option struct {
|
||||
Name string `json:"name"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
Name string `json:"name"`
|
||||
ConfidentialClient bool `json:"confidential_client"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
}
|
||||
|
||||
// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object.
|
||||
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
|
||||
}
|
||||
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.
|
||||
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
|
||||
}
|
||||
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.
|
||||
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
|
||||
}
|
||||
oauth2s := &Oauth2{}
|
||||
|
@ -72,7 +74,7 @@ func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) {
|
|||
|
||||
// ListOauth2 all of your Oauth2 Applications.
|
||||
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
|
||||
}
|
||||
opt.setDefaults()
|
||||
|
@ -83,7 +85,7 @@ func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error)
|
|||
|
||||
// DeleteOauth2 delete an Oauth2 application by ID
|
||||
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
|
||||
}
|
||||
_, 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)
|
||||
c.SetSudo(user.UserName)
|
||||
|
||||
newApp, _, err := c.CreateOauth2(CreateOauth2Option{Name: "test", RedirectURIs: []string{"http://test/test"}})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, newApp)
|
||||
assert.EqualValues(t, "test", newApp.Name)
|
||||
type test struct {
|
||||
name string
|
||||
confidentialClient *bool
|
||||
}
|
||||
boolTrue := true
|
||||
boolFalse := false
|
||||
|
||||
a, _, err := c.ListOauth2(ListOauth2Option{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, a, 1)
|
||||
assert.EqualValues(t, newApp.Name, a[0].Name)
|
||||
testCases := []test{
|
||||
{"ConfidentialClient unset should fallback to false", nil},
|
||||
{"ConfidentialClient true", &boolTrue},
|
||||
{"ConfidentialClient false", &boolFalse},
|
||||
}
|
||||
|
||||
b, _, err := c.GetOauth2(newApp.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, newApp.Name, b.Name)
|
||||
for _, testCase := range testCases {
|
||||
createOptions := CreateOauth2Option{
|
||||
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"}})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, newApp.Name, b.Name)
|
||||
assert.EqualValues(t, "https://test/login", b.RedirectURIs[0])
|
||||
assert.EqualValues(t, newApp.ID, b.ID)
|
||||
assert.NotEqual(t, newApp.ClientSecret, b.ClientSecret)
|
||||
newApp, _, err := c.CreateOauth2(createOptions)
|
||||
assert.NoError(t, err, testCase.name)
|
||||
assert.NotNil(t, newApp, testCase.name)
|
||||
assert.EqualValues(t, "test", newApp.Name, testCase.name)
|
||||
if testCase.confidentialClient != nil {
|
||||
assert.EqualValues(t, *testCase.confidentialClient, newApp.ConfidentialClient, testCase.name)
|
||||
} else {
|
||||
assert.EqualValues(t, false, newApp.ConfidentialClient, testCase.name)
|
||||
}
|
||||
|
||||
_, err = c.DeleteOauth2(newApp.ID)
|
||||
assert.NoError(t, err)
|
||||
a, _, err := c.ListOauth2(ListOauth2Option{})
|
||||
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
|
||||
func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&user); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
orgs := make([]*Organization, 0, opt.PageSize)
|
||||
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
|
||||
func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&orgname); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
org := new(Organization)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org)
|
||||
return org, resp, err
|
||||
|
@ -67,12 +73,13 @@ func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) {
|
|||
|
||||
// CreateOrgOption options for creating an organization
|
||||
type CreateOrgOption struct {
|
||||
Name string `json:"username"`
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
Visibility VisibleType `json:"visibility"`
|
||||
Name string `json:"username"`
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
Visibility VisibleType `json:"visibility"`
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
||||
}
|
||||
|
||||
// checkVisibilityOpt check if mode exist
|
||||
|
@ -86,7 +93,7 @@ func (opt CreateOrgOption) Validate() error {
|
|||
return fmt.Errorf("empty org name")
|
||||
}
|
||||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
|
||||
return fmt.Errorf("infalid bisibility option")
|
||||
return fmt.Errorf("invalid visibility option")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -117,13 +124,16 @@ type EditOrgOption struct {
|
|||
// Validate the EditOrgOption struct
|
||||
func (opt EditOrgOption) Validate() error {
|
||||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
|
||||
return fmt.Errorf("infalid bisibility option")
|
||||
return fmt.Errorf("invalid visibility option")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EditOrg modify one organization via options
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -137,6 +147,9 @@ func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) {
|
|||
|
||||
// DeleteOrg deletes an organization
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -23,10 +26,13 @@ type ListOrgMembershipOption struct {
|
|||
|
||||
// ListOrgMembership list an organization's members
|
||||
func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
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()
|
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
|
||||
return users, resp, err
|
||||
|
@ -34,10 +40,13 @@ func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*
|
|||
|
||||
// ListPublicOrgMembership list an organization's members
|
||||
func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
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()
|
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
|
||||
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
|
||||
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 {
|
||||
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
|
||||
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 {
|
||||
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
|
||||
func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
status int
|
||||
err error
|
||||
resp *Response
|
||||
)
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
/**
|
||||
/*
|
||||
// DeleteOrgMembership remove a member from an organization
|
||||
func (c *Client) DeleteOrgMembership(org, user string) error {}
|
||||
|
||||
|
||||
*/
|
||||
func TestOrgMembership(t *testing.T) {
|
||||
log.Println("== TestOrgMembership ==")
|
||||
|
@ -35,6 +33,11 @@ func TestOrgMembership(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
check, _, err = c.CheckPublicOrgMembership(newOrg.UserName, user.UserName)
|
||||
|
|
|
@ -8,21 +8,47 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Team represents a team in an organization
|
||||
type Team struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||
Units []string `json:"units"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
Units []RepoUnitType `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
|
||||
type ListTeamsOptions struct {
|
||||
ListOptions
|
||||
|
@ -30,6 +56,9 @@ type ListTeamsOptions struct {
|
|||
|
||||
// ListOrgTeams lists all teams of an organization
|
||||
func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
teams := make([]*Team, 0, opt.PageSize)
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
type CreateTeamOption struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||
Units []string `json:"units"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
Units []RepoUnitType `json:"units"`
|
||||
}
|
||||
|
||||
// Validate the CreateTeamOption struct
|
||||
func (opt CreateTeamOption) Validate() error {
|
||||
func (opt *CreateTeamOption) Validate() error {
|
||||
if opt.Permission == AccessModeOwner {
|
||||
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
|
||||
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
|
||||
}
|
||||
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
|
||||
type EditTeamOption struct {
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories *bool `json:"includes_all_repositories"`
|
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||
Units []string `json:"units"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories *bool `json:"includes_all_repositories"`
|
||||
Units []RepoUnitType `json:"units"`
|
||||
}
|
||||
|
||||
// Validate the EditTeamOption struct
|
||||
func (opt EditTeamOption) Validate() error {
|
||||
func (opt *EditTeamOption) Validate() error {
|
||||
if opt.Permission == AccessModeOwner {
|
||||
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
|
||||
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
|
||||
}
|
||||
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
|
||||
func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&user); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
m := new(User)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m)
|
||||
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
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// RemoveTeamMember removes a member from a team
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
@ -191,12 +268,18 @@ func (c *Client) ListTeamRepositories(id int64, opt ListTeamRepositoriesOptions)
|
|||
|
||||
// AddTeamRepository adds a repository to a team
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// RemoveTeamRepository removes a repository from a team
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
PatchURL string `json:"patch_url"`
|
||||
|
||||
Mergeable bool `json:"mergeable"`
|
||||
HasMerged bool `json:"merged"`
|
||||
Merged *time.Time `json:"merged_at"`
|
||||
MergedCommitID *string `json:"merge_commit_sha"`
|
||||
MergedBy *User `json:"merged_by"`
|
||||
Mergeable bool `json:"mergeable"`
|
||||
HasMerged bool `json:"merged"`
|
||||
Merged *time.Time `json:"merged_at"`
|
||||
MergedCommitID *string `json:"merge_commit_sha"`
|
||||
MergedBy *User `json:"merged_by"`
|
||||
AllowMaintainerEdit bool `json:"allow_maintainer_edit"`
|
||||
|
||||
Base *PRBranchInfo `json:"base"`
|
||||
Head *PRBranchInfo `json:"head"`
|
||||
|
@ -59,6 +60,19 @@ type PullRequest struct {
|
|||
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
|
||||
type ListPullRequestsOptions struct {
|
||||
ListOptions
|
||||
|
@ -99,19 +113,37 @@ func (opt *ListPullRequestsOptions) QueryEncode() string {
|
|||
|
||||
// ListRepoPullRequests list PRs of one repository
|
||||
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()
|
||||
prs := make([]*PullRequest, 0, opt.PageSize)
|
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", owner, repo))
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
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
|
||||
}
|
||||
|
||||
// GetPullRequest get information of one PR
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -130,6 +162,9 @@ type CreatePullRequestOption struct {
|
|||
|
||||
// CreatePullRequest create pull request with options
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -143,15 +178,17 @@ func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOpti
|
|||
|
||||
// EditPullRequestOption options when modify pull request
|
||||
type EditPullRequestOption struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Base string `json:"base"`
|
||||
Assignee string `json:"assignee"`
|
||||
Assignees []string `json:"assignees"`
|
||||
Milestone int64 `json:"milestone"`
|
||||
Labels []int64 `json:"labels"`
|
||||
State *StateType `json:"state"`
|
||||
Deadline *time.Time `json:"due_date"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Base string `json:"base"`
|
||||
Assignee string `json:"assignee"`
|
||||
Assignees []string `json:"assignees"`
|
||||
Milestone int64 `json:"milestone"`
|
||||
Labels []int64 `json:"labels"`
|
||||
State *StateType `json:"state"`
|
||||
Deadline *time.Time `json:"due_date"`
|
||||
RemoveDeadline *bool `json:"unset_due_date"`
|
||||
AllowMaintainerEdit *bool `json:"allow_maintainer_edit"`
|
||||
}
|
||||
|
||||
// Validate the EditPullRequestOption struct
|
||||
|
@ -160,7 +197,7 @@ func (opt EditPullRequestOption) Validate(c *Client) error {
|
|||
return fmt.Errorf("title is empty")
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +206,9 @@ func (opt EditPullRequestOption) Validate(c *Client) error {
|
|||
|
||||
// EditPullRequest modify pull request with PR id and options
|
||||
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 {
|
||||
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
|
||||
type MergePullRequestOption struct {
|
||||
Style MergeStyle `json:"Do"`
|
||||
Title string `json:"MergeTitleField"`
|
||||
Message string `json:"MergeMessageField"`
|
||||
Style MergeStyle `json:"Do"`
|
||||
MergeCommitID string `json:"MergeCommitID"`
|
||||
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
|
||||
func (opt MergePullRequestOption) Validate(c *Client) error {
|
||||
if opt.Style == MergeStyleSquash {
|
||||
if err := c.CheckServerVersionConstraint(">=1.11.5"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_5); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -202,6 +247,9 @@ func (opt MergePullRequestOption) Validate(c *Client) error {
|
|||
|
||||
// MergePullRequest merge a PR to repository by PR id
|
||||
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 {
|
||||
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
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
return false, resp, err
|
||||
}
|
||||
|
@ -227,9 +277,32 @@ func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Re
|
|||
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
|
||||
func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64) ([]byte, *Response, error) {
|
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
||||
func (c *Client) getPullRequestDiffOrPatch(owner, repo string, kind pullRequestDiffType, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) {
|
||||
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)
|
||||
if err2 != nil {
|
||||
return nil, nil, err
|
||||
|
@ -237,17 +310,74 @@ func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64
|
|||
if r.Private {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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
|
||||
func (c *Client) GetPullRequestDiff(owner, repo string, index int64) ([]byte, *Response, error) {
|
||||
return c.getPullRequestDiffOrPatch(owner, repo, "diff", index)
|
||||
// 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, opts PullRequestDiffOptions) ([]byte, *Response, error) {
|
||||
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
|
||||
type PullReview struct {
|
||||
ID int64 `json:"id"`
|
||||
Reviewer *User `json:"user"`
|
||||
State ReviewStateType `json:"state"`
|
||||
Body string `json:"body"`
|
||||
CommitID string `json:"commit_id"`
|
||||
Stale bool `json:"stale"`
|
||||
Official bool `json:"official"`
|
||||
CodeCommentsCount int `json:"comments_count"`
|
||||
Submitted time.Time `json:"submitted_at"`
|
||||
ID int64 `json:"id"`
|
||||
Reviewer *User `json:"user"`
|
||||
ReviewerTeam *Team `json:"team"`
|
||||
State ReviewStateType `json:"state"`
|
||||
Body string `json:"body"`
|
||||
CommitID string `json:"commit_id"`
|
||||
// Stale indicates if the pull has changed since the review
|
||||
Stale bool `json:"stale"`
|
||||
// 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"`
|
||||
HTMLPullURL string `json:"pull_request_url"`
|
||||
|
@ -53,6 +57,7 @@ type PullReviewComment struct {
|
|||
Body string `json:"body"`
|
||||
Reviewer *User `json:"user"`
|
||||
ReviewID int64 `json:"pull_request_review_id"`
|
||||
Resolver *User `json:"resolver"`
|
||||
|
||||
Created time.Time `json:"created_at"`
|
||||
Updated time.Time `json:"updated_at"`
|
||||
|
@ -93,6 +98,17 @@ type SubmitPullReviewOptions struct {
|
|||
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
|
||||
type ListPullReviewsOptions struct {
|
||||
ListOptions
|
||||
|
@ -100,7 +116,7 @@ type ListPullReviewsOptions struct {
|
|||
|
||||
// Validate the CreatePullReviewOptions struct
|
||||
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")
|
||||
}
|
||||
for i := range opt.Comments {
|
||||
|
@ -132,7 +148,10 @@ func (opt CreatePullReviewComment) Validate() error {
|
|||
|
||||
// ListPullReviews lists all reviews of a pull request
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -158,7 +180,10 @@ func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview
|
|||
|
||||
// ListPullReviewComments lists all comments of a pull request review
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -180,7 +208,10 @@ func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Respons
|
|||
|
||||
// CreatePullReview create a review to an pull request
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
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)
|
||||
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 ==")
|
||||
c := newTestClient()
|
||||
|
||||
var repoName = "Reviews"
|
||||
repoName := "Reviews"
|
||||
repo, pull, submitter, reviewer, success := preparePullReviewTest(t, c, repoName)
|
||||
if !success {
|
||||
return
|
||||
}
|
||||
defer c.AdminDeleteUser(reviewer.UserName)
|
||||
defer c.AdminDeleteUser(submitter.UserName)
|
||||
|
||||
// CreatePullReview
|
||||
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)
|
||||
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,
|
||||
Body: "lgtm it myself",
|
||||
})
|
||||
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,
|
||||
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{
|
||||
State: ReviewStateApproved,
|
||||
Body: "lgtm",
|
||||
Comments: []CreatePullReviewComment{{
|
||||
Path: "WOW-file",
|
||||
Body: "no better name - really?",
|
||||
NewLineNum: 1,
|
||||
},
|
||||
Comments: []CreatePullReviewComment{
|
||||
{
|
||||
Path: "WOW-file",
|
||||
Body: "no better name - really?",
|
||||
NewLineNum: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
@ -64,9 +63,7 @@ func TestPullReview(t *testing.T) {
|
|||
// ListPullReviews
|
||||
c.SetSudo("")
|
||||
rl, _, err := c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{})
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, rl, 3)
|
||||
for i := range rl {
|
||||
assert.EqualValues(t, pull.HTMLURL, rl[i].HTMLPullURL)
|
||||
|
@ -89,23 +86,26 @@ func TestPullReview(t *testing.T) {
|
|||
|
||||
// SubmitPullReview
|
||||
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: "...",
|
||||
Comments: []CreatePullReviewComment{{
|
||||
Path: "WOW-file",
|
||||
Body: "its ok",
|
||||
NewLineNum: 1,
|
||||
},
|
||||
Comments: []CreatePullReviewComment{
|
||||
{
|
||||
Path: "WOW-file",
|
||||
Body: "its ok",
|
||||
NewLineNum: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, resp)
|
||||
r5, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
|
||||
Body: "...",
|
||||
Comments: []CreatePullReviewComment{{
|
||||
Path: "WOW-file",
|
||||
Body: "hehe and here it is",
|
||||
NewLineNum: 3,
|
||||
},
|
||||
Comments: []CreatePullReviewComment{
|
||||
{
|
||||
Path: "WOW-file",
|
||||
Body: "hehe and here it is",
|
||||
NewLineNum: 3,
|
||||
},
|
||||
},
|
||||
})
|
||||
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) {
|
||||
|
@ -154,7 +203,7 @@ func preparePullReviewTest(t *testing.T, c *Client, repoName string) (*Repositor
|
|||
Content: "QSBuZXcgRmlsZQoKYW5kIHNvbWUgbGluZXMK",
|
||||
FileOptions: FileOptions{
|
||||
Message: "creat a new file",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
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{
|
||||
Base: "master",
|
||||
Base: "main",
|
||||
Head: "new_file",
|
||||
Title: "Creat a NewFile",
|
||||
})
|
||||
|
|
|
@ -17,8 +17,8 @@ func TestPull(t *testing.T) {
|
|||
user, _, err := c.GetMyUserInfo()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var repoName = "repo_pull_test"
|
||||
var forkOrg = "ForkOrg"
|
||||
repoName := "repo_pull_test"
|
||||
forkOrg := "ForkOrg"
|
||||
if !preparePullTest(t, c, repoName, forkOrg) {
|
||||
return
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ func TestPull(t *testing.T) {
|
|||
assert.Len(t, pulls, 0)
|
||||
|
||||
pullUpdateFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||
Base: "master",
|
||||
Base: "main",
|
||||
Head: forkOrg + ":overwrite_licence",
|
||||
Title: "overwrite a file",
|
||||
})
|
||||
|
@ -37,7 +37,7 @@ func TestPull(t *testing.T) {
|
|||
assert.NotNil(t, pullUpdateFile)
|
||||
|
||||
pullNewFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||
Base: "master",
|
||||
Base: "main",
|
||||
Head: forkOrg + ":new_file",
|
||||
Title: "create a file",
|
||||
})
|
||||
|
@ -45,7 +45,7 @@ func TestPull(t *testing.T) {
|
|||
assert.NotNil(t, pullNewFile)
|
||||
|
||||
pullConflict, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||
Base: "master",
|
||||
Base: "main",
|
||||
Head: forkOrg + ":will_conflict",
|
||||
Title: "this pull will conflict",
|
||||
})
|
||||
|
@ -56,16 +56,35 @@ func TestPull(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
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.Len(t, diff, 1310)
|
||||
assert.True(t, len(diff) > 1100 && len(diff) < 1300)
|
||||
patch, _, err := c.GetPullRequestPatch(c.username, repoName, pullUpdateFile.Index)
|
||||
assert.NoError(t, err)
|
||||
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
|
||||
pr, _, err := c.GetPullRequest(user.UserName, repoName, pullUpdateFile.Index)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pr)
|
||||
assert.False(t, pullUpdateFile.HasMerged)
|
||||
assert.True(t, pullUpdateFile.Mergeable)
|
||||
merged, _, err := c.MergePullRequest(user.UserName, repoName, pullUpdateFile.Index, MergePullRequestOption{
|
||||
|
@ -89,8 +108,8 @@ func TestPull(t *testing.T) {
|
|||
// test conflict pull
|
||||
pr, _, err = c.GetPullRequest(user.UserName, repoName, pullConflict.Index)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, pullConflict.HasMerged)
|
||||
assert.False(t, pullConflict.Mergeable)
|
||||
assert.False(t, pr.HasMerged)
|
||||
assert.False(t, pr.Mergeable)
|
||||
merged, _, err = c.MergePullRequest(user.UserName, repoName, pullConflict.Index, MergePullRequestOption{
|
||||
Style: MergeStyleMerge,
|
||||
Title: "pullConflict",
|
||||
|
@ -137,18 +156,18 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
|||
assert.NoError(t, err)
|
||||
assert.NotNil(t, forkRepo)
|
||||
|
||||
masterLicence, _, err := c.GetContents(forkRepo.Owner.UserName, forkRepo.Name, "master", "LICENSE")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, masterLicence) {
|
||||
mainLicense, _, err := c.GetContents(forkRepo.Owner.UserName, forkRepo.Name, "main", "LICENSE")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) {
|
||||
return false
|
||||
}
|
||||
|
||||
updatedFile, _, err := c.UpdateFile(forkRepo.Owner.UserName, forkRepo.Name, "LICENSE", UpdateFileOptions{
|
||||
FileOptions: FileOptions{
|
||||
Message: "Overwrite",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
NewBranchName: "overwrite_licence",
|
||||
},
|
||||
SHA: masterLicence.SHA,
|
||||
SHA: mainLicense.SHA,
|
||||
Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
|
||||
})
|
||||
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=",
|
||||
FileOptions: FileOptions{
|
||||
Message: "creat a new file",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
NewBranchName: "new_file",
|
||||
},
|
||||
})
|
||||
|
@ -171,7 +190,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
|||
Content: "U3RhcnQgQ29uZmxpY3QK",
|
||||
FileOptions: FileOptions{
|
||||
Message: "Start Conflict",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
},
|
||||
})
|
||||
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",
|
||||
FileOptions: FileOptions{
|
||||
Message: "creat a new file witch will conflict",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
NewBranchName: "will_conflict",
|
||||
},
|
||||
})
|
||||
|
|
114
gitea/release.go
114
gitea/release.go
|
@ -8,6 +8,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
@ -20,6 +21,7 @@ type Release struct {
|
|||
Title string `json:"name"`
|
||||
Note string `json:"body"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
TarURL string `json:"tarball_url"`
|
||||
ZipURL string `json:"zipball_url"`
|
||||
IsDraft bool `json:"draft"`
|
||||
|
@ -33,23 +35,72 @@ type Release struct {
|
|||
// ListReleasesOptions options for listing repository's releases
|
||||
type ListReleasesOptions struct {
|
||||
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
|
||||
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()
|
||||
releases := make([]*Release, 0, opt.PageSize)
|
||||
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)
|
||||
return releases, resp, err
|
||||
}
|
||||
|
||||
// GetRelease get a release of a repository
|
||||
func (c *Client) GetRelease(user, repo string, id int64) (*Release, *Response, error) {
|
||||
// GetRelease get a release of a repository by id
|
||||
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)
|
||||
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)
|
||||
return r, resp, err
|
||||
}
|
||||
|
@ -73,7 +124,10 @@ func (opt CreateReleaseOption) Validate() error {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -83,7 +137,7 @@ func (c *Client) CreateRelease(user, repo string, opt CreateReleaseOption) (*Rel
|
|||
}
|
||||
r := new(Release)
|
||||
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)
|
||||
return r, resp, err
|
||||
}
|
||||
|
@ -99,22 +153,62 @@ type EditReleaseOption struct {
|
|||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
r := new(Release)
|
||||
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)
|
||||
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) {
|
||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, resp, err := c.getResponse("DELETE",
|
||||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
|
||||
nil, nil)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -21,42 +22,82 @@ type Permission struct {
|
|||
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
|
||||
type Repository struct {
|
||||
ID int64 `json:"id"`
|
||||
Owner *User `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
Empty bool `json:"empty"`
|
||||
Private bool `json:"private"`
|
||||
Fork bool `json:"fork"`
|
||||
Parent *Repository `json:"parent"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Size int `json:"size"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
SSHURL string `json:"ssh_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
OriginalURL string `json:"original_url"`
|
||||
Website string `json:"website"`
|
||||
Stars int `json:"stars_count"`
|
||||
Forks int `json:"forks_count"`
|
||||
Watchers int `json:"watchers_count"`
|
||||
OpenIssues int `json:"open_issues_count"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
Archived bool `json:"archived"`
|
||||
Created time.Time `json:"created_at"`
|
||||
Updated time.Time `json:"updated_at"`
|
||||
Permissions *Permission `json:"permissions,omitempty"`
|
||||
HasIssues bool `json:"has_issues"`
|
||||
HasWiki bool `json:"has_wiki"`
|
||||
HasPullRequests bool `json:"has_pull_requests"`
|
||||
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"`
|
||||
ID int64 `json:"id"`
|
||||
Owner *User `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
Empty bool `json:"empty"`
|
||||
Private bool `json:"private"`
|
||||
Fork bool `json:"fork"`
|
||||
Template bool `json:"template"`
|
||||
Parent *Repository `json:"parent"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Size int `json:"size"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
SSHURL string `json:"ssh_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
OriginalURL string `json:"original_url"`
|
||||
Website string `json:"website"`
|
||||
Stars int `json:"stars_count"`
|
||||
Forks int `json:"forks_count"`
|
||||
Watchers int `json:"watchers_count"`
|
||||
OpenIssues int `json:"open_issues_count"`
|
||||
OpenPulls int `json:"open_pr_counter"`
|
||||
Releases int `json:"release_counter"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
Archived bool `json:"archived"`
|
||||
Created time.Time `json:"created_at"`
|
||||
Updated time.Time `json:"updated_at"`
|
||||
Permissions *Permission `json:"permissions,omitempty"`
|
||||
HasIssues bool `json:"has_issues"`
|
||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||
HasWiki bool `json:"has_wiki"`
|
||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||
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
|
||||
|
@ -73,6 +114,20 @@ const (
|
|||
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
|
||||
type ListReposOptions struct {
|
||||
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
|
||||
func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&user); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
repos := make([]*Repository, 0, opt.PageSize)
|
||||
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
|
||||
func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
repos := make([]*Repository, 0, opt.PageSize)
|
||||
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 {
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
// 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 {
|
||||
// private repos only not supported on gitea <= 1.11.x
|
||||
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"`
|
||||
// Whether the repository should be auto-intialized?
|
||||
AutoInit bool `json:"auto_init"`
|
||||
// Whether the repository is template
|
||||
Template bool `json:"template"`
|
||||
// Gitignores to use
|
||||
Gitignores string `json:"gitignores"`
|
||||
// License to use
|
||||
|
@ -257,19 +322,35 @@ type CreateRepoOption struct {
|
|||
Readme string `json:"readme"`
|
||||
// DefaultBranch of the repository (used when initializes and in template)
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
// TrustModel of the repository
|
||||
TrustModel TrustModel `json:"trust_model"`
|
||||
}
|
||||
|
||||
// Validate the CreateRepoOption struct
|
||||
func (opt CreateRepoOption) Validate() error {
|
||||
func (opt CreateRepoOption) Validate(c *Client) error {
|
||||
if len(strings.TrimSpace(opt.Name)) == 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// CreateRepo creates a repository for authenticated user.
|
||||
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
|
||||
}
|
||||
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.
|
||||
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
|
||||
}
|
||||
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.
|
||||
func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
repo := new(Repository)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo)
|
||||
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
|
||||
type EditRepoOption struct {
|
||||
// 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
|
||||
// owners and a non-owner tries to change the value of private.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
DefaultBranch *string `json:"default_branch,omitempty"`
|
||||
// either `true` to allow pull requests, or `false` to prevent pull request.
|
||||
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`.
|
||||
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`.
|
||||
|
@ -334,10 +444,22 @@ type EditRepoOption struct {
|
|||
AllowSquash *bool `json:"allow_squash_merge,omitempty"`
|
||||
// set to `true` to archive this repository.
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
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.
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// MirrorSync adds a mirrored repository to the mirror sync queue.
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GetRepoLanguages return language stats of a repo
|
||||
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)
|
||||
|
||||
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
|
||||
// e.g.: ref -> master, 70b7c74b33, v1.2.1, ...
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"`
|
||||
}
|
||||
|
||||
// FIXME: consider using same format as API when commits API are added.
|
||||
// applies to PayloadCommit and PayloadCommitVerification
|
||||
|
||||
// PayloadCommit represents a commit
|
||||
type PayloadCommit struct {
|
||||
// sha1 hash of the commit
|
||||
|
@ -66,6 +63,9 @@ type ListRepoBranchesOptions struct {
|
|||
|
||||
// ListRepoBranches list all the branches of one repository
|
||||
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()
|
||||
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)
|
||||
|
@ -74,6 +74,9 @@ func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions
|
|||
|
||||
// GetRepoBranch get one branch's information of one repository
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b)
|
||||
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
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
if err := opt.Validate(); err != nil {
|
||||
|
|
|
@ -14,75 +14,83 @@ import (
|
|||
|
||||
// BranchProtection represents a branch protection for a repository
|
||||
type BranchProtection struct {
|
||||
BranchName string `json:"branch_name"`
|
||||
EnablePush bool `json:"enable_push"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
||||
EnableStatusCheck bool `json:"enable_status_check"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
||||
RequiredApprovals int64 `json:"required_approvals"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
||||
Created time.Time `json:"created_at"`
|
||||
Updated time.Time `json:"updated_at"`
|
||||
BranchName string `json:"branch_name"`
|
||||
RuleName string `json:"rule_name"`
|
||||
EnablePush bool `json:"enable_push"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
||||
EnableStatusCheck bool `json:"enable_status_check"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
||||
RequiredApprovals int64 `json:"required_approvals"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
||||
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
|
||||
type CreateBranchProtectionOption struct {
|
||||
BranchName string `json:"branch_name"`
|
||||
EnablePush bool `json:"enable_push"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
||||
EnableStatusCheck bool `json:"enable_status_check"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
||||
RequiredApprovals int64 `json:"required_approvals"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
||||
BranchName string `json:"branch_name"`
|
||||
RuleName string `json:"rule_name"`
|
||||
EnablePush bool `json:"enable_push"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
||||
EnableStatusCheck bool `json:"enable_status_check"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
||||
RequiredApprovals int64 `json:"required_approvals"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
||||
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
|
||||
type EditBranchProtectionOption struct {
|
||||
EnablePush *bool `json:"enable_push"`
|
||||
EnablePushWhitelist *bool `json:"enable_push_whitelist"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
||||
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"`
|
||||
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
||||
EnableStatusCheck *bool `json:"enable_status_check"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
||||
RequiredApprovals *int64 `json:"required_approvals"`
|
||||
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"`
|
||||
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
|
||||
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
|
||||
RequireSignedCommits *bool `json:"require_signed_commits"`
|
||||
ProtectedFilePatterns *string `json:"protected_file_patterns"`
|
||||
EnablePush *bool `json:"enable_push"`
|
||||
EnablePushWhitelist *bool `json:"enable_push_whitelist"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
||||
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"`
|
||||
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
||||
EnableStatusCheck *bool `json:"enable_status_check"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
||||
RequiredApprovals *int64 `json:"required_approvals"`
|
||||
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
||||
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"`
|
||||
BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"`
|
||||
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
|
||||
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
|
||||
RequireSignedCommits *bool `json:"require_signed_commits"`
|
||||
ProtectedFilePatterns *string `json:"protected_file_patterns"`
|
||||
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
|
||||
}
|
||||
|
||||
// ListBranchProtectionsOptions list branch protection options
|
||||
|
@ -92,7 +100,10 @@ type ListBranchProtectionsOptions struct {
|
|||
|
||||
// ListBranchProtections list branch protections for a repo
|
||||
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
|
||||
}
|
||||
bps := make([]*BranchProtection, 0, opt.PageSize)
|
||||
|
@ -104,7 +115,10 @@ func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtect
|
|||
|
||||
// GetBranchProtection gets a branch protection
|
||||
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
|
||||
}
|
||||
bp := new(BranchProtection)
|
||||
|
@ -114,7 +128,10 @@ func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtectio
|
|||
|
||||
// CreateBranchProtection creates a branch protection for a repo
|
||||
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
|
||||
}
|
||||
bp := new(BranchProtection)
|
||||
|
@ -128,7 +145,10 @@ func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProt
|
|||
|
||||
// EditBranchProtection edits a branch protection for a repo
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
_, 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 (
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -14,26 +15,31 @@ import (
|
|||
func TestRepoBranches(t *testing.T) {
|
||||
log.Println("== TestRepoBranches ==")
|
||||
c := newTestClient()
|
||||
var repoName = "branches"
|
||||
repoName := "branches"
|
||||
|
||||
repo := prepareBranchTest(t, c, repoName)
|
||||
if repo == nil {
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
bl, _, err := c.ListRepoBranches(repo.Owner.UserName, repo.Name, ListRepoBranchesOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, bl, 3)
|
||||
assert.EqualValues(t, "feature", bl[0].Name)
|
||||
assert.EqualValues(t, "master", bl[1].Name)
|
||||
assert.EqualValues(t, "update", bl[2].Name)
|
||||
|
||||
branchNames := make([]string, len(bl))
|
||||
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")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, bl[2].Commit.ID, b.Commit.ID)
|
||||
assert.EqualValues(t, bl[2].Commit.Added, b.Commit.Added)
|
||||
assert.EqualValues(t, branches["update"].Commit.ID, b.Commit.ID)
|
||||
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.False(t, s)
|
||||
s, _, err = c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "feature")
|
||||
|
@ -59,7 +65,7 @@ func TestRepoBranches(t *testing.T) {
|
|||
func TestRepoBranchProtection(t *testing.T) {
|
||||
log.Println("== TestRepoBranchProtection ==")
|
||||
c := newTestClient()
|
||||
var repoName = "BranchProtection"
|
||||
repoName := "BranchProtection"
|
||||
|
||||
repo := prepareBranchTest(t, c, repoName)
|
||||
if repo == nil {
|
||||
|
@ -74,7 +80,7 @@ func TestRepoBranchProtection(t *testing.T) {
|
|||
|
||||
// CreateBranchProtection
|
||||
bp, _, err := c.CreateBranchProtection(repo.Owner.UserName, repo.Name, CreateBranchProtectionOption{
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
EnablePush: true,
|
||||
EnablePushWhitelist: true,
|
||||
PushWhitelistUsernames: []string{"test01"},
|
||||
|
@ -83,7 +89,7 @@ func TestRepoBranchProtection(t *testing.T) {
|
|||
BlockOnOutdatedBranch: true,
|
||||
})
|
||||
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, true, bp.EnablePush)
|
||||
assert.EqualValues(t, true, bp.EnablePushWhitelist)
|
||||
|
@ -107,17 +113,13 @@ func TestRepoBranchProtection(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, bpl[0], bp)
|
||||
|
||||
optTrue := true
|
||||
optFalse := false
|
||||
one := int64(1)
|
||||
|
||||
// EditBranchProtection
|
||||
bp, _, err = c.EditBranchProtection(repo.Owner.UserName, repo.Name, bpl[0].BranchName, EditBranchProtectionOption{
|
||||
EnablePush: &optFalse,
|
||||
EnablePushWhitelist: &optFalse,
|
||||
EnablePush: OptionalBool(false),
|
||||
EnablePushWhitelist: OptionalBool(false),
|
||||
PushWhitelistUsernames: nil,
|
||||
RequiredApprovals: &one,
|
||||
EnableApprovalsWhitelist: &optTrue,
|
||||
RequiredApprovals: OptionalInt64(1),
|
||||
EnableApprovalsWhitelist: OptionalBool(true),
|
||||
ApprovalsWhitelistUsernames: []string{"test01"},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
@ -140,18 +142,18 @@ func prepareBranchTest(t *testing.T, c *Client, repoName string) *Repository {
|
|||
return nil
|
||||
}
|
||||
|
||||
masterLicence, _, err := c.GetContents(origRepo.Owner.UserName, origRepo.Name, "master", "README.md")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, masterLicence) {
|
||||
mainLicense, _, err := c.GetContents(origRepo.Owner.UserName, origRepo.Name, "main", "README.md")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) {
|
||||
return nil
|
||||
}
|
||||
|
||||
updatedFile, _, err := c.UpdateFile(origRepo.Owner.UserName, origRepo.Name, "README.md", UpdateFileOptions{
|
||||
FileOptions: FileOptions{
|
||||
Message: "update it",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
NewBranchName: "update",
|
||||
},
|
||||
SHA: masterLicence.SHA,
|
||||
SHA: mainLicense.SHA,
|
||||
Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
|
||||
})
|
||||
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=",
|
||||
FileOptions: FileOptions{
|
||||
Message: "creat a new file",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
NewBranchName: "feature",
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
@ -15,8 +16,18 @@ type ListCollaboratorsOptions struct {
|
|||
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
|
||||
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()
|
||||
collaborators := make([]*User, 0, opt.PageSize)
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
return false, resp, err
|
||||
|
@ -37,6 +51,26 @@ func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Respons
|
|||
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
|
||||
type AddCollaboratorOption struct {
|
||||
Permission *AccessMode `json:"permission"`
|
||||
|
@ -59,7 +93,7 @@ const (
|
|||
)
|
||||
|
||||
// Validate the AddCollaboratorOption struct
|
||||
func (opt AddCollaboratorOption) Validate() error {
|
||||
func (opt *AddCollaboratorOption) Validate() error {
|
||||
if opt.Permission != nil {
|
||||
if *opt.Permission == AccessModeOwner {
|
||||
*opt.Permission = AccessModeAdmin
|
||||
|
@ -78,7 +112,10 @@ func (opt AddCollaboratorOption) Validate() error {
|
|||
|
||||
// AddCollaborator add some user as a collaborator of a repository
|
||||
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
|
||||
}
|
||||
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
|
||||
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",
|
||||
fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
type CommitMeta struct {
|
||||
URL string `json:"url"`
|
||||
SHA string `json:"sha"`
|
||||
URL string `json:"url"`
|
||||
SHA string `json:"sha"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
// 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.
|
||||
type RepoCommit struct {
|
||||
URL string `json:"url"`
|
||||
Author *CommitUser `json:"author"`
|
||||
Committer *CommitUser `json:"committer"`
|
||||
Message string `json:"message"`
|
||||
Tree *CommitMeta `json:"tree"`
|
||||
URL string `json:"url"`
|
||||
Author *CommitUser `json:"author"`
|
||||
Committer *CommitUser `json:"committer"`
|
||||
Message string `json:"message"`
|
||||
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.
|
||||
type Commit struct {
|
||||
*CommitMeta
|
||||
HTMLURL string `json:"html_url"`
|
||||
RepoCommit *RepoCommit `json:"commit"`
|
||||
Author *User `json:"author"`
|
||||
Committer *User `json:"committer"`
|
||||
Parents []*CommitMeta `json:"parents"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
RepoCommit *RepoCommit `json:"commit"`
|
||||
Author *User `json:"author"`
|
||||
Committer *User `json:"committer"`
|
||||
Parents []*CommitMeta `json:"parents"`
|
||||
Files []*CommitAffectedFiles `json:"files"`
|
||||
Stats *CommitStats `json:"stats"`
|
||||
}
|
||||
|
||||
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
|
||||
|
@ -54,8 +65,16 @@ type CommitDateOptions struct {
|
|||
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
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s", user, repo, commitID), nil, nil, &commit)
|
||||
return commit, resp, err
|
||||
|
@ -64,21 +83,29 @@ func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Respons
|
|||
// ListCommitOptions list commit options
|
||||
type ListCommitOptions struct {
|
||||
ListOptions
|
||||
//SHA or branch to start listing commits from (usually 'master')
|
||||
// SHA or branch to start listing commits from (usually 'master')
|
||||
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
|
||||
func (opt *ListCommitOptions) QueryEncode() string {
|
||||
query := opt.ListOptions.getURLQuery()
|
||||
query := opt.getURLQuery()
|
||||
if opt.SHA != "" {
|
||||
query.Add("sha", opt.SHA)
|
||||
}
|
||||
if opt.Path != "" {
|
||||
query.Add("path", opt.Path)
|
||||
}
|
||||
return query.Encode()
|
||||
}
|
||||
|
||||
// ListRepoCommits return list of commits from a repo
|
||||
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))
|
||||
opt.setDefaults()
|
||||
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)
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
|
@ -22,4 +23,38 @@ func TestListRepoCommits(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, l, 1)
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FileOptions options for all file APIs
|
||||
|
@ -23,6 +26,8 @@ type FileOptions struct {
|
|||
Author Identity `json:"author"`
|
||||
Committer Identity `json:"committer"`
|
||||
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
|
||||
|
@ -113,25 +118,94 @@ type FileDeleteResponse struct {
|
|||
}
|
||||
|
||||
// GetFile downloads a file of repository, ref can be branch/tag/commit.
|
||||
// e.g.: ref -> master, tree -> macaron.go(no leading slash)
|
||||
func (c *Client) GetFile(user, repo, ref, tree string) ([]byte, *Response, error) {
|
||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", user, repo, ref, tree), nil, nil)
|
||||
// it optional can resolve lfs pointers and server the file instead
|
||||
// e.g.: ref -> master, filepath -> README.md (no leading slash)
|
||||
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
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
func (c *Client) CreateFile(owner, repo, filepath string, opt CreateFileOptions) (*FileResponse, *Response, error) {
|
||||
var err error
|
||||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
filepath = pathEscapeSegments(filepath)
|
||||
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
|
@ -149,6 +223,11 @@ func (c *Client) UpdateFile(owner, repo, filepath string, opt UpdateFileOptions)
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
filepath = pathEscapeSegments(filepath)
|
||||
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filepath = pathEscapeSegments(filepath)
|
||||
|
||||
body, err := json.Marshal(&opt)
|
||||
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) {
|
||||
if len(branch) == 0 {
|
||||
// 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)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"testing"
|
||||
|
@ -20,21 +21,22 @@ func TestFileCreateUpdateGet(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
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.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{
|
||||
Message: "create file A",
|
||||
Message: "create file " + testFileName,
|
||||
},
|
||||
Content: "ZmlsZUEK",
|
||||
})
|
||||
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))
|
||||
|
||||
updatedFile, _, err := c.UpdateFile(repo.Owner.UserName, repo.Name, "A", UpdateFileOptions{
|
||||
updatedFile, _, err := c.UpdateFile(repo.Owner.UserName, repo.Name, testFileName, UpdateFileOptions{
|
||||
FileOptions: FileOptions{
|
||||
Message: "add a new line",
|
||||
},
|
||||
|
@ -44,18 +46,73 @@ func TestFileCreateUpdateGet(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
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.EqualValues(t, updatedFile.Content.SHA, file.SHA)
|
||||
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{
|
||||
Message: "Delete File A",
|
||||
Message: "Delete File " + testFileName,
|
||||
},
|
||||
SHA: updatedFile.Content.SHA,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
_, _, err = c.GetFile(repo.Owner.UserName, repo.Name, "master", "A")
|
||||
assert.EqualValues(t, "404 Not Found", err.Error())
|
||||
_, resp, err := c.GetFile(repo.Owner.UserName, repo.Name, "main", testFileName)
|
||||
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
|
||||
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))
|
||||
opt.setDefaults()
|
||||
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
|
||||
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)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key)
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
@ -16,16 +16,14 @@ type GitServiceType string
|
|||
const (
|
||||
// GitServicePlain represents a plain git service
|
||||
GitServicePlain GitServiceType = "git"
|
||||
//GitServiceGithub represents github.com
|
||||
// GitServiceGithub represents github.com
|
||||
GitServiceGithub GitServiceType = "github"
|
||||
// GitServiceGitlab represents a gitlab service
|
||||
GitServiceGitlab GitServiceType = "gitlab"
|
||||
|
||||
// Not supported jet
|
||||
// // GitServiceGitea represents a gitea service
|
||||
// GitServiceGitea GitServiceType = "gitea"
|
||||
// // GitServiceGogs represents a gogs service
|
||||
// GitServiceGogs GitServiceType = "gogs"
|
||||
// GitServiceGitea represents a gitea service
|
||||
GitServiceGitea GitServiceType = "gitea"
|
||||
// GitServiceGogs represents a gogs service
|
||||
GitServiceGogs GitServiceType = "gogs"
|
||||
)
|
||||
|
||||
// MigrateRepoOption options for migrating a repository from an external service
|
||||
|
@ -33,25 +31,28 @@ type MigrateRepoOption struct {
|
|||
RepoName string `json:"repo_name"`
|
||||
RepoOwner string `json:"repo_owner"`
|
||||
// deprecated use RepoOwner
|
||||
RepoOwnerID int64 `json:"uid"`
|
||||
CloneAddr string `json:"clone_addr"`
|
||||
Service GitServiceType `json:"service"`
|
||||
AuthUsername string `json:"auth_username"`
|
||||
AuthPassword string `json:"auth_password"`
|
||||
AuthToken string `json:"auth_token"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Private bool `json:"private"`
|
||||
Description string `json:"description"`
|
||||
Wiki bool `json:"wiki"`
|
||||
Milestones bool `json:"milestones"`
|
||||
Labels bool `json:"labels"`
|
||||
Issues bool `json:"issues"`
|
||||
PullRequests bool `json:"pull_requests"`
|
||||
Releases bool `json:"releases"`
|
||||
RepoOwnerID int64 `json:"uid"`
|
||||
CloneAddr string `json:"clone_addr"`
|
||||
Service GitServiceType `json:"service"`
|
||||
AuthUsername string `json:"auth_username"`
|
||||
AuthPassword string `json:"auth_password"`
|
||||
AuthToken string `json:"auth_token"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Private bool `json:"private"`
|
||||
Description string `json:"description"`
|
||||
Wiki bool `json:"wiki"`
|
||||
Milestones bool `json:"milestones"`
|
||||
Labels bool `json:"labels"`
|
||||
Issues bool `json:"issues"`
|
||||
PullRequests bool `json:"pull_requests"`
|
||||
Releases bool `json:"releases"`
|
||||
MirrorInterval string `json:"mirror_interval"`
|
||||
LFS bool `json:"lfs"`
|
||||
LFSEndpoint string `json:"lfs_endpoint"`
|
||||
}
|
||||
|
||||
// Validate the MigrateRepoOption struct
|
||||
func (opt *MigrateRepoOption) Validate() error {
|
||||
func (opt *MigrateRepoOption) Validate(c *Client) error {
|
||||
// check user options
|
||||
if len(opt.CloneAddr) == 0 {
|
||||
return fmt.Errorf("CloneAddr required")
|
||||
|
@ -61,13 +62,29 @@ func (opt *MigrateRepoOption) Validate() error {
|
|||
} else if len(opt.RepoName) > 100 {
|
||||
return fmt.Errorf("RepoName to long")
|
||||
}
|
||||
if len(opt.Description) > 255 {
|
||||
if len(opt.Description) > 2048 {
|
||||
return fmt.Errorf("Description to long")
|
||||
}
|
||||
switch opt.Service {
|
||||
case GitServiceGithub:
|
||||
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
|
||||
|
@ -78,11 +95,11 @@ func (opt *MigrateRepoOption) Validate() error {
|
|||
// To migrate a repository for a organization, the authenticated user must be a
|
||||
// owner of the specified organization.
|
||||
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
|
||||
}
|
||||
|
||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
||||
if len(opt.AuthToken) != 0 {
|
||||
// gitea <= 1.12 dont understand AuthToken
|
||||
opt.AuthUsername = opt.AuthToken
|
||||
|
|
|
@ -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
|
||||
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 = pathEscapeSegments(ref)
|
||||
r := new(Reference)
|
||||
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 {
|
||||
|
@ -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
|
||||
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 = pathEscapeSegments(ref)
|
||||
|
||||
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Tag represents a repository tag
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
Message string `json:"message"`
|
||||
ID string `json:"id"`
|
||||
Commit *CommitMeta `json:"commit"`
|
||||
ZipballURL string `json:"zipball_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
|
||||
type ListRepoTagsOptions struct {
|
||||
ListOptions
|
||||
|
@ -24,8 +45,86 @@ type ListRepoTagsOptions struct {
|
|||
|
||||
// ListRepoTags list all the branches of one repository
|
||||
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()
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -18,7 +20,7 @@ func TestCreateRepo(t *testing.T) {
|
|||
user, _, err := c.GetMyUserInfo()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var repoName = "test1"
|
||||
repoName := "test1"
|
||||
_, _, err = c.GetRepo(user.UserName, repoName)
|
||||
if err != nil {
|
||||
repo, _, err := c.CreateRepo(CreateRepoOption{
|
||||
|
@ -55,18 +57,20 @@ func TestRepoMigrateAndLanguages(t *testing.T) {
|
|||
repoG, _, err := c.GetRepo(repoM.Owner.UserName, repoM.Name)
|
||||
assert.NoError(t, err)
|
||||
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.False(t, repoG.Empty)
|
||||
assert.EqualValues(t, 1, repoG.Watchers)
|
||||
var zeroTime time.Time
|
||||
assert.NotEqual(t, zeroTime, repoG.MirrorUpdated)
|
||||
|
||||
log.Println("== TestRepoLanguages ==")
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(time.Second * 2)
|
||||
lang, _, err := c.GetRepoLanguages(repoM.Owner.UserName, repoM.Name)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, lang, 2)
|
||||
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) {
|
||||
|
@ -134,20 +138,54 @@ func TestGetArchive(t *testing.T) {
|
|||
c := newTestClient()
|
||||
repo, _ := createTestRepo(t, "ToDownload", c)
|
||||
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.EqualValues(t, 1620, len(archive))
|
||||
}
|
||||
|
||||
// standard func to create a init repo for test routines
|
||||
func createTestRepo(t *testing.T, name string, c *Client) (*Repository, error) {
|
||||
user, _, uErr := c.GetMyUserInfo()
|
||||
assert.NoError(t, uErr)
|
||||
_, _, err := c.GetRepo(user.UserName, name)
|
||||
if err == nil {
|
||||
repo, _, err := c.GetRepo(user.UserName, name)
|
||||
// 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)
|
||||
}
|
||||
repo, _, err := c.CreateRepo(CreateRepoOption{
|
||||
|
||||
repo, _, err = c.CreateRepo(CreateRepoOption{
|
||||
Name: name,
|
||||
Description: "A test Repo: " + name,
|
||||
AutoInit: true,
|
||||
|
|
|
@ -22,6 +22,9 @@ type topicsList struct {
|
|||
|
||||
// ListRepoTopics list all repository's topics
|
||||
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()
|
||||
|
||||
list := new(topicsList)
|
||||
|
@ -34,9 +37,10 @@ func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([
|
|||
|
||||
// SetRepoTopics replaces the list of repo's topics
|
||||
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}
|
||||
|
||||
body, err := json.Marshal(&l)
|
||||
if err != nil {
|
||||
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
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// DeleteRepoTopic deletes a topic from repo's topics list
|
||||
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)
|
||||
return resp, err
|
||||
}
|
||||
|
|
|
@ -20,7 +20,10 @@ type TransferRepoOption struct {
|
|||
|
||||
// TransferRepo transfers the ownership of a repository
|
||||
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
|
||||
}
|
||||
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)
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
|
||||
newRepo, _, err := c.TransferRepo(repo.Owner.UserName, repo.Name, TransferRepoOption{NewOwner: org.UserName})
|
||||
assert.NoError(t, err)
|
||||
newRepo, _, err := c.TransferRepo(c.username, repo.Name, TransferRepoOption{NewOwner: org.UserName})
|
||||
assert.NoError(t, err) // admin transfer repository will execute immediately but not set as pendding.
|
||||
assert.NotNil(t, newRepo)
|
||||
assert.EqualValues(t, "ToMove", newRepo.Name)
|
||||
|
||||
repo, err = createTestRepo(t, "ToMove", c)
|
||||
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)
|
||||
|
||||
_, 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 New Issue