* first draft * update gitea sdk to 9e280adb4da * adapt feat of updated sdk * releases now works * break the Reactions loop * use convertGiteaLabel * fix endless loop because paggination is not supported there !!! * rename gitea local uploader files * pagination can bite you in the ass * Version Checks * lint * docs * rename gitea sdk import to miss future conficts * go-swagger: dont scan the sdk structs * make sure gitea can shutdown gracefully * make GetPullRequests and GetIssues similar * rm useles * Add Test: started ... * ... add tests ... * Add tests and Fixing things * Workaround missing SHA * Adapt: Ensure that all migration requests are cancellable (714ab71ddc4260937b1480519d453d2dc4e77dd6) * LINT: fix misspells in test set * adapt ListMergeRequestAwardEmoji * update sdk * Return error when creating giteadownloader failed * update sdk * adapt new sdk * adopt new features * check version before err * adapt: 'migrate service type switch page' * optimize * Fix DefaultBranch * impruve * handle subPath * fix test * Fix ReviewCommentPosition * test GetReviews * add DefaultBranch int test set * rm unused * Update SDK to v0.13.0 * addopt sdk changes * found better link * format template * Update Docs * Update Gitea SDK (v0.13.1)tags/v1.13.0-rc1
@@ -120,11 +120,12 @@ endif | |||
GO_SOURCES_OWN := $(filter-out vendor/% %/bindata.go, $(GO_SOURCES)) | |||
#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger@v0.20.1 | |||
#To update swagger use: GO111MODULE=on go get -u github.com/go-swagger/go-swagger/cmd/swagger | |||
SWAGGER := $(GO) run -mod=vendor github.com/go-swagger/go-swagger/cmd/swagger | |||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl | |||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl}}/api/v1"|g | |||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl}}/api/v1"|"basePath": "/api/v1"|g | |||
SWAGGER_EXCLUDE := code.gitea.io/sdk | |||
SWAGGER_NEWLINE_COMMAND := -e '$$a\' | |||
TEST_MYSQL_HOST ?= mysql:3306 | |||
@@ -243,7 +244,7 @@ endif | |||
.PHONY: generate-swagger | |||
generate-swagger: | |||
$(SWAGGER) generate spec -o './$(SWAGGER_SPEC)' | |||
$(SWAGGER) generate spec -x "$(SWAGGER_EXCLUDE)" -o './$(SWAGGER_SPEC)' | |||
$(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' | |||
$(SED_INPLACE) $(SWAGGER_NEWLINE_COMMAND) './$(SWAGGER_SPEC)' | |||
@@ -17,10 +17,10 @@ menu: | |||
The new migration features were introduced in Gitea 1.9.0. It defines two interfaces to support migrating | |||
repositories data from other git host platforms to gitea or, in the future migrating gitea data to other | |||
git host platforms. Currently, only the migrations from github via APIv3 to Gitea is implemented. | |||
git host platforms. Currently, migrations from Github, Gitlab and Gitea to Gitea is implemented. | |||
First of all, Gitea defines some standard objects in packages `modules/migrations/base`. They are | |||
`Repository`, `Milestone`, `Release`, `Label`, `Issue`, `Comment`, `PullRequest`, `Reaction`, `Review`, `ReviewComment`. | |||
`Repository`, `Milestone`, `Release`, `ReleaseAsset`, `Label`, `Issue`, `Comment`, `PullRequest`, `Reaction`, `Review`, `ReviewComment`. | |||
## Downloader Interfaces | |||
@@ -33,6 +33,7 @@ create a Downloader. | |||
```Go | |||
type Downloader interface { | |||
GetAsset(relTag string, relID, id int64) (io.ReadCloser, error) | |||
SetContext(context.Context) | |||
GetRepoInfo() (*Repository, error) | |||
GetTopics() ([]string, error) | |||
@@ -41,15 +42,15 @@ type Downloader interface { | |||
GetLabels() ([]*Label, error) | |||
GetIssues(page, perPage int) ([]*Issue, bool, error) | |||
GetComments(issueNumber int64) ([]*Comment, error) | |||
GetPullRequests(page, perPage int) ([]*PullRequest, error) | |||
GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) | |||
GetReviews(pullRequestNumber int64) ([]*Review, error) | |||
} | |||
``` | |||
```Go | |||
type DownloaderFactory interface { | |||
Match(opts MigrateOptions) (bool, error) | |||
New(opts MigrateOptions) (Downloader, error) | |||
New(ctx context.Context, opts MigrateOptions) (Downloader, error) | |||
GitServiceType() structs.GitServiceType | |||
} | |||
``` | |||
@@ -66,7 +67,7 @@ type Uploader interface { | |||
CreateRepo(repo *Repository, opts MigrateOptions) error | |||
CreateTopics(topic ...string) error | |||
CreateMilestones(milestones ...*Milestone) error | |||
CreateReleases(releases ...*Release) error | |||
CreateReleases(downloader Downloader, releases ...*Release) error | |||
SyncTags() error | |||
CreateLabels(labels ...*Label) error | |||
CreateIssues(issues ...*Issue) error | |||
@@ -4,6 +4,7 @@ go 1.14 | |||
require ( | |||
code.gitea.io/gitea-vet v0.2.1 | |||
code.gitea.io/sdk/gitea v0.13.1 | |||
gitea.com/lunny/levelqueue v0.3.0 | |||
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b | |||
gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 | |||
@@ -49,7 +50,7 @@ require ( | |||
github.com/google/uuid v1.1.1 | |||
github.com/gorilla/context v1.1.1 | |||
github.com/hashicorp/go-retryablehttp v0.6.7 // indirect | |||
github.com/hashicorp/go-version v0.0.0-00010101000000-000000000000 | |||
github.com/hashicorp/go-version v1.2.1 | |||
github.com/huandu/xstrings v1.3.0 | |||
github.com/issue9/assert v1.3.2 // indirect | |||
github.com/issue9/identicon v1.0.1 | |||
@@ -15,6 +15,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k | |||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= | |||
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s= | |||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= | |||
code.gitea.io/sdk/gitea v0.13.1 h1:Y7bpH2iO6Q0KhhMJfjP/LZ0AmiYITeRQlCD8b0oYqhk= | |||
code.gitea.io/sdk/gitea v0.13.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= | |||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= | |||
gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I= | |||
gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= | |||
@@ -36,11 +38,9 @@ gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Y | |||
gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a h1:aOKEXkDTnh4euoH0so/THLXeHtQuqHmDPb1xEk6Ehok= | |||
gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM= | |||
gitea.com/macaron/macaron v1.3.3-0.20190803174002-53e005ff4827/go.mod h1:/rvxMjIkOq4BM8uPUb+VHuU02ZfAO6R4+wD//tiCiRw= | |||
gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb h1:amL0md6orTj1tXY16ANzVU9FmzQB+W7aJwp8pVDbrmA= | |||
gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs= | |||
gitea.com/macaron/macaron v1.5.0 h1:TvWEcHw1/zaHlo0GTuKEukLh3A99+QsU2mjBrXLXjVQ= | |||
gitea.com/macaron/macaron v1.5.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY= | |||
gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705 h1:mvkQGAlON1Z6Y8pqa/+FpYIskk54mazuECUfZK5oTg0= | |||
gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705/go.mod h1:1ujH0jD6Ca4iK9NL0Q2a7fG2chvXx5hVa7hBfABwpkA= | |||
gitea.com/macaron/session v0.0.0-20200902202411-e3a87877db6e h1:BHoJ/xWNt6FrVsL54JennM9HPIQlnbmRvmaC5DO65pU= | |||
gitea.com/macaron/session v0.0.0-20200902202411-e3a87877db6e/go.mod h1:FanKy3WjWb5iw/iZBPk4ggoQT9FcM6bkBPvmDmsH6tY= | |||
@@ -100,7 +100,6 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj | |||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= | |||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= | |||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= | |||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= | |||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= | |||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= | |||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= | |||
@@ -157,11 +156,9 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 | |||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= | |||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= | |||
github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= | |||
github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d h1:XMf4E1U+b9E3ElF0mjvfXZdflBRZz4gLp16nQ/QSHQM= | |||
github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= | |||
github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2 h1:vZryARwW4PSFXd9arwegEywvMTvPuXL3/oa+4L5NTe8= | |||
github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= | |||
github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b h1:bZ9rKU2/V8sY+NulSfxDOnXTWcs1rySqdF1sVepihvo= | |||
github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= | |||
github.com/couchbase/goutils v0.0.0-20191018232750-b49639060d85 h1:0WMIDtuXCKEm4wtAJgAAXa/qtM5O9MariLwgHaRlYmk= | |||
github.com/couchbase/goutils v0.0.0-20191018232750-b49639060d85/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= | |||
@@ -187,7 +184,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs | |||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |||
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= | |||
github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538 h1:bpWCJ5MddHsv4Xtl3azkK89mZzd/vvut32mvAnKbyUA= | |||
github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= | |||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= | |||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg= | |||
@@ -196,7 +192,6 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC | |||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | |||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= | |||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | |||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= | |||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | |||
github.com/dlclark/regexp2 v1.2.1 h1:Ff/S0snjr1oZHUNOkvA/gP6KUaMg5vDDl3Qnhjnwgm8= | |||
github.com/dlclark/regexp2 v1.2.1/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | |||
@@ -212,7 +207,6 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 | |||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= | |||
github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 h1:mhPg/0hGebcpiiQLqJD2PWWyoHRLEdZ3sXKaEvT1EQU= | |||
github.com/editorconfig/editorconfig-core-go/v2 v2.1.1/go.mod h1:/LuhWJiQ9Gvo1DhVpa4ssm5qeg8rrztdtI7j/iCie2k= | |||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= | |||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= | |||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= | |||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= | |||
@@ -235,7 +229,6 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjr | |||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= | |||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= | |||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= | |||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= | |||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | |||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= | |||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= | |||
@@ -270,13 +263,11 @@ github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpR | |||
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= | |||
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= | |||
github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= | |||
github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI= | |||
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= | |||
github.com/go-openapi/analysis v0.19.10 h1:5BHISBAXOc/aJK25irLZnx2D3s6WyYaY9D4gmuz9fdE= | |||
github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= | |||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= | |||
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= | |||
github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= | |||
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= | |||
github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= | |||
github.com/go-openapi/errors v0.19.6 h1:xZMThgv5SQ7SMbWtKFkCf9bBdvR2iEyw9k3zGZONuys= | |||
@@ -291,7 +282,6 @@ github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 | |||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= | |||
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= | |||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= | |||
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= | |||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= | |||
github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= | |||
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= | |||
@@ -299,7 +289,6 @@ github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf | |||
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= | |||
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= | |||
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= | |||
github.com/go-openapi/loads v0.19.3 h1:jwIoahqCmaA5OBoc/B+1+Mu2L0Gr8xYQnbeyQEo/7b0= | |||
github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= | |||
github.com/go-openapi/loads v0.19.5 h1:jZVYWawIQiA1NBnHla28ktg6hrcfTHsCE+3QLVRBIls= | |||
github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= | |||
@@ -312,7 +301,6 @@ github.com/go-openapi/runtime v0.19.20/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pt | |||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= | |||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= | |||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= | |||
github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= | |||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= | |||
github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= | |||
github.com/go-openapi/spec v0.19.8 h1:qAdZLh1r6QF/hI/gTq+TJTvsQUodZsM7KLqkAJdiJNg= | |||
@@ -321,7 +309,6 @@ github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pL | |||
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= | |||
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= | |||
github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= | |||
github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA= | |||
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= | |||
github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= | |||
github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= | |||
@@ -329,14 +316,12 @@ github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk | |||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= | |||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= | |||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= | |||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= | |||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= | |||
github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= | |||
github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= | |||
github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= | |||
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= | |||
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= | |||
github.com/go-openapi/validate v0.19.3 h1:PAH/2DylwWcIU1s0Y7k3yNmeAgWOcKrNE2Q7Ww/kCg4= | |||
github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= | |||
github.com/go-openapi/validate v0.19.10 h1:tG3SZ5DC5KF4cyt7nqLVcQXGj5A7mpaYkAcNPlDK+Yk= | |||
github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= | |||
@@ -344,7 +329,6 @@ github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDA | |||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= | |||
github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= | |||
github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= | |||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= | |||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | |||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= | |||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | |||
@@ -356,7 +340,6 @@ github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l | |||
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= | |||
github.com/go-testfixtures/testfixtures/v3 v3.4.0 h1:cny44xqH4ctXRld/COxFGPC7XDyOU8KNnwmfCxEEqoQ= | |||
github.com/go-testfixtures/testfixtures/v3 v3.4.0/go.mod h1:P4L3WxgOsCLbAeUC50qX5rdj1ULZfUMqgCbqah3OH5U= | |||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= | |||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= | |||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= | |||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= | |||
@@ -411,7 +394,6 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x | |||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= | |||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= | |||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | |||
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= | |||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= | |||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= | |||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | |||
@@ -423,7 +405,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ | |||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | |||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | |||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | |||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= | |||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | |||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= | |||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | |||
@@ -454,7 +435,6 @@ github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl | |||
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= | |||
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= | |||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= | |||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= | |||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | |||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= | |||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | |||
@@ -588,7 +568,6 @@ github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 | |||
github.com/klauspost/compress v1.10.11 h1:K9z59aO18Aywg2b/WSgBaUX99mHy2BES18Cr5lBKZHk= | |||
github.com/klauspost/compress v1.10.11/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= | |||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= | |||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs= | |||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= | |||
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= | |||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= | |||
@@ -600,7 +579,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv | |||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | |||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= | |||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | |||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | |||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | |||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= | |||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | |||
@@ -617,7 +595,6 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | |||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | |||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | |||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | |||
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY= | |||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= | |||
github.com/lib/pq v1.8.1-0.20200908161135-083382b7e6fc h1:ERSU1OvZ6MdWhHieo2oT7xwR/HCksqKdgK6iYPU5pHI= | |||
github.com/lib/pq v1.8.1-0.20200908161135-083382b7e6fc/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= | |||
@@ -644,9 +621,7 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN | |||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= | |||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= | |||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | |||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= | |||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | |||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= | |||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | |||
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= | |||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | |||
@@ -656,11 +631,9 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd | |||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | |||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | |||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= | |||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= | |||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= | |||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= | |||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | |||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= | |||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= | |||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= | |||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= | |||
@@ -693,7 +666,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI | |||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= | |||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= | |||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | |||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= | |||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | |||
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= | |||
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | |||
@@ -730,12 +702,10 @@ github.com/olivere/elastic/v7 v7.0.9 h1:+bTR1xJbfLYD8WnTBt9672mFlKxjfWRJpEQ1y8BM | |||
github.com/olivere/elastic/v7 v7.0.9/go.mod h1:2TeRd0vhLRTK9zqm5xP0uLiVeZ5yUoL7kZ+8SZA9r9Y= | |||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | |||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | |||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= | |||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | |||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= | |||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | |||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | |||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= | |||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | |||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= | |||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | |||
@@ -744,9 +714,7 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ | |||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= | |||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= | |||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | |||
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= | |||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= | |||
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= | |||
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= | |||
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= | |||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= | |||
@@ -834,7 +802,6 @@ github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUr | |||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= | |||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= | |||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | |||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= | |||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | |||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= | |||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | |||
@@ -843,7 +810,6 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO | |||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= | |||
github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU= | |||
github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= | |||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= | |||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | |||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= | |||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | |||
@@ -865,9 +831,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ | |||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= | |||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | |||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | |||
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.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= | |||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | |||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= | |||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |||
@@ -893,7 +857,6 @@ github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4A | |||
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= | |||
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= | |||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= | |||
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 h1:sRrkJEHtNoaSvyXMbRgofEOX4/3gMiraevQKJdIBhYE= | |||
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= | |||
github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c h1:679/gJXwrsHC3RATr0YYjZvDMJPYN7W9FGSGNoLmKxM= | |||
github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= | |||
@@ -928,7 +891,6 @@ github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod | |||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo= | |||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60/go.mod h1:i9VhcIHN2PxXMbQrKqXNueok6QNONoPjNMoj9MygVL0= | |||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= | |||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= | |||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= | |||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= | |||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= | |||
@@ -15,7 +15,7 @@ import ( | |||
// AssetDownloader downloads an asset (attachment) for a release | |||
type AssetDownloader interface { | |||
GetAsset(tag string, id int64) (io.ReadCloser, error) | |||
GetAsset(relTag string, relID, id int64) (io.ReadCloser, error) | |||
} | |||
// Downloader downloads the site repo informations | |||
@@ -29,7 +29,7 @@ type Downloader interface { | |||
GetLabels() ([]*Label, error) | |||
GetIssues(page, perPage int) ([]*Issue, bool, error) | |||
GetComments(issueNumber int64) ([]*Comment, error) | |||
GetPullRequests(page, perPage int) ([]*PullRequest, error) | |||
GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) | |||
GetReviews(pullRequestNumber int64) ([]*Review, error) | |||
} | |||
@@ -209,23 +209,24 @@ func (d *RetryDownloader) GetComments(issueNumber int64) ([]*Comment, error) { | |||
} | |||
// GetPullRequests returns a repository's pull requests with retry | |||
func (d *RetryDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, error) { | |||
func (d *RetryDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) { | |||
var ( | |||
times = d.RetryTimes | |||
prs []*PullRequest | |||
err error | |||
isEnd bool | |||
) | |||
for ; times > 0; times-- { | |||
if prs, err = d.Downloader.GetPullRequests(page, perPage); err == nil { | |||
return prs, nil | |||
if prs, isEnd, err = d.Downloader.GetPullRequests(page, perPage); err == nil { | |||
return prs, isEnd, nil | |||
} | |||
select { | |||
case <-d.ctx.Done(): | |||
return nil, d.ctx.Err() | |||
return nil, false, d.ctx.Err() | |||
case <-time.After(time.Second * time.Duration(d.RetryDelay)): | |||
} | |||
} | |||
return nil, err | |||
return nil, false, err | |||
} | |||
// GetReviews returns pull requests reviews | |||
@@ -23,4 +23,5 @@ type Issue struct { | |||
Closed *time.Time | |||
Labels []*Label | |||
Reactions []*Reaction | |||
Assignees []string | |||
} |
@@ -31,7 +31,6 @@ type PullRequest struct { | |||
MergeCommitSHA string | |||
Head PullRequestBranch | |||
Base PullRequestBranch | |||
Assignee string | |||
Assignees []string | |||
IsLocked bool | |||
Reactions []*Reaction | |||
@@ -15,6 +15,7 @@ type ReleaseAsset struct { | |||
DownloadCount *int | |||
Created time.Time | |||
Updated time.Time | |||
DownloadURL *string | |||
} | |||
// Release represents a release | |||
@@ -12,8 +12,6 @@ type Repository struct { | |||
IsPrivate bool | |||
IsMirror bool | |||
Description string | |||
AuthUsername string | |||
AuthPassword string | |||
CloneURL string | |||
OriginalURL string | |||
DefaultBranch string | |||
@@ -36,6 +36,7 @@ type ReviewComment struct { | |||
TreePath string | |||
DiffHunk string | |||
Position int | |||
Line int | |||
CommitID string | |||
PosterID int64 | |||
Reactions []*Reaction | |||
@@ -66,7 +66,7 @@ func (g *PlainGitDownloader) GetReleases() ([]*base.Release, error) { | |||
} | |||
// GetAsset returns an asset | |||
func (g *PlainGitDownloader) GetAsset(_ string, _ int64) (io.ReadCloser, error) { | |||
func (g *PlainGitDownloader) GetAsset(_ string, _, _ int64) (io.ReadCloser, error) { | |||
return nil, ErrNotSupported | |||
} | |||
@@ -81,8 +81,8 @@ func (g *PlainGitDownloader) GetComments(issueNumber int64) ([]*base.Comment, er | |||
} | |||
// GetPullRequests returns pull requests according page and perPage | |||
func (g *PlainGitDownloader) GetPullRequests(start, limit int) ([]*base.PullRequest, error) { | |||
return nil, ErrNotSupported | |||
func (g *PlainGitDownloader) GetPullRequests(start, limit int) ([]*base.PullRequest, bool, error) { | |||
return nil, false, ErrNotSupported | |||
} | |||
// GetReviews returns reviews according issue number | |||
@@ -0,0 +1,671 @@ | |||
// 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 migrations | |||
import ( | |||
"context" | |||
"errors" | |||
"fmt" | |||
"io" | |||
"net/http" | |||
"net/url" | |||
"strings" | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/migrations/base" | |||
"code.gitea.io/gitea/modules/structs" | |||
gitea_sdk "code.gitea.io/sdk/gitea" | |||
) | |||
var ( | |||
_ base.Downloader = &GiteaDownloader{} | |||
_ base.DownloaderFactory = &GiteaDownloaderFactory{} | |||
) | |||
func init() { | |||
RegisterDownloaderFactory(&GiteaDownloaderFactory{}) | |||
} | |||
// GiteaDownloaderFactory defines a gitea downloader factory | |||
type GiteaDownloaderFactory struct { | |||
} | |||
// New returns a Downloader related to this factory according MigrateOptions | |||
func (f *GiteaDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) { | |||
u, err := url.Parse(opts.CloneAddr) | |||
if err != nil { | |||
return nil, err | |||
} | |||
baseURL := u.Scheme + "://" + u.Host | |||
repoNameSpace := strings.TrimPrefix(u.Path, "/") | |||
repoNameSpace = strings.TrimSuffix(repoNameSpace, ".git") | |||
path := strings.Split(repoNameSpace, "/") | |||
if len(path) < 2 { | |||
return nil, fmt.Errorf("invalid path") | |||
} | |||
repoPath := strings.Join(path[len(path)-2:], "/") | |||
if len(path) > 2 { | |||
subPath := strings.Join(path[:len(path)-2], "/") | |||
baseURL += "/" + subPath | |||
} | |||
log.Trace("Create gitea downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace) | |||
return NewGiteaDownloader(ctx, baseURL, repoPath, opts.AuthUsername, opts.AuthPassword, opts.AuthToken) | |||
} | |||
// GitServiceType returns the type of git service | |||
func (f *GiteaDownloaderFactory) GitServiceType() structs.GitServiceType { | |||
return structs.GiteaService | |||
} | |||
// GiteaDownloader implements a Downloader interface to get repository information's | |||
type GiteaDownloader struct { | |||
ctx context.Context | |||
client *gitea_sdk.Client | |||
repoOwner string | |||
repoName string | |||
pagination bool | |||
maxPerPage int | |||
} | |||
// NewGiteaDownloader creates a gitea Downloader via gitea API | |||
// Use either a username/password or personal token. token is preferred | |||
// Note: Public access only allows very basic access | |||
func NewGiteaDownloader(ctx context.Context, baseURL, repoPath, username, password, token string) (*GiteaDownloader, error) { | |||
giteaClient, err := gitea_sdk.NewClient( | |||
baseURL, | |||
gitea_sdk.SetToken(token), | |||
gitea_sdk.SetBasicAuth(username, password), | |||
gitea_sdk.SetContext(ctx), | |||
) | |||
if err != nil { | |||
log.Error(fmt.Sprintf("NewGiteaDownloader: %s", err.Error())) | |||
return nil, err | |||
} | |||
path := strings.Split(repoPath, "/") | |||
paginationSupport := true | |||
if err := giteaClient.CheckServerVersionConstraint(">=1.12"); err != nil { | |||
paginationSupport = false | |||
} | |||
// set small maxPerPage since we can only guess | |||
// (default would be 50 but this can differ) | |||
maxPerPage := 10 | |||
// new gitea instances can tell us what maximum they have | |||
if giteaClient.CheckServerVersionConstraint(">=1.13.0") == nil { | |||
apiConf, _, err := giteaClient.GetGlobalAPISettings() | |||
if err != nil { | |||
return nil, err | |||
} | |||
maxPerPage = apiConf.MaxResponseItems | |||
} | |||
return &GiteaDownloader{ | |||
ctx: ctx, | |||
client: giteaClient, | |||
repoOwner: path[0], | |||
repoName: path[1], | |||
pagination: paginationSupport, | |||
maxPerPage: maxPerPage, | |||
}, nil | |||
} | |||
// SetContext set context | |||
func (g *GiteaDownloader) SetContext(ctx context.Context) { | |||
g.ctx = ctx | |||
} | |||
// GetRepoInfo returns a repository information | |||
func (g *GiteaDownloader) GetRepoInfo() (*base.Repository, error) { | |||
if g == nil { | |||
return nil, errors.New("error: GiteaDownloader is nil") | |||
} | |||
repo, _, err := g.client.GetRepo(g.repoOwner, g.repoName) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return &base.Repository{ | |||
Name: repo.Name, | |||
Owner: repo.Owner.UserName, | |||
IsPrivate: repo.Private, | |||
Description: repo.Description, | |||
CloneURL: repo.CloneURL, | |||
OriginalURL: repo.HTMLURL, | |||
DefaultBranch: repo.DefaultBranch, | |||
}, nil | |||
} | |||
// GetTopics return gitea topics | |||
func (g *GiteaDownloader) GetTopics() ([]string, error) { | |||
topics, _, err := g.client.ListRepoTopics(g.repoOwner, g.repoName, gitea_sdk.ListRepoTopicsOptions{}) | |||
return topics, err | |||
} | |||
// GetMilestones returns milestones | |||
func (g *GiteaDownloader) GetMilestones() ([]*base.Milestone, error) { | |||
var milestones = make([]*base.Milestone, 0, g.maxPerPage) | |||
for i := 1; ; i++ { | |||
// make sure gitea can shutdown gracefully | |||
select { | |||
case <-g.ctx.Done(): | |||
return nil, nil | |||
default: | |||
} | |||
ms, _, err := g.client.ListRepoMilestones(g.repoOwner, g.repoName, gitea_sdk.ListMilestoneOption{ | |||
ListOptions: gitea_sdk.ListOptions{ | |||
PageSize: g.maxPerPage, | |||
Page: i, | |||
}, | |||
State: gitea_sdk.StateAll, | |||
}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
for i := range ms { | |||
// old gitea instances dont have this information | |||
createdAT := time.Now() | |||
var updatedAT *time.Time | |||
if ms[i].Closed != nil { | |||
createdAT = *ms[i].Closed | |||
updatedAT = ms[i].Closed | |||
} | |||
// new gitea instances (>=1.13) do | |||
if !ms[i].Created.IsZero() { | |||
createdAT = ms[i].Created | |||
} | |||
if ms[i].Updated != nil && !ms[i].Updated.IsZero() { | |||
updatedAT = ms[i].Updated | |||
} | |||
milestones = append(milestones, &base.Milestone{ | |||
Title: ms[i].Title, | |||
Description: ms[i].Description, | |||
Deadline: ms[i].Deadline, | |||
Created: createdAT, | |||
Updated: updatedAT, | |||
Closed: ms[i].Closed, | |||
State: string(ms[i].State), | |||
}) | |||
} | |||
if !g.pagination || len(ms) < g.maxPerPage { | |||
break | |||
} | |||
} | |||
return milestones, nil | |||
} | |||
func (g *GiteaDownloader) convertGiteaLabel(label *gitea_sdk.Label) *base.Label { | |||
return &base.Label{ | |||
Name: label.Name, | |||
Color: label.Color, | |||
Description: label.Description, | |||
} | |||
} | |||
// GetLabels returns labels | |||
func (g *GiteaDownloader) GetLabels() ([]*base.Label, error) { | |||
var labels = make([]*base.Label, 0, g.maxPerPage) | |||
for i := 1; ; i++ { | |||
// make sure gitea can shutdown gracefully | |||
select { | |||
case <-g.ctx.Done(): | |||
return nil, nil | |||
default: | |||
} | |||
ls, _, err := g.client.ListRepoLabels(g.repoOwner, g.repoName, gitea_sdk.ListLabelsOptions{ListOptions: gitea_sdk.ListOptions{ | |||
PageSize: g.maxPerPage, | |||
Page: i, | |||
}}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
for i := range ls { | |||
labels = append(labels, g.convertGiteaLabel(ls[i])) | |||
} | |||
if !g.pagination || len(ls) < g.maxPerPage { | |||
break | |||
} | |||
} | |||
return labels, nil | |||
} | |||
func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Release { | |||
r := &base.Release{ | |||
TagName: rel.TagName, | |||
TargetCommitish: rel.Target, | |||
Name: rel.Title, | |||
Body: rel.Note, | |||
Draft: rel.IsDraft, | |||
Prerelease: rel.IsPrerelease, | |||
PublisherID: rel.Publisher.ID, | |||
PublisherName: rel.Publisher.UserName, | |||
PublisherEmail: rel.Publisher.Email, | |||
Published: rel.PublishedAt, | |||
Created: rel.CreatedAt, | |||
} | |||
for _, asset := range rel.Attachments { | |||
size := int(asset.Size) | |||
dlCount := int(asset.DownloadCount) | |||
r.Assets = append(r.Assets, base.ReleaseAsset{ | |||
ID: asset.ID, | |||
Name: asset.Name, | |||
Size: &size, | |||
DownloadCount: &dlCount, | |||
Created: asset.Created, | |||
DownloadURL: &asset.DownloadURL, | |||
}) | |||
} | |||
return r | |||
} | |||
// GetReleases returns releases | |||
func (g *GiteaDownloader) GetReleases() ([]*base.Release, error) { | |||
var releases = make([]*base.Release, 0, g.maxPerPage) | |||
for i := 1; ; i++ { | |||
// make sure gitea can shutdown gracefully | |||
select { | |||
case <-g.ctx.Done(): | |||
return nil, nil | |||
default: | |||
} | |||
rl, _, err := g.client.ListReleases(g.repoOwner, g.repoName, gitea_sdk.ListReleasesOptions{ListOptions: gitea_sdk.ListOptions{ | |||
PageSize: g.maxPerPage, | |||
Page: i, | |||
}}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
for i := range rl { | |||
releases = append(releases, g.convertGiteaRelease(rl[i])) | |||
} | |||
if !g.pagination || len(rl) < g.maxPerPage { | |||
break | |||
} | |||
} | |||
return releases, nil | |||
} | |||
// GetAsset returns an asset | |||
func (g *GiteaDownloader) GetAsset(_ string, relID, id int64) (io.ReadCloser, error) { | |||
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, relID, id) | |||
if err != nil { | |||
return nil, err | |||
} | |||
resp, err := http.Get(asset.DownloadURL) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// resp.Body is closed by the uploader | |||
return resp.Body, nil | |||
} | |||
func (g *GiteaDownloader) getIssueReactions(index int64) ([]*base.Reaction, error) { | |||
var reactions []*base.Reaction | |||
if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil { | |||
log.Info("GiteaDownloader: instance to old, skip getIssueReactions") | |||
return reactions, nil | |||
} | |||
rl, _, err := g.client.GetIssueReactions(g.repoOwner, g.repoName, index) | |||
if err != nil { | |||
return nil, err | |||
} | |||
for _, reaction := range rl { | |||
reactions = append(reactions, &base.Reaction{ | |||
UserID: reaction.User.ID, | |||
UserName: reaction.User.UserName, | |||
Content: reaction.Reaction, | |||
}) | |||
} | |||
return reactions, nil | |||
} | |||
func (g *GiteaDownloader) getCommentReactions(commentID int64) ([]*base.Reaction, error) { | |||
var reactions []*base.Reaction | |||
if err := g.client.CheckServerVersionConstraint(">=1.11"); err != nil { | |||
log.Info("GiteaDownloader: instance to old, skip getCommentReactions") | |||
return reactions, nil | |||
} | |||
rl, _, err := g.client.GetIssueCommentReactions(g.repoOwner, g.repoName, commentID) | |||
if err != nil { | |||
return nil, err | |||
} | |||
for i := range rl { | |||
reactions = append(reactions, &base.Reaction{ | |||
UserID: rl[i].User.ID, | |||
UserName: rl[i].User.UserName, | |||
Content: rl[i].Reaction, | |||
}) | |||
} | |||
return reactions, nil | |||
} | |||
// GetIssues returns issues according start and limit | |||
func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) { | |||
if perPage > g.maxPerPage { | |||
perPage = g.maxPerPage | |||
} | |||
var allIssues = make([]*base.Issue, 0, perPage) | |||
issues, _, err := g.client.ListRepoIssues(g.repoOwner, g.repoName, gitea_sdk.ListIssueOption{ | |||
ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: perPage}, | |||
State: gitea_sdk.StateAll, | |||
Type: gitea_sdk.IssueTypeIssue, | |||
}) | |||
if err != nil { | |||
return nil, false, fmt.Errorf("error while listing issues: %v", err) | |||
} | |||
for _, issue := range issues { | |||
var labels = make([]*base.Label, 0, len(issue.Labels)) | |||
for i := range issue.Labels { | |||
labels = append(labels, g.convertGiteaLabel(issue.Labels[i])) | |||
} | |||
var milestone string | |||
if issue.Milestone != nil { | |||
milestone = issue.Milestone.Title | |||
} | |||
reactions, err := g.getIssueReactions(issue.Index) | |||
if err != nil { | |||
return nil, false, fmt.Errorf("error while loading reactions: %v", err) | |||
} | |||
var assignees []string | |||
for i := range issue.Assignees { | |||
assignees = append(assignees, issue.Assignees[i].UserName) | |||
} | |||
allIssues = append(allIssues, &base.Issue{ | |||
Title: issue.Title, | |||
Number: issue.Index, | |||
PosterID: issue.Poster.ID, | |||
PosterName: issue.Poster.UserName, | |||
PosterEmail: issue.Poster.Email, | |||
Content: issue.Body, | |||
Milestone: milestone, | |||
State: string(issue.State), | |||
Created: issue.Created, | |||
Updated: issue.Updated, | |||
Closed: issue.Closed, | |||
Reactions: reactions, | |||
Labels: labels, | |||
Assignees: assignees, | |||
IsLocked: issue.IsLocked, | |||
}) | |||
} | |||
isEnd := len(issues) < perPage | |||
if !g.pagination { | |||
isEnd = len(issues) == 0 | |||
} | |||
return allIssues, isEnd, nil | |||
} | |||
// GetComments returns comments according issueNumber | |||
func (g *GiteaDownloader) GetComments(index int64) ([]*base.Comment, error) { | |||
var allComments = make([]*base.Comment, 0, g.maxPerPage) | |||
// for i := 1; ; i++ { | |||
// make sure gitea can shutdown gracefully | |||
select { | |||
case <-g.ctx.Done(): | |||
return nil, nil | |||
default: | |||
} | |||
comments, _, err := g.client.ListIssueComments(g.repoOwner, g.repoName, index, gitea_sdk.ListIssueCommentOptions{ListOptions: gitea_sdk.ListOptions{ | |||
// PageSize: g.maxPerPage, | |||
// Page: i, | |||
}}) | |||
if err != nil { | |||
return nil, fmt.Errorf("error while listing comments: %v", err) | |||
} | |||
for _, comment := range comments { | |||
reactions, err := g.getCommentReactions(comment.ID) | |||
if err != nil { | |||
return nil, fmt.Errorf("error while listing comment creactions: %v", err) | |||
} | |||
allComments = append(allComments, &base.Comment{ | |||
IssueIndex: index, | |||
PosterID: comment.Poster.ID, | |||
PosterName: comment.Poster.UserName, | |||
PosterEmail: comment.Poster.Email, | |||
Content: comment.Body, | |||
Created: comment.Created, | |||
Updated: comment.Updated, | |||
Reactions: reactions, | |||
}) | |||
} | |||
// TODO enable pagination vor (gitea >= 1.14) when it got implemented | |||
// if !g.pagination || len(comments) < g.maxPerPage { | |||
// break | |||
// } | |||
//} | |||
return allComments, nil | |||
} | |||
// GetPullRequests returns pull requests according page and perPage | |||
func (g *GiteaDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { | |||
if perPage > g.maxPerPage { | |||
perPage = g.maxPerPage | |||
} | |||
var allPRs = make([]*base.PullRequest, 0, perPage) | |||
prs, _, err := g.client.ListRepoPullRequests(g.repoOwner, g.repoName, gitea_sdk.ListPullRequestsOptions{ | |||
ListOptions: gitea_sdk.ListOptions{ | |||
Page: page, | |||
PageSize: perPage, | |||
}, | |||
State: gitea_sdk.StateAll, | |||
}) | |||
if err != nil { | |||
return nil, false, fmt.Errorf("error while listing repos: %v", err) | |||
} | |||
for _, pr := range prs { | |||
var milestone string | |||
if pr.Milestone != nil { | |||
milestone = pr.Milestone.Title | |||
} | |||
var labels = make([]*base.Label, 0, len(pr.Labels)) | |||
for i := range pr.Labels { | |||
labels = append(labels, g.convertGiteaLabel(pr.Labels[i])) | |||
} | |||
var ( | |||
headUserName string | |||
headRepoName string | |||
headCloneURL string | |||
headRef string | |||
headSHA string | |||
) | |||
if pr.Head != nil { | |||
if pr.Head.Repository != nil { | |||
headUserName = pr.Head.Repository.Owner.UserName | |||
headRepoName = pr.Head.Repository.Name | |||
headCloneURL = pr.Head.Repository.CloneURL | |||
} | |||
headSHA = pr.Head.Sha | |||
headRef = pr.Head.Ref | |||
if headSHA == "" { | |||
headCommit, _, err := g.client.GetSingleCommit(g.repoOwner, g.repoName, url.PathEscape(pr.Head.Ref)) | |||
if err != nil { | |||
return nil, false, fmt.Errorf("error while resolving git ref: %v", err) | |||
} | |||
headSHA = headCommit.SHA | |||
} | |||
} | |||
var mergeCommitSHA string | |||
if pr.MergedCommitID != nil { | |||
mergeCommitSHA = *pr.MergedCommitID | |||
} | |||
reactions, err := g.getIssueReactions(pr.Index) | |||
if err != nil { | |||
return nil, false, fmt.Errorf("error while loading reactions: %v", err) | |||
} | |||
var assignees []string | |||
for i := range pr.Assignees { | |||
assignees = append(assignees, pr.Assignees[i].UserName) | |||
} | |||
createdAt := time.Now() | |||
if pr.Created != nil { | |||
createdAt = *pr.Created | |||
} | |||
updatedAt := time.Now() | |||
if pr.Created != nil { | |||
updatedAt = *pr.Updated | |||
} | |||
closedAt := pr.Closed | |||
if pr.Merged != nil && closedAt == nil { | |||
closedAt = pr.Merged | |||
} | |||
allPRs = append(allPRs, &base.PullRequest{ | |||
Title: pr.Title, | |||
Number: pr.Index, | |||
PosterID: pr.Poster.ID, | |||
PosterName: pr.Poster.UserName, | |||
PosterEmail: pr.Poster.Email, | |||
Content: pr.Body, | |||
State: string(pr.State), | |||
Created: createdAt, | |||
Updated: updatedAt, | |||
Closed: closedAt, | |||
Labels: labels, | |||
Milestone: milestone, | |||
Reactions: reactions, | |||
Assignees: assignees, | |||
Merged: pr.HasMerged, | |||
MergedTime: pr.Merged, | |||
MergeCommitSHA: mergeCommitSHA, | |||
IsLocked: pr.IsLocked, | |||
PatchURL: pr.PatchURL, | |||
Head: base.PullRequestBranch{ | |||
Ref: headRef, | |||
SHA: headSHA, | |||
RepoName: headRepoName, | |||
OwnerName: headUserName, | |||
CloneURL: headCloneURL, | |||
}, | |||
Base: base.PullRequestBranch{ | |||
Ref: pr.Base.Ref, | |||
SHA: pr.Base.Sha, | |||
RepoName: g.repoName, | |||
OwnerName: g.repoOwner, | |||
}, | |||
}) | |||
} | |||
isEnd := len(prs) < perPage | |||
if !g.pagination { | |||
isEnd = len(prs) == 0 | |||
} | |||
return allPRs, isEnd, nil | |||
} | |||
// GetReviews returns pull requests review | |||
func (g *GiteaDownloader) GetReviews(index int64) ([]*base.Review, error) { | |||
if err := g.client.CheckServerVersionConstraint(">=1.12"); err != nil { | |||
log.Info("GiteaDownloader: instance to old, skip GetReviews") | |||
return nil, nil | |||
} | |||
var allReviews = make([]*base.Review, 0, g.maxPerPage) | |||
for i := 1; ; i++ { | |||
// make sure gitea can shutdown gracefully | |||
select { | |||
case <-g.ctx.Done(): | |||
return nil, nil | |||
default: | |||
} | |||
prl, _, err := g.client.ListPullReviews(g.repoOwner, g.repoName, index, gitea_sdk.ListPullReviewsOptions{ListOptions: gitea_sdk.ListOptions{ | |||
Page: i, | |||
PageSize: g.maxPerPage, | |||
}}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
for _, pr := range prl { | |||
rcl, _, err := g.client.ListPullReviewComments(g.repoOwner, g.repoName, index, pr.ID) | |||
if err != nil { | |||
return nil, err | |||
} | |||
var reviewComments []*base.ReviewComment | |||
for i := range rcl { | |||
line := int(rcl[i].LineNum) | |||
if rcl[i].OldLineNum > 0 { | |||
line = int(rcl[i].OldLineNum) * -1 | |||
} | |||
reviewComments = append(reviewComments, &base.ReviewComment{ | |||
ID: rcl[i].ID, | |||
Content: rcl[i].Body, | |||
TreePath: rcl[i].Path, | |||
DiffHunk: rcl[i].DiffHunk, | |||
Line: line, | |||
CommitID: rcl[i].CommitID, | |||
PosterID: rcl[i].Reviewer.ID, | |||
CreatedAt: rcl[i].Created, | |||
UpdatedAt: rcl[i].Updated, | |||
}) | |||
} | |||
allReviews = append(allReviews, &base.Review{ | |||
ID: pr.ID, | |||
IssueIndex: index, | |||
ReviewerID: pr.Reviewer.ID, | |||
ReviewerName: pr.Reviewer.UserName, | |||
Official: pr.Official, | |||
CommitID: pr.CommitID, | |||
Content: pr.Body, | |||
CreatedAt: pr.Submitted, | |||
State: string(pr.State), | |||
Comments: reviewComments, | |||
}) | |||
} | |||
if len(prl) < g.maxPerPage { | |||
break | |||
} | |||
} | |||
return allReviews, nil | |||
} |
@@ -0,0 +1,365 @@ | |||
// 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 migrations | |||
import ( | |||
"context" | |||
"fmt" | |||
"net/http" | |||
"os" | |||
"sort" | |||
"testing" | |||
"time" | |||
"code.gitea.io/gitea/modules/migrations/base" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func assertEqualIssue(t *testing.T, issueExp, issueGet *base.Issue) { | |||
assert.EqualValues(t, issueExp.Number, issueGet.Number) | |||
assert.EqualValues(t, issueExp.Title, issueGet.Title) | |||
assert.EqualValues(t, issueExp.Content, issueGet.Content) | |||
assert.EqualValues(t, issueExp.Milestone, issueGet.Milestone) | |||
assert.EqualValues(t, issueExp.PosterID, issueGet.PosterID) | |||
assert.EqualValues(t, issueExp.PosterName, issueGet.PosterName) | |||
assert.EqualValues(t, issueExp.PosterEmail, issueGet.PosterEmail) | |||
assert.EqualValues(t, issueExp.IsLocked, issueGet.IsLocked) | |||
assert.EqualValues(t, issueExp.Created.Unix(), issueGet.Created.Unix()) | |||
assert.EqualValues(t, issueExp.Updated.Unix(), issueGet.Updated.Unix()) | |||
if issueExp.Closed != nil { | |||
assert.EqualValues(t, issueExp.Closed.Unix(), issueGet.Closed.Unix()) | |||
} else { | |||
assert.True(t, issueGet.Closed == nil) | |||
} | |||
sort.Strings(issueExp.Assignees) | |||
sort.Strings(issueGet.Assignees) | |||
assert.EqualValues(t, issueExp.Assignees, issueGet.Assignees) | |||
assert.EqualValues(t, issueExp.Labels, issueGet.Labels) | |||
assert.EqualValues(t, issueExp.Reactions, issueGet.Reactions) | |||
} | |||
func TestGiteaDownloadRepo(t *testing.T) { | |||
// Skip tests if Gitea token is not found | |||
giteaToken := os.Getenv("GITEA_TOKEN") | |||
if giteaToken == "" { | |||
t.Skip("skipped test because GITEA_TOKEN was not in the environment") | |||
} | |||
resp, err := http.Get("https://gitea.com/gitea") | |||
if err != nil || resp.StatusCode != 200 { | |||
t.Skipf("Can't reach https://gitea.com, skipping %s", t.Name()) | |||
} | |||
downloader, err := NewGiteaDownloader(context.Background(), "https://gitea.com", "gitea/test_repo", "", "", giteaToken) | |||
if downloader == nil { | |||
t.Fatal("NewGitlabDownloader is nil") | |||
} | |||
if !assert.NoError(t, err) { | |||
t.Fatal("NewGitlabDownloader error occur") | |||
} | |||
repo, err := downloader.GetRepoInfo() | |||
assert.NoError(t, err) | |||
assert.EqualValues(t, &base.Repository{ | |||
Name: "test_repo", | |||
Owner: "gitea", | |||
IsPrivate: false, | |||
Description: "Test repository for testing migration from gitea to gitea", | |||
CloneURL: "https://gitea.com/gitea/test_repo.git", | |||
OriginalURL: "https://gitea.com/gitea/test_repo", | |||
DefaultBranch: "master", | |||
}, repo) | |||
topics, err := downloader.GetTopics() | |||
assert.NoError(t, err) | |||
sort.Strings(topics) | |||
assert.EqualValues(t, []string{"ci", "gitea", "migration", "test"}, topics) | |||
labels, err := downloader.GetLabels() | |||
assert.NoError(t, err) | |||
assert.Len(t, labels, 6) | |||
for _, l := range labels { | |||
switch l.Name { | |||
case "Bug": | |||
assertLabelEqual(t, "Bug", "e11d21", "", l) | |||
case "documentation": | |||
assertLabelEqual(t, "Enhancement", "207de5", "", l) | |||
case "confirmed": | |||
assertLabelEqual(t, "Feature", "0052cc", "a feature request", l) | |||
case "enhancement": | |||
assertLabelEqual(t, "Invalid", "d4c5f9", "", l) | |||
case "critical": | |||
assertLabelEqual(t, "Question", "fbca04", "", l) | |||
case "discussion": | |||
assertLabelEqual(t, "Valid", "53e917", "", l) | |||
default: | |||
assert.Error(t, fmt.Errorf("unexpected label: %s", l.Name)) | |||
} | |||
} | |||
milestones, err := downloader.GetMilestones() | |||
assert.NoError(t, err) | |||
assert.Len(t, milestones, 2) | |||
for _, milestone := range milestones { | |||
switch milestone.Title { | |||
case "V1": | |||
assert.EqualValues(t, "Generate Content", milestone.Description) | |||
// assert.EqualValues(t, "ToDo", milestone.Created) | |||
// assert.EqualValues(t, "ToDo", milestone.Updated) | |||
assert.EqualValues(t, 1598985406, milestone.Closed.Unix()) | |||
assert.True(t, milestone.Deadline == nil) | |||
assert.EqualValues(t, "closed", milestone.State) | |||
case "V2 Finalize": | |||
assert.EqualValues(t, "", milestone.Description) | |||
// assert.EqualValues(t, "ToDo", milestone.Created) | |||
// assert.EqualValues(t, "ToDo", milestone.Updated) | |||
assert.True(t, milestone.Closed == nil) | |||
assert.EqualValues(t, 1599263999, milestone.Deadline.Unix()) | |||
assert.EqualValues(t, "open", milestone.State) | |||
default: | |||
assert.Error(t, fmt.Errorf("unexpected milestone: %s", milestone.Title)) | |||
} | |||
} | |||
releases, err := downloader.GetReleases() | |||
assert.NoError(t, err) | |||
assert.EqualValues(t, []*base.Release{ | |||
{ | |||
Name: "Second Release", | |||
TagName: "v2-rc1", | |||
TargetCommitish: "master", | |||
Body: "this repo has:\r\n* reactions\r\n* wiki\r\n* issues (open/closed)\r\n* pulls (open/closed/merged) (external/internal)\r\n* pull reviews\r\n* projects\r\n* milestones\r\n* labels\r\n* releases\r\n\r\nto test migration against", | |||
Draft: false, | |||
Prerelease: true, | |||
Created: time.Date(2020, 9, 1, 18, 2, 43, 0, time.UTC), | |||
Published: time.Date(2020, 9, 1, 18, 2, 43, 0, time.UTC), | |||
PublisherID: 689, | |||
PublisherName: "6543", | |||
PublisherEmail: "6543@noreply.gitea.io", | |||
}, | |||
{ | |||
Name: "First Release", | |||
TagName: "V1", | |||
TargetCommitish: "master", | |||
Body: "as title", | |||
Draft: false, | |||
Prerelease: false, | |||
Created: time.Date(2020, 9, 1, 17, 30, 32, 0, time.UTC), | |||
Published: time.Date(2020, 9, 1, 17, 30, 32, 0, time.UTC), | |||
PublisherID: 689, | |||
PublisherName: "6543", | |||
PublisherEmail: "6543@noreply.gitea.io", | |||
}, | |||
}, releases) | |||
issues, isEnd, err := downloader.GetIssues(1, 50) | |||
assert.NoError(t, err) | |||
assert.EqualValues(t, 7, len(issues)) | |||
assert.True(t, isEnd) | |||
assert.EqualValues(t, "open", issues[0].State) | |||
issues, isEnd, err = downloader.GetIssues(3, 2) | |||
assert.NoError(t, err) | |||
assert.EqualValues(t, 2, len(issues)) | |||
assert.False(t, isEnd) | |||
var ( | |||
closed4 = time.Date(2020, 9, 1, 15, 49, 34, 0, time.UTC) | |||
closed2 = time.Unix(1598969497, 0) | |||
) | |||
assertEqualIssue(t, &base.Issue{ | |||
Number: 4, | |||
Title: "what is this repo about?", | |||
Content: "", | |||
Milestone: "V1", | |||
PosterID: -1, | |||
PosterName: "Ghost", | |||
PosterEmail: "", | |||
State: "closed", | |||
IsLocked: true, | |||
Created: time.Unix(1598975321, 0), | |||
Updated: time.Unix(1598975400, 0), | |||
Labels: []*base.Label{{ | |||
Name: "Question", | |||
Color: "fbca04", | |||
Description: "", | |||
}}, | |||
Reactions: []*base.Reaction{ | |||
{ | |||
UserID: 689, | |||
UserName: "6543", | |||
Content: "gitea", | |||
}, | |||
{ | |||
UserID: 689, | |||
UserName: "6543", | |||
Content: "laugh", | |||
}, | |||
}, | |||
Closed: &closed4, | |||
}, issues[0]) | |||
assertEqualIssue(t, &base.Issue{ | |||
Number: 2, | |||
Title: "Spam", | |||
Content: ":(", | |||
Milestone: "", | |||
PosterID: 689, | |||
PosterName: "6543", | |||
PosterEmail: "6543@noreply.gitea.io", | |||
State: "closed", | |||
IsLocked: false, | |||
Created: time.Unix(1598919780, 0), | |||
Updated: closed2, | |||
Labels: []*base.Label{{ | |||
Name: "Invalid", | |||
Color: "d4c5f9", | |||
Description: "", | |||
}}, | |||
Reactions: nil, | |||
Closed: &closed2, | |||
}, issues[1]) | |||
comments, err := downloader.GetComments(4) | |||
assert.NoError(t, err) | |||
assert.Len(t, comments, 2) | |||
assert.EqualValues(t, 1598975370, comments[0].Created.Unix()) | |||
assert.EqualValues(t, 1599070865, comments[0].Updated.Unix()) | |||
assert.EqualValues(t, 1598975393, comments[1].Created.Unix()) | |||
assert.EqualValues(t, 1598975393, comments[1].Updated.Unix()) | |||
assert.EqualValues(t, []*base.Comment{ | |||
{ | |||
IssueIndex: 4, | |||
PosterID: 689, | |||
PosterName: "6543", | |||
PosterEmail: "6543@noreply.gitea.io", | |||
Created: comments[0].Created, | |||
Updated: comments[0].Updated, | |||
Content: "a really good question!\n\nIt is the used as TESTSET for gitea2gitea repo migration function", | |||
}, | |||
{ | |||
IssueIndex: 4, | |||
PosterID: -1, | |||
PosterName: "Ghost", | |||
PosterEmail: "", | |||
Created: comments[1].Created, | |||
Updated: comments[1].Updated, | |||
Content: "Oh!", | |||
}, | |||
}, comments) | |||
prs, isEnd, err := downloader.GetPullRequests(1, 50) | |||
assert.NoError(t, err) | |||
assert.True(t, isEnd) | |||
assert.Len(t, prs, 6) | |||
prs, isEnd, err = downloader.GetPullRequests(1, 3) | |||
assert.NoError(t, err) | |||
assert.False(t, isEnd) | |||
assert.Len(t, prs, 3) | |||
merged12 := time.Unix(1598982934, 0) | |||
assertEqualPulls(t, &base.PullRequest{ | |||
Number: 12, | |||
PosterID: 689, | |||
PosterName: "6543", | |||
PosterEmail: "6543@noreply.gitea.io", | |||
Title: "Dont Touch", | |||
Content: "\r\nadd dont touch note", | |||
Milestone: "V2 Finalize", | |||
State: "closed", | |||
IsLocked: false, | |||
Created: time.Unix(1598982759, 0), | |||
Updated: time.Unix(1599023425, 0), | |||
Closed: &merged12, | |||
Assignees: []string{"techknowlogick"}, | |||
Labels: []*base.Label{}, | |||
Base: base.PullRequestBranch{ | |||
CloneURL: "", | |||
Ref: "master", | |||
SHA: "827aa28a907853e5ddfa40c8f9bc52471a2685fd", | |||
RepoName: "test_repo", | |||
OwnerName: "gitea", | |||
}, | |||
Head: base.PullRequestBranch{ | |||
CloneURL: "https://gitea.com/6543-forks/test_repo.git", | |||
Ref: "refs/pull/12/head", | |||
SHA: "b6ab5d9ae000b579a5fff03f92c486da4ddf48b6", | |||
RepoName: "test_repo", | |||
OwnerName: "6543-forks", | |||
}, | |||
Merged: true, | |||
MergedTime: &merged12, | |||
MergeCommitSHA: "827aa28a907853e5ddfa40c8f9bc52471a2685fd", | |||
PatchURL: "https://gitea.com/gitea/test_repo/pulls/12.patch", | |||
}, prs[1]) | |||
reviews, err := downloader.GetReviews(7) | |||
assert.NoError(t, err) | |||
if assert.Len(t, reviews, 3) { | |||
assert.EqualValues(t, 689, reviews[0].ReviewerID) | |||
assert.EqualValues(t, "6543", reviews[0].ReviewerName) | |||
assert.EqualValues(t, "techknowlogick", reviews[1].ReviewerName) | |||
assert.EqualValues(t, "techknowlogick", reviews[2].ReviewerName) | |||
assert.False(t, reviews[1].Official) | |||
assert.EqualValues(t, "I think this needs some changes", reviews[1].Content) | |||
assert.EqualValues(t, "REQUEST_CHANGES", reviews[1].State) | |||
assert.True(t, reviews[2].Official) | |||
assert.EqualValues(t, "looks good", reviews[2].Content) | |||
assert.EqualValues(t, "APPROVED", reviews[2].State) | |||
// TODO: https://github.com/go-gitea/gitea/issues/12846 | |||
// assert.EqualValues(t, 9, reviews[1].ReviewerID) | |||
// assert.EqualValues(t, 9, reviews[2].ReviewerID) | |||
assert.Len(t, reviews[0].Comments, 1) | |||
assert.EqualValues(t, &base.ReviewComment{ | |||
ID: 116561, | |||
InReplyTo: 0, | |||
Content: "is one `\\newline` to less?", | |||
TreePath: "README.md", | |||
DiffHunk: "@@ -2,3 +2,3 @@\n \n-Test repository for testing migration from gitea 2 gitea\n\\ No newline at end of file\n+Test repository for testing migration from gitea 2 gitea", | |||
Position: 0, | |||
Line: 4, | |||
CommitID: "187ece0cb6631e2858a6872e5733433bb3ca3b03", | |||
PosterID: 689, | |||
Reactions: nil, | |||
CreatedAt: time.Date(2020, 9, 1, 16, 12, 58, 0, time.UTC), | |||
UpdatedAt: time.Date(2020, 9, 1, 16, 12, 58, 0, time.UTC), | |||
}, reviews[0].Comments[0]) | |||
} | |||
} | |||
func assertEqualPulls(t *testing.T, pullExp, pullGet *base.PullRequest) { | |||
assertEqualIssue(t, pull2issue(pullExp), pull2issue(pullGet)) | |||
assert.EqualValues(t, 0, pullGet.OriginalNumber) | |||
assert.EqualValues(t, pullExp.PatchURL, pullGet.PatchURL) | |||
assert.EqualValues(t, pullExp.Merged, pullGet.Merged) | |||
assert.EqualValues(t, pullExp.MergedTime.Unix(), pullGet.MergedTime.Unix()) | |||
assert.EqualValues(t, pullExp.MergeCommitSHA, pullGet.MergeCommitSHA) | |||
assert.EqualValues(t, pullExp.Base, pullGet.Base) | |||
assert.EqualValues(t, pullExp.Head, pullGet.Head) | |||
} | |||
func pull2issue(pull *base.PullRequest) *base.Issue { | |||
return &base.Issue{ | |||
Number: pull.Number, | |||
PosterID: pull.PosterID, | |||
PosterName: pull.PosterName, | |||
PosterEmail: pull.PosterEmail, | |||
Title: pull.Title, | |||
Content: pull.Content, | |||
Milestone: pull.Milestone, | |||
State: pull.State, | |||
IsLocked: pull.IsLocked, | |||
Created: pull.Created, | |||
Updated: pull.Updated, | |||
Closed: pull.Closed, | |||
Labels: pull.Labels, | |||
Reactions: pull.Reactions, | |||
Assignees: pull.Assignees, | |||
} | |||
} |
@@ -273,9 +273,18 @@ func (g *GiteaLocalUploader) CreateReleases(downloader base.Downloader, releases | |||
// download attachment | |||
err = func() error { | |||
rc, err := downloader.GetAsset(rel.TagName, asset.ID) | |||
if err != nil { | |||
return err | |||
var rc io.ReadCloser | |||
if asset.DownloadURL == nil { | |||
rc, err = downloader.GetAsset(rel.TagName, rel.ID, asset.ID) | |||
if err != nil { | |||
return err | |||
} | |||
} else { | |||
resp, err := http.Get(*asset.DownloadURL) | |||
if err != nil { | |||
return err | |||
} | |||
rc = resp.Body | |||
} | |||
_, err = storage.Attachments.Save(attach.RelativePath(), rc) | |||
return err | |||
@@ -779,8 +788,12 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error { | |||
} | |||
for _, comment := range review.Comments { | |||
_, _, line, _ := git.ParseDiffHunkString(comment.DiffHunk) | |||
line := comment.Line | |||
if line != 0 { | |||
comment.Position = 1 | |||
} else { | |||
_, _, line, _ = git.ParseDiffHunkString(comment.DiffHunk) | |||
} | |||
headCommitID, err := g.gitRepo.GetRefCommitID(pr.GetGitRefName()) | |||
if err != nil { | |||
return fmt.Errorf("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) |
@@ -329,7 +329,7 @@ func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) { | |||
} | |||
// GetAsset returns an asset | |||
func (g *GithubDownloaderV3) GetAsset(_ string, id int64) (io.ReadCloser, error) { | |||
func (g *GithubDownloaderV3) GetAsset(_ string, _, id int64) (io.ReadCloser, error) { | |||
asset, redir, err := g.client.Repositories.DownloadReleaseAsset(g.ctx, g.repoOwner, g.repoName, id, http.DefaultClient) | |||
if err != nil { | |||
return nil, err | |||
@@ -496,7 +496,7 @@ func (g *GithubDownloaderV3) GetComments(issueNumber int64) ([]*base.Comment, er | |||
} | |||
// GetPullRequests returns pull requests according page and perPage | |||
func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullRequest, error) { | |||
func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { | |||
opt := &github.PullRequestListOptions{ | |||
Sort: "created", | |||
Direction: "asc", | |||
@@ -510,7 +510,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq | |||
g.sleep() | |||
prs, resp, err := g.client.PullRequests.List(g.ctx, g.repoOwner, g.repoName, opt) | |||
if err != nil { | |||
return nil, fmt.Errorf("error while listing repos: %v", err) | |||
return nil, false, fmt.Errorf("error while listing repos: %v", err) | |||
} | |||
g.rate = &resp.Rate | |||
for _, pr := range prs { | |||
@@ -576,7 +576,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq | |||
PerPage: perPage, | |||
}) | |||
if err != nil { | |||
return nil, err | |||
return nil, false, err | |||
} | |||
g.rate = &resp.Rate | |||
if len(res) == 0 { | |||
@@ -626,7 +626,7 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq | |||
}) | |||
} | |||
return allPRs, nil | |||
return allPRs, len(prs) < perPage, nil | |||
} | |||
func convertGithubReview(r *github.PullRequestReview) *base.Review { | |||
@@ -271,7 +271,7 @@ func TestGitHubDownloadRepo(t *testing.T) { | |||
}, comments[:2]) | |||
// downloader.GetPullRequests() | |||
prs, err := downloader.GetPullRequests(1, 2) | |||
prs, _, err := downloader.GetPullRequests(1, 2) | |||
assert.NoError(t, err) | |||
assert.EqualValues(t, 2, len(prs)) | |||
@@ -303,7 +303,7 @@ func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) { | |||
} | |||
// GetAsset returns an asset | |||
func (g *GitlabDownloader) GetAsset(tag string, id int64) (io.ReadCloser, error) { | |||
func (g *GitlabDownloader) GetAsset(tag string, _, id int64) (io.ReadCloser, error) { | |||
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, tag, int(id), gitlab.WithContext(g.ctx)) | |||
if err != nil { | |||
return nil, err | |||
@@ -464,7 +464,7 @@ func (g *GitlabDownloader) GetComments(issueNumber int64) ([]*base.Comment, erro | |||
} | |||
// GetPullRequests returns pull requests according page and perPage | |||
func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, error) { | |||
func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) { | |||
opt := &gitlab.ListProjectMergeRequestsOptions{ | |||
ListOptions: gitlab.ListOptions{ | |||
PerPage: perPage, | |||
@@ -479,7 +479,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque | |||
prs, _, err := g.client.MergeRequests.ListProjectMergeRequests(g.repoID, opt, nil, gitlab.WithContext(g.ctx)) | |||
if err != nil { | |||
return nil, fmt.Errorf("error while listing merge requests: %v", err) | |||
return nil, false, fmt.Errorf("error while listing merge requests: %v", err) | |||
} | |||
for _, pr := range prs { | |||
@@ -521,7 +521,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque | |||
for { | |||
awards, _, err := g.client.AwardEmoji.ListMergeRequestAwardEmoji(g.repoID, pr.IID, &gitlab.ListAwardEmojiOptions{Page: awardPage, PerPage: perPage}, gitlab.WithContext(g.ctx)) | |||
if err != nil { | |||
return nil, fmt.Errorf("error while listing merge requests awards: %v", err) | |||
return nil, false, fmt.Errorf("error while listing merge requests awards: %v", err) | |||
} | |||
if len(awards) < perPage { | |||
break | |||
@@ -569,7 +569,7 @@ func (g *GitlabDownloader) GetPullRequests(page, perPage int) ([]*base.PullReque | |||
}) | |||
} | |||
return allPRs, nil | |||
return allPRs, len(prs) < perPage, nil | |||
} | |||
// GetReviews returns pull requests review | |||
@@ -242,7 +242,7 @@ func TestGitlabDownloadRepo(t *testing.T) { | |||
}, | |||
}, comments[:4]) | |||
prs, err := downloader.GetPullRequests(1, 1) | |||
prs, _, err := downloader.GetPullRequests(1, 1) | |||
assert.NoError(t, err) | |||
assert.Len(t, prs, 1) | |||
@@ -229,7 +229,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts | |||
log.Trace("migrating pull requests and comments") | |||
var prBatchSize = uploader.MaxBatchInsertSize("pullrequest") | |||
for i := 1; ; i++ { | |||
prs, err := downloader.GetPullRequests(i, prBatchSize) | |||
prs, isEnd, err := downloader.GetPullRequests(i, prBatchSize) | |||
if err != nil { | |||
return err | |||
} | |||
@@ -300,7 +300,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts | |||
} | |||
} | |||
if len(prs) < prBatchSize { | |||
if isEnd { | |||
break | |||
} | |||
} | |||
@@ -275,5 +275,6 @@ var ( | |||
SupportedFullGitService = []GitServiceType{ | |||
GithubService, | |||
GitlabService, | |||
GiteaService, | |||
} | |||
) |
@@ -763,6 +763,7 @@ migrate.migrating_failed = Migrating from <b>%s</b> failed. | |||
migrate.github.description = Migrating data from Github.com or Github Enterprise. | |||
migrate.git.description = Migrating or Mirroring git data from Git services | |||
migrate.gitlab.description = Migrating data from GitLab.com or Self-Hosted gitlab server. | |||
migrate.gitea.description = Migrating data from Gitea.com or Self-Hosted Gitea server. | |||
mirror_from = mirror of | |||
forked_from = forked from | |||
@@ -0,0 +1 @@ | |||
<svg viewBox="0 0 135.467 135.467" class="svg gitea-gitea" width="16" height="16" aria-hidden="true"><path d="M27.707 33.619c-9.547-.028-22.338 6.797-21.63 23.903C7.183 84.25 31.532 86.734 41.267 86.95c1.068 5.013 12.521 22.298 21.001 23.209h37.158c22.278-1.668 38.957-75.753 26.59-76.035-20.713 1.097-32.485 1.556-42.97 1.637v23.21l-3.24-1.613-.026-21.53c-11.886-.01-22.487-.603-42.358-1.704-2.495-.027-5.98-.494-9.715-.504zm2.497 9.459c1.352 13.694 3.557 21.7 8.02 33.94-11.382-1.504-21.072-5.222-22.853-19.107-.951-7.411 2.39-15.167 14.833-14.833zm43.334 13.469a5.477 5.477 0 012.108.545l3.878 1.885-2.778 5.689a3.475 3.475 0 00-1.249.198 3.475 3.475 0 00-2.091 4.449 3.475 3.475 0 00.57 1.017l-4.787 9.798a3.475 3.475 0 00-1.15.206 3.475 3.475 0 00-2.091 4.449 3.475 3.475 0 004.44 2.091 3.475 3.475 0 002.1-4.448 3.475 3.475 0 00-.819-1.273l4.663-9.558a3.475 3.475 0 001.514-.19 3.475 3.475 0 001.24-.778c4.537 2.193 5.7 2.866 6.308 3.671.114.151.249.417.298.588.153.527.042 1.51-.281 2.455-.384 1.123-2.275 5.239-3.754 8.302a3.475 3.475 0 00-1.356.198 3.475 3.475 0 00-2.092 4.448 3.475 3.475 0 004.449 2.092 3.475 3.475 0 002.092-4.448 3.475 3.475 0 00-.72-1.174c2.773-5.883 3.781-8.196 4.143-9.749.13-.561.154-.777.157-1.537.003-.776-.011-.935-.124-1.299-.35-1.126-1.165-2.139-2.415-3.01-.942-.655-2.119-1.26-5.515-2.86-.044-.02-.087-.045-.132-.066a3.475 3.475 0 00-.198-1.273 3.475 3.475 0 00-.753-1.216l2.729-5.606 15.13 7.35a5.466 5.466 0 012.53 7.326L89.19 96.226a5.476 5.476 0 01-7.334 2.53L60.457 88.363a5.479 5.479 0 01-2.538-7.334L68.321 59.63a5.472 5.472 0 015.217-3.084z" fill="#609926" stroke="#428f29" stroke-width=".999" paint-order="markers fill stroke"/></svg> |
@@ -0,0 +1,137 @@ | |||
{{template "base/head" .}} | |||
<div class="repository new migrate"> | |||
<div class="ui middle very relaxed page grid"> | |||
<div class="column"> | |||
<form class="ui form" action="{{.Link}}" method="post"> | |||
{{.CsrfTokenHtml}} | |||
<h3 class="ui top attached header"> | |||
{{.i18n.Tr "repo.migrate.migrate" .service.Title}} | |||
<input id="service_type" type="hidden" name="service" value="{{.service}}"> | |||
</h3> | |||
<div class="ui attached segment"> | |||
{{template "base/alert" .}} | |||
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}"> | |||
<label for="clone_addr">{{.i18n.Tr "repo.migrate.clone_address"}}</label> | |||
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required> | |||
<span class="help"> | |||
{{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}} | |||
{{if .LFSActive}}<br />{{.i18n.Tr "repo.migrate.lfs_mirror_unsupported"}}{{end}} | |||
</span> | |||
</div> | |||
<div class="inline field {{if .Err_Auth}}error{{end}}"> | |||
<label for="auth_token">{{.i18n.Tr "access_token"}}</label> | |||
<input id="auth_token" name="auth_token" value="{{.auth_token}}" {{if not .auth_token}} data-need-clear="true" {{end}}> | |||
<a target=”_blank” href="https://docs.gitea.io/en-us/api-usage">{{svg "octicon-question"}}</a> | |||
</div> | |||
<div class="inline field"> | |||
<label>{{.i18n.Tr "repo.migrate_options"}}</label> | |||
<div class="ui checkbox"> | |||
{{if .DisableMirrors}} | |||
<input id="mirror" name="mirror" type="checkbox" readonly> | |||
<label>{{.i18n.Tr "repo.migrate_options_mirror_disabled"}}</label> | |||
{{else}} | |||
<input id="mirror" name="mirror" type="checkbox" {{if .mirror}} checked{{end}}> | |||
<label>{{.i18n.Tr "repo.migrate_options_mirror_helper" | Safe}}</label> | |||
{{end}} | |||
</div> | |||
</div> | |||
<span class="help">{{.i18n.Tr "repo.migrate.migrate_items_options"}}</span> | |||
<div id="migrate_items"> | |||
<div class="inline field"> | |||
<label>{{.i18n.Tr "repo.migrate_items"}}</label> | |||
<div class="ui checkbox"> | |||
<input name="wiki" type="checkbox" {{if .wiki}} checked{{end}}> | |||
<label>{{.i18n.Tr "repo.migrate_items_wiki" | Safe}}</label> | |||
</div> | |||
<div class="ui checkbox"> | |||
<input name="milestones" type="checkbox" {{if .milestones}} checked{{end}}> | |||
<label>{{.i18n.Tr "repo.migrate_items_milestones" | Safe}}</label> | |||
</div> | |||
</div> | |||
<div class="inline field"> | |||
<label></label> | |||
<div class="ui checkbox"> | |||
<input name="labels" type="checkbox" {{if .labels}} checked{{end}}> | |||
<label>{{.i18n.Tr "repo.migrate_items_labels" | Safe}}</label> | |||
</div> | |||
<div class="ui checkbox"> | |||
<input name="issues" type="checkbox" {{if .issues}} checked{{end}}> | |||
<label>{{.i18n.Tr "repo.migrate_items_issues" | Safe}}</label> | |||
</div> | |||
</div> | |||
<div class="inline field"> | |||
<label></label> | |||
<div class="ui checkbox"> | |||
<input name="pull_requests" type="checkbox" {{if .pull_requests}} checked{{end}}> | |||
<label>{{.i18n.Tr "repo.migrate_items_merge_requests" | Safe}}</label> | |||
</div> | |||
<div class="ui checkbox"> | |||
<input name="releases" type="checkbox" {{if .releases}} checked{{end}}> | |||
<label>{{.i18n.Tr "repo.migrate_items_releases" | Safe}}</label> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui divider"></div> | |||
<div class="inline required field {{if .Err_Owner}}error{{end}}"> | |||
<label>{{.i18n.Tr "repo.owner"}}</label> | |||
<div class="ui selection owner dropdown"> | |||
<input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required> | |||
<span class="text" title="{{.ContextUser.Name}}"> | |||
<img class="ui mini image" src="{{.ContextUser.RelAvatarLink}}"> | |||
{{.ContextUser.ShortName 20}} | |||
</span> | |||
<i class="dropdown icon"></i> | |||
<div class="menu" title="{{.SignedUser.Name}}"> | |||
<div class="item" data-value="{{.SignedUser.ID}}"> | |||
<img class="ui mini image" src="{{.SignedUser.RelAvatarLink}}"> | |||
{{.SignedUser.ShortName 20}} | |||
</div> | |||
{{range .Orgs}} | |||
<div class="item" data-value="{{.ID}}" title="{{.Name}}"> | |||
<img class="ui mini image" src="{{.RelAvatarLink}}"> | |||
{{.ShortName 20}} | |||
</div> | |||
{{end}} | |||
</div> | |||
</div> | |||
</div> | |||
<div class="inline required field {{if .Err_RepoName}}error{{end}}"> | |||
<label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> | |||
<input id="repo_name" name="repo_name" value="{{.repo_name}}" required> | |||
</div> | |||
<div class="inline field"> | |||
<label>{{.i18n.Tr "repo.visibility"}}</label> | |||
<div class="ui checkbox"> | |||
{{if .IsForcedPrivate}} | |||
<input name="private" type="checkbox" checked readonly> | |||
<label>{{.i18n.Tr "repo.visibility_helper_forced" | Safe}}</label> | |||
{{else}} | |||
<input name="private" type="checkbox" {{if .private}} checked{{end}}> | |||
<label>{{.i18n.Tr "repo.visibility_helper" | Safe}}</label> | |||
{{end}} | |||
</div> | |||
</div> | |||
<div class="inline field {{if .Err_Description}}error{{end}}"> | |||
<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label> | |||
<textarea id="description" name="description">{{.description}}</textarea> | |||
</div> | |||
<div class="inline field"> | |||
<label></label> | |||
<button class="ui green button"> | |||
{{.i18n.Tr "repo.migrate_repo"}} | |||
</button> | |||
<a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -0,0 +1,20 @@ | |||
Copyright (c) 2016 The Gitea Authors | |||
Copyright (c) 2014 The Gogs Authors | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in | |||
all copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
THE SOFTWARE. |
@@ -0,0 +1,44 @@ | |||
// 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" | |||
"time" | |||
) | |||
// CronTask represents a Cron task | |||
type CronTask struct { | |||
Name string `json:"name"` | |||
Schedule string `json:"schedule"` | |||
Next time.Time `json:"next"` | |||
Prev time.Time `json:"prev"` | |||
ExecTimes int64 `json:"exec_times"` | |||
} | |||
// ListCronTaskOptions list options for ListCronTasks | |||
type ListCronTaskOptions struct { | |||
ListOptions | |||
} | |||
// ListCronTasks list available cron tasks | |||
func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { | |||
return nil, nil, err | |||
} | |||
opt.setDefaults() | |||
ct := make([]*CronTask, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/cron?%s", opt.getURLQuery().Encode()), jsonHeader, nil, &ct) | |||
return ct, resp, err | |||
} | |||
// RunCronTasks run a cron task | |||
func (c *Client) RunCronTasks(task string) (*Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,36 @@ | |||
// Copyright 2015 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 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" | |||
) | |||
// AdminListOrgsOptions options for listing admin's organizations | |||
type AdminListOrgsOptions struct { | |||
ListOptions | |||
} | |||
// AdminListOrgs lists all orgs | |||
func (c *Client) AdminListOrgs(opt AdminListOrgsOptions) ([]*Organization, *Response, error) { | |||
opt.setDefaults() | |||
orgs := make([]*Organization, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs) | |||
return orgs, resp, err | |||
} | |||
// AdminCreateOrg create an organization | |||
func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
org := new(Organization) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/orgs", user), jsonHeader, bytes.NewReader(body), org) | |||
return org, resp, err | |||
} |
@@ -0,0 +1,22 @@ | |||
// Copyright 2015 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. | |||
package gitea | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
) | |||
// AdminCreateRepo create a repo | |||
func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
repo := new(Repository) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/repos", user), jsonHeader, bytes.NewReader(body), repo) | |||
return repo, resp, err | |||
} |
@@ -0,0 +1,114 @@ | |||
// Copyright 2015 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 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" | |||
) | |||
// AdminListUsersOptions options for listing admin users | |||
type AdminListUsersOptions struct { | |||
ListOptions | |||
} | |||
// AdminListUsers lists all users | |||
func (c *Client) AdminListUsers(opt AdminListUsersOptions) ([]*User, *Response, error) { | |||
opt.setDefaults() | |||
users := make([]*User, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/users?%s", opt.getURLQuery().Encode()), nil, nil, &users) | |||
return users, resp, err | |||
} | |||
// 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"` | |||
} | |||
// Validate the CreateUserOption struct | |||
func (opt CreateUserOption) Validate() error { | |||
if len(opt.Email) == 0 { | |||
return fmt.Errorf("email is empty") | |||
} | |||
if len(opt.Username) == 0 { | |||
return fmt.Errorf("username is empty") | |||
} | |||
return nil | |||
} | |||
// AdminCreateUser create a user | |||
func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
user := new(User) | |||
resp, err := c.getParsedResponse("POST", "/admin/users", jsonHeader, bytes.NewReader(body), user) | |||
return user, resp, err | |||
} | |||
// 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"` | |||
} | |||
// AdminEditUser modify user informations | |||
func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/admin/users/%s", user), jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} | |||
// AdminDeleteUser delete one user according name | |||
func (c *Client) AdminDeleteUser(user string) (*Response, error) { | |||
_, 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) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
key := new(PublicKey) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/keys", user), jsonHeader, bytes.NewReader(body), key) | |||
return key, resp, err | |||
} | |||
// AdminDeleteUserPublicKey deletes a user's public key | |||
func (c *Client) AdminDeleteUserPublicKey(user string, keyID int) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s/keys/%d", user, keyID), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,96 @@ | |||
// Copyright 2017 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 "code.gitea.io/sdk/gitea" | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"io" | |||
"mime/multipart" | |||
"net/http" | |||
"time" | |||
) | |||
// Attachment a generic attachment | |||
type Attachment struct { | |||
ID int64 `json:"id"` | |||
Name string `json:"name"` | |||
Size int64 `json:"size"` | |||
DownloadCount int64 `json:"download_count"` | |||
Created time.Time `json:"created_at"` | |||
UUID string `json:"uuid"` | |||
DownloadURL string `json:"browser_download_url"` | |||
} | |||
// ListReleaseAttachmentsOptions options for listing release's attachments | |||
type ListReleaseAttachmentsOptions struct { | |||
ListOptions | |||
} | |||
// ListReleaseAttachments list release's attachments | |||
func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt ListReleaseAttachmentsOptions) ([]*Attachment, *Response, error) { | |||
opt.setDefaults() | |||
attachments := make([]*Attachment, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", | |||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets?%s", user, repo, release, opt.getURLQuery().Encode()), | |||
nil, nil, &attachments) | |||
return attachments, resp, err | |||
} | |||
// GetReleaseAttachment returns the requested attachment | |||
func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64) (*Attachment, *Response, error) { | |||
a := new(Attachment) | |||
resp, err := c.getParsedResponse("GET", | |||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), | |||
nil, nil, &a) | |||
return a, resp, err | |||
} | |||
// 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) { | |||
// Write file to body | |||
body := new(bytes.Buffer) | |||
writer := multipart.NewWriter(body) | |||
part, err := writer.CreateFormFile("attachment", filename) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
if _, err = io.Copy(part, file); err != nil { | |||
return nil, nil, err | |||
} | |||
if err = writer.Close(); err != nil { | |||
return nil, nil, err | |||
} | |||
// Send request | |||
attachment := new(Attachment) | |||
resp, err := c.getParsedResponse("POST", | |||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets", user, repo, release), | |||
http.Header{"Content-Type": {writer.FormDataContentType()}}, body, &attachment) | |||
return attachment, resp, err | |||
} | |||
// EditAttachmentOptions options for editing attachments | |||
type EditAttachmentOptions struct { | |||
Name string `json:"name"` | |||
} | |||
// 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) { | |||
body, err := json.Marshal(&form) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
attach := new(Attachment) | |||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, attachment), jsonHeader, bytes.NewReader(body), attach) | |||
return attach, resp, err | |||
} | |||
// DeleteReleaseAttachment deletes the given attachment including the uploaded file | |||
func (c *Client) DeleteReleaseAttachment(user, repo string, release int64, id int64) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,253 @@ | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// 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 ( | |||
"context" | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"io" | |||
"io/ioutil" | |||
"net/http" | |||
"strings" | |||
"sync" | |||
"github.com/hashicorp/go-version" | |||
) | |||
var jsonHeader = http.Header{"content-type": []string{"application/json"}} | |||
// Version return the library version | |||
func Version() string { | |||
return "0.13.0" | |||
} | |||
// Client represents a Gitea API client. | |||
type Client struct { | |||
url string | |||
accessToken string | |||
username string | |||
password string | |||
otp string | |||
sudo string | |||
debug bool | |||
client *http.Client | |||
ctx context.Context | |||
serverVersion *version.Version | |||
versionLock sync.RWMutex | |||
} | |||
// Response represents the gitea response | |||
type Response struct { | |||
*http.Response | |||
} | |||
// NewClient initializes and returns a API client. | |||
func NewClient(url string, options ...func(*Client)) (*Client, error) { | |||
client := &Client{ | |||
url: strings.TrimSuffix(url, "/"), | |||
client: &http.Client{}, | |||
ctx: context.Background(), | |||
} | |||
for _, opt := range options { | |||
opt(client) | |||
} | |||
if err := client.CheckServerVersionConstraint(">=1.10"); err != nil { | |||
return nil, err | |||
} | |||
return client, nil | |||
} | |||
// NewClientWithHTTP creates an API client with a custom http client | |||
// Deprecated use SetHTTPClient option | |||
func NewClientWithHTTP(url string, httpClient *http.Client) *Client { | |||
client, _ := NewClient(url, SetHTTPClient(httpClient)) | |||
return 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 | |||
} | |||
} | |||
// SetToken is an option for NewClient to set token | |||
func SetToken(token string) func(client *Client) { | |||
return func(client *Client) { | |||
client.accessToken = token | |||
} | |||
} | |||
// SetBasicAuth is an option for NewClient to set username and password | |||
func SetBasicAuth(username, password string) func(client *Client) { | |||
return func(client *Client) { | |||
client.SetBasicAuth(username, password) | |||
} | |||
} | |||
// SetBasicAuth sets username and password | |||
func (c *Client) SetBasicAuth(username, password string) { | |||
c.username, c.password = username, password | |||
} | |||
// SetOTP is an option for NewClient to set OTP for 2FA | |||
func SetOTP(otp string) func(client *Client) { | |||
return func(client *Client) { | |||
client.SetOTP(otp) | |||
} | |||
} | |||
// SetOTP sets OTP for 2FA | |||
func (c *Client) SetOTP(otp string) { | |||
c.otp = otp | |||
} | |||
// SetContext is an option for NewClient to set context | |||
func SetContext(ctx context.Context) func(client *Client) { | |||
return func(client *Client) { | |||
client.SetContext(ctx) | |||
} | |||
} | |||
// SetContext set context witch is used for http requests | |||
func (c *Client) SetContext(ctx context.Context) { | |||
c.ctx = ctx | |||
} | |||
// SetHTTPClient replaces default http.Client with user given one. | |||
func (c *Client) SetHTTPClient(client *http.Client) { | |||
c.client = client | |||
} | |||
// SetSudo is an option for NewClient to set sudo header | |||
func SetSudo(sudo string) func(client *Client) { | |||
return func(client *Client) { | |||
client.SetSudo(sudo) | |||
} | |||
} | |||
// SetSudo sets username to impersonate. | |||
func (c *Client) SetSudo(sudo string) { | |||
c.sudo = sudo | |||
} | |||
// SetDebugMode is an option for NewClient to enable debug mode | |||
func SetDebugMode() func(client *Client) { | |||
return func(client *Client) { | |||
client.debug = true | |||
} | |||
} | |||
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) { | |||
if c.debug { | |||
fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body) | |||
} | |||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
resp, err := c.client.Do(req) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
defer resp.Body.Close() | |||
data, err := ioutil.ReadAll(resp.Body) | |||
if c.debug { | |||
fmt.Printf("Response: %v\n\n", resp) | |||
} | |||
return data, &Response{resp}, nil | |||
} | |||
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) { | |||
if c.debug { | |||
fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, body) | |||
} | |||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if len(c.accessToken) != 0 { | |||
req.Header.Set("Authorization", "token "+c.accessToken) | |||
} | |||
if len(c.otp) != 0 { | |||
req.Header.Set("X-GITEA-OTP", c.otp) | |||
} | |||
if len(c.username) != 0 { | |||
req.SetBasicAuth(c.username, c.password) | |||
} | |||
if len(c.sudo) != 0 { | |||
req.Header.Set("Sudo", c.sudo) | |||
} | |||
for k, v := range header { | |||
req.Header[k] = v | |||
} | |||
resp, err := c.client.Do(req) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if c.debug { | |||
fmt.Printf("Response: %v\n\n", resp) | |||
} | |||
return &Response{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 | |||
} | |||
defer resp.Body.Close() | |||
data, err := ioutil.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 resp, err | |||
} | |||
return resp, json.Unmarshal(data, obj) | |||
} | |||
func (c *Client) getStatusCode(method, path string, header http.Header, body io.Reader) (int, *Response, error) { | |||
resp, err := c.doRequest(method, path, header, body) | |||
if err != nil { | |||
return -1, resp, err | |||
} | |||
defer resp.Body.Close() | |||
return resp.StatusCode, resp, nil | |||
} |
@@ -0,0 +1,5 @@ | |||
// Copyright 2016 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 "code.gitea.io/sdk/gitea" |
@@ -0,0 +1,43 @@ | |||
// Copyright 2016 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" | |||
) | |||
// ListForksOptions options for listing repository's forks | |||
type ListForksOptions struct { | |||
ListOptions | |||
} | |||
// ListForks list a repository's forks | |||
func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*Repository, *Response, error) { | |||
opt.setDefaults() | |||
forks := make([]*Repository, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", | |||
fmt.Sprintf("/repos/%s/%s/forks?%s", user, repo, opt.getURLQuery().Encode()), | |||
nil, nil, &forks) | |||
return forks, resp, err | |||
} | |||
// CreateForkOption options for creating a fork | |||
type CreateForkOption struct { | |||
// organization name, if forking into an organization | |||
Organization *string `json:"organization"` | |||
} | |||
// CreateFork create a fork of a repository | |||
func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) { | |||
body, err := json.Marshal(form) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
fork := new(Repository) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/forks", user, repo), jsonHeader, bytes.NewReader(body), &fork) | |||
return fork, resp, err | |||
} |
@@ -0,0 +1,25 @@ | |||
// 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" | |||
) | |||
// GitBlobResponse represents a git blob | |||
type GitBlobResponse struct { | |||
Content string `json:"content"` | |||
Encoding string `json:"encoding"` | |||
URL string `json:"url"` | |||
SHA string `json:"sha"` | |||
Size int64 `json:"size"` | |||
} | |||
// GetBlob get the blob of a repository file | |||
func (c *Client) GetBlob(user, repo, sha string) (*GitBlobResponse, *Response, error) { | |||
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 | |||
} |
@@ -0,0 +1,59 @@ | |||
// Copyright 2019 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" | |||
) | |||
// GitHook represents a Git repository hook | |||
type GitHook struct { | |||
Name string `json:"name"` | |||
IsActive bool `json:"is_active"` | |||
Content string `json:"content,omitempty"` | |||
} | |||
// ListRepoGitHooksOptions options for listing repository's githooks | |||
type ListRepoGitHooksOptions struct { | |||
ListOptions | |||
} | |||
// ListRepoGitHooks list all the Git hooks of one repository | |||
func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions) ([]*GitHook, *Response, error) { | |||
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) | |||
return hooks, resp, err | |||
} | |||
// GetRepoGitHook get a Git hook of a repository | |||
func (c *Client) GetRepoGitHook(user, repo, id string) (*GitHook, *Response, error) { | |||
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 | |||
} | |||
// EditGitHookOption options when modifying one Git hook | |||
type EditGitHookOption struct { | |||
Content string `json:"content"` | |||
} | |||
// EditRepoGitHook modify one Git hook of a repository | |||
func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (*Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} | |||
// DeleteRepoGitHook delete one Git hook from a repository | |||
func (c *Client) DeleteRepoGitHook(user, repo, id string) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,8 @@ | |||
module code.gitea.io/sdk/gitea | |||
go 1.12 | |||
require ( | |||
github.com/hashicorp/go-version v1.2.0 | |||
github.com/stretchr/testify v1.4.0 | |||
) |
@@ -0,0 +1,13 @@ | |||
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/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= | |||
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= |
@@ -0,0 +1,142 @@ | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// Copyright 2017 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" | |||
"time" | |||
) | |||
// Hook a hook is a web hook when one repository changed | |||
type Hook struct { | |||
ID int64 `json:"id"` | |||
Type string `json:"type"` | |||
URL string `json:"-"` | |||
Config map[string]string `json:"config"` | |||
Events []string `json:"events"` | |||
Active bool `json:"active"` | |||
Updated time.Time `json:"updated_at"` | |||
Created time.Time `json:"created_at"` | |||
} | |||
// ListHooksOptions options for listing hooks | |||
type ListHooksOptions struct { | |||
ListOptions | |||
} | |||
// ListOrgHooks list all the hooks of one organization | |||
func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) { | |||
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 | |||
} | |||
// ListRepoHooks list all the hooks of one repository | |||
func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) { | |||
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) | |||
return hooks, resp, err | |||
} | |||
// GetOrgHook get a hook of an organization | |||
func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) { | |||
h := new(Hook) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, 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) { | |||
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 | |||
} | |||
// 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"` | |||
} | |||
// Validate the CreateHookOption struct | |||
func (opt CreateHookOption) Validate() error { | |||
if len(opt.Type) == 0 { | |||
return fmt.Errorf("hook type needed") | |||
} | |||
return nil | |||
} | |||
// CreateOrgHook create one hook for an organization, with options | |||
func (c *Client) CreateOrgHook(org string, 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", fmt.Sprintf("/orgs/%s/hooks", org), 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) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
h := new(Hook) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/hooks", user, repo), jsonHeader, bytes.NewReader(body), h) | |||
return h, resp, err | |||
} | |||
// 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"` | |||
} | |||
// EditOrgHook modify one hook of an organization, with hook id and options | |||
func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s/hooks/%d", org, 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) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} | |||
// DeleteOrgHook delete one hook from an organization, with hook id | |||
func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/hooks/%d", org, 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) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,233 @@ | |||
// Copyright 2016 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 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/url" | |||
"strings" | |||
"time" | |||
) | |||
// PullRequestMeta PR info if an issue is a PR | |||
type PullRequestMeta struct { | |||
HasMerged bool `json:"merged"` | |||
Merged *time.Time `json:"merged_at"` | |||
} | |||
// RepositoryMeta basic repository information | |||
type RepositoryMeta struct { | |||
ID int64 `json:"id"` | |||
Name string `json:"name"` | |||
Owner string `json:"owner"` | |||
FullName string `json:"full_name"` | |||
} | |||
// Issue represents an issue in a repository | |||
type Issue struct { | |||
ID int64 `json:"id"` | |||
URL string `json:"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"` | |||
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"` | |||
IsLocked bool `json:"is_locked"` | |||
Comments int `json:"comments"` | |||
Created time.Time `json:"created_at"` | |||
Updated time.Time `json:"updated_at"` | |||
Closed *time.Time `json:"closed_at"` | |||
Deadline *time.Time `json:"due_date"` | |||
PullRequest *PullRequestMeta `json:"pull_request"` | |||
Repository *RepositoryMeta `json:"repository"` | |||
} | |||
// ListIssueOption list issue options | |||
type ListIssueOption struct { | |||
ListOptions | |||
State StateType | |||
Type IssueType | |||
Labels []string | |||
Milestones []string | |||
KeyWord string | |||
} | |||
// StateType issue state type | |||
type StateType string | |||
const ( | |||
// StateOpen pr/issue is opend | |||
StateOpen StateType = "open" | |||
// StateClosed pr/issue is closed | |||
StateClosed StateType = "closed" | |||
// StateAll is all | |||
StateAll StateType = "all" | |||
) | |||
// IssueType is issue a pull or only an issue | |||
type IssueType string | |||
const ( | |||
// IssueTypeAll pr and issue | |||
IssueTypeAll IssueType = "" | |||
// IssueTypeIssue only issues | |||
IssueTypeIssue IssueType = "issues" | |||
// IssueTypePull only pulls | |||
IssueTypePull IssueType = "pulls" | |||
) | |||
// QueryEncode turns options into querystring argument | |||
func (opt *ListIssueOption) QueryEncode() string { | |||
query := opt.getURLQuery() | |||
if len(opt.State) > 0 { | |||
query.Add("state", string(opt.State)) | |||
} | |||
if len(opt.Labels) > 0 { | |||
query.Add("labels", strings.Join(opt.Labels, ",")) | |||
} | |||
if len(opt.KeyWord) > 0 { | |||
query.Add("q", opt.KeyWord) | |||
} | |||
query.Add("type", string(opt.Type)) | |||
if len(opt.Milestones) > 0 { | |||
query.Add("milestones", strings.Join(opt.Milestones, ",")) | |||
} | |||
return query.Encode() | |||
} | |||
// ListIssues returns all issues assigned the authenticated user | |||
func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) { | |||
opt.setDefaults() | |||
issues := make([]*Issue, 0, opt.PageSize) | |||
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 { | |||
for i := 0; i < len(issues); i++ { | |||
if issues[i].Repository != nil { | |||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0] | |||
} | |||
} | |||
} | |||
return issues, resp, err | |||
} | |||
// ListRepoIssues returns all issues for a given repository | |||
func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, *Response, error) { | |||
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 { | |||
for i := 0; i < len(issues); i++ { | |||
if issues[i].Repository != nil { | |||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0] | |||
} | |||
} | |||
} | |||
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) { | |||
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 { | |||
issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0] | |||
} | |||
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"` | |||
Assignees []string `json:"assignees"` | |||
Deadline *time.Time `json:"due_date"` | |||
// milestone id | |||
Milestone int64 `json:"milestone"` | |||
// list of label ids | |||
Labels []int64 `json:"labels"` | |||
Closed bool `json:"closed"` | |||
} | |||
// Validate the CreateIssueOption struct | |||
func (opt CreateIssueOption) Validate() error { | |||
if len(strings.TrimSpace(opt.Title)) == 0 { | |||
return fmt.Errorf("title is empty") | |||
} | |||
return nil | |||
} | |||
// CreateIssue create a new issue for a given repository | |||
func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
issue := new(Issue) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo), | |||
jsonHeader, bytes.NewReader(body), 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"` | |||
} | |||
// Validate the EditIssueOption struct | |||
func (opt EditIssueOption) Validate() error { | |||
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { | |||
return fmt.Errorf("title is empty") | |||
} | |||
return nil | |||
} | |||
// 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 := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
issue := new(Issue) | |||
resp, err := c.getParsedResponse("PATCH", | |||
fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), | |||
jsonHeader, bytes.NewReader(body), issue) | |||
return issue, resp, err | |||
} |
@@ -0,0 +1,136 @@ | |||
// 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. | |||
package gitea | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"net/url" | |||
"time" | |||
) | |||
// Comment represents a comment on a commit or issue | |||
type Comment struct { | |||
ID int64 `json:"id"` | |||
HTMLURL string `json:"html_url"` | |||
PRURL string `json:"pull_request_url"` | |||
IssueURL string `json:"issue_url"` | |||
Poster *User `json:"user"` | |||
OriginalAuthor string `json:"original_author"` | |||
OriginalAuthorID int64 `json:"original_author_id"` | |||
Body string `json:"body"` | |||
Created time.Time `json:"created_at"` | |||
Updated time.Time `json:"updated_at"` | |||
} | |||
// ListIssueCommentOptions list comment options | |||
type ListIssueCommentOptions struct { | |||
ListOptions | |||
Since time.Time | |||
Before time.Time | |||
} | |||
// QueryEncode turns options into querystring argument | |||
func (opt *ListIssueCommentOptions) 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)) | |||
} | |||
return query.Encode() | |||
} | |||
// ListIssueComments list comments on an issue. | |||
func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*Comment, *Response, error) { | |||
opt.setDefaults() | |||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index)) | |||
link.RawQuery = opt.QueryEncode() | |||
comments := make([]*Comment, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments) | |||
return comments, resp, err | |||
} | |||
// ListRepoIssueComments list comments for a given repo. | |||
func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentOptions) ([]*Comment, *Response, error) { | |||
opt.setDefaults() | |||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo)) | |||
link.RawQuery = opt.QueryEncode() | |||
comments := make([]*Comment, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments) | |||
return comments, resp, err | |||
} | |||
// GetIssueComment get a comment for a given repo by id. | |||
func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) { | |||
comment := new(Comment) | |||
if err := c.CheckServerVersionConstraint(">=1.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) | |||
return comment, resp, err | |||
} | |||
// CreateIssueCommentOption options for creating a comment on an issue | |||
type CreateIssueCommentOption struct { | |||
Body string `json:"body"` | |||
} | |||
// Validate the CreateIssueCommentOption struct | |||
func (opt CreateIssueCommentOption) Validate() error { | |||
if len(opt.Body) == 0 { | |||
return fmt.Errorf("body is empty") | |||
} | |||
return nil | |||
} | |||
// CreateIssueComment create comment on an issue. | |||
func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
comment := new(Comment) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment) | |||
return comment, resp, err | |||
} | |||
// EditIssueCommentOption options for editing a comment | |||
type EditIssueCommentOption struct { | |||
Body string `json:"body"` | |||
} | |||
// Validate the EditIssueCommentOption struct | |||
func (opt EditIssueCommentOption) Validate() error { | |||
if len(opt.Body) == 0 { | |||
return fmt.Errorf("body is empty") | |||
} | |||
return nil | |||
} | |||
// EditIssueComment edits an issue comment. | |||
func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditIssueCommentOption) (*Comment, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
comment := new(Comment) | |||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), jsonHeader, bytes.NewReader(body), comment) | |||
return comment, resp, err | |||
} | |||
// DeleteIssueComment deletes an issue comment. | |||
func (c *Client) DeleteIssueComment(owner, repo string, commentID int64) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,181 @@ | |||
// 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. | |||
package gitea | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"regexp" | |||
"strings" | |||
) | |||
// Label a label to an issue or a pr | |||
type Label struct { | |||
ID int64 `json:"id"` | |||
Name string `json:"name"` | |||
// example: 00aabb | |||
Color string `json:"color"` | |||
Description string `json:"description"` | |||
URL string `json:"url"` | |||
} | |||
// ListLabelsOptions options for listing repository's labels | |||
type ListLabelsOptions struct { | |||
ListOptions | |||
} | |||
// ListRepoLabels list labels of one repository | |||
func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*Label, *Response, error) { | |||
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) | |||
return labels, resp, err | |||
} | |||
// GetRepoLabel get one label of repository by repo it | |||
func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, *Response, error) { | |||
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 | |||
} | |||
// CreateLabelOption options for creating a label | |||
type CreateLabelOption struct { | |||
Name string `json:"name"` | |||
// example: #00aabb | |||
Color string `json:"color"` | |||
Description string `json:"description"` | |||
} | |||
// Validate the CreateLabelOption struct | |||
func (opt CreateLabelOption) Validate() error { | |||
aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", opt.Color) | |||
if err != nil { | |||
return err | |||
} | |||
if !aw { | |||
return fmt.Errorf("invalid color format") | |||
} | |||
if len(strings.TrimSpace(opt.Name)) == 0 { | |||
return fmt.Errorf("empty name not allowed") | |||
} | |||
return nil | |||
} | |||
// CreateLabel create one label of repository | |||
func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
if len(opt.Color) == 6 { | |||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { | |||
opt.Color = "#" + opt.Color | |||
} | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
label := new(Label) | |||
resp, err := c.getParsedResponse("POST", | |||
fmt.Sprintf("/repos/%s/%s/labels", owner, repo), | |||
jsonHeader, bytes.NewReader(body), label) | |||
return label, resp, err | |||
} | |||
// EditLabelOption options for editing a label | |||
type EditLabelOption struct { | |||
Name *string `json:"name"` | |||
Color *string `json:"color"` | |||
Description *string `json:"description"` | |||
} | |||
// Validate the EditLabelOption struct | |||
func (opt EditLabelOption) Validate() error { | |||
if opt.Color != nil { | |||
aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", *opt.Color) | |||
if err != nil { | |||
return err | |||
} | |||
if !aw { | |||
return fmt.Errorf("invalid color format") | |||
} | |||
} | |||
if opt.Name != nil { | |||
if len(strings.TrimSpace(*opt.Name)) == 0 { | |||
return fmt.Errorf("empty name not allowed") | |||
} | |||
} | |||
return nil | |||
} | |||
// EditLabel modify one label with options | |||
func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
label := new(Label) | |||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), label) | |||
return label, resp, err | |||
} | |||
// DeleteLabel delete one label of repository by id | |||
func (c *Client) DeleteLabel(owner, repo string, id int64) (*Response, error) { | |||
_, 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) { | |||
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 | |||
} | |||
// IssueLabelsOption a collection of labels | |||
type IssueLabelsOption struct { | |||
// list of label IDs | |||
Labels []int64 `json:"labels"` | |||
} | |||
// AddIssueLabels add one or more labels to one issue | |||
func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var labels []*Label | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) | |||
return labels, resp, err | |||
} | |||
// ReplaceIssueLabels replace old labels of issue with new labels | |||
func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
var labels []*Label | |||
resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) | |||
return labels, resp, err | |||
} | |||
// 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) { | |||
_, 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) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,213 @@ | |||
// 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. | |||
package gitea | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"net/url" | |||
"strings" | |||
"time" | |||
) | |||
// Milestone milestone is a collection of issues on one repository | |||
type Milestone struct { | |||
ID int64 `json:"id"` | |||
Title string `json:"title"` | |||
Description string `json:"description"` | |||
State StateType `json:"state"` | |||
OpenIssues int `json:"open_issues"` | |||
ClosedIssues int `json:"closed_issues"` | |||
Created time.Time `json:"created_at"` | |||
Updated *time.Time `json:"updated_at"` | |||
Closed *time.Time `json:"closed_at"` | |||
Deadline *time.Time `json:"due_on"` | |||
} | |||
// ListMilestoneOption list milestone options | |||
type ListMilestoneOption struct { | |||
ListOptions | |||
// open, closed, all | |||
State StateType | |||
Name string | |||
} | |||
// QueryEncode turns options into querystring argument | |||
func (opt *ListMilestoneOption) QueryEncode() string { | |||
query := opt.getURLQuery() | |||
if opt.State != "" { | |||
query.Add("state", string(opt.State)) | |||
} | |||
if len(opt.Name) != 0 { | |||
query.Add("name", opt.Name) | |||
} | |||
return query.Encode() | |||
} | |||
// ListRepoMilestones list all the milestones of one repository | |||
func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption) ([]*Milestone, *Response, error) { | |||
opt.setDefaults() | |||
milestones := make([]*Milestone, 0, opt.PageSize) | |||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/milestones", owner, repo)) | |||
link.RawQuery = opt.QueryEncode() | |||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &milestones) | |||
return milestones, resp, err | |||
} | |||
// GetMilestone get one milestone by repo name and milestone id | |||
func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Response, error) { | |||
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 { | |||
// backwards compatibility mode | |||
m, resp, err := c.resolveMilestoneByName(owner, repo, name) | |||
return m, resp, 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 | |||
} | |||
// CreateMilestoneOption options for creating a milestone | |||
type CreateMilestoneOption struct { | |||
Title string `json:"title"` | |||
Description string `json:"description"` | |||
State StateType `json:"state"` | |||
Deadline *time.Time `json:"due_on"` | |||
} | |||
// Validate the CreateMilestoneOption struct | |||
func (opt CreateMilestoneOption) Validate() error { | |||
if len(strings.TrimSpace(opt.Title)) == 0 { | |||
return fmt.Errorf("title is empty") | |||
} | |||
return nil | |||
} | |||
// CreateMilestone create one milestone with options | |||
func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
milestone := new(Milestone) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/milestones", owner, repo), jsonHeader, bytes.NewReader(body), milestone) | |||
// make creating closed milestones need gitea >= v1.13.0 | |||
// this make it backwards compatible | |||
if err == nil && opt.State == StateClosed && milestone.State != StateClosed { | |||
closed := StateClosed | |||
return c.EditMilestone(owner, repo, milestone.ID, EditMilestoneOption{ | |||
State: &closed, | |||
}) | |||
} | |||
return milestone, resp, err | |||
} | |||
// EditMilestoneOption options for editing a milestone | |||
type EditMilestoneOption struct { | |||
Title string `json:"title"` | |||
Description *string `json:"description"` | |||
State *StateType `json:"state"` | |||
Deadline *time.Time `json:"due_on"` | |||
} | |||
// Validate the EditMilestoneOption struct | |||
func (opt EditMilestoneOption) Validate() error { | |||
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { | |||
return fmt.Errorf("title is empty") | |||
} | |||
return nil | |||
} | |||
// EditMilestone modify milestone with options | |||
func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
milestone := new(Milestone) | |||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), milestone) | |||
return milestone, resp, err | |||
} | |||
// 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 { | |||
// backwards compatibility mode | |||
m, _, err := c.resolveMilestoneByName(owner, repo, name) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
return c.EditMilestone(owner, repo, m.ID, opt) | |||
} | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
milestone := new(Milestone) | |||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), milestone) | |||
return milestone, resp, err | |||
} | |||
// DeleteMilestone delete one milestone by id | |||
func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error) { | |||
_, 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 { | |||
// backwards compatibility mode | |||
m, _, err := c.resolveMilestoneByName(owner, repo, name) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return c.DeleteMilestone(owner, repo, m.ID) | |||
} | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil) | |||
return resp, err | |||
} | |||
// resolveMilestoneByName is a fallback method to find milestone id by name | |||
func (c *Client) resolveMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) { | |||
for i := 1; ; i++ { | |||
miles, resp, err := c.ListRepoMilestones(owner, repo, ListMilestoneOption{ | |||
ListOptions: ListOptions{ | |||
Page: i, | |||
}, | |||
State: "all", | |||
}) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
if len(miles) == 0 { | |||
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)) { | |||
return m, resp, nil | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,104 @@ | |||
// 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 ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"time" | |||
) | |||
// Reaction contain one reaction | |||
type Reaction struct { | |||
User *User `json:"user"` | |||
Reaction string `json:"content"` | |||
Created time.Time `json:"created_at"` | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
reactions := make([]*Reaction, 0, 10) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), nil, nil, &reactions) | |||
return reactions, resp, err | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
reactions := make([]*Reaction, 0, 10) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), nil, nil, &reactions) | |||
return reactions, resp, err | |||
} | |||
// editReactionOption contain the reaction type | |||
type editReactionOption struct { | |||
Reaction string `json:"content"` | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
reactionResponse := new(Reaction) | |||
body, err := json.Marshal(&editReactionOption{Reaction: reaction}) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
resp, err := c.getParsedResponse("POST", | |||
fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), | |||
jsonHeader, bytes.NewReader(body), reactionResponse) | |||
return reactionResponse, resp, err | |||
} | |||
// 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 { | |||
return nil, err | |||
} | |||
body, err := json.Marshal(&editReactionOption{Reaction: reaction}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
reactionResponse := new(Reaction) | |||
body, err := json.Marshal(&editReactionOption{Reaction: reaction}) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
resp, err := c.getParsedResponse("POST", | |||
fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), | |||
jsonHeader, bytes.NewReader(body), reactionResponse) | |||
return reactionResponse, resp, err | |||
} | |||
// 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 { | |||
return nil, err | |||
} | |||
body, err := json.Marshal(&editReactionOption{Reaction: reaction}) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("DELETE", | |||
fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), | |||
jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} |
@@ -0,0 +1,43 @@ | |||
// 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" | |||
"time" | |||
) | |||
// StopWatch represents a running stopwatch of an issue / pr | |||
type StopWatch struct { | |||
Created time.Time `json:"created"` | |||
IssueIndex int64 `json:"issue_index"` | |||
} | |||
// GetMyStopwatches list all stopwatches | |||
func (c *Client) GetMyStopwatches() ([]*StopWatch, *Response, error) { | |||
stopwatches := make([]*StopWatch, 0, 1) | |||
resp, err := c.getParsedResponse("GET", "/user/stopwatches", nil, nil, &stopwatches) | |||
return stopwatches, resp, err | |||
} | |||
// DeleteIssueStopwatch delete / cancel a specific stopwatch | |||
func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/delete", owner, repo, index), nil, nil) | |||
return resp, err | |||
} | |||
// StartIssueStopWatch starts a stopwatch for an existing issue for a given | |||
// repository | |||
func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response, error) { | |||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index), nil, nil) | |||
return resp, err | |||
} | |||
// StopIssueStopWatch stops an existing stopwatch for an issue in a given | |||
// repository | |||
func (c *Client) StopIssueStopWatch(owner, repo string, index int64) (*Response, error) { | |||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,84 @@ | |||
// 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" | |||
"net/http" | |||
) | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
subscribers := make([]*User, 0, 10) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions", owner, repo, index), nil, nil, &subscribers) | |||
return subscribers, resp, err | |||
} | |||
// 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 { | |||
return nil, err | |||
} | |||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil) | |||
if err != nil { | |||
return resp, err | |||
} | |||
if status == http.StatusCreated { | |||
return resp, nil | |||
} | |||
if status == http.StatusOK { | |||
return resp, fmt.Errorf("already subscribed") | |||
} | |||
return resp, fmt.Errorf("unexpected Status: %d", status) | |||
} | |||
// 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 { | |||
return nil, err | |||
} | |||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil) | |||
if err != nil { | |||
return resp, err | |||
} | |||
if status == http.StatusCreated { | |||
return resp, nil | |||
} | |||
if status == http.StatusOK { | |||
return resp, fmt.Errorf("already unsubscribed") | |||
} | |||
return resp, fmt.Errorf("unexpected Status: %d", status) | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
wi := new(WatchInfo) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/check", owner, repo, index), nil, nil, wi) | |||
return wi, resp, err | |||
} | |||
// IssueSubscribe subscribe current user to an issue | |||
func (c *Client) IssueSubscribe(owner, repo string, index int64) (*Response, error) { | |||
u, _, err := c.GetMyUserInfo() | |||
if err != nil { | |||
return nil, err | |||
} | |||
return c.AddIssueSubscription(owner, repo, index, u.UserName) | |||
} | |||
// IssueUnSubscribe unsubscribe current user from an issue | |||
func (c *Client) IssueUnSubscribe(owner, repo string, index int64) (*Response, error) { | |||
u, _, err := c.GetMyUserInfo() | |||
if err != nil { | |||
return nil, err | |||
} | |||
return c.DeleteIssueSubscription(owner, repo, index, u.UserName) | |||
} |
@@ -0,0 +1,127 @@ | |||
// Copyright 2017 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" | |||
"time" | |||
) | |||
// TrackedTime worked time for an issue / pr | |||
type TrackedTime struct { | |||
ID int64 `json:"id"` | |||
Created time.Time `json:"created"` | |||
// Time in seconds | |||
Time int64 `json:"time"` | |||
// deprecated (only for backwards compatibility) | |||
UserID int64 `json:"user_id"` | |||
UserName string `json:"user_name"` | |||
// deprecated (only for backwards compatibility) | |||
IssueID int64 `json:"issue_id"` | |||
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 | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
times := make([]*TrackedTime, 0, 10) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/times", owner, repo), nil, 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, ×) | |||
return times, resp, err | |||
} | |||
// AddTimeOption options for adding time to an issue | |||
type AddTimeOption struct { | |||
// time in seconds | |||
Time int64 `json:"time"` | |||
// optional | |||
Created time.Time `json:"created"` | |||
// optional | |||
User string `json:"user_name"` | |||
} | |||
// Validate the AddTimeOption struct | |||
func (opt AddTimeOption) Validate() error { | |||
if opt.Time == 0 { | |||
return fmt.Errorf("no time to add") | |||
} | |||
return nil | |||
} | |||
// 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 { | |||
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(TrackedTime) | |||
resp, err := c.getParsedResponse("POST", | |||
fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), | |||
jsonHeader, bytes.NewReader(body), 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 { | |||
return nil, nil, err | |||
} | |||
opt.setDefaults() | |||
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, ×) | |||
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 { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), nil, 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 { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,37 @@ | |||
// 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" | |||
"net/url" | |||
) | |||
const defaultPageSize = 10 | |||
const maxPageSize = 50 | |||
// ListOptions options for using Gitea's API pagination | |||
type ListOptions struct { | |||
Page int | |||
PageSize int | |||
} | |||
func (o ListOptions) getURLQuery() url.Values { | |||
query := make(url.Values) | |||
query.Add("page", fmt.Sprintf("%d", o.Page)) | |||
query.Add("limit", fmt.Sprintf("%d", o.PageSize)) | |||
return query | |||
} | |||
func (o ListOptions) setDefaults() { | |||
if o.Page < 1 { | |||
o.Page = 1 | |||
} | |||
if o.PageSize < 0 || o.PageSize > maxPageSize { | |||
o.PageSize = defaultPageSize | |||
} | |||
} |
@@ -0,0 +1,199 @@ | |||
// 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" | |||
"net/url" | |||
"time" | |||
) | |||
// NotificationThread expose Notification on API | |||
type NotificationThread struct { | |||
ID int64 `json:"id"` | |||
Repository *Repository `json:"repository"` | |||
Subject *NotificationSubject `json:"subject"` | |||
Unread bool `json:"unread"` | |||
Pinned bool `json:"pinned"` | |||
UpdatedAt time.Time `json:"updated_at"` | |||
URL string `json:"url"` | |||
} | |||
// 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"` | |||
State StateType `json:"state"` | |||
} | |||
// NotifyStatus notification status type | |||
type NotifyStatus string | |||
const ( | |||
// NotifyStatusUnread was not read | |||
NotifyStatusUnread NotifyStatus = "unread" | |||
// NotifyStatusRead was already read by user | |||
NotifyStatusRead NotifyStatus = "read" | |||
// NotifyStatusPinned notification is pinned by user | |||
NotifyStatusPinned NotifyStatus = "pinned" | |||
) | |||
// ListNotificationOptions represents the filter options | |||
type ListNotificationOptions struct { | |||
ListOptions | |||
Since time.Time | |||
Before time.Time | |||
Status []NotifyStatus | |||
} | |||
// MarkNotificationOptions represents the filter & modify options | |||
type MarkNotificationOptions struct { | |||
LastReadAt time.Time | |||
Status []NotifyStatus | |||
ToStatus NotifyStatus | |||
} | |||
// QueryEncode encode options to url query | |||
func (opt *ListNotificationOptions) 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)) | |||
} | |||
for _, s := range opt.Status { | |||
query.Add("status-types", 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 nil | |||
} | |||
// QueryEncode encode options to url query | |||
func (opt *MarkNotificationOptions) QueryEncode() string { | |||
query := make(url.Values) | |||
if !opt.LastReadAt.IsZero() { | |||
query.Add("last_read_at", opt.LastReadAt.Format(time.RFC3339)) | |||
} | |||
for _, s := range opt.Status { | |||
query.Add("status-types", string(s)) | |||
} | |||
if len(opt.ToStatus) != 0 { | |||
query.Add("to-status", string(opt.ToStatus)) | |||
} | |||
return query.Encode() | |||
} | |||
// 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 nil | |||
} | |||
// CheckNotifications list users's notification threads | |||
func (c *Client) CheckNotifications() (int64, *Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { | |||
return 0, nil, err | |||
} | |||
new := struct { | |||
New int64 `json:"new"` | |||
}{} | |||
resp, err := c.getParsedResponse("GET", "/notifications/new", jsonHeader, nil, &new) | |||
return new.New, resp, err | |||
} | |||
// GetNotification get notification thread by ID | |||
func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { | |||
return nil, nil, err | |||
} | |||
thread := new(NotificationThread) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/notifications/threads/%d", id), nil, nil, thread) | |||
return thread, resp, err | |||
} | |||
// 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 | |||
} | |||
link := fmt.Sprintf("/notifications/threads/%d", id) | |||
if len(status) != 0 { | |||
link += fmt.Sprintf("?to-status=%s", status[0]) | |||
} | |||
_, resp, err := c.getResponse("PATCH", link, nil, nil) | |||
return 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 { | |||
return nil, nil, err | |||
} | |||
if err := opt.Validate(c); err != nil { | |||
return nil, nil, err | |||
} | |||
link, _ := url.Parse("/notifications") | |||
link.RawQuery = opt.QueryEncode() | |||
threads := make([]*NotificationThread, 0, 10) | |||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads) | |||
return threads, resp, err | |||
} | |||
// 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 | |||
} | |||
if err := opt.Validate(c); err != nil { | |||
return nil, err | |||
} | |||
link, _ := url.Parse("/notifications") | |||
link.RawQuery = opt.QueryEncode() | |||
_, resp, err := c.getResponse("PUT", link.String(), nil, nil) | |||
return 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 { | |||
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.RawQuery = opt.QueryEncode() | |||
threads := make([]*NotificationThread, 0, 10) | |||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads) | |||
return threads, resp, err | |||
} | |||
// 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 | |||
} | |||
if err := opt.Validate(c); err != nil { | |||
return nil, err | |||
} | |||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, reponame)) | |||
link.RawQuery = opt.QueryEncode() | |||
_, resp, err := c.getResponse("PUT", link.String(), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,91 @@ | |||
// 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 ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"time" | |||
) | |||
// 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"` | |||
} | |||
// ListOauth2Option for listing Oauth2 Applications | |||
type ListOauth2Option struct { | |||
ListOptions | |||
} | |||
// CreateOauth2Option required options for creating an Application | |||
type CreateOauth2Option struct { | |||
Name string `json:"name"` | |||
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 { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
oauth := new(Oauth2) | |||
resp, err := c.getParsedResponse("POST", "/user/applications/oauth2", jsonHeader, bytes.NewReader(body), oauth) | |||
return oauth, resp, err | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
oauth := new(Oauth2) | |||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), jsonHeader, bytes.NewReader(body), oauth) | |||
return oauth, resp, err | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
oauth2s := &Oauth2{} | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil, &oauth2s) | |||
return oauth2s, resp, err | |||
} | |||
// ListOauth2 all of your Oauth2 Applications. | |||
func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { | |||
return nil, nil, err | |||
} | |||
opt.setDefaults() | |||
oauth2s := make([]*Oauth2, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2?%s", opt.getURLQuery().Encode()), nil, nil, &oauth2s) | |||
return oauth2s, resp, err | |||
} | |||
// DeleteOauth2 delete an Oauth2 application by ID | |||
func (c *Client) DeleteOauth2(oauth2id int64) (*Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,142 @@ | |||
// Copyright 2015 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 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" | |||
) | |||
// Organization represents an organization | |||
type Organization struct { | |||
ID int64 `json:"id"` | |||
UserName string `json:"username"` | |||
FullName string `json:"full_name"` | |||
AvatarURL string `json:"avatar_url"` | |||
Description string `json:"description"` | |||
Website string `json:"website"` | |||
Location string `json:"location"` | |||
Visibility string `json:"visibility"` | |||
} | |||
// VisibleType defines the visibility | |||
type VisibleType string | |||
const ( | |||
// VisibleTypePublic Visible for everyone | |||
VisibleTypePublic VisibleType = "public" | |||
// VisibleTypeLimited Visible for every connected user | |||
VisibleTypeLimited VisibleType = "limited" | |||
// VisibleTypePrivate Visible only for organization's members | |||
VisibleTypePrivate VisibleType = "private" | |||
) | |||
// ListOrgsOptions options for listing organizations | |||
type ListOrgsOptions struct { | |||
ListOptions | |||
} | |||
// ListMyOrgs list all of current user's organizations | |||
func (c *Client) ListMyOrgs(opt ListOrgsOptions) ([]*Organization, *Response, error) { | |||
opt.setDefaults() | |||
orgs := make([]*Organization, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs) | |||
return orgs, resp, err | |||
} | |||
// ListUserOrgs list all of some user's organizations | |||
func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) { | |||
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) | |||
return orgs, resp, err | |||
} | |||
// GetOrg get one organization by name | |||
func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) { | |||
org := new(Organization) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org) | |||
return org, resp, err | |||
} | |||
// 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"` | |||
} | |||
// checkVisibilityOpt check if mode exist | |||
func checkVisibilityOpt(v VisibleType) bool { | |||
return v == VisibleTypePublic || v == VisibleTypeLimited || v == VisibleTypePrivate | |||
} | |||
// Validate the CreateOrgOption struct | |||
func (opt CreateOrgOption) Validate() error { | |||
if len(opt.Name) == 0 { | |||
return fmt.Errorf("empty org name") | |||
} | |||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) { | |||
return fmt.Errorf("infalid bisibility option") | |||
} | |||
return nil | |||
} | |||
// CreateOrg creates an organization | |||
func (c *Client) CreateOrg(opt CreateOrgOption) (*Organization, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
org := new(Organization) | |||
resp, err := c.getParsedResponse("POST", "/orgs", jsonHeader, bytes.NewReader(body), org) | |||
return org, resp, err | |||
} | |||
// EditOrgOption options for editing an organization | |||
type EditOrgOption struct { | |||
FullName string `json:"full_name"` | |||
Description string `json:"description"` | |||
Website string `json:"website"` | |||
Location string `json:"location"` | |||
Visibility VisibleType `json:"visibility"` | |||
} | |||
// Validate the EditOrgOption struct | |||
func (opt EditOrgOption) Validate() error { | |||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) { | |||
return fmt.Errorf("infalid bisibility option") | |||
} | |||
return nil | |||
} | |||
// EditOrg modify one organization via options | |||
func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} | |||
// DeleteOrg deletes an organization | |||
func (c *Client) DeleteOrg(orgname string) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,101 @@ | |||
// 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" | |||
"net/http" | |||
"net/url" | |||
) | |||
// 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) | |||
return resp, err | |||
} | |||
// ListOrgMembershipOption list OrgMembership options | |||
type ListOrgMembershipOption struct { | |||
ListOptions | |||
} | |||
// ListOrgMembership list an organization's members | |||
func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) { | |||
opt.setDefaults() | |||
users := make([]*User, 0, opt.PageSize) | |||
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", url.PathEscape(org))) | |||
link.RawQuery = opt.getURLQuery().Encode() | |||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) | |||
return users, resp, err | |||
} | |||
// ListPublicOrgMembership list an organization's members | |||
func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) { | |||
opt.setDefaults() | |||
users := make([]*User, 0, opt.PageSize) | |||
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", url.PathEscape(org))) | |||
link.RawQuery = opt.getURLQuery().Encode() | |||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users) | |||
return users, resp, err | |||
} | |||
// 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 != nil { | |||
return false, resp, err | |||
} | |||
switch status { | |||
case http.StatusNoContent: | |||
return true, resp, nil | |||
case http.StatusNotFound: | |||
return false, resp, nil | |||
default: | |||
return false, resp, fmt.Errorf("unexpected Status: %d", status) | |||
} | |||
} | |||
// 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 != nil { | |||
return false, resp, err | |||
} | |||
switch status { | |||
case http.StatusNoContent: | |||
return true, resp, nil | |||
case http.StatusNotFound: | |||
return false, resp, nil | |||
default: | |||
return false, resp, fmt.Errorf("unexpected Status: %d", status) | |||
} | |||
} | |||
// SetPublicOrgMembership publicize/conceal a user's membership | |||
func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) { | |||
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) | |||
} else { | |||
status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", url.PathEscape(org), url.PathEscape(user)), nil, nil) | |||
} | |||
if err != nil { | |||
return resp, err | |||
} | |||
switch status { | |||
case http.StatusNoContent: | |||
return resp, nil | |||
case http.StatusNotFound: | |||
return resp, fmt.Errorf("forbidden") | |||
default: | |||
return resp, fmt.Errorf("unexpected Status: %d", status) | |||
} | |||
} |
@@ -0,0 +1,202 @@ | |||
// Copyright 2019 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" | |||
) | |||
// 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"` | |||
} | |||
// ListTeamsOptions options for listing teams | |||
type ListTeamsOptions struct { | |||
ListOptions | |||
} | |||
// ListOrgTeams lists all teams of an organization | |||
func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) { | |||
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) | |||
return teams, resp, err | |||
} | |||
// ListMyTeams lists all the teams of the current user | |||
func (c *Client) ListMyTeams(opt *ListTeamsOptions) ([]*Team, *Response, error) { | |||
opt.setDefaults() | |||
teams := make([]*Team, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/teams?%s", opt.getURLQuery().Encode()), nil, nil, &teams) | |||
return teams, resp, err | |||
} | |||
// GetTeam gets a team by ID | |||
func (c *Client) GetTeam(id int64) (*Team, *Response, error) { | |||
t := new(Team) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d", id), nil, nil, t) | |||
return t, 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"` | |||
} | |||
// Validate the CreateTeamOption struct | |||
func (opt CreateTeamOption) Validate() error { | |||
if opt.Permission == AccessModeOwner { | |||
opt.Permission = AccessModeAdmin | |||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin { | |||
return fmt.Errorf("permission mode invalid") | |||
} | |||
if len(opt.Name) == 0 { | |||
return fmt.Errorf("name required") | |||
} | |||
if len(opt.Name) > 30 { | |||
return fmt.Errorf("name to long") | |||
} | |||
if len(opt.Description) > 255 { | |||
return fmt.Errorf("description to long") | |||
} | |||
return nil | |||
} | |||
// CreateTeam creates a team for an organization | |||
func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
t := new(Team) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/teams", org), jsonHeader, bytes.NewReader(body), t) | |||
return t, resp, err | |||
} | |||
// 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"` | |||
} | |||
// Validate the EditTeamOption struct | |||
func (opt EditTeamOption) Validate() error { | |||
if opt.Permission == AccessModeOwner { | |||
opt.Permission = AccessModeAdmin | |||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin { | |||
return fmt.Errorf("permission mode invalid") | |||
} | |||
if len(opt.Name) == 0 { | |||
return fmt.Errorf("name required") | |||
} | |||
if len(opt.Name) > 30 { | |||
return fmt.Errorf("name to long") | |||
} | |||
if opt.Description != nil && len(*opt.Description) > 255 { | |||
return fmt.Errorf("description to long") | |||
} | |||
return nil | |||
} | |||
// EditTeam edits a team of an organization | |||
func (c *Client) EditTeam(id int64, opt EditTeamOption) (*Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/teams/%d", id), jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} | |||
// DeleteTeam deletes a team of an organization | |||
func (c *Client) DeleteTeam(id int64) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d", id), nil, nil) | |||
return resp, err | |||
} | |||
// ListTeamMembersOptions options for listing team's members | |||
type ListTeamMembersOptions struct { | |||
ListOptions | |||
} | |||
// ListTeamMembers lists all members of a team | |||
func (c *Client) ListTeamMembers(id int64, opt ListTeamMembersOptions) ([]*User, *Response, error) { | |||
opt.setDefaults() | |||
members := make([]*User, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members?%s", id, opt.getURLQuery().Encode()), nil, nil, &members) | |||
return members, resp, err | |||
} | |||
// GetTeamMember gets a member of a team | |||
func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) { | |||
m := new(User) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m) | |||
return m, resp, err | |||
} | |||
// AddTeamMember adds a member to a team | |||
func (c *Client) AddTeamMember(id int64, user string) (*Response, error) { | |||
_, 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) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil) | |||
return resp, err | |||
} | |||
// ListTeamRepositoriesOptions options for listing team's repositories | |||
type ListTeamRepositoriesOptions struct { | |||
ListOptions | |||
} | |||
// ListTeamRepositories lists all repositories of a team | |||
func (c *Client) ListTeamRepositories(id int64, opt ListTeamRepositoriesOptions) ([]*Repository, *Response, error) { | |||
opt.setDefaults() | |||
repos := make([]*Repository, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/repos?%s", id, opt.getURLQuery().Encode()), nil, nil, &repos) | |||
return repos, resp, err | |||
} | |||
// AddTeamRepository adds a repository to a team | |||
func (c *Client) AddTeamRepository(id int64, org, repo string) (*Response, error) { | |||
_, 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) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,253 @@ | |||
// Copyright 2016 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 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/url" | |||
"strings" | |||
"time" | |||
) | |||
// PRBranchInfo information about a branch | |||
type PRBranchInfo struct { | |||
Name string `json:"label"` | |||
Ref string `json:"ref"` | |||
Sha string `json:"sha"` | |||
RepoID int64 `json:"repo_id"` | |||
Repository *Repository `json:"repo"` | |||
} | |||
// PullRequest represents a pull request | |||
type PullRequest struct { | |||
ID int64 `json:"id"` | |||
URL string `json:"url"` | |||
Index int64 `json:"number"` | |||
Poster *User `json:"user"` | |||
Title string `json:"title"` | |||
Body string `json:"body"` | |||
Labels []*Label `json:"labels"` | |||
Milestone *Milestone `json:"milestone"` | |||
Assignee *User `json:"assignee"` | |||
Assignees []*User `json:"assignees"` | |||
State StateType `json:"state"` | |||
IsLocked bool `json:"is_locked"` | |||
Comments int `json:"comments"` | |||
HTMLURL string `json:"html_url"` | |||
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"` | |||
Base *PRBranchInfo `json:"base"` | |||
Head *PRBranchInfo `json:"head"` | |||
MergeBase string `json:"merge_base"` | |||
Deadline *time.Time `json:"due_date"` | |||
Created *time.Time `json:"created_at"` | |||
Updated *time.Time `json:"updated_at"` | |||
Closed *time.Time `json:"closed_at"` | |||
} | |||
// ListPullRequestsOptions options for listing pull requests | |||
type ListPullRequestsOptions struct { | |||
ListOptions | |||
State StateType `json:"state"` | |||
// oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority | |||
Sort string | |||
Milestone int64 | |||
} | |||
// MergeStyle is used specify how a pull is merged | |||
type MergeStyle string | |||
const ( | |||
// MergeStyleMerge merge pull as usual | |||
MergeStyleMerge MergeStyle = "merge" | |||
// MergeStyleRebase rebase pull | |||
MergeStyleRebase MergeStyle = "rebase" | |||
// MergeStyleRebaseMerge rebase and merge pull | |||
MergeStyleRebaseMerge MergeStyle = "rebase-merge" | |||
// MergeStyleSquash squash and merge pull | |||
MergeStyleSquash MergeStyle = "squash" | |||
) | |||
// QueryEncode turns options into querystring argument | |||
func (opt *ListPullRequestsOptions) QueryEncode() string { | |||
query := opt.getURLQuery() | |||
if len(opt.State) > 0 { | |||
query.Add("state", string(opt.State)) | |||
} | |||
if len(opt.Sort) > 0 { | |||
query.Add("sort", opt.Sort) | |||
} | |||
if opt.Milestone > 0 { | |||
query.Add("milestone", fmt.Sprintf("%d", opt.Milestone)) | |||
} | |||
return query.Encode() | |||
} | |||
// ListRepoPullRequests list PRs of one repository | |||
func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, *Response, error) { | |||
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) | |||
return prs, resp, err | |||
} | |||
// GetPullRequest get information of one PR | |||
func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, *Response, error) { | |||
pr := new(PullRequest) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr) | |||
return pr, resp, err | |||
} | |||
// CreatePullRequestOption options when creating a pull request | |||
type CreatePullRequestOption struct { | |||
Head string `json:"head"` | |||
Base string `json:"base"` | |||
Title string `json:"title"` | |||
Body string `json:"body"` | |||
Assignee string `json:"assignee"` | |||
Assignees []string `json:"assignees"` | |||
Milestone int64 `json:"milestone"` | |||
Labels []int64 `json:"labels"` | |||
Deadline *time.Time `json:"due_date"` | |||
} | |||
// CreatePullRequest create pull request with options | |||
func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
pr := new(PullRequest) | |||
resp, err := c.getParsedResponse("POST", | |||
fmt.Sprintf("/repos/%s/%s/pulls", owner, repo), | |||
jsonHeader, bytes.NewReader(body), pr) | |||
return pr, resp, err | |||
} | |||
// 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"` | |||
} | |||
// Validate the EditPullRequestOption struct | |||
func (opt EditPullRequestOption) Validate(c *Client) error { | |||
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 { | |||
return fmt.Errorf("title is empty") | |||
} | |||
if len(opt.Base) != 0 { | |||
if err := c.CheckServerVersionConstraint(">=1.12.0"); err != nil { | |||
return fmt.Errorf("can not change base gitea to old") | |||
} | |||
} | |||
return nil | |||
} | |||
// 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 := opt.Validate(c); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
pr := new(PullRequest) | |||
resp, err := c.getParsedResponse("PATCH", | |||
fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), | |||
jsonHeader, bytes.NewReader(body), pr) | |||
return pr, resp, err | |||
} | |||
// MergePullRequestOption options when merging a pull request | |||
type MergePullRequestOption struct { | |||
Style MergeStyle `json:"Do"` | |||
Title string `json:"MergeTitleField"` | |||
Message string `json:"MergeMessageField"` | |||
} | |||
// Validate the MergePullRequestOption struct | |||
func (opt MergePullRequestOption) Validate(c *Client) error { | |||
if opt.Style == MergeStyleSquash { | |||
if err := c.CheckServerVersionConstraint(">=1.11.5"); err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
// 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 := opt.Validate(c); err != nil { | |||
return false, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return false, nil, err | |||
} | |||
status, resp, err := c.getStatusCode("POST", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), jsonHeader, bytes.NewReader(body)) | |||
if err != nil { | |||
return false, resp, err | |||
} | |||
return status == 200, resp, nil | |||
} | |||
// IsPullRequestMerged test if one PR is merged to one repository | |||
func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Response, error) { | |||
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 | |||
} | |||
return status == 204, resp, nil | |||
} | |||
// 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 { | |||
r, _, err2 := c.GetRepo(owner, repo) | |||
if err2 != nil { | |||
return nil, nil, err | |||
} | |||
if r.Private { | |||
return nil, nil, err | |||
} | |||
return c.getWebResponse("GET", fmt.Sprintf("/%s/%s/pulls/%d.%s", owner, repo, index, kind), 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 | |||
func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) { | |||
return c.getPullRequestDiffOrPatch(owner, repo, "patch", index) | |||
} | |||
// 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) | |||
} |
@@ -0,0 +1,219 @@ | |||
// 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 ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"net/url" | |||
"strings" | |||
"time" | |||
) | |||
// ReviewStateType review state type | |||
type ReviewStateType string | |||
const ( | |||
// ReviewStateApproved pr is approved | |||
ReviewStateApproved ReviewStateType = "APPROVED" | |||
// ReviewStatePending pr state is pending | |||
ReviewStatePending ReviewStateType = "PENDING" | |||
// ReviewStateComment is a comment review | |||
ReviewStateComment ReviewStateType = "COMMENT" | |||
// ReviewStateRequestChanges changes for pr are requested | |||
ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES" | |||
// ReviewStateRequestReview review is requested from user | |||
ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW" | |||
// ReviewStateUnknown state of pr is unknown | |||
ReviewStateUnknown ReviewStateType = "" | |||
) | |||
// 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"` | |||
HTMLURL string `json:"html_url"` | |||
HTMLPullURL string `json:"pull_request_url"` | |||
} | |||
// PullReviewComment represents a comment on a pull request review | |||
type PullReviewComment struct { | |||
ID int64 `json:"id"` | |||
Body string `json:"body"` | |||
Reviewer *User `json:"user"` | |||
ReviewID int64 `json:"pull_request_review_id"` | |||
Created time.Time `json:"created_at"` | |||
Updated time.Time `json:"updated_at"` | |||
Path string `json:"path"` | |||
CommitID string `json:"commit_id"` | |||
OrigCommitID string `json:"original_commit_id"` | |||
DiffHunk string `json:"diff_hunk"` | |||
LineNum uint64 `json:"position"` | |||
OldLineNum uint64 `json:"original_position"` | |||
HTMLURL string `json:"html_url"` | |||
HTMLPullURL string `json:"pull_request_url"` | |||
} | |||
// CreatePullReviewOptions are options to create a pull review | |||
type CreatePullReviewOptions struct { | |||
State ReviewStateType `json:"event"` | |||
Body string `json:"body"` | |||
CommitID string `json:"commit_id"` | |||
Comments []CreatePullReviewComment `json:"comments"` | |||
} | |||
// CreatePullReviewComment represent a review comment for creation api | |||
type CreatePullReviewComment struct { | |||
// the tree path | |||
Path string `json:"path"` | |||
Body string `json:"body"` | |||
// if comment to old file line or 0 | |||
OldLineNum int64 `json:"old_position"` | |||
// if comment to new file line or 0 | |||
NewLineNum int64 `json:"new_position"` | |||
} | |||
// SubmitPullReviewOptions are options to submit a pending pull review | |||
type SubmitPullReviewOptions struct { | |||
State ReviewStateType `json:"event"` | |||
Body string `json:"body"` | |||
} | |||
// ListPullReviewsOptions options for listing PullReviews | |||
type ListPullReviewsOptions struct { | |||
ListOptions | |||
} | |||
// Validate the CreatePullReviewOptions struct | |||
func (opt CreatePullReviewOptions) Validate() error { | |||
if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 { | |||
return fmt.Errorf("body is empty") | |||
} | |||
for i := range opt.Comments { | |||
if err := opt.Comments[i].Validate(); err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
// Validate the SubmitPullReviewOptions struct | |||
func (opt SubmitPullReviewOptions) Validate() error { | |||
if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 { | |||
return fmt.Errorf("body is empty") | |||
} | |||
return nil | |||
} | |||
// Validate the CreatePullReviewComment struct | |||
func (opt CreatePullReviewComment) Validate() error { | |||
if len(strings.TrimSpace(opt.Body)) == 0 { | |||
return fmt.Errorf("body is empty") | |||
} | |||
if opt.NewLineNum != 0 && opt.OldLineNum != 0 { | |||
return fmt.Errorf("old and new line num are set, cant identify the code comment position") | |||
} | |||
return nil | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
opt.setDefaults() | |||
rs := make([]*PullReview, 0, opt.PageSize) | |||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index)) | |||
link.RawQuery = opt.ListOptions.getURLQuery().Encode() | |||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rs) | |||
return rs, resp, err | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
r := new(PullReview) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil, &r) | |||
return r, resp, err | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
rcl := make([]*PullReviewComment, 0, 4) | |||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/comments", owner, repo, index, id)) | |||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rcl) | |||
return rcl, resp, err | |||
} | |||
// 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 { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil) | |||
return resp, err | |||
} | |||
// 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 { | |||
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 | |||
} | |||
r := new(PullReview) | |||
resp, err := c.getParsedResponse("POST", | |||
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index), | |||
jsonHeader, bytes.NewReader(body), r) | |||
return r, resp, err | |||
} | |||
// 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 { | |||
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 | |||
} | |||
r := new(PullReview) | |||
resp, err := c.getParsedResponse("POST", | |||
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), | |||
jsonHeader, bytes.NewReader(body), r) | |||
return r, resp, err | |||
} |
@@ -0,0 +1,153 @@ | |||
// Copyright 2016 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" | |||
"strings" | |||
"time" | |||
) | |||
// Release represents a repository release | |||
type Release struct { | |||
ID int64 `json:"id"` | |||
TagName string `json:"tag_name"` | |||
Target string `json:"target_commitish"` | |||
Title string `json:"name"` | |||
Note string `json:"body"` | |||
URL string `json:"url"` | |||
TarURL string `json:"tarball_url"` | |||
ZipURL string `json:"zipball_url"` | |||
IsDraft bool `json:"draft"` | |||
IsPrerelease bool `json:"prerelease"` | |||
CreatedAt time.Time `json:"created_at"` | |||
PublishedAt time.Time `json:"published_at"` | |||
Publisher *User `json:"author"` | |||
Attachments []*Attachment `json:"assets"` | |||
} | |||
// ListReleasesOptions options for listing repository's releases | |||
type ListReleasesOptions struct { | |||
ListOptions | |||
} | |||
// ListReleases list releases of a repository | |||
func (c *Client) ListReleases(user, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) { | |||
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()), | |||
nil, nil, &releases) | |||
return releases, resp, err | |||
} | |||
// GetRelease get a release of a repository by id | |||
func (c *Client) GetRelease(user, repo string, id int64) (*Release, *Response, error) { | |||
r := new(Release) | |||
resp, err := c.getParsedResponse("GET", | |||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), | |||
jsonHeader, nil, &r) | |||
return r, resp, err | |||
} | |||
// GetReleaseByTag get a release of a repository by tag | |||
func (c *Client) GetReleaseByTag(user, repo string, tag string) (*Release, *Response, error) { | |||
if c.CheckServerVersionConstraint(">=1.13.0") != nil { | |||
return c.fallbackGetReleaseByTag(user, repo, tag) | |||
} | |||
r := new(Release) | |||
resp, err := c.getParsedResponse("GET", | |||
fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag), | |||
nil, nil, &r) | |||
return r, resp, err | |||
} | |||
// CreateReleaseOption options when creating a release | |||
type CreateReleaseOption struct { | |||
TagName string `json:"tag_name"` | |||
Target string `json:"target_commitish"` | |||
Title string `json:"name"` | |||
Note string `json:"body"` | |||
IsDraft bool `json:"draft"` | |||
IsPrerelease bool `json:"prerelease"` | |||
} | |||
// Validate the CreateReleaseOption struct | |||
func (opt CreateReleaseOption) Validate() error { | |||
if len(strings.TrimSpace(opt.Title)) == 0 { | |||
return fmt.Errorf("title is empty") | |||
} | |||
return nil | |||
} | |||
// CreateRelease create a release | |||
func (c *Client) CreateRelease(user, repo string, opt CreateReleaseOption) (*Release, *Response, error) { | |||
if err := opt.Validate(); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
r := new(Release) | |||
resp, err := c.getParsedResponse("POST", | |||
fmt.Sprintf("/repos/%s/%s/releases", user, repo), | |||
jsonHeader, bytes.NewReader(body), r) | |||
return r, resp, err | |||
} | |||
// EditReleaseOption options when editing a release | |||
type EditReleaseOption struct { | |||
TagName string `json:"tag_name"` | |||
Target string `json:"target_commitish"` | |||
Title string `json:"name"` | |||
Note string `json:"body"` | |||
IsDraft *bool `json:"draft"` | |||
IsPrerelease *bool `json:"prerelease"` | |||
} | |||
// EditRelease edit a release | |||
func (c *Client) EditRelease(user, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) { | |||
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), | |||
jsonHeader, bytes.NewReader(body), r) | |||
return r, resp, err | |||
} | |||
// DeleteRelease delete a release from a repository | |||
func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", | |||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), | |||
nil, nil) | |||
return resp, err | |||
} | |||
// fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 ) | |||
func (c *Client) fallbackGetReleaseByTag(user, repo string, tag string) (*Release, *Response, error) { | |||
for i := 1; ; i++ { | |||
rl, resp, err := c.ListReleases(user, repo, ListReleasesOptions{ListOptions{Page: i}}) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
if len(rl) == 0 { | |||
return nil, | |||
&Response{&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,390 @@ | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// 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 ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"net/url" | |||
"strings" | |||
"time" | |||
) | |||
// Permission represents a set of permissions | |||
type Permission struct { | |||
Admin bool `json:"admin"` | |||
Push bool `json:"push"` | |||
Pull bool `json:"pull"` | |||
} | |||
// 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"` | |||
} | |||
// RepoType represent repo type | |||
type RepoType string | |||
const ( | |||
// RepoTypeNone dont specify a type | |||
RepoTypeNone RepoType = "" | |||
// RepoTypeSource is the default repo type | |||
RepoTypeSource RepoType = "source" | |||
// RepoTypeFork is a repo witch was forked from an other one | |||
RepoTypeFork RepoType = "fork" | |||
// RepoTypeMirror represents an mirror repo | |||
RepoTypeMirror RepoType = "mirror" | |||
) | |||
// ListReposOptions options for listing repositories | |||
type ListReposOptions struct { | |||
ListOptions | |||
} | |||
// ListMyRepos lists all repositories for the authenticated user that has access to. | |||
func (c *Client) ListMyRepos(opt ListReposOptions) ([]*Repository, *Response, error) { | |||
opt.setDefaults() | |||
repos := make([]*Repository, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/repos?%s", opt.getURLQuery().Encode()), nil, nil, &repos) | |||
return repos, resp, err | |||
} | |||
// ListUserRepos list all repositories of one user by user's name | |||
func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) { | |||
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) | |||
return repos, resp, err | |||
} | |||
// ListOrgReposOptions options for a organization's repositories | |||
type ListOrgReposOptions struct { | |||
ListOptions | |||
} | |||
// ListOrgRepos list all repositories of one organization by organization's name | |||
func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) { | |||
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) | |||
return repos, resp, err | |||
} | |||
// SearchRepoOptions options for searching repositories | |||
type SearchRepoOptions struct { | |||
ListOptions | |||
// The keyword to query | |||
Keyword string | |||
// Limit search to repositories with keyword as topic | |||
KeywordIsTopic bool | |||
// Include search of keyword within repository description | |||
KeywordInDescription bool | |||
/* | |||
User Filter | |||
*/ | |||
// Repo Owner | |||
OwnerID int64 | |||
// Stared By UserID | |||
StarredByUserID int64 | |||
/* | |||
Repo Attributes | |||
*/ | |||
// pubic, private or all repositories (defaults to all) | |||
IsPrivate *bool | |||
// archived, non-archived or all repositories (defaults to all) | |||
IsArchived *bool | |||
// Exclude template repos from search | |||
ExcludeTemplate bool | |||
// Filter by "fork", "source", "mirror" | |||
Type RepoType | |||
/* | |||
Sort Filters | |||
*/ | |||
// sort repos by attribute. Supported values are "alpha", "created", "updated", "size", and "id". Default is "alpha" | |||
Sort string | |||
// sort order, either "asc" (ascending) or "desc" (descending). Default is "asc", ignored if "sort" is not specified. | |||
Order string | |||
// Repo owner to prioritize in the results | |||
PrioritizedByOwnerID int64 | |||
/* | |||
Cover EdgeCases | |||
*/ | |||
// if set all other options are ignored and this string is used as query | |||
RawQuery string | |||
} | |||
// QueryEncode turns options into querystring argument | |||
func (opt *SearchRepoOptions) QueryEncode() string { | |||
query := opt.getURLQuery() | |||
if opt.Keyword != "" { | |||
query.Add("q", opt.Keyword) | |||
} | |||
if opt.KeywordIsTopic { | |||
query.Add("topic", "true") | |||
} | |||
if opt.KeywordInDescription { | |||
query.Add("includeDesc", "true") | |||
} | |||
// User Filter | |||
if opt.OwnerID > 0 { | |||
query.Add("uid", fmt.Sprintf("%d", opt.OwnerID)) | |||
query.Add("exclusive", "true") | |||
} | |||
if opt.StarredByUserID > 0 { | |||
query.Add("starredBy", fmt.Sprintf("%d", opt.StarredByUserID)) | |||
} | |||
// Repo Attributes | |||
if opt.IsPrivate != nil { | |||
query.Add("is_private", fmt.Sprintf("%v", opt.IsPrivate)) | |||
} | |||
if opt.IsArchived != nil { | |||
query.Add("archived", fmt.Sprintf("%v", opt.IsArchived)) | |||
} | |||
if opt.ExcludeTemplate { | |||
query.Add("template", "false") | |||
} | |||
if len(opt.Type) != 0 { | |||
query.Add("mode", string(opt.Type)) | |||
} | |||
// Sort Filters | |||
if opt.Sort != "" { | |||
query.Add("sort", opt.Sort) | |||
} | |||
if opt.PrioritizedByOwnerID > 0 { | |||
query.Add("priority_owner_id", fmt.Sprintf("%d", opt.PrioritizedByOwnerID)) | |||
} | |||
if opt.Order != "" { | |||
query.Add("order", opt.Order) | |||
} | |||
return query.Encode() | |||
} | |||
type searchRepoResponse struct { | |||
Repos []*Repository `json:"data"` | |||
} | |||
// SearchRepos searches for repositories matching the given filters | |||
func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, error) { | |||
opt.setDefaults() | |||
repos := new(searchRepoResponse) | |||
link, _ := url.Parse("/repos/search") | |||
if len(opt.RawQuery) != 0 { | |||
link.RawQuery = opt.RawQuery | |||
} 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 *opt.IsPrivate { | |||
// private repos only not supported on gitea <= 1.11.x | |||
return nil, nil, err | |||
} | |||
link.Query().Add("private", "false") | |||
} | |||
} | |||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &repos) | |||
return repos.Repos, resp, err | |||
} | |||
// CreateRepoOption options when creating repository | |||
type CreateRepoOption struct { | |||
// Name of the repository to create | |||
Name string `json:"name"` | |||
// Description of the repository to create | |||
Description string `json:"description"` | |||
// Whether the repository is private | |||
Private bool `json:"private"` | |||
// Issue Label set to use | |||
IssueLabels string `json:"issue_labels"` | |||
// Whether the repository should be auto-intialized? | |||
AutoInit bool `json:"auto_init"` | |||
// Gitignores to use | |||
Gitignores string `json:"gitignores"` | |||
// License to use | |||
License string `json:"license"` | |||
// Readme of the repository to create | |||
Readme string `json:"readme"` | |||
// DefaultBranch of the repository (used when initializes and in template) | |||
DefaultBranch string `json:"default_branch"` | |||
} | |||
// Validate the CreateRepoOption struct | |||
func (opt CreateRepoOption) Validate() error { | |||
if len(strings.TrimSpace(opt.Name)) == 0 { | |||
return fmt.Errorf("name is empty") | |||
} | |||
return nil | |||
} | |||
// CreateRepo creates a repository for authenticated user. | |||
func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) { | |||
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", "/user/repos", jsonHeader, bytes.NewReader(body), repo) | |||
return repo, resp, err | |||
} | |||
// 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 { | |||
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("/org/%s/repos", org), jsonHeader, bytes.NewReader(body), repo) | |||
return repo, resp, err | |||
} | |||
// GetRepo returns information of a repository of given owner. | |||
func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) { | |||
repo := new(Repository) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo) | |||
return repo, resp, err | |||
} | |||
// EditRepoOption options when editing a repository's properties | |||
type EditRepoOption struct { | |||
// name of the repository | |||
Name *string `json:"name,omitempty"` | |||
// a short description of the repository. | |||
Description *string `json:"description,omitempty"` | |||
// a URL with more information about the repository. | |||
Website *string `json:"website,omitempty"` | |||
// either `true` to make the repository private or `false` to make it public. | |||
// 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 enable issues for this repository or `false` to disable them. | |||
HasIssues *bool `json:"has_issues,omitempty"` | |||
// either `true` to enable the wiki for this repository or `false` to disable it. | |||
HasWiki *bool `json:"has_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 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`. | |||
AllowMerge *bool `json:"allow_merge_commits,omitempty"` | |||
// either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging. `has_pull_requests` must be `true`. | |||
AllowRebase *bool `json:"allow_rebase,omitempty"` | |||
// either `true` to allow rebase with explicit merge commits (--no-ff), or `false` to prevent rebase with explicit merge commits. `has_pull_requests` must be `true`. | |||
AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"` | |||
// either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. `has_pull_requests` must be `true`. | |||
AllowSquash *bool `json:"allow_squash_merge,omitempty"` | |||
// set to `true` to archive this repository. | |||
Archived *bool `json:"archived,omitempty"` | |||
} | |||
// EditRepo edit the properties of a repository | |||
func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
repo := new(Repository) | |||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s", owner, reponame), jsonHeader, bytes.NewReader(body), repo) | |||
return repo, resp, err | |||
} | |||
// DeleteRepo deletes a repository of user or organization. | |||
func (c *Client) DeleteRepo(owner, repo string) (*Response, error) { | |||
_, 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) { | |||
_, 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) { | |||
langMap := make(map[string]int64) | |||
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
if err = json.Unmarshal(data, &langMap); err != nil { | |||
return nil, resp, err | |||
} | |||
return langMap, resp, nil | |||
} | |||
// ArchiveType represent supported archive formats by gitea | |||
type ArchiveType string | |||
const ( | |||
// ZipArchive represent zip format | |||
ZipArchive ArchiveType = ".zip" | |||
// TarGZArchive represent tar.gz format | |||
TarGZArchive ArchiveType = ".tar.gz" | |||
) | |||
// 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) | |||
} |
@@ -0,0 +1,134 @@ | |||
// Copyright 2016 The Gogs Authors. All rights reserved. | |||
// 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 ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"time" | |||
) | |||
// PayloadUser represents the author or committer of a commit | |||
type PayloadUser struct { | |||
// Full name of the commit author | |||
Name string `json:"name"` | |||
Email string `json:"email"` | |||
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 | |||
ID string `json:"id"` | |||
Message string `json:"message"` | |||
URL string `json:"url"` | |||
Author *PayloadUser `json:"author"` | |||
Committer *PayloadUser `json:"committer"` | |||
Verification *PayloadCommitVerification `json:"verification"` | |||
Timestamp time.Time `json:"timestamp"` | |||
Added []string `json:"added"` | |||
Removed []string `json:"removed"` | |||
Modified []string `json:"modified"` | |||
} | |||
// PayloadCommitVerification represents the GPG verification of a commit | |||
type PayloadCommitVerification struct { | |||
Verified bool `json:"verified"` | |||
Reason string `json:"reason"` | |||
Signature string `json:"signature"` | |||
Payload string `json:"payload"` | |||
} | |||
// Branch represents a repository branch | |||
type Branch struct { | |||
Name string `json:"name"` | |||
Commit *PayloadCommit `json:"commit"` | |||
Protected bool `json:"protected"` | |||
RequiredApprovals int64 `json:"required_approvals"` | |||
EnableStatusCheck bool `json:"enable_status_check"` | |||
StatusCheckContexts []string `json:"status_check_contexts"` | |||
UserCanPush bool `json:"user_can_push"` | |||
UserCanMerge bool `json:"user_can_merge"` | |||
EffectiveBranchProtectionName string `json:"effective_branch_protection_name"` | |||
} | |||
// ListRepoBranchesOptions options for listing a repository's branches | |||
type ListRepoBranchesOptions struct { | |||
ListOptions | |||
} | |||
// ListRepoBranches list all the branches of one repository | |||
func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions) ([]*Branch, *Response, error) { | |||
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) | |||
return branches, resp, err | |||
} | |||
// GetRepoBranch get one branch's information of one repository | |||
func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, error) { | |||
b := new(Branch) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return b, resp, nil | |||
} | |||
// 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 { | |||
return false, nil, err | |||
} | |||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil) | |||
if err != nil { | |||
return false, resp, err | |||
} | |||
return status == 204, resp, nil | |||
} | |||
// CreateBranchOption options when creating a branch in a repository | |||
type CreateBranchOption struct { | |||
// Name of the branch to create | |||
BranchName string `json:"new_branch_name"` | |||
// Name of the old branch to create from (optional) | |||
OldBranchName string `json:"old_branch_name"` | |||
} | |||
// Validate the CreateBranchOption struct | |||
func (opt CreateBranchOption) Validate() error { | |||
if len(opt.BranchName) == 0 { | |||
return fmt.Errorf("BranchName is empty") | |||
} | |||
if len(opt.BranchName) > 100 { | |||
return fmt.Errorf("BranchName to long") | |||
} | |||
if len(opt.OldBranchName) > 100 { | |||
return fmt.Errorf("OldBranchName to long") | |||
} | |||
return nil | |||
} | |||
// 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 { | |||
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 | |||
} | |||
branch := new(Branch) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branches", owner, repo), jsonHeader, bytes.NewReader(body), branch) | |||
return branch, resp, err | |||
} |
@@ -0,0 +1,150 @@ | |||
// 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 ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"net/url" | |||
"time" | |||
) | |||
// 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"` | |||
} | |||
// 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"` | |||
} | |||
// 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"` | |||
} | |||
// ListBranchProtectionsOptions list branch protection options | |||
type ListBranchProtectionsOptions struct { | |||
ListOptions | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
bps := make([]*BranchProtection, 0, opt.PageSize) | |||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo)) | |||
link.RawQuery = opt.getURLQuery().Encode() | |||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &bps) | |||
return bps, resp, err | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
bp := new(BranchProtection) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil, bp) | |||
return bp, resp, err | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
bp := new(BranchProtection) | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo), jsonHeader, bytes.NewReader(body), bp) | |||
return bp, resp, err | |||
} | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
bp := new(BranchProtection) | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), bp) | |||
return bp, resp, err | |||
} | |||
// 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 { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,97 @@ | |||
// 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. | |||
package gitea | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
) | |||
// ListCollaboratorsOptions options for listing a repository's collaborators | |||
type ListCollaboratorsOptions struct { | |||
ListOptions | |||
} | |||
// ListCollaborators list a repository's collaborators | |||
func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) { | |||
opt.setDefaults() | |||
collaborators := make([]*User, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", | |||
fmt.Sprintf("/repos/%s/%s/collaborators?%s", user, repo, opt.getURLQuery().Encode()), | |||
nil, nil, &collaborators) | |||
return collaborators, resp, err | |||
} | |||
// IsCollaborator check if a user is a collaborator of a repository | |||
func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Response, error) { | |||
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 | |||
} | |||
if status == 204 { | |||
return true, resp, nil | |||
} | |||
return false, resp, nil | |||
} | |||
// AddCollaboratorOption options when adding a user as a collaborator of a repository | |||
type AddCollaboratorOption struct { | |||
Permission *AccessMode `json:"permission"` | |||
} | |||
// AccessMode represent the grade of access you have to something | |||
type AccessMode string | |||
const ( | |||
// AccessModeNone no access | |||
AccessModeNone AccessMode = "none" | |||
// AccessModeRead read access | |||
AccessModeRead AccessMode = "read" | |||
// AccessModeWrite write access | |||
AccessModeWrite AccessMode = "write" | |||
// AccessModeAdmin admin access | |||
AccessModeAdmin AccessMode = "admin" | |||
// AccessModeOwner owner | |||
AccessModeOwner AccessMode = "owner" | |||
) | |||
// Validate the AddCollaboratorOption struct | |||
func (opt AddCollaboratorOption) Validate() error { | |||
if opt.Permission != nil { | |||
if *opt.Permission == AccessModeOwner { | |||
*opt.Permission = AccessModeAdmin | |||
return nil | |||
} | |||
if *opt.Permission == AccessModeNone { | |||
opt.Permission = nil | |||
return nil | |||
} | |||
if *opt.Permission != AccessModeRead && *opt.Permission != AccessModeWrite && *opt.Permission != AccessModeAdmin { | |||
return fmt.Errorf("permission mode invalid") | |||
} | |||
} | |||
return nil | |||
} | |||
// 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 { | |||
return nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} | |||
// DeleteCollaborator remove a collaborator from a repository | |||
func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", | |||
fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,88 @@ | |||
// Copyright 2018 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 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/url" | |||
"time" | |||
) | |||
// Identity for a person's identity like an author or committer | |||
type Identity struct { | |||
Name string `json:"name"` | |||
Email string `json:"email"` | |||
} | |||
// CommitMeta contains meta information of a commit in terms of API. | |||
type CommitMeta struct { | |||
URL string `json:"url"` | |||
SHA string `json:"sha"` | |||
} | |||
// CommitUser contains information of a user in the context of a commit. | |||
type CommitUser struct { | |||
Identity | |||
Date string `json:"date"` | |||
} | |||
// 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"` | |||
} | |||
// 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"` | |||
} | |||
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE | |||
type CommitDateOptions struct { | |||
Author time.Time `json:"author"` | |||
Committer time.Time `json:"committer"` | |||
} | |||
// GetSingleCommit returns a single commit | |||
func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Response, error) { | |||
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 | |||
} | |||
// ListCommitOptions list commit options | |||
type ListCommitOptions struct { | |||
ListOptions | |||
//SHA or branch to start listing commits from (usually 'master') | |||
SHA string | |||
} | |||
// QueryEncode turns options into querystring argument | |||
func (opt *ListCommitOptions) QueryEncode() string { | |||
query := opt.ListOptions.getURLQuery() | |||
if opt.SHA != "" { | |||
query.Add("sha", opt.SHA) | |||
} | |||
return query.Encode() | |||
} | |||
// ListRepoCommits return list of commits from a repo | |||
func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) { | |||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo)) | |||
opt.setDefaults() | |||
commits := make([]*Commit, 0, opt.PageSize) | |||
link.RawQuery = opt.QueryEncode() | |||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits) | |||
return commits, resp, err | |||
} |
@@ -0,0 +1,194 @@ | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 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" | |||
) | |||
// FileOptions options for all file APIs | |||
type FileOptions struct { | |||
// message (optional) for the commit of this file. if not supplied, a default message will be used | |||
Message string `json:"message"` | |||
// branch (optional) to base this file from. if not given, the default branch is used | |||
BranchName string `json:"branch"` | |||
// new_branch (optional) will make a new branch from `branch` before creating the file | |||
NewBranchName string `json:"new_branch"` | |||
// `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) | |||
Author Identity `json:"author"` | |||
Committer Identity `json:"committer"` | |||
Dates CommitDateOptions `json:"dates"` | |||
} | |||
// CreateFileOptions options for creating files | |||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) | |||
type CreateFileOptions struct { | |||
FileOptions | |||
// content must be base64 encoded | |||
// required: true | |||
Content string `json:"content"` | |||
} | |||
// DeleteFileOptions options for deleting files (used for other File structs below) | |||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) | |||
type DeleteFileOptions struct { | |||
FileOptions | |||
// sha is the SHA for the file that already exists | |||
// required: true | |||
SHA string `json:"sha"` | |||
} | |||
// UpdateFileOptions options for updating files | |||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) | |||
type UpdateFileOptions struct { | |||
FileOptions | |||
// sha is the SHA for the file that already exists | |||
// required: true | |||
SHA string `json:"sha"` | |||
// content must be base64 encoded | |||
// required: true | |||
Content string `json:"content"` | |||
// from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL | |||
FromPath string `json:"from_path"` | |||
} | |||
// FileLinksResponse contains the links for a repo's file | |||
type FileLinksResponse struct { | |||
Self *string `json:"self"` | |||
GitURL *string `json:"git"` | |||
HTMLURL *string `json:"html"` | |||
} | |||
// ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content | |||
type ContentsResponse struct { | |||
Name string `json:"name"` | |||
Path string `json:"path"` | |||
SHA string `json:"sha"` | |||
// `type` will be `file`, `dir`, `symlink`, or `submodule` | |||
Type string `json:"type"` | |||
Size int64 `json:"size"` | |||
// `encoding` is populated when `type` is `file`, otherwise null | |||
Encoding *string `json:"encoding"` | |||
// `content` is populated when `type` is `file`, otherwise null | |||
Content *string `json:"content"` | |||
// `target` is populated when `type` is `symlink`, otherwise null | |||
Target *string `json:"target"` | |||
URL *string `json:"url"` | |||
HTMLURL *string `json:"html_url"` | |||
GitURL *string `json:"git_url"` | |||
DownloadURL *string `json:"download_url"` | |||
// `submodule_git_url` is populated when `type` is `submodule`, otherwise null | |||
SubmoduleGitURL *string `json:"submodule_git_url"` | |||
Links *FileLinksResponse `json:"_links"` | |||
} | |||
// FileCommitResponse contains information generated from a Git commit for a repo's file. | |||
type FileCommitResponse struct { | |||
CommitMeta | |||
HTMLURL string `json:"html_url"` | |||
Author *CommitUser `json:"author"` | |||
Committer *CommitUser `json:"committer"` | |||
Parents []*CommitMeta `json:"parents"` | |||
Message string `json:"message"` | |||
Tree *CommitMeta `json:"tree"` | |||
} | |||
// FileResponse contains information about a repo's file | |||
type FileResponse struct { | |||
Content *ContentsResponse `json:"content"` | |||
Commit *FileCommitResponse `json:"commit"` | |||
Verification *PayloadCommitVerification `json:"verification"` | |||
} | |||
// FileDeleteResponse contains information about a repo's file that was deleted | |||
type FileDeleteResponse struct { | |||
Content interface{} `json:"content"` // to be set to nil | |||
Commit *FileCommitResponse `json:"commit"` | |||
Verification *PayloadCommitVerification `json:"verification"` | |||
} | |||
// 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) | |||
} | |||
// GetContents get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir | |||
// ref is optional | |||
func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) { | |||
cr := new(ContentsResponse) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, ref), jsonHeader, nil, cr) | |||
return cr, resp, err | |||
} | |||
// 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 | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
fr := new(FileResponse) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr) | |||
return fr, resp, err | |||
} | |||
// UpdateFile update a file in a repository | |||
func (c *Client) UpdateFile(owner, repo, filepath string, opt UpdateFileOptions) (*FileResponse, *Response, error) { | |||
var err error | |||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { | |||
return nil, nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
fr := new(FileResponse) | |||
resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr) | |||
return fr, resp, err | |||
} | |||
// DeleteFile delete a file from repository | |||
func (c *Client) DeleteFile(owner, repo, filepath string, opt DeleteFileOptions) (*Response, error) { | |||
var err error | |||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil { | |||
return nil, err | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body)) | |||
if err != nil { | |||
return resp, err | |||
} | |||
if status != 200 && status != 204 { | |||
return resp, fmt.Errorf("unexpected Status: %d", status) | |||
} | |||
return resp, nil | |||
} | |||
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 { | |||
r, _, err := c.GetRepo(owner, repo) | |||
if err != nil { | |||
return "", err | |||
} | |||
return r.DefaultBranch, nil | |||
} | |||
} | |||
return branch, nil | |||
} |
@@ -0,0 +1,79 @@ | |||
// Copyright 2015 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. | |||
package gitea | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"net/url" | |||
"time" | |||
) | |||
// DeployKey a deploy key | |||
type DeployKey struct { | |||
ID int64 `json:"id"` | |||
KeyID int64 `json:"key_id"` | |||
Key string `json:"key"` | |||
URL string `json:"url"` | |||
Title string `json:"title"` | |||
Fingerprint string `json:"fingerprint"` | |||
Created time.Time `json:"created_at"` | |||
ReadOnly bool `json:"read_only"` | |||
Repository *Repository `json:"repository,omitempty"` | |||
} | |||
// ListDeployKeysOptions options for listing a repository's deploy keys | |||
type ListDeployKeysOptions struct { | |||
ListOptions | |||
KeyID int64 | |||
Fingerprint string | |||
} | |||
// QueryEncode turns options into querystring argument | |||
func (opt *ListDeployKeysOptions) QueryEncode() string { | |||
query := opt.getURLQuery() | |||
if opt.KeyID > 0 { | |||
query.Add("key_id", fmt.Sprintf("%d", opt.KeyID)) | |||
} | |||
if len(opt.Fingerprint) > 0 { | |||
query.Add("fingerprint", opt.Fingerprint) | |||
} | |||
return query.Encode() | |||
} | |||
// ListDeployKeys list all the deploy keys of one repository | |||
func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([]*DeployKey, *Response, error) { | |||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/keys", user, repo)) | |||
opt.setDefaults() | |||
link.RawQuery = opt.QueryEncode() | |||
keys := make([]*DeployKey, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &keys) | |||
return keys, resp, err | |||
} | |||
// GetDeployKey get one deploy key with key id | |||
func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Response, error) { | |||
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 | |||
} | |||
// CreateDeployKey options when create one deploy key | |||
func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
key := new(DeployKey) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/keys", user, repo), jsonHeader, bytes.NewReader(body), key) | |||
return key, resp, err | |||
} | |||
// DeleteDeployKey delete deploy key with key id | |||
func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,115 @@ | |||
// 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 ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
) | |||
// GitServiceType represents a git service | |||
type GitServiceType string | |||
const ( | |||
// GitServicePlain represents a plain git service | |||
GitServicePlain GitServiceType = "git" | |||
//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" | |||
) | |||
// MigrateRepoOption options for migrating a repository from an external service | |||
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"` | |||
} | |||
// Validate the MigrateRepoOption struct | |||
func (opt *MigrateRepoOption) Validate() error { | |||
// check user options | |||
if len(opt.CloneAddr) == 0 { | |||
return fmt.Errorf("CloneAddr required") | |||
} | |||
if len(opt.RepoName) == 0 { | |||
return fmt.Errorf("RepoName required") | |||
} else if len(opt.RepoName) > 100 { | |||
return fmt.Errorf("RepoName to long") | |||
} | |||
if len(opt.Description) > 255 { | |||
return fmt.Errorf("Description to long") | |||
} | |||
switch opt.Service { | |||
case GitServiceGithub: | |||
if len(opt.AuthToken) == 0 { | |||
return fmt.Errorf("github require token authentication") | |||
} | |||
} | |||
return nil | |||
} | |||
// MigrateRepo migrates a repository from other Git hosting sources for the authenticated user. | |||
// | |||
// 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 { | |||
return nil, nil, err | |||
} | |||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { | |||
if len(opt.AuthToken) != 0 { | |||
// gitea <= 1.12 dont understand AuthToken | |||
opt.AuthUsername = opt.AuthToken | |||
opt.AuthPassword, opt.AuthToken = "", "" | |||
} | |||
if len(opt.RepoOwner) != 0 { | |||
// gitea <= 1.12 dont understand RepoOwner | |||
u, _, err := c.GetUserInfo(opt.RepoOwner) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
opt.RepoOwnerID = u.ID | |||
} else if opt.RepoOwnerID == 0 { | |||
// gitea <= 1.12 require RepoOwnerID | |||
u, _, err := c.GetMyUserInfo() | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
opt.RepoOwnerID = u.ID | |||
} | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
repo := new(Repository) | |||
resp, err := c.getParsedResponse("POST", "/repos/migrate", jsonHeader, bytes.NewReader(body), repo) | |||
return repo, resp, err | |||
} |
@@ -0,0 +1,69 @@ | |||
// Copyright 2018 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 ( | |||
"encoding/json" | |||
"errors" | |||
"fmt" | |||
"strings" | |||
) | |||
// Reference represents a Git reference. | |||
type Reference struct { | |||
Ref string `json:"ref"` | |||
URL string `json:"url"` | |||
Object *GitObject `json:"object"` | |||
} | |||
// GitObject represents a Git object. | |||
type GitObject struct { | |||
Type string `json:"type"` | |||
SHA string `json:"sha"` | |||
URL string `json:"url"` | |||
} | |||
// GetRepoRef get one ref's information of one repository | |||
func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, error) { | |||
ref = strings.TrimPrefix(ref, "refs/") | |||
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 { | |||
// Multiple refs | |||
return nil, resp, errors.New("no exact match found for this ref") | |||
} else if err != nil { | |||
return nil, resp, err | |||
} | |||
return r, resp, nil | |||
} | |||
// GetRepoRefs get list of ref's information of one repository | |||
func (c *Client) GetRepoRefs(user, repo, ref string) ([]*Reference, *Response, error) { | |||
ref = strings.TrimPrefix(ref, "refs/") | |||
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 | |||
} | |||
// Attempt to unmarshal single returned ref. | |||
r := new(Reference) | |||
refErr := json.Unmarshal(data, r) | |||
if refErr == nil { | |||
return []*Reference{r}, resp, nil | |||
} | |||
// Attempt to unmarshal multiple refs. | |||
var rs []*Reference | |||
refsErr := json.Unmarshal(data, &rs) | |||
if refsErr == nil { | |||
if len(rs) == 0 { | |||
return nil, resp, errors.New("unexpected response: an array of refs with length 0") | |||
} | |||
return rs, resp, nil | |||
} | |||
return nil, resp, fmt.Errorf("unmarshalling failed for both single and multiple refs: %s and %s", refErr, refsErr) | |||
} |
@@ -0,0 +1,31 @@ | |||
// Copyright 2019 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" | |||
) | |||
// Tag represents a repository tag | |||
type Tag struct { | |||
Name string `json:"name"` | |||
ID string `json:"id"` | |||
Commit *CommitMeta `json:"commit"` | |||
ZipballURL string `json:"zipball_url"` | |||
TarballURL string `json:"tarball_url"` | |||
} | |||
// ListRepoTagsOptions options for listing a repository's tags | |||
type ListRepoTagsOptions struct { | |||
ListOptions | |||
} | |||
// ListRepoTags list all the branches of one repository | |||
func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Tag, *Response, error) { | |||
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 | |||
} |
@@ -0,0 +1,58 @@ | |||
// Copyright 2019 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" | |||
) | |||
// ListRepoTopicsOptions options for listing repo's topics | |||
type ListRepoTopicsOptions struct { | |||
ListOptions | |||
} | |||
// topicsList represents a list of repo's topics | |||
type topicsList struct { | |||
Topics []string `json:"topics"` | |||
} | |||
// ListRepoTopics list all repository's topics | |||
func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([]string, *Response, error) { | |||
opt.setDefaults() | |||
list := new(topicsList) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/topics?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, list) | |||
if err != nil { | |||
return nil, resp, err | |||
} | |||
return list.Topics, resp, nil | |||
} | |||
// SetRepoTopics replaces the list of repo's topics | |||
func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, error) { | |||
l := topicsList{Topics: list} | |||
body, err := json.Marshal(&l) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics", user, repo), jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} | |||
// AddRepoTopic adds a topic to a repo's topics list | |||
func (c *Client) AddRepoTopic(user, repo, topic string) (*Response, error) { | |||
_, 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) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,33 @@ | |||
// 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 ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
) | |||
// TransferRepoOption options when transfer a repository's ownership | |||
type TransferRepoOption struct { | |||
// required: true | |||
NewOwner string `json:"new_owner"` | |||
// ID of the team or teams to add to the repository. Teams can only be added to organization-owned repositories. | |||
TeamIDs *[]int64 `json:"team_ids"` | |||
} | |||
// 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 { | |||
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/transfer", owner, reponame), jsonHeader, bytes.NewReader(body), repo) | |||
return repo, resp, err | |||
} |
@@ -0,0 +1,41 @@ | |||
// Copyright 2018 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" | |||
) | |||
// GitEntry represents a git tree | |||
type GitEntry struct { | |||
Path string `json:"path"` | |||
Mode string `json:"mode"` | |||
Type string `json:"type"` | |||
Size int64 `json:"size"` | |||
SHA string `json:"sha"` | |||
URL string `json:"url"` | |||
} | |||
// GitTreeResponse returns a git tree | |||
type GitTreeResponse struct { | |||
SHA string `json:"sha"` | |||
URL string `json:"url"` | |||
Entries []GitEntry `json:"tree"` | |||
Truncated bool `json:"truncated"` | |||
Page int `json:"page"` | |||
TotalCount int `json:"total_count"` | |||
} | |||
// GetTrees downloads a file of repository, ref can be branch/tag/commit. | |||
// e.g.: ref -> master, tree -> macaron.go(no leading slash) | |||
func (c *Client) GetTrees(user, repo, ref string, recursive bool) (*GitTreeResponse, *Response, error) { | |||
trees := new(GitTreeResponse) | |||
var path = fmt.Sprintf("/repos/%s/%s/git/trees/%s", user, repo, ref) | |||
if recursive { | |||
path += "?recursive=1" | |||
} | |||
resp, err := c.getParsedResponse("GET", path, nil, nil, trees) | |||
return trees, resp, err | |||
} |
@@ -0,0 +1,75 @@ | |||
// Copyright 2017 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" | |||
"time" | |||
) | |||
// WatchInfo represents an API watch status of one repository | |||
type WatchInfo struct { | |||
Subscribed bool `json:"subscribed"` | |||
Ignored bool `json:"ignored"` | |||
Reason interface{} `json:"reason"` | |||
CreatedAt time.Time `json:"created_at"` | |||
URL string `json:"url"` | |||
RepositoryURL string `json:"repository_url"` | |||
} | |||
// GetWatchedRepos list all the watched repos of user | |||
func (c *Client) GetWatchedRepos(user string) ([]*Repository, *Response, error) { | |||
repos := make([]*Repository, 0, 10) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/subscriptions", user), nil, nil, &repos) | |||
return repos, resp, err | |||
} | |||
// GetMyWatchedRepos list repositories watched by the authenticated user | |||
func (c *Client) GetMyWatchedRepos() ([]*Repository, *Response, error) { | |||
repos := make([]*Repository, 0, 10) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/subscriptions"), nil, nil, &repos) | |||
return repos, resp, err | |||
} | |||
// CheckRepoWatch check if the current user is watching a repo | |||
func (c *Client) CheckRepoWatch(repoUser, repoName string) (bool, *Response, error) { | |||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), nil, nil) | |||
if err != nil { | |||
return false, resp, err | |||
} | |||
switch status { | |||
case http.StatusNotFound: | |||
return false, resp, nil | |||
case http.StatusOK: | |||
return true, resp, nil | |||
default: | |||
return false, resp, fmt.Errorf("unexpected Status: %d", status) | |||
} | |||
} | |||
// WatchRepo start to watch a repository | |||
func (c *Client) WatchRepo(repoUser, repoName string) (*Response, error) { | |||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), nil, nil) | |||
if err != nil { | |||
return resp, err | |||
} | |||
if status == http.StatusOK { | |||
return resp, nil | |||
} | |||
return resp, fmt.Errorf("unexpected Status: %d", status) | |||
} | |||
// UnWatchRepo stop to watch a repository | |||
func (c *Client) UnWatchRepo(repoUser, repoName string) (*Response, error) { | |||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), nil, nil) | |||
if err != nil { | |||
return resp, err | |||
} | |||
if status == http.StatusNoContent { | |||
return resp, nil | |||
} | |||
return resp, fmt.Errorf("unexpected Status: %d", status) | |||
} |
@@ -0,0 +1,72 @@ | |||
// 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 | |||
// GlobalUISettings represent the global ui settings of a gitea instance witch is exposed by API | |||
type GlobalUISettings struct { | |||
AllowedReactions []string `json:"allowed_reactions"` | |||
} | |||
// GlobalRepoSettings represent the global repository settings of a gitea instance witch is exposed by API | |||
type GlobalRepoSettings struct { | |||
MirrorsDisabled bool `json:"mirrors_disabled"` | |||
HTTPGitDisabled bool `json:"http_git_disabled"` | |||
} | |||
// GlobalAPISettings contains global api settings exposed by it | |||
type GlobalAPISettings struct { | |||
MaxResponseItems int `json:"max_response_items"` | |||
DefaultPagingNum int `json:"default_paging_num"` | |||
DefaultGitTreesPerPage int `json:"default_git_trees_per_page"` | |||
DefaultMaxBlobSize int64 `json:"default_max_blob_size"` | |||
} | |||
// GlobalAttachmentSettings contains global Attachment settings exposed by API | |||
type GlobalAttachmentSettings struct { | |||
Enabled bool `json:"enabled"` | |||
AllowedTypes string `json:"allowed_types"` | |||
MaxSize int64 `json:"max_size"` | |||
MaxFiles int `json:"max_files"` | |||
} | |||
// GetGlobalUISettings get global ui settings witch are exposed by API | |||
func (c *Client) GetGlobalUISettings() (*GlobalUISettings, *Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { | |||
return nil, nil, err | |||
} | |||
conf := new(GlobalUISettings) | |||
resp, err := c.getParsedResponse("GET", "/settings/ui", jsonHeader, nil, &conf) | |||
return conf, resp, err | |||
} | |||
// GetGlobalRepoSettings get global repository settings witch are exposed by API | |||
func (c *Client) GetGlobalRepoSettings() (*GlobalRepoSettings, *Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { | |||
return nil, nil, err | |||
} | |||
conf := new(GlobalRepoSettings) | |||
resp, err := c.getParsedResponse("GET", "/settings/repository", jsonHeader, nil, &conf) | |||
return conf, resp, err | |||
} | |||
// GetGlobalAPISettings get global api settings witch are exposed by it | |||
func (c *Client) GetGlobalAPISettings() (*GlobalAPISettings, *Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { | |||
return nil, nil, err | |||
} | |||
conf := new(GlobalAPISettings) | |||
resp, err := c.getParsedResponse("GET", "/settings/api", jsonHeader, nil, &conf) | |||
return conf, resp, err | |||
} | |||
// GetGlobalAttachmentSettings get global repository settings witch are exposed by API | |||
func (c *Client) GetGlobalAttachmentSettings() (*GlobalAttachmentSettings, *Response, error) { | |||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil { | |||
return nil, nil, err | |||
} | |||
conf := new(GlobalAttachmentSettings) | |||
resp, err := c.getParsedResponse("GET", "/settings/attachment", jsonHeader, nil, &conf) | |||
return conf, resp, err | |||
} |
@@ -0,0 +1,92 @@ | |||
// Copyright 2017 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" | |||
"time" | |||
) | |||
// StatusState holds the state of a Status | |||
// It can be "pending", "success", "error", "failure", and "warning" | |||
type StatusState string | |||
const ( | |||
// StatusPending is for when the Status is Pending | |||
StatusPending StatusState = "pending" | |||
// StatusSuccess is for when the Status is Success | |||
StatusSuccess StatusState = "success" | |||
// StatusError is for when the Status is Error | |||
StatusError StatusState = "error" | |||
// StatusFailure is for when the Status is Failure | |||
StatusFailure StatusState = "failure" | |||
// StatusWarning is for when the Status is Warning | |||
StatusWarning StatusState = "warning" | |||
) | |||
// Status holds a single Status of a single Commit | |||
type Status struct { | |||
ID int64 `json:"id"` | |||
State StatusState `json:"status"` | |||
TargetURL string `json:"target_url"` | |||
Description string `json:"description"` | |||
URL string `json:"url"` | |||
Context string `json:"context"` | |||
Creator *User `json:"creator"` | |||
Created time.Time `json:"created_at"` | |||
Updated time.Time `json:"updated_at"` | |||
} | |||
// CreateStatusOption holds the information needed to create a new Status for a Commit | |||
type CreateStatusOption struct { | |||
State StatusState `json:"state"` | |||
TargetURL string `json:"target_url"` | |||
Description string `json:"description"` | |||
Context string `json:"context"` | |||
} | |||
// CreateStatus creates a new Status for a given Commit | |||
func (c *Client) CreateStatus(owner, repo, sha string, opts CreateStatusOption) (*Status, *Response, error) { | |||
body, err := json.Marshal(&opts) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
status := new(Status) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/statuses/%s", owner, repo, sha), jsonHeader, bytes.NewReader(body), status) | |||
return status, resp, err | |||
} | |||
// ListStatusesOption options for listing a repository's commit's statuses | |||
type ListStatusesOption struct { | |||
ListOptions | |||
} | |||
// ListStatuses returns all statuses for a given Commit | |||
func (c *Client) ListStatuses(owner, repo, sha string, opt ListStatusesOption) ([]*Status, *Response, error) { | |||
opt.setDefaults() | |||
statuses := make([]*Status, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/statuses?%s", owner, repo, sha, opt.getURLQuery().Encode()), nil, nil, &statuses) | |||
return statuses, resp, err | |||
} | |||
// CombinedStatus holds the combined state of several statuses for a single commit | |||
type CombinedStatus struct { | |||
State StatusState `json:"state"` | |||
SHA string `json:"sha"` | |||
TotalCount int `json:"total_count"` | |||
Statuses []*Status `json:"statuses"` | |||
Repository *Repository `json:"repository"` | |||
CommitURL string `json:"commit_url"` | |||
URL string `json:"url"` | |||
} | |||
// GetCombinedStatus returns the CombinedStatus for a given Commit | |||
func (c *Client) GetCombinedStatus(owner, repo, sha string) (*CombinedStatus, *Response, error) { | |||
status := new(CombinedStatus) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/status", owner, repo, sha), nil, nil, status) | |||
return status, resp, err | |||
} |
@@ -0,0 +1,43 @@ | |||
// Copyright 2014 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. | |||
package gitea | |||
import ( | |||
"fmt" | |||
"time" | |||
) | |||
// User represents a user | |||
type User struct { | |||
// the user's id | |||
ID int64 `json:"id"` | |||
// the user's username | |||
UserName string `json:"login"` | |||
// the user's full name | |||
FullName string `json:"full_name"` | |||
Email string `json:"email"` | |||
// URL to the user's avatar | |||
AvatarURL string `json:"avatar_url"` | |||
// User locale | |||
Language string `json:"language"` | |||
// Is the user an administrator | |||
IsAdmin bool `json:"is_admin"` | |||
LastLogin time.Time `json:"last_login,omitempty"` | |||
Created time.Time `json:"created,omitempty"` | |||
} | |||
// GetUserInfo get user info by user's name | |||
func (c *Client) GetUserInfo(user string) (*User, *Response, error) { | |||
u := new(User) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s", user), nil, nil, u) | |||
return u, resp, err | |||
} | |||
// GetMyUserInfo get user info of current user | |||
func (c *Client) GetMyUserInfo() (*User, *Response, error) { | |||
u := new(User) | |||
resp, err := c.getParsedResponse("GET", "/user", nil, nil, u) | |||
return u, resp, err | |||
} |
@@ -0,0 +1,80 @@ | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// Copyright 2019 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" | |||
"reflect" | |||
) | |||
// AccessToken represents an API access token. | |||
type AccessToken struct { | |||
ID int64 `json:"id"` | |||
Name string `json:"name"` | |||
Token string `json:"sha1"` | |||
TokenLastEight string `json:"token_last_eight"` | |||
} | |||
// ListAccessTokensOptions options for listing a users's access tokens | |||
type ListAccessTokensOptions struct { | |||
ListOptions | |||
} | |||
// ListAccessTokens lists all the access tokens of user | |||
func (c *Client) ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, *Response, error) { | |||
if len(c.username) == 0 { | |||
return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") | |||
} | |||
opts.setDefaults() | |||
tokens := make([]*AccessToken, 0, opts.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/tokens?%s", c.username, opts.getURLQuery().Encode()), jsonHeader, nil, &tokens) | |||
return tokens, resp, err | |||
} | |||
// CreateAccessTokenOption options when create access token | |||
type CreateAccessTokenOption struct { | |||
Name string `json:"name"` | |||
} | |||
// CreateAccessToken create one access token with options | |||
func (c *Client) CreateAccessToken(opt CreateAccessTokenOption) (*AccessToken, *Response, error) { | |||
if len(c.username) == 0 { | |||
return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") | |||
} | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
t := new(AccessToken) | |||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/users/%s/tokens", c.username), jsonHeader, bytes.NewReader(body), t) | |||
return t, resp, err | |||
} | |||
// DeleteAccessToken delete token, identified by ID and if not available by name | |||
func (c *Client) DeleteAccessToken(value interface{}) (*Response, error) { | |||
if len(c.username) == 0 { | |||
return nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed") | |||
} | |||
var token = "" | |||
switch reflect.ValueOf(value).Kind() { | |||
case reflect.Int64: | |||
token = fmt.Sprintf("%d", value.(int64)) | |||
case reflect.String: | |||
if err := c.CheckServerVersionConstraint(">= 1.13.0"); err != nil { | |||
return nil, err | |||
} | |||
token = value.(string) | |||
default: | |||
return nil, fmt.Errorf("only string and int64 supported") | |||
} | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/users/%s/tokens/%s", c.username, token), jsonHeader, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,64 @@ | |||
// Copyright 2015 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. | |||
package gitea | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
) | |||
// Email an email address belonging to a user | |||
type Email struct { | |||
Email string `json:"email"` | |||
Verified bool `json:"verified"` | |||
Primary bool `json:"primary"` | |||
} | |||
// ListEmailsOptions options for listing current's user emails | |||
type ListEmailsOptions struct { | |||
ListOptions | |||
} | |||
// ListEmails all the email addresses of user | |||
func (c *Client) ListEmails(opt ListEmailsOptions) ([]*Email, *Response, error) { | |||
opt.setDefaults() | |||
emails := make([]*Email, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/emails?%s", opt.getURLQuery().Encode()), nil, nil, &emails) | |||
return emails, resp, err | |||
} | |||
// CreateEmailOption options when creating email addresses | |||
type CreateEmailOption struct { | |||
// email addresses to add | |||
Emails []string `json:"emails"` | |||
} | |||
// AddEmail add one email to current user with options | |||
func (c *Client) AddEmail(opt CreateEmailOption) ([]*Email, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
emails := make([]*Email, 0, 3) | |||
resp, err := c.getParsedResponse("POST", "/user/emails", jsonHeader, bytes.NewReader(body), &emails) | |||
return emails, resp, err | |||
} | |||
// DeleteEmailOption options when deleting email addresses | |||
type DeleteEmailOption struct { | |||
// email addresses to delete | |||
Emails []string `json:"emails"` | |||
} | |||
// DeleteEmail delete one email of current users' | |||
func (c *Client) DeleteEmail(opt DeleteEmailOption) (*Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
_, resp, err := c.getResponse("DELETE", "/user/emails", jsonHeader, bytes.NewReader(body)) | |||
return resp, err | |||
} |
@@ -0,0 +1,73 @@ | |||
// Copyright 2015 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. | |||
package gitea | |||
import "fmt" | |||
// ListFollowersOptions options for listing followers | |||
type ListFollowersOptions struct { | |||
ListOptions | |||
} | |||
// ListMyFollowers list all the followers of current user | |||
func (c *Client) ListMyFollowers(opt ListFollowersOptions) ([]*User, *Response, error) { | |||
opt.setDefaults() | |||
users := make([]*User, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/followers?%s", opt.getURLQuery().Encode()), nil, nil, &users) | |||
return users, resp, err | |||
} | |||
// ListFollowers list all the followers of one user | |||
func (c *Client) ListFollowers(user string, opt ListFollowersOptions) ([]*User, *Response, error) { | |||
opt.setDefaults() | |||
users := make([]*User, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/followers?%s", user, opt.getURLQuery().Encode()), nil, nil, &users) | |||
return users, resp, err | |||
} | |||
// ListFollowingOptions options for listing a user's users being followed | |||
type ListFollowingOptions struct { | |||
ListOptions | |||
} | |||
// ListMyFollowing list all the users current user followed | |||
func (c *Client) ListMyFollowing(opt ListFollowingOptions) ([]*User, *Response, error) { | |||
opt.setDefaults() | |||
users := make([]*User, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/following?%s", opt.getURLQuery().Encode()), nil, nil, &users) | |||
return users, resp, err | |||
} | |||
// ListFollowing list all the users the user followed | |||
func (c *Client) ListFollowing(user string, opt ListFollowingOptions) ([]*User, *Response, error) { | |||
opt.setDefaults() | |||
users := make([]*User, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/following?%s", user, opt.getURLQuery().Encode()), nil, nil, &users) | |||
return users, resp, err | |||
} | |||
// IsFollowing if current user followed the target | |||
func (c *Client) IsFollowing(target string) (bool, *Response) { | |||
_, resp, err := c.getResponse("GET", fmt.Sprintf("/user/following/%s", target), nil, nil) | |||
return err == nil, resp | |||
} | |||
// IsUserFollowing if the user followed the target | |||
func (c *Client) IsUserFollowing(user, target string) (bool, *Response) { | |||
_, resp, err := c.getResponse("GET", fmt.Sprintf("/users/%s/following/%s", user, target), nil, nil) | |||
return err == nil, resp | |||
} | |||
// Follow set current user follow the target | |||
func (c *Client) Follow(target string) (*Response, error) { | |||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/following/%s", target), nil, nil) | |||
return resp, err | |||
} | |||
// Unfollow set current user unfollow the target | |||
func (c *Client) Unfollow(target string) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/following/%s", target), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,86 @@ | |||
// Copyright 2017 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" | |||
"time" | |||
) | |||
// GPGKey a user GPG key to sign commit and tag in repository | |||
type GPGKey struct { | |||
ID int64 `json:"id"` | |||
PrimaryKeyID string `json:"primary_key_id"` | |||
KeyID string `json:"key_id"` | |||
PublicKey string `json:"public_key"` | |||
Emails []*GPGKeyEmail `json:"emails"` | |||
SubsKey []*GPGKey `json:"subkeys"` | |||
CanSign bool `json:"can_sign"` | |||
CanEncryptComms bool `json:"can_encrypt_comms"` | |||
CanEncryptStorage bool `json:"can_encrypt_storage"` | |||
CanCertify bool `json:"can_certify"` | |||
Created time.Time `json:"created_at,omitempty"` | |||
Expires time.Time `json:"expires_at,omitempty"` | |||
} | |||
// GPGKeyEmail an email attached to a GPGKey | |||
type GPGKeyEmail struct { | |||
Email string `json:"email"` | |||
Verified bool `json:"verified"` | |||
} | |||
// ListGPGKeysOptions options for listing a user's GPGKeys | |||
type ListGPGKeysOptions struct { | |||
ListOptions | |||
} | |||
// ListGPGKeys list all the GPG keys of the user | |||
func (c *Client) ListGPGKeys(user string, opt ListGPGKeysOptions) ([]*GPGKey, *Response, error) { | |||
opt.setDefaults() | |||
keys := make([]*GPGKey, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/gpg_keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys) | |||
return keys, resp, err | |||
} | |||
// ListMyGPGKeys list all the GPG keys of current user | |||
func (c *Client) ListMyGPGKeys(opt *ListGPGKeysOptions) ([]*GPGKey, *Response, error) { | |||
opt.setDefaults() | |||
keys := make([]*GPGKey, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys) | |||
return keys, resp, err | |||
} | |||
// GetGPGKey get current user's GPG key by key id | |||
func (c *Client) GetGPGKey(keyID int64) (*GPGKey, *Response, error) { | |||
key := new(GPGKey) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil, &key) | |||
return key, resp, err | |||
} | |||
// CreateGPGKeyOption options create user GPG key | |||
type CreateGPGKeyOption struct { | |||
// An armored GPG key to add | |||
// | |||
ArmoredKey string `json:"armored_public_key"` | |||
} | |||
// CreateGPGKey create GPG key with options | |||
func (c *Client) CreateGPGKey(opt CreateGPGKeyOption) (*GPGKey, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
key := new(GPGKey) | |||
resp, err := c.getParsedResponse("POST", "/user/gpg_keys", jsonHeader, bytes.NewReader(body), key) | |||
return key, resp, err | |||
} | |||
// DeleteGPGKey delete GPG key with key id | |||
func (c *Client) DeleteGPGKey(keyID int64) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,80 @@ | |||
// Copyright 2015 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. | |||
package gitea | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"time" | |||
) | |||
// PublicKey publickey is a user key to push code to repository | |||
type PublicKey struct { | |||
ID int64 `json:"id"` | |||
Key string `json:"key"` | |||
URL string `json:"url,omitempty"` | |||
Title string `json:"title,omitempty"` | |||
Fingerprint string `json:"fingerprint,omitempty"` | |||
Created time.Time `json:"created_at,omitempty"` | |||
Owner *User `json:"user,omitempty"` | |||
ReadOnly bool `json:"read_only,omitempty"` | |||
KeyType string `json:"key_type,omitempty"` | |||
} | |||
// ListPublicKeysOptions options for listing a user's PublicKeys | |||
type ListPublicKeysOptions struct { | |||
ListOptions | |||
} | |||
// ListPublicKeys list all the public keys of the user | |||
func (c *Client) ListPublicKeys(user string, opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) { | |||
opt.setDefaults() | |||
keys := make([]*PublicKey, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys) | |||
return keys, resp, err | |||
} | |||
// ListMyPublicKeys list all the public keys of current user | |||
func (c *Client) ListMyPublicKeys(opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) { | |||
opt.setDefaults() | |||
keys := make([]*PublicKey, 0, opt.PageSize) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys) | |||
return keys, resp, err | |||
} | |||
// GetPublicKey get current user's public key by key id | |||
func (c *Client) GetPublicKey(keyID int64) (*PublicKey, *Response, error) { | |||
key := new(PublicKey) | |||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys/%d", keyID), nil, nil, &key) | |||
return key, resp, err | |||
} | |||
// CreateKeyOption options when creating a key | |||
type CreateKeyOption struct { | |||
// Title of the key to add | |||
Title string `json:"title"` | |||
// An armored SSH key to add | |||
Key string `json:"key"` | |||
// Describe if the key has only read access or read/write | |||
ReadOnly bool `json:"read_only"` | |||
} | |||
// CreatePublicKey create public key with options | |||
func (c *Client) CreatePublicKey(opt CreateKeyOption) (*PublicKey, *Response, error) { | |||
body, err := json.Marshal(&opt) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
key := new(PublicKey) | |||
resp, err := c.getParsedResponse("POST", "/user/keys", jsonHeader, bytes.NewReader(body), key) | |||
return key, resp, err | |||
} | |||
// DeletePublicKey delete public key with key id | |||
func (c *Client) DeletePublicKey(keyID int64) (*Response, error) { | |||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/keys/%d", keyID), nil, nil) | |||
return resp, err | |||
} |
@@ -0,0 +1,44 @@ | |||
// 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" | |||
"net/url" | |||
) | |||
type searchUsersResponse struct { | |||
Users []*User `json:"data"` | |||
} | |||
// SearchUsersOption options for SearchUsers | |||
type SearchUsersOption struct { | |||
ListOptions | |||
KeyWord string | |||
} | |||
// QueryEncode turns options into querystring argument | |||
func (opt *SearchUsersOption) QueryEncode() string { | |||
query := make(url.Values) | |||
if opt.Page > 0 { | |||
query.Add("page", fmt.Sprintf("%d", opt.Page)) | |||
} | |||
if opt.PageSize > 0 { | |||
query.Add("limit", fmt.Sprintf("%d", opt.PageSize)) | |||
} | |||
if len(opt.KeyWord) > 0 { | |||
query.Add("q", opt.KeyWord) | |||
} | |||
return query.Encode() | |||
} | |||
// SearchUsers finds users by query | |||
func (c *Client) SearchUsers(opt SearchUsersOption) ([]*User, *Response, error) { | |||
link, _ := url.Parse("/users/search") | |||
link.RawQuery = opt.QueryEncode() | |||
userResp := new(searchUsersResponse) | |||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &userResp) | |||
return userResp.Users, 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" | |||
"github.com/hashicorp/go-version" | |||
) | |||
// ServerVersion returns the version of the server | |||
func (c *Client) ServerVersion() (string, *Response, error) { | |||
var v = struct { | |||
Version string `json:"version"` | |||
}{} | |||
resp, err := c.getParsedResponse("GET", "/version", nil, nil, &v) | |||
return v.Version, resp, err | |||
} | |||
// CheckServerVersionConstraint validates that the login's server satisfies a | |||
// given version constraint such as ">= 1.11.0+dev" | |||
func (c *Client) CheckServerVersionConstraint(constraint string) error { | |||
c.versionLock.RLock() | |||
if c.serverVersion == nil { | |||
c.versionLock.RUnlock() | |||
if err := c.loadClientServerVersion(); err != nil { | |||
return err | |||
} | |||
} else { | |||
c.versionLock.RUnlock() | |||
} | |||
check, err := version.NewConstraint(constraint) | |||
if err != nil { | |||
return err | |||
} | |||
if !check.Check(c.serverVersion) { | |||
return fmt.Errorf("gitea server at %s does not satisfy version constraint %s", c.url, constraint) | |||
} | |||
return nil | |||
} | |||
// loadClientServerVersion init the serverVersion variable | |||
func (c *Client) loadClientServerVersion() error { | |||
c.versionLock.Lock() | |||
defer c.versionLock.Unlock() | |||
raw, _, err := c.ServerVersion() | |||
if err != nil { | |||
return err | |||
} | |||
if c.serverVersion, err = version.NewVersion(raw); err != nil { | |||
return err | |||
} | |||
return nil | |||
} |
@@ -4,6 +4,9 @@ cloud.google.com/go/compute/metadata | |||
## explicit | |||
code.gitea.io/gitea-vet | |||
code.gitea.io/gitea-vet/checks | |||
# code.gitea.io/sdk/gitea v0.13.1 | |||
## explicit | |||
code.gitea.io/sdk/gitea | |||
# gitea.com/lunny/levelqueue v0.3.0 | |||
## explicit | |||
gitea.com/lunny/levelqueue | |||
@@ -423,7 +426,7 @@ github.com/hashicorp/go-cleanhttp | |||
# github.com/hashicorp/go-retryablehttp v0.6.7 | |||
## explicit | |||
github.com/hashicorp/go-retryablehttp | |||
# github.com/hashicorp/go-version v0.0.0-00010101000000-000000000000 => github.com/6543/go-version v1.2.3 | |||
# github.com/hashicorp/go-version v1.2.1 => github.com/6543/go-version v1.2.3 | |||
## explicit | |||
github.com/hashicorp/go-version | |||
# github.com/hashicorp/hcl v1.0.0 | |||
@@ -0,0 +1 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 135.467 135.467"><path d="M27.707 33.619c-9.547-.028-22.338 6.797-21.63 23.903C7.183 84.25 31.532 86.734 41.267 86.95c1.068 5.013 12.521 22.298 21.001 23.209h37.158c22.278-1.668 38.957-75.753 26.59-76.035-20.713 1.097-32.485 1.556-42.97 1.637v23.21l-3.24-1.613-.026-21.53c-11.886-.01-22.487-.603-42.358-1.704-2.495-.027-5.98-.494-9.715-.504zm2.497 9.459c1.352 13.694 3.557 21.7 8.02 33.94-11.382-1.504-21.072-5.222-22.853-19.107-.951-7.411 2.39-15.167 14.833-14.833zm43.334 13.469a5.477 5.477 0 0 1 2.108.545l3.878 1.885-2.778 5.689a3.475 3.475 0 0 0-1.249.198 3.475 3.475 0 0 0-2.091 4.449 3.475 3.475 0 0 0 .57 1.017l-4.787 9.798a3.475 3.475 0 0 0-1.15.206 3.475 3.475 0 0 0-2.091 4.449 3.475 3.475 0 0 0 4.44 2.091 3.475 3.475 0 0 0 2.1-4.448 3.475 3.475 0 0 0-.819-1.273l4.663-9.558a3.475 3.475 0 0 0 1.514-.19 3.475 3.475 0 0 0 1.24-.778c4.537 2.193 5.7 2.866 6.308 3.671.114.151.249.417.298.588.153.527.042 1.51-.281 2.455-.384 1.123-2.275 5.239-3.754 8.302a3.475 3.475 0 0 0-1.356.198 3.475 3.475 0 0 0-2.092 4.448 3.475 3.475 0 0 0 4.449 2.092 3.475 3.475 0 0 0 2.092-4.448 3.475 3.475 0 0 0-.72-1.174c2.773-5.883 3.781-8.196 4.143-9.749.13-.561.154-.777.157-1.537.003-.776-.011-.935-.124-1.299-.35-1.126-1.165-2.139-2.415-3.01-.942-.655-2.119-1.26-5.515-2.86-.044-.02-.087-.045-.132-.066a3.475 3.475 0 0 0-.198-1.273 3.475 3.475 0 0 0-.753-1.216l2.729-5.606 15.13 7.35a5.466 5.466 0 0 1 2.53 7.326L89.19 96.226a5.476 5.476 0 0 1-7.334 2.53L60.457 88.363a5.479 5.479 0 0 1-2.538-7.334L68.321 59.63a5.472 5.472 0 0 1 5.217-3.084z" fill="#609926" stroke="#428f29" stroke-width=".9990682399999999" paint-order="markers fill stroke"/></svg> |