Compare commits

...

81 Commits

Author SHA1 Message Date
appleboy 8110db1e6d feat: implement Gitea Repo Action Secrets Management (#662)
- Add imports for "bytes", "encoding/json", and "net/http" in `repo_action.go`
- Implement `CreateRepoActionSecret` function to create a secret for a repository in Gitea Actions
- Add a new test file `repo_action_test.go` with tests for creating, updating, and listing repository action secrets

<img width="518" alt="image" src="/attachments/c8e457c4-395f-4ffe-8482-0b7f5d2541cb">

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/662
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2024-05-08 09:29:30 +00:00
Bo-Yi Wu 83c73e79a5 ci: update Gitea testing configurations for 1.22 release (#663)
- Update Gitea image version to `1.22.0-rc1` in testing workflow
- Increase the number of milestones to be checked in the test from `2` to `3`
- Modify the theme setting from `auto` to `gitea-auto` in user settings test
- Update the Avatar URL in user test to use `http://gitea:3000/avatars/` instead of `https://secure.gravatar.com/avatar/`
- Increase the number of user emails to be checked in the test from `2` to `3`

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/663
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2024-05-08 01:53:26 +00:00
Bo-Yi Wu 6c0938dc15 feat: implement organization action secret creation (#661)
- Add `bytes` and `encoding/json` imports to `org_action.go`
- Implement `CreateSecretOption` struct with validation in `org_action.go`
- Add `CreateOrgActionSecret` function to create or update organization secrets in `org_action.go`
- Create new test file `org_action_test.go` with tests for creating and updating organization secrets
- Add `Data` field to `Secret` struct in `secret.go`

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/661
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2024-05-03 13:52:34 +00:00
tobiasbp 0a99bda0cb Get fields loginName & SourceID for users (#654)
This _PR_ adds the following fields to the _User_ struct:

-  _LoginName_: Currently returned by the Gitea API.
- _SourceID_: [Scheduled to be returned by the Gitea API as of version 1.22.0](https://github.com/go-gitea/gitea/pull/29718).

The fields are used for users with non-local Authentication Sources (LDAP, oAuth etc.).

Co-authored-by: tobias.petersen <tobias.petersen@unity3d.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/654
Reviewed-by: appleboy <appleboy.tw@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: tobiasbp <tobiasbp@noreply.gitea.com>
Co-committed-by: tobiasbp <tobiasbp@noreply.gitea.com>
2024-05-02 17:33:09 +00:00
Bo-Yi Wu 36723e11c8 feat(repo): support list of repo secrets (#660)
- Update comment to correctly describe `ListOrgActionSecretOption` as listing organization secrets instead of members
- Add new file `repo_action.go` with functionality to list a repository's secrets including the structure and API call

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/660
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2024-05-02 06:04:51 +00:00
appleboy a991e370c5 feat: implement commit comparison feature in Gitea client (#659)
See the API: https://github.com/go-gitea/gitea/pull/30349

- Add a new file `repo_compare.go` with package `gitea` and `Compare` struct
- Implement `CompareCommits` method in `Client` struct in `repo_compare.go`
- Add `version1_22_0` constant in `version.go`

Signed-off-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/659
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2024-04-17 05:47:25 +00:00
appleboy bb25c989a0 chore: update Go module dependencies to latest versions (#658)
- Update the version of `golang.org/x/crypto` from `v0.17.0` to `v0.22.0` in `go.mod`
- Update the version of `golang.org/x/sys` from `v0.15.0` to `v0.19.0` in `go.mod`

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/658
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2024-04-16 08:15:01 +00:00
appleboy 18e5522f90 ci: update Gitea versions in workflows and Makefile (#657)
- Update Gitea Docker image version from `1.21.8` to `1.21.10` in testing workflow
- Update Gitea version from `1.21.1` to `1.21.10` in Makefile

Signed-off-by: appleboy <appleboy.tw@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/657
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2024-04-14 13:09:31 +00:00
Lunny Xiao bad4de0196 Upgrade CI (#655)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/655
2024-03-16 08:34:20 +00:00
rjhaverkamp 9ec1b82849 Fix typo in visibilty error (#653)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/653
Reviewed-by: John Olheiser <jolheiser@noreply.gitea.com>
Co-authored-by: rjhaverkamp <rjhaverkamp@noreply.gitea.com>
Co-committed-by: rjhaverkamp <rjhaverkamp@noreply.gitea.com>
2024-02-28 00:12:39 +00:00
6543 bf71ec2fd9 Make only referenc of ErrUnknownVersion an error (#648)
just a refactor nit

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/648
Reviewed-by: John Olheiser <jolheiser@noreply.gitea.com>
2024-02-22 21:33:21 +00:00
Martijn van der Kleijn 001d5fad51 chore: Bump required Go version to 1.18 (#650)
Reasoning for the bump:
- Enforce (instead of assume) use of newer Go version to ensure presence of security fixes
- Enable use of more recent language features
- Go < 1.21 is EOL
- Testing workflow already uses >= 1.20

For sec fixes, see: https://go.dev/doc/devel/release#go1.21.minor

Signed-off-by: Martijn van der Kleijn <martijn.niji@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/650
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Martijn van der Kleijn <martijn.niji@gmail.com>
Co-committed-by: Martijn van der Kleijn <martijn.niji@gmail.com>
2024-02-22 21:28:11 +00:00
diogo464 5d0143e4e7 fix: updated repository field in Package struct (#647)
The definition for `Package` in the most recent api switch the
repository field from a string to a `Repository`.
Without this fix api calls like ListPackages will fail to deserialize
the response.

Co-authored-by: diogo464 <diogo464@protonmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/647
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: John Olheiser <jolheiser@noreply.gitea.com>
Co-authored-by: diogo464 <diogo464@noreply.gitea.com>
Co-committed-by: diogo464 <diogo464@noreply.gitea.com>
2024-01-29 05:38:16 +00:00
Simon Ser 53f735b911 Add support for user hooks (#644)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/644
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-by: John Olheiser <jolheiser@noreply.gitea.com>
Co-authored-by: Simon Ser <contact@emersion.fr>
Co-committed-by: Simon Ser <contact@emersion.fr>
2024-01-25 16:04:00 +00:00
Christian Groschupp 98b424a8af feat(release): add GetLatestRelease method (#639)
This PR adds the GetLatestRelease method to support the /repos/{owner}/{repo}/releases/latest endpoint.
ref: https://docs.gitea.com/api/1.20/#tag/repository/operation/repoGetLatestRelease

Resolves gitea/go-sdk#630

Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/639
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Christian Groschupp <christian@groschupp.org>
Co-committed-by: Christian Groschupp <christian@groschupp.org>
2024-01-06 05:14:12 +00:00
Bo-Yi Wu 2abfdd7ba6 chore: update dependencies to latest versions (#646)
- Update the version of `github.com/hashicorp/go-version` from `v1.5.0` to `v1.6.0`
- Update the version of `golang.org/x/crypto` from `v0.0.0-20220525230936-793ad666bf5e` to `v0.17.0`

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/646
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2023-12-31 04:06:05 +00:00
Andreas Wachter 8f4d3d8916 increased description limits to 2048 (#645)
I noticed that the description limits on gitea were changed to 2048 characters last year, but it was still 255 in the SDK.

Here the corresponding commit on gitea:
8351172b6e

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/645
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Andreas Wachter <andreas.wachter@buddyspencer.monster>
Co-committed-by: Andreas Wachter <andreas.wachter@buddyspencer.monster>
2023-12-30 04:48:39 +00:00
ysicing bde56fb9bb feat(repo): update repo field (#642)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/642
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: ysicing <i@ysicing.me>
Co-committed-by: ysicing <i@ysicing.me>
2023-12-24 20:16:02 +00:00
ysicing 228d6931f4 feat(repo-mirrors): add repo sync mirrors (#640)
add sync mirrors

Signed-off-by: ysicing <i@ysicing.me>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/640
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: ysicing <i@ysicing.me>
Co-committed-by: ysicing <i@ysicing.me>
2023-12-24 20:15:31 +00:00
appleboy c8745e1599 feat: add new constants for repository actions (#643)
- Add a new constant `RepoUnitActions` to represent the actions of a repository

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/643
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2023-12-17 04:36:39 +00:00
Lauris BH 0e0a4691b6 Add Link header parsing in response (#638)
Helps if need to get all data that can not be requested in single page

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/638
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: appleboy <appleboy.tw@gmail.com>
Co-authored-by: Lauris BH <lauris@nix.lv>
Co-committed-by: Lauris BH <lauris@nix.lv>
2023-12-04 14:50:48 +00:00
crapStone e23e8aa300 add option to set user-agent to gitea client (#637)
This PR adds the ability to set a custom user-agent to the gitea client

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/637
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Co-authored-by: crapStone <crapstone01@gmail.com>
Co-committed-by: crapStone <crapstone01@gmail.com>
2023-11-15 01:43:37 +00:00
venc0r d5e174e5b5 feat(hook): add support for authorization_header in webhooks (#633)
fixes #632

Co-authored-by: Jörg Markert <venc0r@live.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/633
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Co-authored-by: venc0r <venc0r@noreply.gitea.com>
Co-committed-by: venc0r <venc0r@noreply.gitea.com>
2023-09-26 00:01:48 +00:00
appleboy 0141b499c9 docs: update badges in README.md file (#628)
- Remove the badge for build status from the README.md file
- Add a badge for the MIT license to the README.md file
- Add a badge for the release to the README.md file
- Add a badge for the chat to the README.md file
- Add a badge for microbadger to the README.md file
- Add a badge for the Go Report Card to the README.md file
- Add a badge for GoDoc to the README.md file

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/628
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2023-09-06 02:23:01 +00:00
Bo-Yi Wu 091528835f feat: Add API routes to list org secrets (#629)
- Add a new file `gitea/org_action.go`
- Add a new file `gitea/secret.go`

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

block by https://github.com/go-gitea/gitea/pull/26485

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/629
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2023-08-15 15:15:48 +00:00
infinoid ae1583a7be Add missing fields to BranchProtection and related structs (#626)
the `gitea.BranchProtection` struct is missing some fields which are present in the actual gitea API (and swagger).

* BranchProtection.RuleName was added in gitea v1.19.0, via [PR #20825](https://github.com/go-gitea/gitea/pull/20825)
* BranchProtection.UnprotectedFilePatterns was added in gitea v1.16.0, via [PR #16395](https://github.com/go-gitea/gitea/pull/16395)

This PR adds those fields to the BranchProtection struct, and the related Create and Edit option structs, to match the structs [defined](https://github.com/go-gitea/gitea/blob/main/modules/structs/repo_branch.go#L27) on the gitea server side.

Co-authored-by: Mark Glines <mark@glines.org>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/626
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Reviewed-by: delvh <dev.lh@web.de>
Co-authored-by: infinoid <infinoid@noreply.gitea.com>
Co-committed-by: infinoid <infinoid@noreply.gitea.com>
2023-08-01 16:26:41 +00:00
John Olheiser 93cfc320cf Update PR template (#627)
Noted by @ infinoid (thanks!)

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/627
2023-08-01 14:50:16 +00:00
b398f0fcac 0fe2ace132 Access the details of packages (#620)
This pull request provides access to the packages API by allowing packages to be listed, retrieved individually and for the files within a package to be listed.

Resolves gitea/go-sdk#619.

Co-authored-by: root <root@prxdevgw.westeros>
Co-authored-by: John Olheiser <john+gitea@jolheiser.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/620
Co-authored-by: b398f0fcac <b398f0fcac@noreply.gitea.com>
Co-committed-by: b398f0fcac <b398f0fcac@noreply.gitea.com>
2023-07-15 02:59:55 +00:00
Alessandro De Blasis f4be505bf6 feat(oauth2): confidential_client handling (#625)
This PR adds support for the `confidential_client` in `oauth2` to reflect the swagger APIs.

It has been surfaced here: https://github.com/Lerentis/terraform-provider-gitea/pull/46

Simple tests have also been added.

Please note that in this PR I am considering the current behaviour:

> if confidential_client is not set, it's assumed that it's false

However, from the swagger, it seems that the implicit default is `true` instead.

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/625
Co-authored-by: Alessandro De Blasis <alex@deblasis.net>
Co-committed-by: Alessandro De Blasis <alex@deblasis.net>
2023-07-15 02:55:06 +00:00
JohnWalkerx 315cf7aac8 Add missing MergePullRequestOption parameters for head_commit_id and merge_when_checks_succeed (#624)
This PR adds the missing parameters for MergePullRequestOption `head_commit_id` and `merge_when_checks_succed` like in the current Gitea API.
Resolves #623

Signed-off-by: Johannes Lauffer <johnwalkerx@mailbox.org>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/624
Co-authored-by: JohnWalkerx <johnwalkerx@mailbox.org>
Co-committed-by: JohnWalkerx <johnwalkerx@mailbox.org>
2023-07-15 02:53:03 +00:00
Zettat123 970776d1c1 Switch to Gitea Actions (#618)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/618
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
2023-05-09 11:50:20 +08:00
Norwin 24a404e561 Add `GetIssueTemplates()` (#603)
This API was added in 1.16, [template forms](https://github.com/go-gitea/gitea/pull/20987) will be in 1.18.
I don't think we need to add version checks, as the respective `.Form` field will just be empty on unsupported versions, but correct me if I'm wrong here.

fixes #547

- i took the freedom to change the struct property names from the API names for clarity
- I didn't add  tests because it would effectively just test gitea's API to be functional  (as long as there is only this GET method)

Co-authored-by: John Olheiser <john+gitea@jolheiser.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/603
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Co-authored-by: Norwin <git@nroo.de>
Co-committed-by: Norwin <git@nroo.de>
2023-04-20 00:19:26 +08:00
jolheiser 3c5cad657b Add token scopes enum and add to payloads (#617)
Closes #616

/cc @chmouel

---

This should work fine, as the Gitea API accepts a string array and then internally converts the scopes.

Co-authored-by: Chmouel Boudjnah <chmouel@chmouel.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/617
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.io>
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-committed-by: jolheiser <john.olheiser@gmail.com>
2023-04-19 23:57:54 +08:00
6543 6d1bcd107f support error.Is for ErrUnknownVersion (#615)
followup of https://gitea.com/gitea/go-sdk/pulls/612

see https://gitea.com/gitea/tea/pulls/538#issuecomment-734707

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/615
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2023-04-03 11:34:49 +08:00
John Olheiser 7511c6d3cd Set client version to lowest for compat if server version can't be recognized and return specific error (#612)
This is a possible resolution for gitea/tea#531

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/612
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: John Olheiser <john+gitea@jolheiser.com>
Co-committed-by: John Olheiser <john+gitea@jolheiser.com>
2023-04-03 05:30:53 +08:00
Dmitry Afanasiev 846d53e967 add support for api /repos/{owner}/{repo}/collaborators/{collaborator}/permission (#611)
Co-authored-by: Dmitry Afanasiev <afanasiev.dmitry@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/611
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Dmitry Afanasiev <dafanasiev@noreply.gitea.io>
Co-committed-by: Dmitry Afanasiev <dafanasiev@noreply.gitea.io>
2023-03-04 20:31:32 +08:00
John Olheiser df1269f18d Pin CI (#614)
This PR pins the CI and various versions of tools in our Makefile to ensure a more stable run.

Note that they aren't necessarily the most current versions, as the SDK likely needs more work to bring it back up to par with the main repo.

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/614
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: John Olheiser <john+gitea@jolheiser.com>
Co-committed-by: John Olheiser <john+gitea@jolheiser.com>
2023-03-01 08:50:04 +08:00
John Olheiser 2c35c11772 Add confidential_client to oauth2 struct (#613)
Just updating the struct to match the current API

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/613
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: John Olheiser <john+gitea@jolheiser.com>
Co-committed-by: John Olheiser <john+gitea@jolheiser.com>
2023-03-01 06:21:34 +08:00
edieruby 2d9ee57af1 update go.mod (#608)
add the depend of github.com/davidmz/go-pageant

Co-authored-by: Y <469960757@qq.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/608
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: edieruby <edieruby@noreply.gitea.io>
Co-committed-by: edieruby <edieruby@noreply.gitea.io>
2022-10-17 02:35:12 +08:00
qwerty287 1c9c71d84a Add `ListPullRequestFiles` (#607)
Was added in 1dfa28ffa5.

Co-authored-by: qwerty287 <ndev@web.de>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/607
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Gusted <williamzijl7@hotmail.com>
Co-authored-by: qwerty287 <qwerty287@noreply.gitea.io>
Co-committed-by: qwerty287 <qwerty287@noreply.gitea.io>
2022-10-17 02:33:29 +08:00
6543 8f846bdb9b Update PullRequest struct (#606)
Add AllowMaintainerEdit

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/606
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2022-09-27 10:50:37 +08:00
earl-warren aef4e5e2bd Add `DeleteIssue()` (#598)
Fixes: #596

Signed-off-by: Earl Warren <contact@earl-warren.org>

Co-authored-by: Earl Warren <contact@earl-warren.org>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/598
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: earl-warren <contact@earl-warren.org>
Co-committed-by: earl-warren <contact@earl-warren.org>
2022-09-16 05:45:01 +08:00
6543 7aeaaa45e1 More integration test fixes (#604) 2022-09-16 02:26:31 +08:00
Norwin e5b5b3447a Fix broken CI (#602)
closes #599
"blocks" #598

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/602
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-09-16 01:47:16 +08:00
6543 5852fcc4a3 Update EditPullRequestOption struct (#601)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/601
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
2022-09-15 09:29:50 +08:00
6543 a0127ed0e7 use build tag to support darwin again (#600)
fix https://ci.woodpecker-ci.org/woodpecker-ci/woodpecker/build/3344/11

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/600
Reviewed-by: John Olheiser <john+gitea@jolheiser.com>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
2022-08-31 08:41:39 +08:00
6543 cc14c63ccc Add GetFileReader() & GetFile() support git-lfs (#595)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/595
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: KN4CK3R <kn4ck3r@noreply.gitea.io>
2022-07-29 18:51:05 +08:00
lafriks de34275bb6 Add method to get user organization permissions (#594)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/594
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: lafriks <lafriks@noreply.gitea.io>
Co-committed-by: lafriks <lafriks@noreply.gitea.io>
2022-07-20 10:57:09 +08:00
Wim e5f0c189f2 Add support for http signatures (#553)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/553
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Wim <42wim@noreply.gitea.io>
Co-committed-by: Wim <42wim@noreply.gitea.io>
2022-07-13 00:45:08 +08:00
6543 359c771ce3 Make CI work again (#593)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/593
2022-05-31 06:08:44 +08:00
Gusted 99a9de3172 Add response to `ReadRepoNotifications` (#590)
- This is a breaking change.
- Return the relevant notifications when the Gitea server is 1.16.0 or higher.
- Ref: https://github.com/go-gitea/gitea/pull/17064
- Resolves #543

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/590
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-05-15 23:47:38 +08:00
6543 23e1316337 Relax TestAdminCronTasks (#591)
Relax test since this will be variable upstream ...

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/591
Reviewed-by: Gusted <williamzijl7@hotmail.com>
2022-05-15 06:22:02 +08:00
Gusted f3ebdb8afe Add function to get commit diff/patch (#589)
- Adds function the gets a commit's diff or patch.
- Ref: https://github.com/go-gitea/gitea/pull/17095

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/589
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-05-15 06:16:26 +08:00
arkamar 319a978c6c Allow PR review with comments only (#570)
It is common to create PR review with Comments only, where foreword Body is empty. This is allowed by Gitea API, therefore SDK should return empty body error only if there are no comments.

Co-authored-by: Petr Vaněk <arkamar@atlas.cz>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/570
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: arkamar <arkamar@noreply.gitea.io>
Co-committed-by: arkamar <arkamar@noreply.gitea.io>
2022-05-02 03:09:34 +08:00
Gusted adebf1cd11 Add file commit history (#588)
- Allow to specify to only get commit history of specific file.
- Ref: https://github.com/go-gitea/gitea/pull/17652

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/588
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-05-01 15:48:52 +08:00
Gusted 8fab37e740 Enforce golangci-lint + gofumpt (#587)
- Enforce [gofumpt](https://github.com/mvdan/gofumpt) to enforce a more idiomatic go style.
- Enforce golangci-lint a bunch of linters! Which were able to detect a few issues in the current codebase and have been fixed by this PR.
- Updated the Makefile to use `go install ....` instead of the old deprecated way of `go get`

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/587
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-04-28 23:33:21 +08:00
John Olheiser ce9d46682d Add commit stats and verification (#584)
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/584
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-committed-by: John Olheiser <john.olheiser@gmail.com>
2022-04-28 18:58:54 +08:00
John Olheiser 89a4b0be6e Return error message for 4xx errors (#583)
Resolves #582
Resolves #551

As noted in both issues, we _could_ put the `resp.Body` back, however I think this should also suffice, as it will return error messages when appropriate based on the JSON response.

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/583
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-committed-by: John Olheiser <john.olheiser@gmail.com>
2022-04-28 10:56:33 +08:00
Gusted ad3580e44d Add the Accept/Reject transfer of a repository. (#586)
- Allow to Accept or Reject a transfer of a repository via the API. Only
- available for v1.16+
- Ref: https://github.com/go-gitea/gitea/pull/17963

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/586
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-04-28 09:27:56 +08:00
Gusted 559cc2fb2a Add sorting by owner/team for list search on user (#585)
- Add sorting by Owner and Team for the user list issues endpoint.
- Ref: https://github.com/go-gitea/gitea/pull/16662

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/585
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-04-28 08:43:52 +08:00
Gusted 468d48c978 Add search teams on org API (#577)
- Add the API to search for teams on a organization by the `/orgs/{org}/teams/search` API of Gitea.
- The response body of the API is a bit weird because it the JSON can return three fields "data", "error", "ok", first check if there's a general HTTP error. Otherwise when ok is set to false, return a error with the given error message of the error field. When ok is set to true, simply return the data.

CC @fnetx

Co-authored-by: Andrew Thornton <art27@cantab.net>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/577
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-04-27 03:21:25 +08:00
Norwin 223f0a75e0 ListOptions.setDefaults(): remove artificial and buggy pagination limits (#573)
fixes #571

Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/573
Reviewed-by: Gusted <williamzijl7@hotmail.com>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-04-27 03:07:37 +08:00
Gusted 2e8bb53b30 Add `Name` field to `CreateForkOption` (#576)
- As per #558, due https://github.com/go-gitea/gitea/pull/18066

Co-authored-by: Andrew Thornton <art27@cantab.net>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/576
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-04-27 03:04:20 +08:00
John Olheiser 321bd56d93 Add webhook verification funcs (#580)
This PR adds a func for verifying incoming webhooks from Gitea, as well as a middleware for easier addition to a router stack.

Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/580
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Gusted <williamzijl7@hotmail.com>
2022-04-27 02:59:02 +08:00
John Olheiser 2616d10528 Pull newest Gitea dev image always (#581)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/581
Reviewed-by: Gusted <williamzijl7@hotmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
Co-committed-by: John Olheiser <john.olheiser@gmail.com>
2022-04-27 00:00:40 +08:00
Gusted 603e4358f8 Fix CI tests (#579)
- `master` -> `main`, due to https://github.com/go-gitea/gitea/pull/19354
- Fix incorrect variable being passed into assert in `TestPull`.
- Fix `createTestRepo` to only delete existing TestRepo if the result's repo wasn't redirected(e.g. due to being transferred or renamed). Fixes the error in TransferRepo.
- Remove a check for tag verfication, for some developers this will always fail due to local git configs forcing signing on tags.

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/579
Reviewed-by: 6543 <6543@obermui.de>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-04-22 15:52:45 +08:00
Gusted a56a62a4df Update length of cron tasks (#578)
- Bump amount of cron tasks that the dev image of Gitea has to 21.
- Fix TestAdminCronTasks.

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/578
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-04-10 20:10:20 +08:00
Gusted f3162e5333 Update dependencies (#575)
- Update testify to v1.7.0(Thus fixing vulnerabilities https://deps.dev/go/code.gitea.io%2Fsdk%2Fgitea/, quite minor because testify is only used for test code).
- Update go-version to v1.4.0

Co-authored-by: Andrew Thornton <art27@cantab.net>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/575
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Gusted <williamzijl7@hotmail.com>
Co-committed-by: Gusted <williamzijl7@hotmail.com>
2022-03-29 09:16:23 +08:00
Lunny Xiao 22f2853429 Use goproxy.io instead of goproxy.cn (#574)
Fix CI, Fix #572

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/574
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Reviewed-by: John Olheiser <john.olheiser@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-03-29 07:09:57 +08:00
petergardfjall f0663b3c13 add mirror_updated field to Repository struct (#565)
This PR resolves https://gitea.com/gitea/go-sdk/issues/566.

It ensures that the client-side SDK extracts the `mirror_updated` field introduced on the server by https://github.com/go-gitea/gitea/pull/18267 (issue: https://github.com/go-gitea/gitea/issues/18266).

Co-authored-by: Peter Gardfjäll <peter.gardfjall.work@gmail.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/565
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Co-authored-by: petergardfjall <petergardfjall@noreply.gitea.io>
Co-committed-by: petergardfjall <petergardfjall@noreply.gitea.io>
2022-03-28 19:06:48 +08:00
qwerty287 29e6eb37fe Fix URL param (#568)
The URL param for pre-releases was `draft`, it should be `pre-release`.

Co-authored-by: qwerty287 <ndev@web.de>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/568
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: qwerty287 <qwerty287@noreply.gitea.io>
Co-committed-by: qwerty287 <qwerty287@noreply.gitea.io>
2022-02-15 23:14:18 +08:00
Norwin 36c7f8c8de Fix CI (run gitea without root previleges) (#569)
CI is broken due to gitea upstream changes in deployment; may not run as root.
I have not tested if this actually fixes things, for upstream reference see f0bd1e9896 (diff-b54b39f1afced2465e1f3641db9d5bbf4f3a7fcf890996dfedd3c197bcb7f8c7)

I also switched from `:latest` to `:dev`, see https://github.com/go-gitea/gitea/pull/16421

edit: sorry for the messy commit list below, it's actually just a single commit.
Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/569
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-02-14 23:05:57 +08:00
takirala 73182f46eb More descriptive errors when validating repo metadata (#567)
When creating a repository using gitea client, it performs some validation on client options and the errors returned are not descriptive enough. This PR makes the errors more precise so that the user can fix the errors easily.

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/567
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: takirala <takirala@noreply.gitea.io>
Co-committed-by: takirala <takirala@noreply.gitea.io>
2022-02-01 07:14:45 +08:00
Norwin 3ff2c60a86 GetPullRequestDiff: add PullRequestDiffOptions param (#542)
this is for the upstream change in https://github.com/go-gitea/gitea/pull/17158

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/542
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: 6543 <6543@obermui.de>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2022-01-14 01:30:39 +08:00
6543 d19bc07721 Add Changelog for v0.15.1 & url fixes (#564)
as title

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/564
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
2022-01-12 08:44:20 +08:00
6543 635de1b821 Add ignoreVersion & manuall version set option (#560)
be able to skip version check if needed.

!!! Be careful, because using it incorrectly can result in infinite loops with pagination !!!

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/560
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Norwin <noerw@noreply.gitea.io>
2022-01-05 00:31:31 +08:00
6543 a87a2c7390 Fix version for next release (#561)
as title

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/561
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2022-01-03 02:27:24 +08:00
spawn2kill 5d54a04f8d Added missing fields to MergePullRequestOption (#554)
Adds `MergeCommitID`, `delete_branch_after_merge` and `force_merge` missing fields according to [Gitea Swagger docummentation](https://try.gitea.io/api/swagger#/repository/repoMergePullRequest).

Co-authored-by: Hilário Coelho <hilario.coelho@securityside.com>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/554
Reviewed-by: Andrew Thornton <art27@cantab.net>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: spawn2kill <spawn2kill@noreply.gitea.io>
Co-committed-by: spawn2kill <spawn2kill@noreply.gitea.io>
2021-11-12 20:57:44 +08:00
Norwin 0fb32ac11f NotificationSubject: add HTMLURL fields (#548)
upstream change: https://github.com/go-gitea/gitea/pull/17178

Co-authored-by: Norwin <git@nroo.de>
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/548
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Norwin <noerw@noreply.gitea.io>
Co-committed-by: Norwin <noerw@noreply.gitea.io>
2021-10-14 16:24:30 +08:00
Lunny Xiao bf0e0883a8 Fix debug (#545)
As title.

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/545
Reviewed-by: Norwin <noerw@noreply.gitea.io>
Reviewed-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-10-05 13:16:14 +08:00
6543 4debc6ca4b Extend Label Test (#540)
make test coverate 53.8%

Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/540
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2021-08-29 02:28:00 +08:00
80 changed files with 2615 additions and 492 deletions

View File

@ -1,69 +0,0 @@
---
kind: pipeline
name: default
platform:
os: linux
arch: amd64
workspace:
base: /go
path: src/code.gitea.io/sdk
steps:
- name: gitea
image: gitea/gitea:latest
detach: true
commands:
- mkdir -p /tmp/conf/
- mkdir -p /tmp/data/
- echo "[security]" > /tmp/conf/app.ini
- echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> /tmp/conf/app.ini
- echo "INSTALL_LOCK = true" >> /tmp/conf/app.ini
- echo "SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo" >> /tmp/conf/app.ini
- echo "PASSWORD_COMPLEXITY = off" >> /tmp/conf/app.ini
- echo "[database]" >> /tmp/conf/app.ini
- echo "DB_TYPE = sqlite3" >> /tmp/conf/app.ini
- echo "[repository]" >> /tmp/conf/app.ini
- echo "ROOT = /tmp/data/" >> /tmp/conf/app.ini
- echo "[server]" >> /tmp/conf/app.ini
- echo "ROOT_URL = http://gitea:3000" >> /tmp/conf/app.ini
- gitea migrate -c /tmp/conf/app.ini
- gitea admin user create --username=test01 --password=test01 --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c /tmp/conf/app.ini
- gitea web -c /tmp/conf/app.ini
- name: testing
pull: always
image: golang:1.16
environment:
GOPROXY: "https://goproxy.cn"
GO111MODULE: "on"
HTTP_PROXY: ""
GITEA_SDK_TEST_URL: "http://gitea:3000"
GITEA_SDK_TEST_USERNAME: "test01"
GITEA_SDK_TEST_PASSWORD: "test01"
#GITEA_SDK_TEST_RUN_GITEA: "true"
commands:
- make clean
- make vet
- make revive
- make build
- curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance
- make test
- name: discord
pull: always
image: appleboy/drone-discord:1.0.0
environment:
DISCORD_WEBHOOK_ID:
from_secret: discord_webhook_id
DISCORD_WEBHOOK_TOKEN:
from_secret: discord_webhook_token
when:
event:
- push
- tag
- pull_request
status:
- changed
- failure

View File

@ -0,0 +1,52 @@
name: testing
on:
- pull_request
- push
jobs:
testing:
runs-on: ubuntu-latest
env:
GOPROXY: "https://goproxy.io"
GO111MODULE: "on"
HTTP_PROXY: ""
GITEA_SDK_TEST_URL: "http://gitea:3000"
GITEA_SDK_TEST_USERNAME: "test01"
GITEA_SDK_TEST_PASSWORD: "test01"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ">=1.21"
check-latest: true
- run: make clean
- run: make vet
- run: make ci-lint
- run: make build
- run: curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance
- run: make test
services:
gitea:
image: gitea/gitea:1.22.0-rc1
cmd:
- bash
- -c
- >-
mkdir -p /tmp/conf/
&& mkdir -p /tmp/data/
&& echo "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT = true" > /tmp/conf/app.ini
&& echo "[security]" >> /tmp/conf/app.ini
&& echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> /tmp/conf/app.ini
&& echo "INSTALL_LOCK = true" >> /tmp/conf/app.ini
&& echo "SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo" >> /tmp/conf/app.ini
&& echo "PASSWORD_COMPLEXITY = off" >> /tmp/conf/app.ini
&& echo "[database]" >> /tmp/conf/app.ini
&& echo "DB_TYPE = sqlite3" >> /tmp/conf/app.ini
&& echo "[repository]" >> /tmp/conf/app.ini
&& echo "ROOT = /tmp/data/" >> /tmp/conf/app.ini
&& echo "[server]" >> /tmp/conf/app.ini
&& echo "ROOT_URL = http://gitea:3000" >> /tmp/conf/app.ini
&& gitea migrate -c /tmp/conf/app.ini
&& gitea admin user create --username=test01 --password=test01 --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c /tmp/conf/app.ini
&& gitea web -c /tmp/conf/app.ini

View File

@ -1,7 +1,7 @@
Please check the following:
1. Make sure you are targeting the `master` branch, pull requests on release branches are only allowed for bug fixes.
2. Read contributing guidelines: https://gitea.com/gitea/go-sdk/src/branch/master/CONTRIBUTING.md
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for bug fixes.
2. Read contributing guidelines: https://gitea.com/gitea/go-sdk/src/branch/main/CONTRIBUTING.md
3. Describe what your pull request does and which issue you're targeting (if any)
**You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.**

View File

@ -1,6 +1,14 @@
# Changelog
## [v0.15.0](https://gitea.com/gitea/go-sdk/releases/tag/v0.15.0) - 2021-08-13
## [v0.15.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.15.1) - 2022-01-04
* FEATURES
* Add ignoreVersion & manuall version set option (#560) (#562)
* BUGFIXES
* Fix version string for next release (#559)
## [v0.15.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.15.0) - 2021-08-13
* BREAKING
* Introduce NotifySubjectState (#520)
@ -20,13 +28,13 @@
* ListFunctions: option to disable pagination (#509)
## [v0.14.1](https://gitea.com/gitea/go-sdk/releases/tag/v0.14.1) - 2021-06-30
## [v0.14.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.14.1) - 2021-06-30
* BUGFIXES
* Fix setDefaults (#508) (#510)
## [v0.14.0](https://gitea.com/gitea/go-sdk/releases/tag/v0.14.0) - 2021-03-21
## [v0.14.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.14.0) - 2021-03-21
* BREAKING
* Update Structs (#486)
@ -64,7 +72,7 @@
* Improve PullReview docs (#469)
## [v0.13.3](https://gitea.com/gitea/go-sdk/releases/tag/v0.13.3) - 2021-03-22
## [v0.13.3](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.3) - 2021-03-22
* BUGFIXES
* Fix GetCombinedStatus() (#470) (#472)

View File

@ -8,7 +8,12 @@ GITEA_SDK_TEST_PASSWORD ?= test01
PACKAGE := code.gitea.io/sdk/gitea
GITEA_DL := https://dl.gitea.io/gitea/main/gitea-main-
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.4.0
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0
GITEA_VET_PACKAGE ?= code.gitea.io/gitea-vet@v0.2.1
GITEA_VERSION := 1.21.10
GITEA_DL := https://dl.gitea.com/gitea/$(GITEA_VERSION)/gitea-$(GITEA_VERSION)-
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
GITEA_DL := $(GITEA_DL)linux-
@ -54,27 +59,32 @@ clean:
.PHONY: fmt
fmt:
find . -name "*.go" -type f ! -path "./vendor/*" ! -path "./benchmark/*" | xargs gofmt -s -w
find . -name "*.go" -type f | xargs gofmt -s -w; \
$(GO) run $(GOFUMPT_PACKAGE) -extra -w ./gitea
.PHONY: vet
vet:
# Default vet
cd gitea && $(GO) vet $(PACKAGE)
# Custom vet
cd gitea && $(GO) get code.gitea.io/gitea-vet
cd gitea && $(GO) get $(GITEA_VET_PACKAGE)
cd gitea && $(GO) build code.gitea.io/gitea-vet
cd gitea && $(GO) vet -vettool=gitea-vet $(PACKAGE)
.PHONY: lint
lint:
@echo 'make lint is depricated. Use "make revive" if you want to use the old lint tool, or "make golangci-lint" to run a complete code check.'
.PHONY: revive
revive:
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/mgechev/revive; \
fi
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
.PHONY: ci-lint
ci-lint:
@cd gitea/; echo -n "gofumpt ...";\
diff=$$($(GO) run $(GOFUMPT_PACKAGE) -extra -l .); \
if [ -n "$$diff" ]; then \
echo; echo "Not gofumpt-ed"; \
exit 1; \
fi; echo " done"; echo -n "golangci-lint ...";\
$(GO) run $(GOLANGCI_LINT_PACKAGE) run --timeout 5m; \
if [ $$? -eq 1 ]; then \
echo; echo "Doesn't pass golangci-lint"; \
exit 1; \
fi; echo " done"; \
cd -; \
.PHONY: test
test:
@ -112,10 +122,3 @@ bench:
build:
cd gitea && $(GO) build
.PHONY: golangci-lint
golangci-lint:
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
export BINARY="golangci-lint"; \
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.22.2; \
fi
golangci-lint run --timeout 5m

View File

@ -1,8 +1,12 @@
# Gitea SDK for Go
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Release](https://raster.shields.io/badge/dynamic/json.svg?label=release&url=https://gitea.com/api/v1/repos/gitea/go-sdk/releases&query=$[0].tag_name)](https://gitea.com/gitea/go-sdk/releases) [![Build Status](https://drone.gitea.com/api/badges/gitea/go-sdk/status.svg)](https://drone.gitea.com/gitea/go-sdk) [![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/Gitea) [![](https://images.microbadger.com/badges/image/gitea/gitea.svg)](http://microbadger.com/images/gitea/gitea "Get your own image badge on microbadger.com") [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/sdk)](https://goreportcard.com/report/code.gitea.io/sdk) [![GoDoc](https://godoc.org/code.gitea.io/sdk/gitea?status.svg)](https://godoc.org/code.gitea.io/sdk/gitea)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Release](https://raster.shields.io/badge/dynamic/json.svg?label=release&url=https://gitea.com/api/v1/repos/gitea/go-sdk/releases&query=$[0].tag_name)](https://gitea.com/gitea/go-sdk/releases)
[![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/Gitea)
[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/sdk)](https://goreportcard.com/report/code.gitea.io/sdk)
[![GoDoc](https://godoc.org/code.gitea.io/sdk/gitea?status.svg)](https://godoc.org/code.gitea.io/sdk/gitea)
This project acts as a client SDK implementation written in Go to interact with the Gitea API implementation. For further informations take a look at the current [documentation](https://godoc.org/code.gitea.io/sdk/gitea).
This project acts as a client SDK implementation written in Go to interact with the Gitea API implementation. For further informations take a look at the current [documentation](https://pkg.go.dev/code.gitea.io/sdk/gitea).
Note: function arguments are escaped by the SDK.

View File

@ -1,7 +1,7 @@
# Migration Guide: v0.14 to v0.15
v0.15.0 introduces a number of api changes, through which it should not be difficult to migrate.
Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue.
v0.15.0 introduces a number of API changes, which should be simple to migrate.
Just follow this guide and if you still encounter problems, ask for help on Discord or feel free to create an issue.
<!-- toc -->

View File

@ -0,0 +1,28 @@
# Migration Guide: v0.15 to v0.16
v0.16.0 introduces a number of API changes, which should be simple to migrate.
Just follow this guide and if you still encounter problems, ask for help on Discord or feel free to create an issue.
<!-- toc -->
- [Upstream API changes](#upstream-api-changes)
- [GetPullRequestDiff: add PullRequestDiffOption parameter (#542)](#getpullrequestdiff)
<!-- tocstop -->
## Upstream API changes
As we aim to track API changes in Gitea 1.16 with this SDK release, you may find this [summary listing of changes](https://gitea.com/gitea/go-sdk/issues/558) helpful.
## GetPullRequestDiff
Added new parameter `opts PullRequestDiffOption`. Gitea 1.16 will default to omit binary file changes in diffs; if you still need that information, set `opts.Binary = true`.
Related PRs:
- [go-sdk#542](https://gitea.com/gitea/go-sdk/pulls/542)
- [gitea#17158](https://github.com/go-gitea/gitea/pull/17158)
## ReadNotification, ReadNotifications, ReadRepoNotifications
The function now has a new return argument. The read notifications will now be returned by Gitea 1.16. If you don't require this information, use a blank identifier for the return variable.
Related PRs:
- [go-sdk#590](https://gitea.com/gitea/go-sdk/pulls/590)
- [gitea#17064](https://github.com/go-gitea/gitea/pull/17064)

View File

@ -1,40 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"log"
"testing"
"github.com/stretchr/testify/assert"
)
// TestLabels test label related func
func TestLabels(t *testing.T) {
log.Println("== TestLabels ==")
c := newTestClient()
repo, err := createTestRepo(t, "LabelTestsRepo", c)
assert.NoError(t, err)
createOpts := CreateLabelOption{
Name: " ",
Description: "",
Color: "",
}
err = createOpts.Validate()
assert.Error(t, err)
assert.EqualValues(t, "invalid color format", err.Error())
createOpts.Color = "12345f"
err = createOpts.Validate()
assert.Error(t, err)
assert.EqualValues(t, "empty name not allowed", err.Error())
createOpts.Name = "label one"
label1, _, err := c.CreateLabel(repo.Owner.UserName, repo.Name, createOpts)
assert.NoError(t, err)
assert.EqualValues(t, createOpts.Name, label1.Name)
assert.EqualValues(t, createOpts.Color, label1.Color)
}

View File

@ -45,7 +45,7 @@ func TestAdminCronTasks(t *testing.T) {
tasks, _, err := c.ListCronTasks(ListCronTaskOptions{})
assert.NoError(t, err)
assert.Len(t, tasks, 18)
assert.True(t, len(tasks) > 15)
_, err = c.RunCronTasks(tasks[0].Name)
assert.NoError(t, err)
}

38
gitea/agent.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build !windows
package gitea
import (
"fmt"
"net"
"os"
"golang.org/x/crypto/ssh/agent"
)
// hasAgent returns true if the ssh agent is available
func hasAgent() bool {
if _, err := os.Stat(os.Getenv("SSH_AUTH_SOCK")); err != nil {
return false
}
return true
}
// GetAgent returns a ssh agent
func GetAgent() (agent.Agent, error) {
if !hasAgent() {
return nil, fmt.Errorf("no ssh agent available")
}
sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
if err != nil {
return nil, err
}
return agent.NewClient(sshAgent), nil
}

28
gitea/agent_windows.go Normal file
View File

@ -0,0 +1,28 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//go:build windows
package gitea
import (
"fmt"
"github.com/davidmz/go-pageant"
"golang.org/x/crypto/ssh/agent"
)
// hasAgent returns true if pageant is available
func hasAgent() bool {
return pageant.Available()
}
// GetAgent returns a ssh agent
func GetAgent() (agent.Agent, error) {
if !hasAgent() {
return nil, fmt.Errorf("no pageant available")
}
return pageant.New(), nil
}

View File

@ -43,7 +43,7 @@ func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt Li
}
// GetReleaseAttachment returns the requested attachment
func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64) (*Attachment, *Response, error) {
func (c *Client) GetReleaseAttachment(user, repo string, release, id int64) (*Attachment, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
@ -88,7 +88,7 @@ type EditAttachmentOptions struct {
}
// EditReleaseAttachment updates the given attachment with the given options
func (c *Client) EditReleaseAttachment(user, repo string, release int64, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) {
func (c *Client) EditReleaseAttachment(user, repo string, release, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
@ -102,7 +102,7 @@ func (c *Client) EditReleaseAttachment(user, repo string, release int64, attachm
}
// DeleteReleaseAttachment deletes the given attachment including the uploaded file
func (c *Client) DeleteReleaseAttachment(user, repo string, release int64, id int64) (*Response, error) {
func (c *Client) DeleteReleaseAttachment(user, repo string, release, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}

View File

@ -6,14 +6,15 @@
package gitea
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
@ -24,7 +25,7 @@ var jsonHeader = http.Header{"content-type": []string{"application/json"}}
// Version return the library version
func Version() string {
return "0.14.0"
return "0.16.0"
}
// Client represents a thread-safe Gitea API client.
@ -35,33 +36,50 @@ type Client struct {
password string
otp string
sudo string
userAgent string
debug bool
httpsigner *HTTPSign
client *http.Client
ctx context.Context
mutex sync.RWMutex
serverVersion *version.Version
getVersionOnce sync.Once
ignoreVersion bool // only set by SetGiteaVersion so don't need a mutex lock
}
// Response represents the gitea response
type Response struct {
*http.Response
FirstPage int
PrevPage int
NextPage int
LastPage int
}
// ClientOption are functions used to init a new client
type ClientOption func(*Client) error
// NewClient initializes and returns a API client.
// Usage of all gitea.Client methods is concurrency-safe.
func NewClient(url string, options ...func(*Client)) (*Client, error) {
func NewClient(url string, options ...ClientOption) (*Client, error) {
client := &Client{
url: strings.TrimSuffix(url, "/"),
client: &http.Client{},
ctx: context.Background(),
}
for _, opt := range options {
opt(client)
if err := opt(client); err != nil {
return nil, err
}
}
if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
if errors.Is(err, &ErrUnknownVersion{}) {
return client, err
}
return nil, err
}
return client, nil
}
@ -73,9 +91,10 @@ func NewClientWithHTTP(url string, httpClient *http.Client) *Client {
}
// SetHTTPClient is an option for NewClient to set custom http client
func SetHTTPClient(httpClient *http.Client) func(client *Client) {
return func(client *Client) {
func SetHTTPClient(httpClient *http.Client) ClientOption {
return func(client *Client) error {
client.SetHTTPClient(httpClient)
return nil
}
}
@ -87,18 +106,66 @@ func (c *Client) SetHTTPClient(client *http.Client) {
}
// SetToken is an option for NewClient to set token
func SetToken(token string) func(client *Client) {
return func(client *Client) {
func SetToken(token string) ClientOption {
return func(client *Client) error {
client.mutex.Lock()
client.accessToken = token
client.mutex.Unlock()
return nil
}
}
// SetBasicAuth is an option for NewClient to set username and password
func SetBasicAuth(username, password string) func(client *Client) {
return func(client *Client) {
func SetBasicAuth(username, password string) ClientOption {
return func(client *Client) error {
client.SetBasicAuth(username, password)
return nil
}
}
// UseSSHCert is an option for NewClient to enable SSH certificate authentication via HTTPSign
// If you want to auth against the ssh-agent you'll need to set a principal, if you want to
// use a file on disk you'll need to specify sshKey.
// If you have an encrypted sshKey you'll need to also set the passphrase.
func UseSSHCert(principal, sshKey, passphrase string) ClientOption {
return func(client *Client) error {
if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
return err
}
client.mutex.Lock()
defer client.mutex.Unlock()
var err error
client.httpsigner, err = NewHTTPSignWithCert(principal, sshKey, passphrase)
if err != nil {
return err
}
return nil
}
}
// UseSSHPubkey is an option for NewClient to enable SSH pubkey authentication via HTTPSign
// If you want to auth against the ssh-agent you'll need to set a fingerprint, if you want to
// use a file on disk you'll need to specify sshKey.
// If you have an encrypted sshKey you'll need to also set the passphrase.
func UseSSHPubkey(fingerprint, sshKey, passphrase string) ClientOption {
return func(client *Client) error {
if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
return err
}
client.mutex.Lock()
defer client.mutex.Unlock()
var err error
client.httpsigner, err = NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase)
if err != nil {
return err
}
return nil
}
}
@ -110,9 +177,10 @@ func (c *Client) SetBasicAuth(username, password string) {
}
// SetOTP is an option for NewClient to set OTP for 2FA
func SetOTP(otp string) func(client *Client) {
return func(client *Client) {
func SetOTP(otp string) ClientOption {
return func(client *Client) error {
client.SetOTP(otp)
return nil
}
}
@ -123,14 +191,15 @@ func (c *Client) SetOTP(otp string) {
c.mutex.Unlock()
}
// SetContext is an option for NewClient to set context
func SetContext(ctx context.Context) func(client *Client) {
return func(client *Client) {
// SetContext is an option for NewClient to set the default context
func SetContext(ctx context.Context) ClientOption {
return func(client *Client) error {
client.SetContext(ctx)
return nil
}
}
// SetContext set context witch is used for http requests
// SetContext set default context witch is used for http requests
func (c *Client) SetContext(ctx context.Context) {
c.mutex.Lock()
c.ctx = ctx
@ -138,9 +207,10 @@ func (c *Client) SetContext(ctx context.Context) {
}
// SetSudo is an option for NewClient to set sudo header
func SetSudo(sudo string) func(client *Client) {
return func(client *Client) {
func SetSudo(sudo string) ClientOption {
return func(client *Client) error {
client.SetSudo(sudo)
return nil
}
}
@ -151,12 +221,79 @@ func (c *Client) SetSudo(sudo string) {
c.mutex.Unlock()
}
// SetUserAgent is an option for NewClient to set user-agent header
func SetUserAgent(userAgent string) ClientOption {
return func(client *Client) error {
client.SetUserAgent(userAgent)
return nil
}
}
// SetUserAgent sets the user-agent to send with every request.
func (c *Client) SetUserAgent(userAgent string) {
c.mutex.Lock()
c.userAgent = userAgent
c.mutex.Unlock()
}
// SetDebugMode is an option for NewClient to enable debug mode
func SetDebugMode() func(client *Client) {
return func(client *Client) {
func SetDebugMode() ClientOption {
return func(client *Client) error {
client.mutex.Lock()
client.debug = true
client.mutex.Unlock()
return nil
}
}
func newResponse(r *http.Response) *Response {
response := &Response{Response: r}
response.parseLinkHeader()
return response
}
func (r *Response) parseLinkHeader() {
link := r.Header.Get("Link")
if link == "" {
return
}
links := strings.Split(link, ",")
for _, l := range links {
u, param, ok := strings.Cut(l, ";")
if !ok {
continue
}
u = strings.Trim(u, " <>")
key, value, ok := strings.Cut(strings.TrimSpace(param), "=")
if !ok || key != "rel" {
continue
}
value = strings.Trim(value, "\"")
parsed, err := url.Parse(u)
if err != nil {
continue
}
page := parsed.Query().Get("page")
if page == "" {
continue
}
switch value {
case "first":
r.FirstPage, _ = strconv.Atoi(page)
case "prev":
r.PrevPage, _ = strconv.Atoi(page)
case "next":
r.NextPage, _ = strconv.Atoi(page)
case "last":
r.LastPage, _ = strconv.Atoi(page)
}
}
}
@ -181,18 +318,25 @@ func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *R
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if debug {
fmt.Printf("Response: %v\n\n", resp)
}
return data, &Response{resp}, nil
return data, newResponse(resp), err
}
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
c.mutex.RLock()
debug := c.debug
if debug {
fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, body)
var bodyStr string
if body != nil {
bs, _ := io.ReadAll(body)
body = bytes.NewReader(bs)
bodyStr = string(bs)
}
fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, bodyStr)
}
req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body)
if err != nil {
@ -211,6 +355,9 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
if len(c.sudo) != 0 {
req.Header.Set("Sudo", c.sudo)
}
if len(c.userAgent) != 0 {
req.Header.Set("User-Agent", c.userAgent)
}
client := c.client // client ref can change from this point on so safe it
c.mutex.RUnlock()
@ -219,6 +366,13 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
req.Header[k] = v
}
if c.httpsigner != nil {
err = c.SignRequest(req)
if err != nil {
return nil, err
}
}
resp, err := client.Do(req)
if err != nil {
return nil, err
@ -226,7 +380,8 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
if debug {
fmt.Printf("Response: %v\n\n", resp)
}
return &Response{resp}, nil
return newResponse(resp), nil
}
// Converts a response for a HTTP status code indicating an error condition
@ -243,38 +398,49 @@ func statusCodeToErr(resp *Response) (body []byte, err error) {
// error: body will be read for details
//
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("body read on HTTP error %d: %v", resp.StatusCode, err)
}
switch resp.StatusCode {
case 403:
return data, errors.New("403 Forbidden")
case 404:
return data, errors.New("404 Not Found")
case 409:
return data, errors.New("409 Conflict")
case 422:
return data, fmt.Errorf("422 Unprocessable Entity: %s", string(data))
}
path := resp.Request.URL.Path
method := resp.Request.Method
header := resp.Request.Header
// Try to unmarshal and get an error message
errMap := make(map[string]interface{})
if err = json.Unmarshal(data, &errMap); err != nil {
// when the JSON can't be parsed, data was probably empty or a
// plain string, so we try to return a helpful error anyway
path := resp.Request.URL.Path
method := resp.Request.Method
header := resp.Request.Header
return data, fmt.Errorf("Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body", resp.StatusCode, path, method, header, string(data))
}
return data, errors.New(errMap["message"].(string))
if msg, ok := errMap["message"]; ok {
return data, fmt.Errorf("%v", msg)
}
// If no error message, at least give status and data
return data, fmt.Errorf("%s: %s", resp.Status, string(data))
}
func (c *Client) getResponseReader(method, path string, header http.Header, body io.Reader) (io.ReadCloser, *Response, error) {
resp, err := c.doRequest(method, path, header, body)
if err != nil {
return nil, resp, err
}
// check for errors
data, err := statusCodeToErr(resp)
if err != nil {
return io.NopCloser(bytes.NewReader(data)), resp, err
}
return resp.Body, resp, nil
}
func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) {
resp, err := c.doRequest(method, path, header, body)
if err != nil {
return nil, nil, err
return nil, resp, err
}
defer resp.Body.Close()
@ -285,7 +451,7 @@ func (c *Client) getResponse(method, path string, header http.Header, body io.Re
}
// success (2XX), read body
data, err = ioutil.ReadAll(resp.Body)
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, resp, err
}

35
gitea/client_test.go Normal file
View File

@ -0,0 +1,35 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParsedPaging(t *testing.T) {
resp := newResponse(&http.Response{
Header: http.Header{
"Link": []string{
strings.Join(
[]string{
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=3>; rel="next"`,
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=4>; rel="last"`,
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=1>; rel="first"`,
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=1>; rel="prev"`,
}, ",",
),
},
},
})
assert.Equal(t, 1, resp.FirstPage)
assert.Equal(t, 1, resp.PrevPage)
assert.Equal(t, 3, resp.NextPage)
assert.Equal(t, 4, resp.LastPage)
}

View File

@ -2,4 +2,8 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package gitea implements a client for the Gitea API.
// The version corresponds to the highest supported version
// of the gitea API, but backwards-compatibility is mostly
// given.
package gitea // import "code.gitea.io/sdk/gitea"

View File

@ -16,7 +16,7 @@ type ListForksOptions struct {
}
// ListForks list a repository's forks
func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*Repository, *Response, error) {
func (c *Client) ListForks(user, repo string, opt ListForksOptions) ([]*Repository, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
@ -32,6 +32,8 @@ func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*R
type CreateForkOption struct {
// organization name, if forking into an organization
Organization *string `json:"organization"`
// name of the forked repository
Name *string `json:"name"`
}
// CreateFork create a fork of a repository

View File

@ -1,9 +1,18 @@
module code.gitea.io/sdk/gitea
go 1.13
go 1.18
require (
code.gitea.io/gitea-vet v0.2.1 // indirect
github.com/hashicorp/go-version v1.2.1
github.com/stretchr/testify v1.4.0
github.com/davidmz/go-pageant v1.0.2
github.com/go-fed/httpsig v1.1.0
github.com/hashicorp/go-version v1.6.0
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.22.0
)
require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

View File

@ -1,33 +1,34 @@
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
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.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI=
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -62,6 +62,14 @@ func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Respo
return hooks, resp, err
}
// ListMyHooks list all the hooks of the authenticated user
func (c *Client) ListMyHooks(opt ListHooksOptions) ([]*Hook, *Response, error) {
opt.setDefaults()
hooks := make([]*Hook, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/hooks?%s", opt.getURLQuery().Encode()), nil, nil, &hooks)
return hooks, resp, err
}
// ListRepoHooks list all the hooks of one repository
func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
@ -83,6 +91,13 @@ func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) {
return h, resp, err
}
// GetMyHook get a hook of the authenticated user
func (c *Client) GetMyHook(id int64) (*Hook, *Response, error) {
h := new(Hook)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/hooks/%d", id), nil, nil, h)
return h, resp, err
}
// GetRepoHook get a hook of a repository
func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
@ -95,11 +110,12 @@ func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, err
// CreateHookOption options when create a hook
type CreateHookOption struct {
Type HookType `json:"type"`
Config map[string]string `json:"config"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter"`
Active bool `json:"active"`
Type HookType `json:"type"`
Config map[string]string `json:"config"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter"`
Active bool `json:"active"`
AuthorizationHeader string `json:"authorization_header"`
}
// Validate the CreateHookOption struct
@ -127,6 +143,20 @@ func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Respon
return h, resp, err
}
// CreateMyHook create one hook for the authenticated user, with options
func (c *Client) CreateMyHook(opt CreateHookOption) (*Hook, *Response, error) {
if err := opt.Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, nil, err
}
h := new(Hook)
resp, err := c.getParsedResponse("POST", "/user/hooks", jsonHeader, bytes.NewReader(body), h)
return h, resp, err
}
// CreateRepoHook create one hook for a repository, with options
func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
@ -143,10 +173,11 @@ func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook,
// EditHookOption options when modify one hook
type EditHookOption struct {
Config map[string]string `json:"config"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter"`
Active *bool `json:"active"`
Config map[string]string `json:"config"`
Events []string `json:"events"`
BranchFilter string `json:"branch_filter"`
Active *bool `json:"active"`
AuthorizationHeader string `json:"authorization_header"`
}
// EditOrgHook modify one hook of an organization, with hook id and options
@ -162,6 +193,16 @@ func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Respons
return resp, err
}
// EditMyHook modify one hook of the authenticated user, with hook id and options
func (c *Client) EditMyHook(id int64, opt EditHookOption) (*Response, error) {
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/user/hooks/%d", id), jsonHeader, bytes.NewReader(body))
return resp, err
}
// EditRepoHook modify one hook of a repository, with hook id and options
func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
@ -184,6 +225,12 @@ func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) {
return resp, err
}
// DeleteMyHook delete one hook from the authenticated user, with hook id
func (c *Client) DeleteMyHook(id int64) (*Response, error) {
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/hooks/%d", id), nil, nil)
return resp, err
}
// DeleteRepoHook delete one hook from a repository, with hook id
func (c *Client) DeleteRepoHook(user, repo string, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {

59
gitea/hook_validate.go Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
)
// VerifyWebhookSignature verifies that a payload matches the X-Gitea-Signature based on a secret
func VerifyWebhookSignature(secret, expected string, payload []byte) (bool, error) {
hash := hmac.New(sha256.New, []byte(secret))
if _, err := hash.Write(payload); err != nil {
return false, err
}
expectedSum, err := hex.DecodeString(expected)
if err != nil {
return false, err
}
return hmac.Equal(hash.Sum(nil), expectedSum), nil
}
// VerifyWebhookSignatureMiddleware is a http.Handler for verifying X-Gitea-Signature on incoming webhooks
func VerifyWebhookSignatureMiddleware(secret string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var b bytes.Buffer
if _, err := io.Copy(&b, r.Body); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
expected := r.Header.Get("X-Gitea-Signature")
if expected == "" {
http.Error(w, "no signature found", http.StatusBadRequest)
return
}
ok, err := VerifyWebhookSignature(secret, expected, b.Bytes())
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
if !ok {
http.Error(w, "invalid payload", http.StatusUnauthorized)
return
}
r.Body = io.NopCloser(&b)
next.ServeHTTP(w, r)
})
}
}

125
gitea/hook_validate_test.go Normal file
View File

@ -0,0 +1,125 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
// Hashers are based on https://github.com/go-gitea/gitea/blob/0dfc2e55ea258d2b1a3cd86e2b6f27a481e495ff/services/webhook/deliver.go#L105-L116
func TestVerifyWebhookSignature(t *testing.T) {
secret := "s3cr3t"
payload := []byte(`{"foo": "bar", "baz": true}`)
hasher := hmac.New(sha256.New, []byte(secret))
hasher.Write(payload)
sig := hex.EncodeToString(hasher.Sum(nil))
tt := []struct {
Name string
Secret string
Payload string
Succeed bool
}{
{
Name: "Correct secret and payload",
Secret: "s3cr3t",
Payload: `{"foo": "bar", "baz": true}`,
Succeed: true,
},
{
Name: "Correct secret bad payload",
Secret: "s3cr3t",
Payload: "{}",
Succeed: false,
},
{
Name: "Incorrect secret good payload",
Secret: "secret",
Payload: `{"foo": "bar", "baz": true}`,
Succeed: false,
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
ok, err := VerifyWebhookSignature(tc.Secret, sig, []byte(tc.Payload))
assert.NoError(t, err, "verification should not error")
assert.True(t, ok == tc.Succeed, "verification should be %t", tc.Succeed)
})
}
}
func TestVerifyWebhookSignatureHandler(t *testing.T) {
secret := "s3cr3t"
payload := []byte(`{"foo": "bar", "baz": true}`)
hasher := hmac.New(sha256.New, []byte(secret))
hasher.Write(payload)
sig := hex.EncodeToString(hasher.Sum(nil))
tt := []struct {
Name string
Secret string
Payload string
Signature string
Status int
}{
{
Name: "Correct secret and payload",
Secret: "s3cr3t",
Payload: `{"foo": "bar", "baz": true}`,
Signature: sig,
Status: http.StatusOK,
},
{
Name: "Correct secret bad payload",
Secret: "s3cr3t",
Payload: "{}",
Signature: sig,
Status: http.StatusUnauthorized,
},
{
Name: "Incorrect secret good payload",
Secret: "secret",
Payload: `{"foo": "bar", "baz": true}`,
Signature: sig,
Status: http.StatusUnauthorized,
},
{
Name: "No signature",
Status: http.StatusBadRequest,
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
server := httptest.NewServer(VerifyWebhookSignatureMiddleware(tc.Secret)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write(nil)
})))
defer server.Close()
req, err := http.NewRequest(http.MethodPost, server.URL, strings.NewReader(tc.Payload))
assert.NoError(t, err, "should create request")
if tc.Signature != "" {
req.Header.Set("X-Gitea-Signature", tc.Signature)
}
resp, err := http.DefaultClient.Do(req)
assert.NoError(t, err, "request should be delivered")
assert.True(t, resp.StatusCode == tc.Status, "status should be %d, but got %d", tc.Status, resp.StatusCode)
})
}
}

253
gitea/httpsign.go Normal file
View File

@ -0,0 +1,253 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"crypto"
"encoding/base64"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/go-fed/httpsig"
"golang.org/x/crypto/ssh"
)
// HTTPSign contains the signer used for signing requests
type HTTPSign struct {
ssh.Signer
cert bool
}
// HTTPSignConfig contains the configuration for creating a HTTPSign
type HTTPSignConfig struct {
fingerprint string
principal string
pubkey bool
cert bool
sshKey string
passphrase string
}
// NewHTTPSignWithPubkey can be used to create a HTTPSign with a public key
// if no fingerprint is specified it returns the first public key found
func NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase string) (*HTTPSign, error) {
return newHTTPSign(&HTTPSignConfig{
fingerprint: fingerprint,
pubkey: true,
sshKey: sshKey,
passphrase: passphrase,
})
}
// NewHTTPSignWithCert can be used to create a HTTPSign with a certificate
// if no principal is specified it returns the first certificate found
func NewHTTPSignWithCert(principal, sshKey, passphrase string) (*HTTPSign, error) {
return newHTTPSign(&HTTPSignConfig{
principal: principal,
cert: true,
sshKey: sshKey,
passphrase: passphrase,
})
}
// NewHTTPSign returns a new HTTPSign
// It will check the ssh-agent or a local file is config.sshKey is set.
// Depending on the configuration it will either use a certificate or a public key
func newHTTPSign(config *HTTPSignConfig) (*HTTPSign, error) {
var signer ssh.Signer
if config.sshKey != "" {
priv, err := os.ReadFile(config.sshKey)
if err != nil {
return nil, err
}
if config.passphrase == "" {
signer, err = ssh.ParsePrivateKey(priv)
if err != nil {
return nil, err
}
} else {
signer, err = ssh.ParsePrivateKeyWithPassphrase(priv, []byte(config.passphrase))
if err != nil {
return nil, err
}
}
if config.cert {
certbytes, err := os.ReadFile(config.sshKey + "-cert.pub")
if err != nil {
return nil, err
}
pub, _, _, _, err := ssh.ParseAuthorizedKey(certbytes)
if err != nil {
return nil, err
}
cert, ok := pub.(*ssh.Certificate)
if !ok {
return nil, fmt.Errorf("failed to parse certificate")
}
signer, err = ssh.NewCertSigner(cert, signer)
if err != nil {
return nil, err
}
}
} else {
// if no sshKey is specified, check if we have a ssh-agent and use it
agent, err := GetAgent()
if err != nil {
return nil, err
}
signers, err := agent.Signers()
if err != nil {
return nil, err
}
if len(signers) == 0 {
return nil, fmt.Errorf("no signers found")
}
if config.cert {
signer = findCertSigner(signers, config.principal)
if signer == nil {
return nil, fmt.Errorf("no certificate found for %s", config.principal)
}
}
if config.pubkey {
signer = findPubkeySigner(signers, config.fingerprint)
if signer == nil {
return nil, fmt.Errorf("no public key found for %s", config.fingerprint)
}
}
}
return &HTTPSign{
Signer: signer,
cert: config.cert,
}, nil
}
// SignRequest signs a HTTP request
func (c *Client) SignRequest(r *http.Request) error {
var contents []byte
headersToSign := []string{httpsig.RequestTarget, "(created)", "(expires)"}
if c.httpsigner.cert {
// add our certificate to the headers to sign
pubkey, _ := ssh.ParsePublicKey(c.httpsigner.Signer.PublicKey().Marshal())
if cert, ok := pubkey.(*ssh.Certificate); ok {
certString := base64.RawStdEncoding.EncodeToString(cert.Marshal())
r.Header.Add("x-ssh-certificate", certString)
headersToSign = append(headersToSign, "x-ssh-certificate")
} else {
return fmt.Errorf("no ssh certificate found")
}
}
// if we have a body, the Digest header will be added and we'll include this also in
// our signature.
if r.Body != nil {
body, err := r.GetBody()
if err != nil {
return fmt.Errorf("getBody() failed: %s", err)
}
contents, err = io.ReadAll(body)
if err != nil {
return fmt.Errorf("failed reading body: %s", err)
}
headersToSign = append(headersToSign, "Digest")
}
// create a signer for the request and headers, the signature will be valid for 10 seconds
signer, _, err := httpsig.NewSSHSigner(c.httpsigner.Signer, httpsig.DigestSha512, headersToSign, httpsig.Signature, 10)
if err != nil {
return fmt.Errorf("httpsig.NewSSHSigner failed: %s", err)
}
// sign the request, use the fingerprint if we don't have a certificate
keyID := "gitea"
if !c.httpsigner.cert {
keyID = ssh.FingerprintSHA256(c.httpsigner.Signer.PublicKey())
}
err = signer.SignRequest(keyID, r, contents)
if err != nil {
return fmt.Errorf("httpsig.Signrequest failed: %s", err)
}
return nil
}
// findCertSigner returns the Signer containing a valid certificate
// if no principal is specified it returns the first certificate found
func findCertSigner(sshsigners []ssh.Signer, principal string) ssh.Signer {
for _, s := range sshsigners {
// Check if the key is a certificate
if !strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
continue
}
// convert the ssh.Signer to a ssh.Certificate
mpubkey, _ := ssh.ParsePublicKey(s.PublicKey().Marshal())
cryptopub := mpubkey.(crypto.PublicKey)
cert := cryptopub.(*ssh.Certificate)
t := time.Unix(int64(cert.ValidBefore), 0)
// make sure the certificate is at least 10 seconds valid
if time.Until(t) <= time.Second*10 {
continue
}
if principal == "" {
return s
}
for _, p := range cert.ValidPrincipals {
if p == principal {
return s
}
}
}
return nil
}
// findPubkeySigner returns the Signer containing a valid public key
// if no fingerprint is specified it returns the first public key found
func findPubkeySigner(sshsigners []ssh.Signer, fingerprint string) ssh.Signer {
for _, s := range sshsigners {
// Check if the key is a certificate
if strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
continue
}
if fingerprint == "" {
return s
}
if strings.TrimSpace(string(ssh.MarshalAuthorizedKey(s.PublicKey()))) == fingerprint {
return s
}
if ssh.FingerprintSHA256(s.PublicKey()) == fingerprint {
return s
}
}
return nil
}

View File

@ -71,6 +71,10 @@ type ListIssueOption struct {
AssignedBy string
// filter by username mentioned
MentionedBy string
// filter by owner (only works on ListIssues on User)
Owner string
// filter by team (requires organization owner parameter to be provided and only works on ListIssues on User)
Team string
}
// StateType issue state type
@ -135,6 +139,12 @@ func (opt *ListIssueOption) QueryEncode() string {
if len(opt.MentionedBy) > 0 {
query.Add("mentioned_by", opt.MentionedBy)
}
if len(opt.Owner) > 0 {
query.Add("owner", opt.Owner)
}
if len(opt.Team) > 0 {
query.Add("team", opt.MentionedBy)
}
return query.Encode()
}
@ -279,6 +289,17 @@ func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption)
return issue, resp, err
}
// DeleteIssue delete a issue from a repository
func (c *Client) DeleteIssue(user, repo string, id int64) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE",
fmt.Sprintf("/repos/%s/%s/issues/%d", user, repo, id),
nil, nil)
return resp, err
}
func (c *Client) issueBackwardsCompatibility(issue *Issue) {
if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil {
c.mutex.RLock()

121
gitea/issue_label_test.go Normal file
View File

@ -0,0 +1,121 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"log"
"testing"
"github.com/stretchr/testify/assert"
)
// TestLabels test label related func
func TestLabels(t *testing.T) {
log.Println("== TestLabels ==")
c := newTestClient()
repo, err := createTestRepo(t, "LabelTestsRepo", c)
assert.NoError(t, err)
createOpts := CreateLabelOption{
Name: " ",
Description: "",
Color: "",
}
err = createOpts.Validate()
assert.Error(t, err)
assert.EqualValues(t, "invalid color format", err.Error())
createOpts.Color = "12345f"
err = createOpts.Validate()
assert.Error(t, err)
assert.EqualValues(t, "empty name not allowed", err.Error())
createOpts.Name = "label one"
labelOne, _, err := c.CreateLabel(repo.Owner.UserName, repo.Name, createOpts)
assert.NoError(t, err)
assert.EqualValues(t, createOpts.Name, labelOne.Name)
assert.EqualValues(t, createOpts.Color, labelOne.Color)
labelTwo, _, err := c.CreateLabel(repo.Owner.UserName, repo.Name, CreateLabelOption{
Name: "blue",
Color: "#0000FF",
Description: "CMYB(100%, 100%, 0%, 0%)",
})
assert.NoError(t, err)
_, _, err = c.CreateLabel(repo.Owner.UserName, repo.Name, CreateLabelOption{
Name: "gray",
Color: "808080",
Description: "CMYB(0%, 0%, 0%, 50%)",
})
assert.NoError(t, err)
_, _, err = c.CreateLabel(repo.Owner.UserName, repo.Name, CreateLabelOption{
Name: "green",
Color: "#98F76C",
Description: "CMYB(38%, 0%, 56%, 3%)",
})
assert.NoError(t, err)
labels, resp, err := c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{ListOptions: ListOptions{PageSize: 3}})
assert.NoError(t, err)
assert.Len(t, labels, 3)
assert.NotNil(t, resp)
assert.Contains(t, labels, labelTwo)
assert.NotContains(t, labels, labelOne)
label, _, err := c.GetRepoLabel(repo.Owner.UserName, repo.Name, labelTwo.ID)
assert.NoError(t, err)
assert.EqualValues(t, labelTwo, label)
label, _, err = c.EditLabel(repo.Owner.UserName, repo.Name, labelTwo.ID, EditLabelOption{
Color: OptionalString("#0e0175"),
Description: OptionalString("blueish"),
})
assert.NoError(t, err)
assert.EqualValues(t, &Label{
ID: labelTwo.ID,
Name: labelTwo.Name,
Color: "0e0175",
Description: "blueish",
URL: labelTwo.URL,
}, label)
labels, _, _ = c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{ListOptions: ListOptions{PageSize: 3}})
createTestIssue(t, c, repo.Name, "test-issue", "", nil, nil, 0, []int64{label.ID}, false, false)
issueIndex := int64(1)
issueLabels, _, err := c.GetIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, ListLabelsOptions{})
assert.NoError(t, err)
assert.Len(t, issueLabels, 1)
assert.EqualValues(t, label, issueLabels[0])
_, _, err = c.AddIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, IssueLabelsOption{Labels: []int64{labels[0].ID}})
assert.NoError(t, err)
issueLabels, _, err = c.AddIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, IssueLabelsOption{Labels: []int64{labels[1].ID, labels[2].ID}})
assert.NoError(t, err)
assert.Len(t, issueLabels, 3)
assert.EqualValues(t, labels, issueLabels)
labels, _, _ = c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{})
assert.Len(t, labels, 11)
issueLabels, _, err = c.ReplaceIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, IssueLabelsOption{Labels: []int64{labels[0].ID, labels[1].ID}})
assert.NoError(t, err)
assert.Len(t, issueLabels, 2)
_, err = c.DeleteIssueLabel(repo.Owner.UserName, repo.Name, issueIndex, labels[0].ID)
assert.NoError(t, err)
issueLabels, _, _ = c.GetIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, ListLabelsOptions{})
assert.Len(t, issueLabels, 1)
_, err = c.ClearIssueLabels(repo.Owner.UserName, repo.Name, issueIndex)
assert.NoError(t, err)
issueLabels, _, _ = c.GetIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, ListLabelsOptions{})
assert.Len(t, issueLabels, 0)
_, err = c.DeleteLabel(repo.Owner.UserName, repo.Name, labelTwo.ID)
assert.NoError(t, err)
labels, _, _ = c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{})
assert.Len(t, labels, 10)
}

View File

@ -72,8 +72,8 @@ func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Respon
}
// GetMilestoneByName get one milestone by repo and milestone name
func (c *Client) GetMilestoneByName(owner, repo string, name string) (*Milestone, *Response, error) {
if c.CheckServerVersionConstraint(">=1.13") != nil {
func (c *Client) GetMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) {
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
// backwards compatibility mode
m, resp, err := c.resolveMilestoneByName(owner, repo, name)
return m, resp, err
@ -163,8 +163,8 @@ func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOp
}
// EditMilestoneByName modify milestone with options
func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMilestoneOption) (*Milestone, *Response, error) {
if c.CheckServerVersionConstraint(">=1.13") != nil {
func (c *Client) EditMilestoneByName(owner, repo, name string, opt EditMilestoneOption) (*Milestone, *Response, error) {
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
// backwards compatibility mode
m, _, err := c.resolveMilestoneByName(owner, repo, name)
if err != nil {
@ -197,8 +197,8 @@ func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error
}
// DeleteMilestoneByName delete one milestone by name
func (c *Client) DeleteMilestoneByName(owner, repo string, name string) (*Response, error) {
if c.CheckServerVersionConstraint(">=1.13") != nil {
func (c *Client) DeleteMilestoneByName(owner, repo, name string) (*Response, error) {
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
// backwards compatibility mode
m, _, err := c.resolveMilestoneByName(owner, repo, name)
if err != nil {
@ -229,7 +229,7 @@ func (c *Client) resolveMilestoneByName(owner, repo, name string) (*Milestone, *
return nil, nil, fmt.Errorf("milestone '%s' do not exist", name)
}
for _, m := range miles {
if strings.ToLower(strings.TrimSpace(m.Title)) == strings.ToLower(strings.TrimSpace(name)) {
if strings.EqualFold(strings.TrimSpace(m.Title), strings.TrimSpace(name)) {
return m, resp, nil
}
}

View File

@ -18,7 +18,7 @@ func TestMilestones(t *testing.T) {
repo, _ := createTestRepo(t, "TestMilestones", c)
now := time.Now()
future := time.Unix(1896134400, 0) //2030-02-01
future := time.Unix(1896134400, 0) // 2030-02-01
closed := "closed"
sClosed := StateClosed
@ -43,7 +43,7 @@ func TestMilestones(t *testing.T) {
// ListRepoMilestones
ml, _, err := c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{})
assert.NoError(t, err)
assert.Len(t, ml, 2)
assert.Len(t, ml, 3)
ml, _, err = c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{State: StateClosed})
assert.NoError(t, err)
assert.Len(t, ml, 1)
@ -59,7 +59,7 @@ func TestMilestones(t *testing.T) {
m, _, err := c.resolveMilestoneByName(repo.Owner.UserName, repo.Name, "V3.0")
assert.NoError(t, err)
assert.EqualValues(t, ml[0].ID, m.ID)
m, _, err = c.resolveMilestoneByName(repo.Owner.UserName, repo.Name, "NoEvidenceOfExist")
_, _, err = c.resolveMilestoneByName(repo.Owner.UserName, repo.Name, "NoEvidenceOfExist")
assert.Error(t, err)
assert.EqualValues(t, "milestone 'NoEvidenceOfExist' do not exist", err.Error())

97
gitea/issue_template.go Normal file
View File

@ -0,0 +1,97 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"fmt"
)
// IssueTemplate provides metadata and content on an issue template.
// There are two types of issue templates: .Markdown- and .Form-based.
type IssueTemplate struct {
Name string `json:"name"`
About string `json:"about"`
Filename string `json:"file_name"`
IssueTitle string `json:"title"`
IssueLabels []string `json:"labels"`
IssueRef string `json:"ref"`
// If non-nil, this is a form-based template
Form []IssueFormElement `json:"body"`
// Should only be used when .Form is nil.
MarkdownContent string `json:"content"`
}
// IssueFormElement describes a part of a IssueTemplate form
type IssueFormElement struct {
ID string `json:"id"`
Type IssueFormElementType `json:"type"`
Attributes IssueFormElementAttributes `json:"attributes"`
Validations IssueFormElementValidations `json:"validations"`
}
// IssueFormElementAttributes contains the combined set of attributes available on all element types.
type IssueFormElementAttributes struct {
// required for all element types.
// A brief description of the expected user input, which is also displayed in the form.
Label string `json:"label"`
// required for element types "dropdown", "checkboxes"
// for dropdown, contains the available options
Options []string `json:"options"`
// for element types "markdown", "textarea", "input"
// Text that is pre-filled in the input
Value string `json:"value"`
// for element types "textarea", "input", "dropdown", "checkboxes"
// A description of the text area to provide context or guidance, which is displayed in the form.
Description string `json:"description"`
// for element types "textarea", "input"
// A semi-opaque placeholder that renders in the text area when empty.
Placeholder string `json:"placeholder"`
// for element types "textarea"
// A language specifier. If set, the input is rendered as codeblock with syntax highlighting.
SyntaxHighlighting string `json:"render"`
// for element types "dropdown"
Multiple bool `json:"multiple"`
}
// IssueFormElementValidations contains the combined set of validations available on all element types.
type IssueFormElementValidations struct {
// for all element types
Required bool `json:"required"`
// for element types "input"
IsNumber bool `json:"is_number"`
// for element types "input"
Regex string `json:"regex"`
}
// IssueFormElementType is an enum
type IssueFormElementType string
const (
// IssueFormElementMarkdown is markdown rendered to the form for context, but omitted in the resulting issue
IssueFormElementMarkdown IssueFormElementType = "markdown"
// IssueFormElementTextarea is a multi line input
IssueFormElementTextarea IssueFormElementType = "textarea"
// IssueFormElementInput is a single line input
IssueFormElementInput IssueFormElementType = "input"
// IssueFormElementDropdown is a select form
IssueFormElementDropdown IssueFormElementType = "dropdown"
// IssueFormElementCheckboxes are a multi checkbox input
IssueFormElementCheckboxes IssueFormElementType = "checkboxes"
)
// GetIssueTemplates lists all issue templates of the repository
func (c *Client) GetIssueTemplates(owner, repo string) ([]*IssueTemplate, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
templates := new([]*IssueTemplate)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issue_templates", owner, repo), nil, nil, templates)
return *templates, resp, err
}
// IsForm tells if this template is a form instead of a markdown-based template.
func (t IssueTemplate) IsForm() bool {
return t.Form != nil
}

View File

@ -19,9 +19,10 @@ func TestIssue(t *testing.T) {
createIssue(t, c)
// Little sleep in order to give some time for gitea to properly store all information on database. Without this sleep, CI is a bit unstable
time.Sleep(100 * time.Millisecond)
time.Sleep(200 * time.Millisecond)
editIssues(t, c)
listIssues(t, c)
deleteIssue(t, c)
}
func createIssue(t *testing.T, c *Client) {
@ -47,9 +48,21 @@ func createIssue(t *testing.T, c *Client) {
createTestIssue(t, c, repo.Name, "", "you never know", nil, nil, mile.ID, nil, true, true)
}
func deleteIssue(t *testing.T, c *Client) {
log.Println("== TestDeleteIssues ==")
user, _, err := c.GetMyUserInfo()
assert.NoError(t, err)
repo, _ := createTestRepo(t, "IssueTestsRepo", c)
issue := createTestIssue(t, c, repo.Name, "Deleteable Issue", "", nil, nil, 0, nil, false, false)
_, err = c.DeleteIssue(user.UserName, repo.Name, issue.Index)
assert.NoError(t, err)
}
func editIssues(t *testing.T, c *Client) {
log.Println("== TestEditIssues ==")
il, _, err := c.ListIssues(ListIssueOption{KeyWord: "soon"})
il, _, err := c.ListIssues(ListIssueOption{KeyWord: "soon!"})
assert.NoError(t, err)
issue, _, err := c.GetIssue(il[0].Poster.UserName, il[0].Repository.Name, il[0].Index)
assert.NoError(t, err)
@ -59,13 +72,13 @@ func editIssues(t *testing.T, c *Client) {
Title: "Edited",
Body: OptionalString("123 test and go"),
State: &state,
Ref: OptionalString("master"),
Ref: OptionalString("main"),
})
assert.NoError(t, err)
assert.EqualValues(t, issue.ID, issueNew.ID)
assert.EqualValues(t, "123 test and go", issueNew.Body)
assert.EqualValues(t, "Edited", issueNew.Title)
assert.EqualValues(t, "master", issueNew.Ref)
assert.EqualValues(t, "main", issueNew.Ref)
}
func listIssues(t *testing.T, c *Client) {
@ -104,7 +117,7 @@ func listIssues(t *testing.T, c *Client) {
assert.Len(t, issues, 3)
}
func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assignees []string, deadline *time.Time, milestone int64, labels []int64, closed, shouldFail bool) {
func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assignees []string, deadline *time.Time, milestone int64, labels []int64, closed, shouldFail bool) *Issue {
user, _, err := c.GetMyUserInfo()
assert.NoError(t, err)
issue, _, e := c.CreateIssue(user.UserName, repoName, CreateIssueOption{
@ -118,7 +131,7 @@ func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assi
})
if shouldFail {
assert.Error(t, e)
return
return nil
}
assert.NoError(t, e)
assert.NotEmpty(t, issue)
@ -137,4 +150,5 @@ func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assi
} else {
assert.Empty(t, issue.Closed)
}
return issue
}

View File

@ -9,12 +9,13 @@ import (
"net/url"
)
const defaultPageSize = 10
const maxPageSize = 50
// ListOptions options for using Gitea's API pagination
type ListOptions struct {
Page int
// Setting Page to -1 disables pagination on endpoints that support it.
// Page numbering starts at 1.
Page int
// The default value depends on the server config DEFAULT_PAGING_NUM
// The highest valid value depends on the server config MAX_RESPONSE_ITEMS
PageSize int
}
@ -26,8 +27,9 @@ func (o ListOptions) getURLQuery() url.Values {
return query
}
// setDefaults set default pagination options if none or wrong are set
// if you set -1 as page it will set all to 0
// setDefaults applies default pagination options.
// If .Page is set to -1, it will disable pagination.
// WARNING: This function is not idempotent, make sure to never call this method twice!
func (o *ListOptions) setDefaults() {
if o.Page < 0 {
o.Page, o.PageSize = 0, 0
@ -35,8 +37,4 @@ func (o *ListOptions) setDefaults() {
} else if o.Page == 0 {
o.Page = 1
}
if o.PageSize < 0 || o.PageSize > maxPageSize {
o.PageSize = defaultPageSize
}
}

View File

@ -40,14 +40,16 @@ func enableRunGitea() bool {
}
func newTestClient() *Client {
c, _ := NewClient(getGiteaURL(), newTestClientAuth())
return c
}
func newTestClientAuth() ClientOption {
token := getGiteaToken()
if token == "" {
client := NewClientWithHTTP(getGiteaURL(), &http.Client{})
client.SetBasicAuth(getGiteaUsername(), getGiteaPassword())
return client
return SetBasicAuth(getGiteaUsername(), getGiteaPassword())
}
c, _ := NewClient(getGiteaURL(), SetToken(getGiteaToken()))
return c
return SetToken(getGiteaToken())
}
func giteaMasterPath() string {
@ -80,7 +82,7 @@ func downGitea() (string, error) {
continue
}
if err = os.Chmod(f.Name(), 700); err != nil {
if err = os.Chmod(f.Name(), 0o700); err != nil {
return "", err
}
@ -99,7 +101,10 @@ func runGitea() (*os.Process, error) {
giteaDir := filepath.Dir(p)
cfgDir := filepath.Join(giteaDir, "custom", "conf")
os.MkdirAll(cfgDir, os.ModePerm)
err = os.MkdirAll(cfgDir, os.ModePerm)
if err != nil {
log.Fatal(err)
}
cfg, err := os.Create(filepath.Join(cfgDir, "app.ini"))
if err != nil {
log.Fatal(err)
@ -148,7 +153,9 @@ func TestMain(m *testing.M) {
return
}
defer func() {
p.Kill()
if err := p.Kill(); err != nil {
log.Fatal(err)
}
}()
}
log.Printf("testing with %v, %v, %v\n", getGiteaURL(), getGiteaUsername(), getGiteaPassword())

View File

@ -8,12 +8,6 @@ import (
"fmt"
"net/url"
"time"
"github.com/hashicorp/go-version"
)
var (
version1_12_3, _ = version.NewVersion("1.12.3")
)
// NotificationThread expose Notification on API
@ -29,11 +23,13 @@ type NotificationThread struct {
// NotificationSubject contains the notification subject (Issue/Pull/Commit)
type NotificationSubject struct {
Title string `json:"title"`
URL string `json:"url"`
LatestCommentURL string `json:"latest_comment_url"`
Type NotifySubjectType `json:"type"`
State NotifySubjectState `json:"state"`
Title string `json:"title"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
LatestCommentURL string `json:"latest_comment_url"`
LatestCommentHTMLURL string `json:"latest_comment_html_url"`
Type NotifySubjectType `json:"type"`
State NotifySubjectState `json:"state"`
}
// NotifyStatus notification status type
@ -164,16 +160,22 @@ func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, erro
// ReadNotification mark notification thread as read by ID
// It optionally takes a second argument if status has to be set other than 'read'
func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*Response, error) {
// The relevant notification will be returned as the first parameter when the Gitea server is 1.16.0 or higher.
func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*NotificationThread, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, err
return nil, nil, err
}
link := fmt.Sprintf("/notifications/threads/%d", id)
if len(status) != 0 {
link += fmt.Sprintf("?to-status=%s", status[0])
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil {
thread := &NotificationThread{}
resp, err := c.getParsedResponse("PATCH", link, nil, nil, thread)
return thread, resp, err
}
_, resp, err := c.getResponse("PATCH", link, nil, nil)
return resp, err
return nil, resp, err
}
// ListNotifications list users's notification threads
@ -192,17 +194,24 @@ func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*Notification
}
// ReadNotifications mark notification threads as read
func (c *Client) ReadNotifications(opt MarkNotificationOptions) (*Response, error) {
// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher.
func (c *Client) ReadNotifications(opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, err
return nil, nil, err
}
if err := opt.Validate(c); err != nil {
return nil, err
return nil, nil, err
}
link, _ := url.Parse("/notifications")
link.RawQuery = opt.QueryEncode()
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil {
threads := make([]*NotificationThread, 0, 10)
resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads)
return threads, resp, err
}
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
return resp, err
return nil, resp, err
}
// ListRepoNotifications list users's notification threads on a specific repo
@ -224,18 +233,25 @@ func (c *Client) ListRepoNotifications(owner, repo string, opt ListNotificationO
}
// ReadRepoNotifications mark notification threads as read on a specific repo
func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) (*Response, error) {
// The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher.
func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, err
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
return nil, err
return nil, nil, err
}
if err := opt.Validate(c); err != nil {
return nil, err
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo))
link.RawQuery = opt.QueryEncode()
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil {
threads := make([]*NotificationThread, 0, 10)
resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads)
return threads, resp, err
}
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
return resp, err
return nil, resp, err
}

View File

@ -23,7 +23,7 @@ func TestNotifications(t *testing.T) {
assert.NoError(t, err)
user2 := createTestUser(t, "notify2", c)
//create 2 repos
// create 2 repos
repoA, err := createTestRepo(t, "TestNotifications_A", c)
assert.NoError(t, err)
@ -35,8 +35,9 @@ func TestNotifications(t *testing.T) {
assert.NoError(t, err)
c.sudo = user2.UserName
_, err = c.ReadNotifications(MarkNotificationOptions{})
notifications, _, err := c.ReadNotifications(MarkNotificationOptions{})
assert.NoError(t, err)
assert.Len(t, notifications, 0)
count, _, err := c.CheckNotifications()
assert.EqualValues(t, 0, count)
assert.NoError(t, err)
@ -45,7 +46,7 @@ func TestNotifications(t *testing.T) {
assert.NoError(t, err)
issue, _, err := c.CreateIssue(repoB.Owner.UserName, repoB.Name, CreateIssueOption{Title: "B Issue", Closed: false})
assert.NoError(t, err)
time.Sleep(time.Second * 1)
time.Sleep(time.Second * 5)
// CheckNotifications of user2
c.sudo = user2.UserName
@ -77,8 +78,9 @@ func TestNotifications(t *testing.T) {
assert.Len(t, nList, 1)
assert.EqualValues(t, "A Issue", nList[0].Subject.Title)
// ReadRepoNotifications
_, err = c.ReadRepoNotifications(repoA.Owner.UserName, repoA.Name, MarkNotificationOptions{})
notifications, _, err = c.ReadRepoNotifications(repoA.Owner.UserName, repoA.Name, MarkNotificationOptions{})
assert.NoError(t, err)
assert.Len(t, notifications, 1)
// GetThread
n, _, err := c.GetNotification(nList[0].ID)
@ -87,8 +89,9 @@ func TestNotifications(t *testing.T) {
assert.EqualValues(t, "A Issue", n.Subject.Title)
// ReadNotifications
_, err = c.ReadNotifications(MarkNotificationOptions{})
notifications, _, err = c.ReadNotifications(MarkNotificationOptions{})
assert.NoError(t, err)
assert.Len(t, notifications, 1)
nList, _, err = c.ListNotifications(ListNotificationOptions{})
assert.NoError(t, err)
assert.Len(t, nList, 0)
@ -98,7 +101,7 @@ func TestNotifications(t *testing.T) {
c.sudo = ""
_, _, err = c.EditIssue(repoB.Owner.UserName, repoB.Name, issue.Index, EditIssueOption{State: &iState})
assert.NoError(t, err)
time.Sleep(time.Second * 1)
time.Sleep(time.Second * 5)
c.sudo = user2.UserName
nList, _, err = c.ListNotifications(ListNotificationOptions{})
@ -108,26 +111,30 @@ func TestNotifications(t *testing.T) {
assert.EqualValues(t, 1, count)
if assert.Len(t, nList, 1) {
assert.EqualValues(t, NotifySubjectClosed, nList[0].Subject.State)
_, err = c.ReadNotification(nList[0].ID)
notification, _, err := c.ReadNotification(nList[0].ID)
assert.NoError(t, err)
assert.EqualValues(t, notification.ID, nList[0].ID)
}
c.sudo = ""
_, err = c.ReadNotifications(MarkNotificationOptions{})
notifications, _, err = c.ReadNotifications(MarkNotificationOptions{})
assert.NoError(t, err)
_, _ = c.DeleteRepo("test01", "Reviews")
assert.Len(t, notifications, 2)
nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusRead}})
assert.NoError(t, err)
assert.Len(t, nList, 4)
if assert.Len(t, nList, 2) {
notification, _, err := c.ReadNotification(nList[0].ID, NotifyStatusPinned)
assert.EqualValues(t, notification.ID, nList[0].ID)
assert.NoError(t, err)
_, err = c.ReadNotification(nList[2].ID, NotifyStatusPinned)
assert.NoError(t, err)
_, err = c.ReadNotification(nList[3].ID, NotifyStatusUnread)
assert.NoError(t, err)
notification, _, err = c.ReadNotification(nList[1].ID, NotifyStatusUnread)
assert.EqualValues(t, notification.ID, nList[1].ID)
assert.NoError(t, err)
}
nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusPinned, NotifyStatusUnread}})
assert.NoError(t, err)
if assert.Len(t, nList, 2) {
assert.EqualValues(t, NotifySubjectClosed, nList[0].Subject.State)
assert.EqualValues(t, NotifySubjectClosed, nList[1].Subject.State)
assert.EqualValues(t, NotifySubjectOpen, nList[0].Subject.State)
assert.EqualValues(t, NotifySubjectOpen, nList[1].Subject.State)
}
}

View File

@ -13,12 +13,13 @@ import (
// Oauth2 represents an Oauth2 Application
type Oauth2 struct {
ID int64 `json:"id"`
Name string `json:"name"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectURIs []string `json:"redirect_uris"`
Created time.Time `json:"created"`
ID int64 `json:"id"`
Name string `json:"name"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectURIs []string `json:"redirect_uris"`
ConfidentialClient bool `json:"confidential_client"`
Created time.Time `json:"created"`
}
// ListOauth2Option for listing Oauth2 Applications
@ -28,8 +29,9 @@ type ListOauth2Option struct {
// CreateOauth2Option required options for creating an Application
type CreateOauth2Option struct {
Name string `json:"name"`
RedirectURIs []string `json:"redirect_uris"`
Name string `json:"name"`
ConfidentialClient bool `json:"confidential_client"`
RedirectURIs []string `json:"redirect_uris"`
}
// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object.

View File

@ -18,27 +18,62 @@ func TestOauth2(t *testing.T) {
user := createTestUser(t, "oauth2_user", c)
c.SetSudo(user.UserName)
newApp, _, err := c.CreateOauth2(CreateOauth2Option{Name: "test", RedirectURIs: []string{"http://test/test"}})
assert.NoError(t, err)
assert.NotNil(t, newApp)
assert.EqualValues(t, "test", newApp.Name)
type test struct {
name string
confidentialClient *bool
}
boolTrue := true
boolFalse := false
a, _, err := c.ListOauth2(ListOauth2Option{})
assert.NoError(t, err)
assert.Len(t, a, 1)
assert.EqualValues(t, newApp.Name, a[0].Name)
testCases := []test{
{"ConfidentialClient unset should fallback to false", nil},
{"ConfidentialClient true", &boolTrue},
{"ConfidentialClient false", &boolFalse},
}
b, _, err := c.GetOauth2(newApp.ID)
assert.NoError(t, err)
assert.EqualValues(t, newApp.Name, b.Name)
for _, testCase := range testCases {
createOptions := CreateOauth2Option{
Name: "test",
RedirectURIs: []string{"http://test/test"},
}
if testCase.confidentialClient != nil {
createOptions.ConfidentialClient = *testCase.confidentialClient
}
b, _, err = c.UpdateOauth2(newApp.ID, CreateOauth2Option{Name: newApp.Name, RedirectURIs: []string{"https://test/login"}})
assert.NoError(t, err)
assert.EqualValues(t, newApp.Name, b.Name)
assert.EqualValues(t, "https://test/login", b.RedirectURIs[0])
assert.EqualValues(t, newApp.ID, b.ID)
assert.NotEqual(t, newApp.ClientSecret, b.ClientSecret)
newApp, _, err := c.CreateOauth2(createOptions)
assert.NoError(t, err, testCase.name)
assert.NotNil(t, newApp, testCase.name)
assert.EqualValues(t, "test", newApp.Name, testCase.name)
if testCase.confidentialClient != nil {
assert.EqualValues(t, *testCase.confidentialClient, newApp.ConfidentialClient, testCase.name)
} else {
assert.EqualValues(t, false, newApp.ConfidentialClient, testCase.name)
}
_, err = c.DeleteOauth2(newApp.ID)
assert.NoError(t, err)
a, _, err := c.ListOauth2(ListOauth2Option{})
assert.NoError(t, err, testCase.name)
assert.Len(t, a, 1, testCase.name)
assert.EqualValues(t, newApp.Name, a[0].Name, testCase.name)
assert.EqualValues(t, newApp.ConfidentialClient, a[0].ConfidentialClient, testCase.name)
b, _, err := c.GetOauth2(newApp.ID)
assert.NoError(t, err, testCase.name)
assert.EqualValues(t, newApp.Name, b.Name, testCase.name)
assert.EqualValues(t, newApp.ConfidentialClient, b.ConfidentialClient, testCase.name)
b, _, err = c.UpdateOauth2(newApp.ID, CreateOauth2Option{
Name: newApp.Name,
ConfidentialClient: !newApp.ConfidentialClient,
RedirectURIs: []string{"https://test/login"},
})
assert.NoError(t, err, testCase.name)
assert.EqualValues(t, newApp.Name, b.Name, testCase.name)
assert.EqualValues(t, "https://test/login", b.RedirectURIs[0], testCase.name)
assert.EqualValues(t, newApp.ID, b.ID, testCase.name)
assert.NotEqual(t, newApp.ClientSecret, b.ClientSecret, testCase.name)
assert.NotEqual(t, newApp.ConfidentialClient, b.ConfidentialClient, testCase.name)
_, err = c.DeleteOauth2(newApp.ID)
assert.NoError(t, err, testCase.name)
}
}

View File

@ -93,7 +93,7 @@ func (opt CreateOrgOption) Validate() error {
return fmt.Errorf("empty org name")
}
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
return fmt.Errorf("infalid bisibility option")
return fmt.Errorf("invalid visibility option")
}
return nil
}
@ -124,7 +124,7 @@ type EditOrgOption struct {
// Validate the EditOrgOption struct
func (opt EditOrgOption) Validate() error {
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
return fmt.Errorf("infalid bisibility option")
return fmt.Errorf("invalid visibility option")
}
return nil
}

87
gitea/org_action.go Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
// ListOrgActionSecretOption list OrgActionSecret options
type ListOrgActionSecretOption struct {
ListOptions
}
// ListOrgActionSecret list an organization's secrets
func (c *Client) ListOrgActionSecret(org string, opt ListOrgActionSecretOption) ([]*Secret, *Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
opt.setDefaults()
secrets := make([]*Secret, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/actions/secrets", org))
link.RawQuery = opt.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets)
return secrets, resp, err
}
// CreateSecretOption represents the options for creating a secret.
type CreateSecretOption struct {
Name string `json:"name"` // Name is the name of the secret.
Data string `json:"data"` // Data is the data of the secret.
}
// Validate checks if the CreateSecretOption is valid.
// It returns an error if any of the validation checks fail.
func (opt *CreateSecretOption) Validate() error {
if len(opt.Name) == 0 {
return fmt.Errorf("name required")
}
if len(opt.Name) > 30 {
return fmt.Errorf("name to long")
}
if len(opt.Data) == 0 {
return fmt.Errorf("data required")
}
return nil
}
// CreateOrgActionSecret creates a secret for the specified organization in the Gitea Actions.
// It takes the organization name and the secret options as parameters.
// The function returns the HTTP response and an error, if any.
func (c *Client) CreateOrgActionSecret(org string, opt CreateSecretOption) (*Response, error) {
if err := escapeValidatePathSegments(&org); err != nil {
return nil, err
}
if err := (&opt).Validate(); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/actions/secrets/%s", org, opt.Name), jsonHeader, bytes.NewReader(body))
if err != nil {
return nil, err
}
switch status {
case http.StatusCreated:
return resp, nil
case http.StatusNoContent:
return resp, nil
case http.StatusNotFound:
return resp, fmt.Errorf("forbidden")
case http.StatusBadRequest:
return resp, fmt.Errorf("bad request")
default:
return resp, fmt.Errorf("unexpected Status: %d", status)
}
}

39
gitea/org_action_test.go Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"log"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateOrgActionSecret(t *testing.T) {
log.Println("== TestCreateOrgActionSecret ==")
c := newTestClient()
user := createTestUser(t, "org_action_user", c)
c.SetSudo(user.UserName)
newOrg, _, err := c.CreateOrg(CreateOrgOption{Name: "ActionOrg"})
assert.NoError(t, err)
assert.NotNil(t, newOrg)
// create secret
resp, err := c.CreateOrgActionSecret(newOrg.UserName, CreateSecretOption{Name: "test", Data: "test"})
assert.NoError(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
// update secret
resp, err = c.CreateOrgActionSecret(newOrg.UserName, CreateSecretOption{Name: "test", Data: "test2"})
assert.NoError(t, err)
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
// list secrets
secrets, _, err := c.ListOrgActionSecret(newOrg.UserName, ListOrgActionSecretOption{})
assert.NoError(t, err)
assert.Len(t, secrets, 1)
}

View File

@ -117,3 +117,26 @@ func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Respon
return resp, fmt.Errorf("unexpected Status: %d", status)
}
}
// OrgPermissions represents the permissions for an user in an organization
type OrgPermissions struct {
CanCreateRepository bool `json:"can_create_repository"`
CanRead bool `json:"can_read"`
CanWrite bool `json:"can_write"`
IsAdmin bool `json:"is_admin"`
IsOwner bool `json:"is_owner"`
}
// GetOrgPermissions returns user permissions for specific organization.
func (c *Client) GetOrgPermissions(org, user string) (*OrgPermissions, *Response, error) {
if err := escapeValidatePathSegments(&org, &user); err != nil {
return nil, nil, err
}
perm := &OrgPermissions{}
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs/%s/permissions", user, org), jsonHeader, nil, &perm)
if err != nil {
return nil, resp, err
}
return perm, resp, nil
}

View File

@ -11,11 +11,9 @@ import (
"github.com/stretchr/testify/assert"
)
/**
/*
// DeleteOrgMembership remove a member from an organization
func (c *Client) DeleteOrgMembership(org, user string) error {}
*/
func TestOrgMembership(t *testing.T) {
log.Println("== TestOrgMembership ==")
@ -35,6 +33,11 @@ func TestOrgMembership(t *testing.T) {
assert.NoError(t, err)
assert.True(t, check)
perm, _, err := c.GetOrgPermissions(newOrg.UserName, user.UserName)
assert.NoError(t, err)
assert.NotNil(t, perm)
assert.True(t, perm.IsOwner)
_, err = c.SetPublicOrgMembership(newOrg.UserName, user.UserName, true)
assert.NoError(t, err)
check, _, err = c.CheckPublicOrgMembership(newOrg.UserName, user.UserName)

View File

@ -8,6 +8,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"net/url"
)
// Team represents a team in an organization
@ -42,6 +43,10 @@ const (
RepoUnitReleases RepoUnitType = "repo.releases"
// RepoUnitProjects represent projects of a repository
RepoUnitProjects RepoUnitType = "repo.projects"
// RepoUnitPackages represents packages of a repository
RepoUnitPackages RepoUnitType = "repo.packages"
// RepoUnitActions represents actions of a repository
RepoUnitActions RepoUnitType = "repo.actions"
)
// ListTeamsOptions options for listing teams
@ -75,6 +80,44 @@ func (c *Client) GetTeam(id int64) (*Team, *Response, error) {
return t, resp, err
}
// SearchTeamsOptions options for searching teams.
type SearchTeamsOptions struct {
ListOptions
Query string
IncludeDescription bool
}
func (o SearchTeamsOptions) getURLQuery() url.Values {
query := make(url.Values)
query.Add("page", fmt.Sprintf("%d", o.Page))
query.Add("limit", fmt.Sprintf("%d", o.PageSize))
query.Add("q", o.Query)
query.Add("include_desc", fmt.Sprintf("%t", o.IncludeDescription))
return query
}
// TeamSearchResults is the JSON struct that is returned from Team search API.
type TeamSearchResults struct {
OK bool `json:"ok"`
Error string `json:"error"`
Data []*Team `json:"data"`
}
// SearchOrgTeams search for teams in a org.
func (c *Client) SearchOrgTeams(org string, opt *SearchTeamsOptions) ([]*Team, *Response, error) {
responseBody := TeamSearchResults{}
opt.setDefaults()
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams/search?%s", org, opt.getURLQuery().Encode()), nil, nil, &responseBody)
if err != nil {
return nil, resp, err
}
if !responseBody.OK {
return nil, resp, fmt.Errorf("gitea error: %v", responseBody.Error)
}
return responseBody.Data, resp, err
}
// CreateTeamOption options for creating a team
type CreateTeamOption struct {
Name string `json:"name"`
@ -86,7 +129,7 @@ type CreateTeamOption struct {
}
// Validate the CreateTeamOption struct
func (opt CreateTeamOption) Validate() error {
func (opt *CreateTeamOption) Validate() error {
if opt.Permission == AccessModeOwner {
opt.Permission = AccessModeAdmin
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
@ -109,7 +152,7 @@ func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response,
if err := escapeValidatePathSegments(&org); err != nil {
return nil, nil, err
}
if err := opt.Validate(); err != nil {
if err := (&opt).Validate(); err != nil {
return nil, nil, err
}
body, err := json.Marshal(&opt)
@ -132,7 +175,7 @@ type EditTeamOption struct {
}
// Validate the EditTeamOption struct
func (opt EditTeamOption) Validate() error {
func (opt *EditTeamOption) Validate() error {
if opt.Permission == AccessModeOwner {
opt.Permission = AccessModeAdmin
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
@ -152,7 +195,7 @@ func (opt EditTeamOption) Validate() error {
// EditTeam edits a team of an organization
func (c *Client) EditTeam(id int64, opt EditTeamOption) (*Response, error) {
if err := opt.Validate(); err != nil {
if err := (&opt).Validate(); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)

View File

@ -5,6 +5,7 @@
package gitea
import (
"log"
"testing"
"github.com/stretchr/testify/assert"
@ -23,3 +24,33 @@ func createTestOrgTeams(t *testing.T, c *Client, org, name string, accessMode Ac
assert.NotNil(t, team)
return team, e
}
func TestTeamSearch(t *testing.T) {
log.Println("== TestTeamSearch ==")
c := newTestClient()
orgName := "TestTeamsOrg"
// prepare for test
_, _, err := c.CreateOrg(CreateOrgOption{
Name: orgName,
Visibility: VisibleTypePublic,
RepoAdminChangeTeamAccess: true,
})
defer func() {
_, _ = c.DeleteOrg(orgName)
}()
assert.NoError(t, err)
if _, err = createTestOrgTeams(t, c, orgName, "Admins", AccessModeAdmin, []RepoUnitType{RepoUnitCode, RepoUnitIssues, RepoUnitPulls, RepoUnitReleases}); err != nil {
return
}
teams, _, err := c.SearchOrgTeams(orgName, &SearchTeamsOptions{
Query: "Admins",
})
assert.NoError(t, err)
if assert.Len(t, teams, 1) {
assert.Equal(t, "Admins", teams[0].Name)
}
}

93
gitea/package.go Normal file
View File

@ -0,0 +1,93 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"fmt"
"time"
)
// Package represents a package
type Package struct {
// the package's id
ID int64 `json:"id"`
// the package's owner
Owner User `json:"owner"`
// the repo this package belongs to (if any)
Repository *Repository `json:"repository"`
// the package's creator
Creator User `json:"creator"`
// the type of package:
Type string `json:"type"`
// the name of the package
Name string `json:"name"`
// the version of the package
Version string `json:"version"`
// the date the package was uploaded
CreatedAt time.Time `json:"created_at"`
}
// PackageFile represents a file from a package
type PackageFile struct {
// the file's ID
ID int64 `json:"id"`
// the size of the file in bytes
Size int64 `json:"size"`
// the name of the file
Name string `json:"name"`
// the md5 hash of the file
MD5 string `json:"md5"`
// the sha1 hash of the file
SHA1 string `json:"sha1"`
// the sha256 hash of the file
SHA256 string `json:"sha256"`
// the sha512 hash of the file
SHA512 string `json:"sha512"`
}
// ListPackagesOptions options for listing packages
type ListPackagesOptions struct {
ListOptions
}
// ListPackages lists all the packages owned by a given owner (user, organisation)
func (c *Client) ListPackages(owner string, opt ListPackagesOptions) ([]*Package, *Response, error) {
if err := escapeValidatePathSegments(&owner); err != nil {
return nil, nil, err
}
opt.setDefaults()
packages := make([]*Package, 0, opt.PageSize)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s?%s", owner, opt.getURLQuery().Encode()), nil, nil, &packages)
return packages, resp, err
}
// GetPackage gets the details of a specific package version
func (c *Client) GetPackage(owner, packageType, name, version string) (*Package, *Response, error) {
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
return nil, nil, err
}
foundPackage := new(Package)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil, foundPackage)
return foundPackage, resp, err
}
// DeletePackage deletes a specific package version
func (c *Client) DeletePackage(owner, packageType, name, version string) (*Response, error) {
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
return nil, err
}
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil)
return resp, err
}
// ListPackageFiles lists the files within a package
func (c *Client) ListPackageFiles(owner, packageType, name, version string) ([]*PackageFile, *Response, error) {
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
return nil, nil, err
}
packageFiles := make([]*PackageFile, 0)
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s/files", owner, packageType, name, version), nil, nil, &packageFiles)
return packageFiles, resp, err
}

108
gitea/package_test.go Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"bytes"
"fmt"
"log"
"net/http"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// create an org with a single package for testing purposes
func createTestPackage(t *testing.T, c *Client) error {
_, _ = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1")
_, _ = c.DeleteOrg("PackageOrg")
_, _, _ = c.CreateOrg(CreateOrgOption{Name: "PackageOrg"})
client := &http.Client{
Timeout: time.Second * 10,
}
reader := bytes.NewReader([]byte("Hello world!"))
url := fmt.Sprintf("%s/api/packages/PackageOrg/generic/MyPackage/v1/file1.txt", os.Getenv("GITEA_SDK_TEST_URL"))
req, err := http.NewRequest(http.MethodPut, url, reader)
if err != nil {
log.Println(err)
return err
}
req.SetBasicAuth(os.Getenv("GITEA_SDK_TEST_USERNAME"), os.Getenv("GITEA_SDK_TEST_PASSWORD"))
response, err := client.Do(req)
if err != nil {
return err
}
defer response.Body.Close()
return nil
}
func TestListPackages(t *testing.T) {
log.Println("== TestListPackages ==")
c := newTestClient()
err := createTestPackage(t, c)
assert.NoError(t, err)
packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{
ListOptions{
Page: 1,
PageSize: 1000,
},
})
assert.NoError(t, err)
assert.Len(t, packagesList, 1)
}
func TestGetPackage(t *testing.T) {
log.Println("== TestGetPackage ==")
c := newTestClient()
err := createTestPackage(t, c)
assert.NoError(t, err)
pkg, _, err := c.GetPackage("PackageOrg", "generic", "MyPackage", "v1")
assert.NoError(t, err)
assert.NotNil(t, pkg)
assert.True(t, pkg.Name == "MyPackage")
assert.True(t, pkg.Version == "v1")
assert.NotEmpty(t, pkg.CreatedAt)
}
func TestDeletePackage(t *testing.T) {
log.Println("== TestDeletePackage ==")
c := newTestClient()
err := createTestPackage(t, c)
assert.NoError(t, err)
_, err = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1")
assert.NoError(t, err)
// no packages should be listed following deletion
packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{
ListOptions{
Page: 1,
PageSize: 1000,
},
})
assert.NoError(t, err)
assert.Len(t, packagesList, 0)
}
func TestListPackageFiles(t *testing.T) {
log.Println("== TestListPackageFiles ==")
c := newTestClient()
err := createTestPackage(t, c)
assert.NoError(t, err)
packageFiles, _, err := c.ListPackageFiles("PackageOrg", "generic", "MyPackage", "v1")
assert.NoError(t, err)
assert.Len(t, packageFiles, 1)
assert.True(t, packageFiles[0].Name == "file1.txt")
}

View File

@ -43,11 +43,12 @@ type PullRequest struct {
DiffURL string `json:"diff_url"`
PatchURL string `json:"patch_url"`
Mergeable bool `json:"mergeable"`
HasMerged bool `json:"merged"`
Merged *time.Time `json:"merged_at"`
MergedCommitID *string `json:"merge_commit_sha"`
MergedBy *User `json:"merged_by"`
Mergeable bool `json:"mergeable"`
HasMerged bool `json:"merged"`
Merged *time.Time `json:"merged_at"`
MergedCommitID *string `json:"merge_commit_sha"`
MergedBy *User `json:"merged_by"`
AllowMaintainerEdit bool `json:"allow_maintainer_edit"`
Base *PRBranchInfo `json:"base"`
Head *PRBranchInfo `json:"head"`
@ -59,6 +60,19 @@ type PullRequest struct {
Closed *time.Time `json:"closed_at"`
}
// ChangedFile is a changed file in a diff
type ChangedFile struct {
Filename string `json:"filename"`
PreviousFilename string `json:"previous_filename"`
Status string `json:"status"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
Changes int `json:"changes"`
HTMLURL string `json:"html_url"`
ContentsURL string `json:"contents_url"`
RawURL string `json:"raw_url"`
}
// ListPullRequestsOptions options for listing pull requests
type ListPullRequestsOptions struct {
ListOptions
@ -164,15 +178,17 @@ func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOpti
// EditPullRequestOption options when modify pull request
type EditPullRequestOption struct {
Title string `json:"title"`
Body string `json:"body"`
Base string `json:"base"`
Assignee string `json:"assignee"`
Assignees []string `json:"assignees"`
Milestone int64 `json:"milestone"`
Labels []int64 `json:"labels"`
State *StateType `json:"state"`
Deadline *time.Time `json:"due_date"`
Title string `json:"title"`
Body string `json:"body"`
Base string `json:"base"`
Assignee string `json:"assignee"`
Assignees []string `json:"assignees"`
Milestone int64 `json:"milestone"`
Labels []int64 `json:"labels"`
State *StateType `json:"state"`
Deadline *time.Time `json:"due_date"`
RemoveDeadline *bool `json:"unset_due_date"`
AllowMaintainerEdit *bool `json:"allow_maintainer_edit"`
}
// Validate the EditPullRequestOption struct
@ -209,15 +225,20 @@ func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRe
// MergePullRequestOption options when merging a pull request
type MergePullRequestOption struct {
Style MergeStyle `json:"Do"`
Title string `json:"MergeTitleField"`
Message string `json:"MergeMessageField"`
Style MergeStyle `json:"Do"`
MergeCommitID string `json:"MergeCommitID"`
Title string `json:"MergeTitleField"`
Message string `json:"MergeMessageField"`
DeleteBranchAfterMerge bool `json:"delete_branch_after_merge"`
ForceMerge bool `json:"force_merge"`
HeadCommitId string `json:"head_commit_id"`
MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed"`
}
// Validate the MergePullRequestOption struct
func (opt MergePullRequestOption) Validate(c *Client) error {
if opt.Style == MergeStyleSquash {
if err := c.CheckServerVersionConstraint(">=1.11.5"); err != nil {
if err := c.checkServerVersionGreaterThanOrEqual(version1_11_5); err != nil {
return err
}
}
@ -249,7 +270,6 @@ func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Re
return false, nil, err
}
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil)
if err != nil {
return false, resp, err
}
@ -257,9 +277,29 @@ func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Re
return status == 204, resp, nil
}
// PullRequestDiffOptions options for GET /repos/<owner>/<repo>/pulls/<idx>.[diff|patch]
type PullRequestDiffOptions struct {
// Include binary file changes when requesting a .diff
Binary bool
}
// QueryEncode converts the options to a query string
func (o PullRequestDiffOptions) QueryEncode() string {
query := make(url.Values)
query.Add("binary", fmt.Sprintf("%v", o.Binary))
return query.Encode()
}
type pullRequestDiffType string
const (
pullRequestDiffTypeDiff pullRequestDiffType = "diff"
pullRequestDiffTypePatch pullRequestDiffType = "patch"
)
// getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR
func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64) ([]byte, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo, &kind); err != nil {
func (c *Client) getPullRequestDiffOrPatch(owner, repo string, kind pullRequestDiffType, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
@ -270,19 +310,20 @@ func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64
if r.Private {
return nil, nil, err
}
return c.getWebResponse("GET", fmt.Sprintf("/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil)
url := fmt.Sprintf("/%s/%s/pulls/%d.%s?%s", owner, repo, index, kind, opts.QueryEncode())
return c.getWebResponse("GET", url, nil)
}
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil)
}
// GetPullRequestPatch gets the .patch file as bytes for a PR
// GetPullRequestPatch gets the git patchset of a PR
func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) {
return c.getPullRequestDiffOrPatch(owner, repo, "patch", index)
return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypePatch, index, PullRequestDiffOptions{})
}
// GetPullRequestDiff gets the .diff file as bytes for a PR
func (c *Client) GetPullRequestDiff(owner, repo string, index int64) ([]byte, *Response, error) {
return c.getPullRequestDiffOrPatch(owner, repo, "diff", index)
// GetPullRequestDiff gets the diff of a PR. For Gitea >= 1.16, you must set includeBinary to get an applicable diff
func (c *Client) GetPullRequestDiff(owner, repo string, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) {
return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypeDiff, index, opts)
}
// ListPullRequestCommitsOptions options for listing pull requests
@ -322,3 +363,21 @@ func fixPullHeadSha(client *Client, pr *PullRequest) error {
}
return nil
}
// ListPullRequestFilesOptions options for listing pull request files
type ListPullRequestFilesOptions struct {
ListOptions
}
// ListPullRequestFiles list changed files for a pull request
func (c *Client) ListPullRequestFiles(owner, repo string, index int64, opt ListPullRequestFilesOptions) ([]*ChangedFile, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/files", owner, repo, index))
opt.setDefaults()
files := make([]*ChangedFile, 0, opt.PageSize)
link.RawQuery = opt.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &files)
return files, resp, err
}

View File

@ -116,7 +116,7 @@ type ListPullReviewsOptions struct {
// Validate the CreatePullReviewOptions struct
func (opt CreatePullReviewOptions) Validate() error {
if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 {
if opt.State != ReviewStateApproved && len(opt.Comments) == 0 && len(strings.TrimSpace(opt.Body)) == 0 {
return fmt.Errorf("body is empty")
}
for i := range opt.Comments {

View File

@ -15,7 +15,7 @@ func TestPullReview(t *testing.T) {
log.Println("== TestPullReview ==")
c := newTestClient()
var repoName = "Reviews"
repoName := "Reviews"
repo, pull, submitter, reviewer, success := preparePullReviewTest(t, c, repoName)
if !success {
return
@ -33,12 +33,12 @@ func TestPullReview(t *testing.T) {
}
c.SetSudo(submitter.UserName)
r2, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
_, _, err = c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
State: ReviewStateApproved,
Body: "lgtm it myself",
})
assert.Error(t, err)
r2, _, err = c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
r2, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
State: ReviewStateComment,
Body: "no seriously please have a look at it",
})
@ -49,11 +49,12 @@ func TestPullReview(t *testing.T) {
r3, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
State: ReviewStateApproved,
Body: "lgtm",
Comments: []CreatePullReviewComment{{
Path: "WOW-file",
Body: "no better name - really?",
NewLineNum: 1,
},
Comments: []CreatePullReviewComment{
{
Path: "WOW-file",
Body: "no better name - really?",
NewLineNum: 1,
},
},
})
assert.NoError(t, err)
@ -87,22 +88,24 @@ func TestPullReview(t *testing.T) {
c.SetSudo("")
r4, resp, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
Body: "...",
Comments: []CreatePullReviewComment{{
Path: "WOW-file",
Body: "its ok",
NewLineNum: 1,
},
Comments: []CreatePullReviewComment{
{
Path: "WOW-file",
Body: "its ok",
NewLineNum: 1,
},
},
})
assert.NoError(t, err)
assert.NotNil(t, resp)
r5, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{
Body: "...",
Comments: []CreatePullReviewComment{{
Path: "WOW-file",
Body: "hehe and here it is",
NewLineNum: 3,
},
Comments: []CreatePullReviewComment{
{
Path: "WOW-file",
Body: "hehe and here it is",
NewLineNum: 3,
},
},
})
assert.NoError(t, err)
@ -200,7 +203,7 @@ func preparePullReviewTest(t *testing.T, c *Client, repoName string) (*Repositor
Content: "QSBuZXcgRmlsZQoKYW5kIHNvbWUgbGluZXMK",
FileOptions: FileOptions{
Message: "creat a new file",
BranchName: "master",
BranchName: "main",
NewBranchName: "new_file",
},
})
@ -210,7 +213,7 @@ func preparePullReviewTest(t *testing.T, c *Client, repoName string) (*Repositor
}
pull, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
Base: "master",
Base: "main",
Head: "new_file",
Title: "Creat a NewFile",
})

View File

@ -17,8 +17,8 @@ func TestPull(t *testing.T) {
user, _, err := c.GetMyUserInfo()
assert.NoError(t, err)
var repoName = "repo_pull_test"
var forkOrg = "ForkOrg"
repoName := "repo_pull_test"
forkOrg := "ForkOrg"
if !preparePullTest(t, c, repoName, forkOrg) {
return
}
@ -29,7 +29,7 @@ func TestPull(t *testing.T) {
assert.Len(t, pulls, 0)
pullUpdateFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
Base: "master",
Base: "main",
Head: forkOrg + ":overwrite_licence",
Title: "overwrite a file",
})
@ -37,7 +37,7 @@ func TestPull(t *testing.T) {
assert.NotNil(t, pullUpdateFile)
pullNewFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
Base: "master",
Base: "main",
Head: forkOrg + ":new_file",
Title: "create a file",
})
@ -45,7 +45,7 @@ func TestPull(t *testing.T) {
assert.NotNil(t, pullNewFile)
pullConflict, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
Base: "master",
Base: "main",
Head: forkOrg + ":will_conflict",
Title: "this pull will conflict",
})
@ -56,7 +56,9 @@ func TestPull(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, pulls, 3)
diff, _, err := c.GetPullRequestDiff(c.username, repoName, pullUpdateFile.Index)
diff, _, err := c.GetPullRequestDiff(c.username, repoName, pullUpdateFile.Index, PullRequestDiffOptions{
Binary: true,
})
assert.NoError(t, err)
assert.True(t, len(diff) > 1100 && len(diff) < 1300)
patch, _, err := c.GetPullRequestPatch(c.username, repoName, pullUpdateFile.Index)
@ -69,9 +71,20 @@ func TestPull(t *testing.T) {
assert.EqualValues(t, "LICENSE", commits[0].Files[0].Filename)
}
files, _, err := c.ListPullRequestFiles(c.username, repoName, pullUpdateFile.Index, ListPullRequestFilesOptions{})
assert.NoError(t, err)
assert.Len(t, files, 1)
file := files[0]
assert.EqualValues(t, "LICENSE", file.Filename)
assert.EqualValues(t, "changed", file.Status)
assert.EqualValues(t, 3, file.Additions)
assert.EqualValues(t, 9, file.Deletions)
assert.EqualValues(t, 12, file.Changes)
// test Update pull
pr, _, err := c.GetPullRequest(user.UserName, repoName, pullUpdateFile.Index)
assert.NoError(t, err)
assert.NotNil(t, pr)
assert.False(t, pullUpdateFile.HasMerged)
assert.True(t, pullUpdateFile.Mergeable)
merged, _, err := c.MergePullRequest(user.UserName, repoName, pullUpdateFile.Index, MergePullRequestOption{
@ -95,8 +108,8 @@ func TestPull(t *testing.T) {
// test conflict pull
pr, _, err = c.GetPullRequest(user.UserName, repoName, pullConflict.Index)
assert.NoError(t, err)
assert.False(t, pullConflict.HasMerged)
assert.False(t, pullConflict.Mergeable)
assert.False(t, pr.HasMerged)
assert.False(t, pr.Mergeable)
merged, _, err = c.MergePullRequest(user.UserName, repoName, pullConflict.Index, MergePullRequestOption{
Style: MergeStyleMerge,
Title: "pullConflict",
@ -143,18 +156,18 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
assert.NoError(t, err)
assert.NotNil(t, forkRepo)
masterLicence, _, err := c.GetContents(forkRepo.Owner.UserName, forkRepo.Name, "master", "LICENSE")
if !assert.NoError(t, err) || !assert.NotNil(t, masterLicence) {
mainLicense, _, err := c.GetContents(forkRepo.Owner.UserName, forkRepo.Name, "main", "LICENSE")
if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) {
return false
}
updatedFile, _, err := c.UpdateFile(forkRepo.Owner.UserName, forkRepo.Name, "LICENSE", UpdateFileOptions{
FileOptions: FileOptions{
Message: "Overwrite",
BranchName: "master",
BranchName: "main",
NewBranchName: "overwrite_licence",
},
SHA: masterLicence.SHA,
SHA: mainLicense.SHA,
Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
})
if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) {
@ -165,7 +178,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
Content: "QSBuZXcgRmlsZQo=",
FileOptions: FileOptions{
Message: "creat a new file",
BranchName: "master",
BranchName: "main",
NewBranchName: "new_file",
},
})
@ -177,7 +190,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
Content: "U3RhcnQgQ29uZmxpY3QK",
FileOptions: FileOptions{
Message: "Start Conflict",
BranchName: "master",
BranchName: "main",
},
})
if !assert.NoError(t, err) || !assert.NotNil(t, conflictFile1) {
@ -188,7 +201,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
Content: "V2lsbEhhdmUgQ29uZmxpY3QK",
FileOptions: FileOptions{
Message: "creat a new file witch will conflict",
BranchName: "master",
BranchName: "main",
NewBranchName: "will_conflict",
},
})

View File

@ -47,7 +47,7 @@ func (opt *ListReleasesOptions) QueryEncode() string {
query.Add("draft", fmt.Sprintf("%t", *opt.IsDraft))
}
if opt.IsPreRelease != nil {
query.Add("draft", fmt.Sprintf("%t", *opt.IsPreRelease))
query.Add("pre-release", fmt.Sprintf("%t", *opt.IsPreRelease))
}
return query.Encode()
@ -78,8 +78,20 @@ func (c *Client) GetRelease(owner, repo string, id int64) (*Release, *Response,
return r, resp, err
}
// GetLatestRelease get the latest release of a repository
func (c *Client) GetLatestRelease(owner, repo string) (*Release, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
r := new(Release)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/releases/latest", owner, repo),
jsonHeader, nil, &r)
return r, resp, err
}
// GetReleaseByTag get a release of a repository by tag
func (c *Client) GetReleaseByTag(owner, repo string, tag string) (*Release, *Response, error) {
func (c *Client) GetReleaseByTag(owner, repo, tag string) (*Release, *Response, error) {
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
return c.fallbackGetReleaseByTag(owner, repo, tag)
}
@ -168,7 +180,7 @@ func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) {
}
// DeleteReleaseByTag deletes a release frm a repository by tag
func (c *Client) DeleteReleaseByTag(user, repo string, tag string) (*Response, error) {
func (c *Client) DeleteReleaseByTag(user, repo, tag string) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
return nil, err
}
@ -182,7 +194,7 @@ func (c *Client) DeleteReleaseByTag(user, repo string, tag string) (*Response, e
}
// fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 )
func (c *Client) fallbackGetReleaseByTag(owner, repo string, tag string) (*Release, *Response, error) {
func (c *Client) fallbackGetReleaseByTag(owner, repo, tag string) (*Release, *Response, error) {
for i := 1; ; i++ {
rl, resp, err := c.ListReleases(owner, repo, ListReleasesOptions{ListOptions: ListOptions{Page: i}})
if err != nil {
@ -190,7 +202,7 @@ func (c *Client) fallbackGetReleaseByTag(owner, repo string, tag string) (*Relea
}
if len(rl) == 0 {
return nil,
&Response{&http.Response{StatusCode: 404}},
newResponse(&http.Response{StatusCode: 404}),
fmt.Errorf("release with tag '%s' not found", tag)
}
for _, r := range rl {

View File

@ -26,7 +26,7 @@ func TestRelease(t *testing.T) {
// CreateRelease
r, _, err := c.CreateRelease(repo.Owner.UserName, repo.Name, CreateReleaseOption{
TagName: "awesome",
Target: "master",
Target: "main",
Title: "Release 1",
Note: "yes it's awesome",
IsDraft: true,
@ -38,7 +38,7 @@ func TestRelease(t *testing.T) {
assert.EqualValues(t, true, r.IsDraft)
assert.EqualValues(t, "Release 1", r.Title)
assert.EqualValues(t, fmt.Sprintf("%s/api/v1/repos/%s/releases/%d", c.url, repo.FullName, r.ID), r.URL)
assert.EqualValues(t, "master", r.Target)
assert.EqualValues(t, "main", r.Target)
assert.EqualValues(t, "yes it's awesome", r.Note)
assert.EqualValues(t, c.username, r.Publisher.UserName)
rl, _, _ = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{})
@ -51,6 +51,13 @@ func TestRelease(t *testing.T) {
r2, _, err = c.GetReleaseByTag(repo.Owner.UserName, repo.Name, r.TagName)
assert.NoError(t, err)
assert.EqualValues(t, r, r2)
// ListRelease without pre-releases
tr := true
rl, _, err = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{
IsPreRelease: &tr,
})
assert.NoError(t, err)
assert.Len(t, rl, 1) // created release is a pre-release
// test fallback
r2, _, err = c.fallbackGetReleaseByTag(repo.Owner.UserName, repo.Name, r.TagName)
assert.NoError(t, err)
@ -69,6 +76,11 @@ func TestRelease(t *testing.T) {
assert.EqualValues(t, false, r2.IsPrerelease)
assert.EqualValues(t, r.Note, r2.Note)
// GetLatestRelease
r3, _, err := c.GetLatestRelease(repo.Owner.UserName, repo.Name)
assert.NoError(t, err)
assert.EqualValues(t, r2, r3)
// DeleteRelease
_, err = c.DeleteRelease(repo.Owner.UserName, repo.Name, r.ID)
assert.NoError(t, err)
@ -78,7 +90,7 @@ func TestRelease(t *testing.T) {
// CreateRelease
_, _, err = c.CreateRelease(repo.Owner.UserName, repo.Name, CreateReleaseOption{
TagName: "aNewReleaseTag",
Target: "master",
Target: "main",
Title: "Title of aNewReleaseTag",
})
assert.NoError(t, err)

View File

@ -85,6 +85,9 @@ type Repository struct {
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
HasPullRequests bool `json:"has_pull_requests"`
HasProjects bool `json:"has_projects"`
HasReleases bool `json:"has_releases,omitempty"`
HasPackages bool `json:"has_packages,omitempty"`
HasActions bool `json:"has_actions,omitempty"`
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"`
AllowMerge bool `json:"allow_merge_commits"`
AllowRebase bool `json:"allow_rebase"`
@ -93,6 +96,7 @@ type Repository struct {
AvatarURL string `json:"avatar_url"`
Internal bool `json:"internal"`
MirrorInterval string `json:"mirror_interval"`
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
DefaultMergeStyle MergeStyle `json:"default_merge_style"`
}
@ -286,7 +290,9 @@ func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, e
// private repos only not supported on gitea <= 1.11.x
return nil, nil, err
}
link.Query().Add("private", "false")
newQuery := link.Query()
newQuery.Add("private", "false")
link.RawQuery = newQuery.Encode()
}
}
@ -328,14 +334,14 @@ func (opt CreateRepoOption) Validate(c *Client) error {
if len(opt.Name) > 100 {
return fmt.Errorf("name has more than 100 chars")
}
if len(opt.Description) > 255 {
return fmt.Errorf("name has more than 255 chars")
if len(opt.Description) > 2048 {
return fmt.Errorf("description has more than 2048 chars")
}
if len(opt.DefaultBranch) > 100 {
return fmt.Errorf("name has more than 100 chars")
return fmt.Errorf("default branch name has more than 100 chars")
}
if len(opt.TrustModel) != 0 {
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
return err
}
}
@ -420,6 +426,12 @@ type EditRepoOption struct {
HasPullRequests *bool `json:"has_pull_requests,omitempty"`
// either `true` to enable project unit, or `false` to disable them.
HasProjects *bool `json:"has_projects,omitempty"`
// either `true` to enable release, or `false` to disable them.
HasReleases *bool `json:"has_releases,omitempty"`
// either `true` to enable packages, or `false` to disable them.
HasPackages *bool `json:"has_packages,omitempty"`
// either `true` to enable actions, or `false` to disable them.
HasActions *bool `json:"has_actions,omitempty"`
// either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`.
IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"`
// either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`.

66
gitea/repo_action.go Normal file
View File

@ -0,0 +1,66 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
// ListRepoActionSecretOption list RepoActionSecret options
type ListRepoActionSecretOption struct {
ListOptions
}
// ListRepoActionSecret list a repository's secrets
func (c *Client) ListRepoActionSecret(user, repo string, opt ListRepoActionSecretOption) ([]*Secret, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
opt.setDefaults()
secrets := make([]*Secret, 0, opt.PageSize)
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/secrets", user, repo))
link.RawQuery = opt.getURLQuery().Encode()
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets)
return secrets, resp, err
}
// CreateRepoActionSecret creates a secret for the specified repository in the Gitea Actions.
// It takes the organization name and the secret options as parameters.
// The function returns the HTTP response and an error, if any.
func (c *Client) CreateRepoActionSecret(user, repo string, opt CreateSecretOption) (*Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, err
}
if err := (&opt).Validate(); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)
if err != nil {
return nil, err
}
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/actions/secrets/%s", user, repo, opt.Name), jsonHeader, bytes.NewReader(body))
if err != nil {
return nil, err
}
switch status {
case http.StatusCreated:
return resp, nil
case http.StatusNoContent:
return resp, nil
case http.StatusNotFound:
return resp, fmt.Errorf("forbidden")
case http.StatusBadRequest:
return resp, fmt.Errorf("bad request")
default:
return resp, fmt.Errorf("unexpected Status: %d", status)
}
}

41
gitea/repo_action_test.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"log"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateRepoActionSecret(t *testing.T) {
log.Println("== TestCreateRepoActionSecret ==")
c := newTestClient()
user := createTestUser(t, "repo_action_user", c)
c.SetSudo(user.UserName)
newRepo, _, err := c.CreateRepo(CreateRepoOption{
Name: "test",
})
assert.NoError(t, err)
assert.NotNil(t, newRepo)
// create secret
resp, err := c.CreateRepoActionSecret(newRepo.Owner.UserName, newRepo.Name, CreateSecretOption{Name: "test", Data: "test"})
assert.NoError(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
// update secret
resp, err = c.CreateRepoActionSecret(newRepo.Owner.UserName, newRepo.Name, CreateSecretOption{Name: "test", Data: "test2"})
assert.NoError(t, err)
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
// list secrets
secrets, _, err := c.ListRepoActionSecret(newRepo.Owner.UserName, newRepo.Name, ListRepoActionSecretOption{})
assert.NoError(t, err)
assert.Len(t, secrets, 1)
}

View File

@ -15,6 +15,7 @@ import (
// BranchProtection represents a branch protection for a repository
type BranchProtection struct {
BranchName string `json:"branch_name"`
RuleName string `json:"rule_name"`
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
@ -35,6 +36,7 @@ type BranchProtection struct {
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
Created time.Time `json:"created_at"`
Updated time.Time `json:"updated_at"`
}
@ -42,6 +44,7 @@ type BranchProtection struct {
// CreateBranchProtectionOption options for creating a branch protection
type CreateBranchProtectionOption struct {
BranchName string `json:"branch_name"`
RuleName string `json:"rule_name"`
EnablePush bool `json:"enable_push"`
EnablePushWhitelist bool `json:"enable_push_whitelist"`
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
@ -62,6 +65,7 @@ type CreateBranchProtectionOption struct {
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
RequireSignedCommits bool `json:"require_signed_commits"`
ProtectedFilePatterns string `json:"protected_file_patterns"`
UnprotectedFilePatterns string `json:"unprotected_file_patterns"`
}
// EditBranchProtectionOption options for editing a branch protection
@ -86,6 +90,7 @@ type EditBranchProtectionOption struct {
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
RequireSignedCommits *bool `json:"require_signed_commits"`
ProtectedFilePatterns *string `json:"protected_file_patterns"`
UnprotectedFilePatterns *string `json:"unprotected_file_patterns"`
}
// ListBranchProtectionsOptions list branch protection options

View File

@ -7,6 +7,7 @@ package gitea
import (
"log"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
@ -14,26 +15,31 @@ import (
func TestRepoBranches(t *testing.T) {
log.Println("== TestRepoBranches ==")
c := newTestClient()
var repoName = "branches"
repoName := "branches"
repo := prepareBranchTest(t, c, repoName)
if repo == nil {
return
}
time.Sleep(1 * time.Second)
bl, _, err := c.ListRepoBranches(repo.Owner.UserName, repo.Name, ListRepoBranchesOptions{})
assert.NoError(t, err)
assert.Len(t, bl, 3)
assert.EqualValues(t, "feature", bl[0].Name)
assert.EqualValues(t, "master", bl[1].Name)
assert.EqualValues(t, "update", bl[2].Name)
branchNames := make([]string, len(bl))
branches := make(map[string]Branch, len(bl))
for index, branch := range bl {
branchNames[index] = branch.Name
branches[branch.Name] = *branch
}
assert.ElementsMatch(t, []string{"feature", "main", "update"}, branchNames)
b, _, err := c.GetRepoBranch(repo.Owner.UserName, repo.Name, "update")
assert.NoError(t, err)
assert.EqualValues(t, bl[2].Commit.ID, b.Commit.ID)
assert.EqualValues(t, bl[2].Commit.Added, b.Commit.Added)
assert.EqualValues(t, branches["update"].Commit.ID, b.Commit.ID)
assert.EqualValues(t, branches["update"].Commit.Added, b.Commit.Added)
s, _, err := c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "master")
s, _, err := c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "main")
assert.NoError(t, err)
assert.False(t, s)
s, _, err = c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "feature")
@ -59,7 +65,7 @@ func TestRepoBranches(t *testing.T) {
func TestRepoBranchProtection(t *testing.T) {
log.Println("== TestRepoBranchProtection ==")
c := newTestClient()
var repoName = "BranchProtection"
repoName := "BranchProtection"
repo := prepareBranchTest(t, c, repoName)
if repo == nil {
@ -74,7 +80,7 @@ func TestRepoBranchProtection(t *testing.T) {
// CreateBranchProtection
bp, _, err := c.CreateBranchProtection(repo.Owner.UserName, repo.Name, CreateBranchProtectionOption{
BranchName: "master",
BranchName: "main",
EnablePush: true,
EnablePushWhitelist: true,
PushWhitelistUsernames: []string{"test01"},
@ -83,7 +89,7 @@ func TestRepoBranchProtection(t *testing.T) {
BlockOnOutdatedBranch: true,
})
assert.NoError(t, err)
assert.EqualValues(t, "master", bp.BranchName)
assert.EqualValues(t, "main", bp.BranchName)
assert.EqualValues(t, false, bp.EnableStatusCheck)
assert.EqualValues(t, true, bp.EnablePush)
assert.EqualValues(t, true, bp.EnablePushWhitelist)
@ -136,18 +142,18 @@ func prepareBranchTest(t *testing.T, c *Client, repoName string) *Repository {
return nil
}
masterLicence, _, err := c.GetContents(origRepo.Owner.UserName, origRepo.Name, "master", "README.md")
if !assert.NoError(t, err) || !assert.NotNil(t, masterLicence) {
mainLicense, _, err := c.GetContents(origRepo.Owner.UserName, origRepo.Name, "main", "README.md")
if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) {
return nil
}
updatedFile, _, err := c.UpdateFile(origRepo.Owner.UserName, origRepo.Name, "README.md", UpdateFileOptions{
FileOptions: FileOptions{
Message: "update it",
BranchName: "master",
BranchName: "main",
NewBranchName: "update",
},
SHA: masterLicence.SHA,
SHA: mainLicense.SHA,
Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
})
if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) {
@ -158,7 +164,7 @@ func prepareBranchTest(t *testing.T, c *Client, repoName string) *Repository {
Content: "QSBuZXcgRmlsZQo=",
FileOptions: FileOptions{
Message: "creat a new file",
BranchName: "master",
BranchName: "main",
NewBranchName: "feature",
},
})

View File

@ -16,6 +16,13 @@ type ListCollaboratorsOptions struct {
ListOptions
}
// CollaboratorPermissionResult result type for CollaboratorPermission
type CollaboratorPermissionResult struct {
Permission AccessMode `json:"permission"`
Role string `json:"role_name"`
User *User `json:"user"`
}
// ListCollaborators list a repository's collaborators
func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
@ -44,6 +51,26 @@ func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Respons
return false, resp, nil
}
// CollaboratorPermission gets collaborator permission of a repository
func (c *Client) CollaboratorPermission(user, repo, collaborator string) (*CollaboratorPermissionResult, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
return nil, nil, err
}
rv := new(CollaboratorPermissionResult)
resp, err := c.getParsedResponse("GET",
fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", user, repo, collaborator),
nil,
nil,
rv)
if err != nil {
return nil, resp, err
}
if resp.StatusCode != 200 {
rv = nil
}
return rv, resp, nil
}
// AddCollaboratorOption options when adding a user as a collaborator of a repository
type AddCollaboratorOption struct {
Permission *AccessMode `json:"permission"`
@ -66,7 +93,7 @@ const (
)
// Validate the AddCollaboratorOption struct
func (opt AddCollaboratorOption) Validate() error {
func (opt *AddCollaboratorOption) Validate() error {
if opt.Permission != nil {
if *opt.Permission == AccessModeOwner {
*opt.Permission = AccessModeAdmin
@ -88,7 +115,7 @@ func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollabo
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
return nil, err
}
if err := opt.Validate(); err != nil {
if err := (&opt).Validate(); err != nil {
return nil, err
}
body, err := json.Marshal(&opt)

View File

@ -18,8 +18,12 @@ func TestRepoCollaborator(t *testing.T) {
repo, _ := createTestRepo(t, "RepoCollaborators", c)
createTestUser(t, "ping", c)
createTestUser(t, "pong", c)
defer c.AdminDeleteUser("ping")
defer c.AdminDeleteUser("pong")
defer func() {
_, err := c.AdminDeleteUser("ping")
assert.NoError(t, err)
_, err = c.AdminDeleteUser("pong")
assert.NoError(t, err)
}()
collaborators, _, err := c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{})
assert.NoError(t, err)
@ -30,10 +34,22 @@ func TestRepoCollaborator(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, 204, resp.StatusCode)
permissonPing, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "ping")
assert.NoError(t, err)
assert.EqualValues(t, 200, resp.StatusCode)
assert.EqualValues(t, AccessModeAdmin, permissonPing.Permission)
assert.EqualValues(t, "ping", permissonPing.User.UserName)
mode = AccessModeRead
_, err = c.AddCollaborator(repo.Owner.UserName, repo.Name, "pong", AddCollaboratorOption{Permission: &mode})
assert.NoError(t, err)
permissonPong, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "pong")
assert.NoError(t, err)
assert.EqualValues(t, 200, resp.StatusCode)
assert.EqualValues(t, AccessModeRead, permissonPong.Permission)
assert.EqualValues(t, "pong", permissonPong.User.UserName)
collaborators, _, err = c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{})
assert.NoError(t, err)
assert.Len(t, collaborators, 2)
@ -41,8 +57,8 @@ func TestRepoCollaborator(t *testing.T) {
reviewers, _, err := c.GetReviewers(repo.Owner.UserName, repo.Name)
assert.NoError(t, err)
assert.Len(t, reviewers, 2)
assert.EqualValues(t, []string{"ping", "pong"}, userToStringSlice(reviewers))
assert.Len(t, reviewers, 3)
assert.EqualValues(t, []string{"ping", "pong", "test01"}, userToStringSlice(reviewers))
assignees, _, err := c.GetAssignees(repo.Owner.UserName, repo.Name)
assert.NoError(t, err)
@ -56,4 +72,9 @@ func TestRepoCollaborator(t *testing.T) {
collaborators, _, err = c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{})
assert.NoError(t, err)
assert.Len(t, collaborators, 1)
permissonNotExists, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "user_that_not_exists")
assert.Error(t, err)
assert.EqualValues(t, 404, resp.StatusCode)
assert.Nil(t, permissonNotExists)
}

View File

@ -32,11 +32,19 @@ type CommitUser struct {
// RepoCommit contains information of a commit in the context of a repository.
type RepoCommit struct {
URL string `json:"url"`
Author *CommitUser `json:"author"`
Committer *CommitUser `json:"committer"`
Message string `json:"message"`
Tree *CommitMeta `json:"tree"`
URL string `json:"url"`
Author *CommitUser `json:"author"`
Committer *CommitUser `json:"committer"`
Message string `json:"message"`
Tree *CommitMeta `json:"tree"`
Verification *PayloadCommitVerification `json:"verification"`
}
// CommitStats contains stats from a Git commit
type CommitStats struct {
Total int `json:"total"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
}
// Commit contains information generated from a Git commit.
@ -48,6 +56,7 @@ type Commit struct {
Committer *User `json:"committer"`
Parents []*CommitMeta `json:"parents"`
Files []*CommitAffectedFiles `json:"files"`
Stats *CommitStats `json:"stats"`
}
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
@ -74,16 +83,21 @@ func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Respons
// ListCommitOptions list commit options
type ListCommitOptions struct {
ListOptions
//SHA or branch to start listing commits from (usually 'master')
// SHA or branch to start listing commits from (usually 'master')
SHA string
// Path indicates that only commits that include the path's file/dir should be returned.
Path string
}
// QueryEncode turns options into querystring argument
func (opt *ListCommitOptions) QueryEncode() string {
query := opt.ListOptions.getURLQuery()
query := opt.getURLQuery()
if opt.SHA != "" {
query.Add("sha", opt.SHA)
}
if opt.Path != "" {
query.Add("path", opt.Path)
}
return query.Encode()
}
@ -99,3 +113,29 @@ func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*C
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits)
return commits, resp, err
}
// GetCommitDiff returns the commit's raw diff.
func (c *Client) GetCommitDiff(user, repo, commitID string) ([]byte, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypeDiff), nil, nil)
}
// GetCommitPatch returns the commit's raw patch.
func (c *Client) GetCommitPatch(user, repo, commitID string) ([]byte, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypePatch), nil, nil)
}

View File

@ -5,6 +5,7 @@
package gitea
import (
"encoding/base64"
"log"
"testing"
@ -22,4 +23,38 @@ func TestListRepoCommits(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, l, 1)
assert.EqualValues(t, "Initial commit\n", l[0].RepoCommit.Message)
assert.EqualValues(t, "gpg.error.not_signed_commit", l[0].RepoCommit.Verification.Reason)
assert.EqualValues(t, 100, l[0].Stats.Additions)
}
func TestGetCommitDiffOrPatch(t *testing.T) {
log.Println("== TestGetCommitDiffOrPatch ==")
c := newTestClient()
repo, err := createTestRepo(t, "TestGetCommitDiffOrPatch", c)
assert.NoError(t, err)
// Add a new simple small commit to the repository.
fileResponse, _, err := c.CreateFile(repo.Owner.UserName, repo.Name, "NOT_A_LICENSE", CreateFileOptions{
Content: base64.StdEncoding.EncodeToString([]byte("But is it?\n")),
FileOptions: FileOptions{
Message: "Ensure people know it's not a license!",
Committer: Identity{
Name: "Sup3rCookie",
Email: "Sup3rCookie@example.com",
},
},
})
assert.NoError(t, err)
// Test the diff output.
diffOutput, _, err := c.GetCommitDiff(repo.Owner.UserName, repo.Name, fileResponse.Commit.SHA)
assert.NoError(t, err)
assert.EqualValues(t, "diff --git a/NOT_A_LICENSE b/NOT_A_LICENSE\nnew file mode 100644\nindex 0000000..f27a20a\n--- /dev/null\n+++ b/NOT_A_LICENSE\n@@ -0,0 +1 @@\n+But is it?\n", string(diffOutput))
// Test the patch output.
patchOutput, _, err := c.GetCommitPatch(repo.Owner.UserName, repo.Name, fileResponse.Commit.SHA)
assert.NoError(t, err)
// Use contains, because we cannot include the first part, because of dates + non-static CommitID..
assert.Contains(t, string(patchOutput), "Subject: [PATCH] Ensure people know it's not a license!\n\n---\n NOT_A_LICENSE | 1 +\n 1 file changed, 1 insertion(+)\n create mode 100644 NOT_A_LICENSE\n\ndiff --git a/NOT_A_LICENSE b/NOT_A_LICENSE\nnew file mode 100644\nindex 0000000..f27a20a\n--- /dev/null\n+++ b/NOT_A_LICENSE\n@@ -0,0 +1 @@\n+But is it?\n")
}

33
gitea/repo_compare.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import "fmt"
// Compare represents a comparison between two commits.
type Compare struct {
TotalCommits int `json:"total_commits"` // Total number of commits in the comparison.
Commits []*Commit `json:"commits"` // List of commits in the comparison.
}
// CompareCommits compares two commits in a repository.
func (c *Client) CompareCommits(user, repo, prev, current string) (*Compare, *Response, error) {
if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil {
return nil, nil, err
}
if err := escapeValidatePathSegments(&user, &repo, &prev, &current); err != nil {
return nil, nil, err
}
basehead := fmt.Sprintf("%s...%s", prev, current)
apiResp := new(Compare)
resp, err := c.getParsedResponse(
"GET",
fmt.Sprintf("/repos/%s/%s/compare/%s", user, repo, basehead),
nil, nil, apiResp,
)
return apiResp, resp, err
}

View File

@ -9,6 +9,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/url"
"strings"
)
@ -117,17 +118,46 @@ type FileDeleteResponse struct {
}
// GetFile downloads a file of repository, ref can be branch/tag/commit.
// it optional can resolve lfs pointers and server the file instead
// e.g.: ref -> master, filepath -> README.md (no leading slash)
func (c *Client) GetFile(owner, repo, ref, filepath string) ([]byte, *Response, error) {
func (c *Client) GetFile(owner, repo, ref, filepath string, resolveLFS ...bool) ([]byte, *Response, error) {
reader, resp, err := c.GetFileReader(owner, repo, ref, filepath, resolveLFS...)
if reader == nil {
return nil, resp, err
}
defer reader.Close()
data, err2 := io.ReadAll(reader)
if err2 != nil {
return nil, resp, err2
}
return data, resp, err
}
// GetFileReader return reader for download a file of repository, ref can be branch/tag/commit.
// it optional can resolve lfs pointers and server the file instead
// e.g.: ref -> master, filepath -> README.md (no leading slash)
func (c *Client) GetFileReader(owner, repo, ref, filepath string, resolveLFS ...bool) (io.ReadCloser, *Response, error) {
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
return nil, nil, err
}
// resolve lfs
if len(resolveLFS) != 0 && resolveLFS[0] {
if err := c.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
return nil, nil, err
}
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/media/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil)
}
// normal get
filepath = pathEscapeSegments(filepath)
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
ref = pathEscapeSegments(ref)
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", owner, repo, ref, filepath), nil, nil)
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", owner, repo, ref, filepath), nil, nil)
}
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil)
return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil)
}
// GetContents get the metadata and contents of a file in a repository

View File

@ -21,7 +21,7 @@ func TestFileCreateUpdateGet(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, repo)
raw, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "master", "README.md")
raw, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "main", "README.md")
assert.NoError(t, err)
assert.EqualValues(t, "IyBDaGFuZ2VGaWxlcwoKQSB0ZXN0IFJlcG86IENoYW5nZUZpbGVz", base64.StdEncoding.EncodeToString(raw))
@ -33,7 +33,7 @@ func TestFileCreateUpdateGet(t *testing.T) {
Content: "ZmlsZUEK",
})
assert.NoError(t, err)
raw, _, _ = c.GetFile(repo.Owner.UserName, repo.Name, "master", testFileName)
raw, _, _ = c.GetFile(repo.Owner.UserName, repo.Name, "main", testFileName)
assert.EqualValues(t, "ZmlsZUEK", base64.StdEncoding.EncodeToString(raw))
updatedFile, _, err := c.UpdateFile(repo.Owner.UserName, repo.Name, testFileName, UpdateFileOptions{
@ -46,7 +46,7 @@ func TestFileCreateUpdateGet(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, updatedFile)
file, _, err := c.GetContents(repo.Owner.UserName, repo.Name, "master", testFileName)
file, _, err := c.GetContents(repo.Owner.UserName, repo.Name, "main", testFileName)
assert.NoError(t, err)
assert.EqualValues(t, updatedFile.Content.SHA, file.SHA)
assert.EqualValues(t, &updatedFile.Content.Content, &file.Content)
@ -58,18 +58,20 @@ func TestFileCreateUpdateGet(t *testing.T) {
SHA: updatedFile.Content.SHA,
})
assert.NoError(t, err)
_, resp, err := c.GetFile(repo.Owner.UserName, repo.Name, "master", testFileName)
_, resp, err := c.GetFile(repo.Owner.UserName, repo.Name, "main", testFileName)
assert.Error(t, err)
assert.EqualValues(t, "The target couldn't be found.", err.Error())
assert.EqualValues(t, 404, resp.StatusCode)
licence, _, err := c.GetContents(repo.Owner.UserName, repo.Name, "", "LICENSE")
assert.NoError(t, err)
licenceRaw, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "", "LICENSE")
assert.NoError(t, err)
testContent := "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo="
updatedFile, _, err = c.UpdateFile(repo.Owner.UserName, repo.Name, "LICENSE", UpdateFileOptions{
FileOptions: FileOptions{
Message: "Overwrite",
BranchName: "master",
BranchName: "main",
NewBranchName: "overwrite-a+/&licence",
},
SHA: licence.SHA,

View File

@ -16,7 +16,7 @@ type GitServiceType string
const (
// GitServicePlain represents a plain git service
GitServicePlain GitServiceType = "git"
//GitServiceGithub represents github.com
// GitServiceGithub represents github.com
GitServiceGithub GitServiceType = "github"
// GitServiceGitlab represents a gitlab service
GitServiceGitlab GitServiceType = "gitlab"
@ -62,7 +62,7 @@ func (opt *MigrateRepoOption) Validate(c *Client) error {
} else if len(opt.RepoName) > 100 {
return fmt.Errorf("RepoName to long")
}
if len(opt.Description) > 255 {
if len(opt.Description) > 2048 {
return fmt.Errorf("Description to long")
}
switch opt.Service {

45
gitea/repo_mirror.go Normal file
View File

@ -0,0 +1,45 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
"bytes"
"encoding/json"
"fmt"
)
type CreatePushMirrorOption struct {
Interval string `json:"interval"`
RemoteAddress string `json:"remote_address"`
RemotePassword string `json:"remote_password"`
RemoteUsername string `json:"remote_username"`
SyncONCommit bool `json:"sync_on_commit"`
}
// PushMirrorResponse returns a git push mirror
type PushMirrorResponse struct {
Created string `json:"created"`
Interval string `json:"interval"`
LastError string `json:"last_error"`
LastUpdate string `json:"last_update"`
RemoteAddress string `json:"remote_address"`
RemoteName string `json:"remote_name"`
RepoName string `json:"repo_name"`
SyncONCommit bool `json:"sync_on_commit"`
}
// PushMirrors add a push mirror to the repository
func (c *Client) PushMirrors(user, repo string, opt CreatePushMirrorOption) (*PushMirrorResponse, *Response, error) {
if err := escapeValidatePathSegments(&user, &repo); err != nil {
return nil, nil, err
}
body, err := json.Marshal(opt)
if err != nil {
return nil, nil, err
}
pm := new(PushMirrorResponse)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/push_mirrors", user, repo), jsonHeader, bytes.NewReader(body), &pm)
return pm, resp, err
}

View File

@ -23,12 +23,12 @@ func TestTags(t *testing.T) {
cTag, resp, err := c.CreateTag(repo.Owner.UserName, repo.Name, CreateTagOption{
TagName: "tag1",
Message: cTagMSG,
Target: "master",
Target: "main",
})
assert.NoError(t, err)
assert.EqualValues(t, 201, resp.StatusCode)
assert.EqualValues(t, cTagMSG, cTag.Message)
assert.EqualValues(t, fmt.Sprintf("%s/test01/TestTags/archive/tag1.zip", c.url), cTag.ZipballURL)
assert.EqualValues(t, fmt.Sprintf("%s/%s/TestTags/archive/tag1.zip", c.url, c.username), cTag.ZipballURL)
tags, _, err := c.ListRepoTags(repo.Owner.UserName, repo.Name, ListRepoTagsOptions{})
assert.NoError(t, err)
@ -44,9 +44,8 @@ func TestTags(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, cTag.Name, aTag.Tag)
assert.EqualValues(t, cTag.ID, aTag.SHA)
assert.EqualValues(t, fmt.Sprintf("%s/api/v1/repos/test01/TestTags/git/tags/%s", c.url, cTag.ID), aTag.URL)
assert.EqualValues(t, fmt.Sprintf("%s/api/v1/repos/%s/TestTags/git/tags/%s", c.url, c.username, cTag.ID), aTag.URL)
assert.EqualValues(t, cTag.Message+"\n", aTag.Message)
assert.EqualValues(t, false, aTag.Verification.Verified)
assert.EqualValues(t, "commit", aTag.Object.Type)
// DeleteReleaseTag

View File

@ -20,7 +20,7 @@ func TestCreateRepo(t *testing.T) {
user, _, err := c.GetMyUserInfo()
assert.NoError(t, err)
var repoName = "test1"
repoName := "test1"
_, _, err = c.GetRepo(user.UserName, repoName)
if err != nil {
repo, _, err := c.CreateRepo(CreateRepoOption{
@ -57,13 +57,15 @@ func TestRepoMigrateAndLanguages(t *testing.T) {
repoG, _, err := c.GetRepo(repoM.Owner.UserName, repoM.Name)
assert.NoError(t, err)
assert.EqualValues(t, repoM.ID, repoG.ID)
assert.EqualValues(t, "master", repoG.DefaultBranch)
assert.EqualValues(t, "main", repoG.DefaultBranch)
assert.True(t, repoG.Mirror)
assert.False(t, repoG.Empty)
assert.EqualValues(t, 1, repoG.Watchers)
var zeroTime time.Time
assert.NotEqual(t, zeroTime, repoG.MirrorUpdated)
log.Println("== TestRepoLanguages ==")
time.Sleep(time.Second)
time.Sleep(time.Second * 2)
lang, _, err := c.GetRepoLanguages(repoM.Owner.UserName, repoM.Name)
assert.NoError(t, err)
assert.Len(t, lang, 2)
@ -136,7 +138,7 @@ func TestGetArchive(t *testing.T) {
c := newTestClient()
repo, _ := createTestRepo(t, "ToDownload", c)
time.Sleep(time.Second / 2)
archive, _, err := c.GetArchive(repo.Owner.UserName, repo.Name, "master", ZipArchive)
archive, _, err := c.GetArchive(repo.Owner.UserName, repo.Name, "main", ZipArchive)
assert.NoError(t, err)
assert.True(t, len(archive) > 1500 && len(archive) < 1700)
}
@ -146,7 +148,7 @@ func TestGetArchiveReader(t *testing.T) {
c := newTestClient()
repo, _ := createTestRepo(t, "ToDownload", c)
time.Sleep(time.Second / 2)
r, _, err := c.GetArchiveReader(repo.Owner.UserName, repo.Name, "master", ZipArchive)
r, _, err := c.GetArchiveReader(repo.Owner.UserName, repo.Name, "main", ZipArchive)
assert.NoError(t, err)
defer r.Close()
@ -175,11 +177,15 @@ func TestGetRepoByID(t *testing.T) {
func createTestRepo(t *testing.T, name string, c *Client) (*Repository, error) {
user, _, uErr := c.GetMyUserInfo()
assert.NoError(t, uErr)
_, _, err := c.GetRepo(user.UserName, name)
if err == nil {
repo, _, err := c.GetRepo(user.UserName, name)
// We need to check that the received repo is not a
// redirected one, it could be the case that gitea redirect us
// to a new repo(because it e.g. was transferred or renamed).
if err == nil && repo.Owner.UserName == user.UserName {
_, _ = c.DeleteRepo(user.UserName, name)
}
repo, _, err := c.CreateRepo(CreateRepoOption{
repo, _, err = c.CreateRepo(CreateRepoOption{
Name: name,
Description: "A test Repo: " + name,
AutoInit: true,

View File

@ -34,3 +34,29 @@ func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer", owner, reponame), jsonHeader, bytes.NewReader(body), repo)
return repo, resp, err
}
// AcceptRepoTransfer accepts a repo transfer.
func (c *Client) AcceptRepoTransfer(owner, reponame string) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/accept", owner, reponame), jsonHeader, nil, repo)
return repo, resp, err
}
// RejectRepoTransfer rejects a repo transfer.
func (c *Client) RejectRepoTransfer(owner, reponame string) (*Repository, *Response, error) {
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
return nil, nil, err
}
if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil {
return nil, nil, err
}
repo := new(Repository)
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/reject", owner, reponame), jsonHeader, nil, repo)
return repo, resp, err
}

View File

@ -20,13 +20,15 @@ func TestRepoTransfer(t *testing.T) {
repo, err := createTestRepo(t, "ToMove", c)
assert.NoError(t, err)
newRepo, _, err := c.TransferRepo(repo.Owner.UserName, repo.Name, TransferRepoOption{NewOwner: org.UserName})
assert.NoError(t, err)
newRepo, _, err := c.TransferRepo(c.username, repo.Name, TransferRepoOption{NewOwner: org.UserName})
assert.NoError(t, err) // admin transfer repository will execute immediately but not set as pendding.
assert.NotNil(t, newRepo)
assert.EqualValues(t, "ToMove", newRepo.Name)
repo, err = createTestRepo(t, "ToMove", c)
assert.NoError(t, err)
_, _, err = c.TransferRepo(repo.Owner.UserName, repo.Name, TransferRepoOption{NewOwner: org.UserName})
_, resp, err := c.TransferRepo(c.username, repo.Name, TransferRepoOption{NewOwner: org.UserName})
assert.EqualValues(t, 422, resp.StatusCode)
assert.Error(t, err)
_, err = c.DeleteRepo(repo.Owner.UserName, repo.Name)

View File

@ -35,7 +35,7 @@ func (c *Client) GetTrees(user, repo, ref string, recursive bool) (*GitTreeRespo
return nil, nil, err
}
trees := new(GitTreeResponse)
var path = fmt.Sprintf("/repos/%s/%s/git/trees/%s", user, repo, ref)
path := fmt.Sprintf("/repos/%s/%s/git/trees/%s", user, repo, ref)
if recursive {
path += "?recursive=1"
}

View File

@ -33,7 +33,7 @@ func (c *Client) GetWatchedRepos(user string) ([]*Repository, *Response, error)
// 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)
resp, err := c.getParsedResponse("GET", "/user/subscriptions", nil, nil, &repos)
return repos, resp, err
}

View File

@ -22,29 +22,29 @@ func TestRepoWatch(t *testing.T) {
repo2, _ := createTestRepo(t, "TestRepoWatch_2", c)
assert.NotEqual(t, repo1, repo2)
//GetWatchedRepos
// GetWatchedRepos
wl, _, err := c.GetWatchedRepos("test01")
assert.NoError(t, err)
assert.NotNil(t, wl)
maxcount := len(wl)
//GetMyWatchedRepos
// GetMyWatchedRepos
wl, _, err = c.GetMyWatchedRepos()
assert.NoError(t, err)
assert.Len(t, wl, maxcount)
//CheckRepoWatch
// CheckRepoWatch
isWatching, _, err := c.CheckRepoWatch(repo1.Owner.UserName, repo1.Name)
assert.NoError(t, err)
assert.True(t, isWatching)
//UnWatchRepo
// UnWatchRepo
_, err = c.UnWatchRepo(repo1.Owner.UserName, repo1.Name)
assert.NoError(t, err)
isWatching, _, _ = c.CheckRepoWatch(repo1.Owner.UserName, repo1.Name)
assert.False(t, isWatching)
//WatchRepo
// WatchRepo
_, err = c.WatchRepo(repo1.Owner.UserName, repo1.Name)
assert.NoError(t, err)
isWatching, _, _ = c.CheckRepoWatch(repo1.Owner.UserName, repo1.Name)

16
gitea/secret.go Normal file
View File

@ -0,0 +1,16 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import "time"
type Secret struct {
// the secret's name
Name string `json:"name"`
// the secret's data
Data string `json:"data"`
// Date and Time of secret creation
Created time.Time `json:"created_at"`
}

View File

@ -39,10 +39,12 @@ func TestGetGlobalSettings(t *testing.T) {
attachSettings, _, err := c.GetGlobalAttachmentSettings()
assert.NoError(t, err)
if assert.NotEmpty(t, attachSettings.AllowedTypes) {
attachSettings.AllowedTypes = ""
}
assert.EqualValues(t, &GlobalAttachmentSettings{
Enabled: true,
AllowedTypes: ".docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip",
MaxSize: 4,
MaxFiles: 5,
Enabled: true,
MaxSize: 2048,
MaxFiles: 5,
}, attachSettings)
}

View File

@ -17,7 +17,7 @@ func TestCommitStatus(t *testing.T) {
user, _, err := c.GetMyUserInfo()
assert.NoError(t, err)
var repoName = "CommitStatuses"
repoName := "CommitStatuses"
origRepo, err := createTestRepo(t, repoName, c)
if !assert.NoError(t, err) {
return

View File

@ -17,6 +17,10 @@ type User struct {
ID int64 `json:"id"`
// the user's username
UserName string `json:"login"`
// The login_name of non local users (e.g. LDAP / OAuth / SMTP)
LoginName string `json:"login_name"`
// The ID of the Authentication Source for non local users.
SourceID int64 `json:"source_id"`
// the user's full name
FullName string `json:"full_name"`
Email string `json:"email"`
@ -77,7 +81,6 @@ func (c *Client) GetUserByID(id int64) (*User, *Response, error) {
query := make(url.Values)
query.Add("uid", strconv.FormatInt(id, 10))
users, resp, err := c.searchUsers(query.Encode())
if err != nil {
return nil, resp, err
}

View File

@ -13,12 +13,64 @@ import (
"reflect"
)
// AccessTokenScope represents the scope for an access token.
type AccessTokenScope string
const (
AccessTokenScopeAll AccessTokenScope = "all"
AccessTokenScopeRepo AccessTokenScope = "repo"
AccessTokenScopeRepoStatus AccessTokenScope = "repo:status"
AccessTokenScopePublicRepo AccessTokenScope = "public_repo"
AccessTokenScopeAdminOrg AccessTokenScope = "admin:org"
AccessTokenScopeWriteOrg AccessTokenScope = "write:org"
AccessTokenScopeReadOrg AccessTokenScope = "read:org"
AccessTokenScopeAdminPublicKey AccessTokenScope = "admin:public_key"
AccessTokenScopeWritePublicKey AccessTokenScope = "write:public_key"
AccessTokenScopeReadPublicKey AccessTokenScope = "read:public_key"
AccessTokenScopeAdminRepoHook AccessTokenScope = "admin:repo_hook"
AccessTokenScopeWriteRepoHook AccessTokenScope = "write:repo_hook"
AccessTokenScopeReadRepoHook AccessTokenScope = "read:repo_hook"
AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook"
AccessTokenScopeAdminUserHook AccessTokenScope = "admin:user_hook"
AccessTokenScopeNotification AccessTokenScope = "notification"
AccessTokenScopeUser AccessTokenScope = "user"
AccessTokenScopeReadUser AccessTokenScope = "read:user"
AccessTokenScopeUserEmail AccessTokenScope = "user:email"
AccessTokenScopeUserFollow AccessTokenScope = "user:follow"
AccessTokenScopeDeleteRepo AccessTokenScope = "delete_repo"
AccessTokenScopePackage AccessTokenScope = "package"
AccessTokenScopeWritePackage AccessTokenScope = "write:package"
AccessTokenScopeReadPackage AccessTokenScope = "read:package"
AccessTokenScopeDeletePackage AccessTokenScope = "delete:package"
AccessTokenScopeAdminGPGKey AccessTokenScope = "admin:gpg_key"
AccessTokenScopeWriteGPGKey AccessTokenScope = "write:gpg_key"
AccessTokenScopeReadGPGKey AccessTokenScope = "read:gpg_key"
AccessTokenScopeAdminApplication AccessTokenScope = "admin:application"
AccessTokenScopeWriteApplication AccessTokenScope = "write:application"
AccessTokenScopeReadApplication AccessTokenScope = "read:application"
AccessTokenScopeSudo AccessTokenScope = "sudo"
)
// 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"`
ID int64 `json:"id"`
Name string `json:"name"`
Token string `json:"sha1"`
TokenLastEight string `json:"token_last_eight"`
Scopes []AccessTokenScope `json:"scopes"`
}
// ListAccessTokensOptions options for listing a users's access tokens
@ -42,7 +94,8 @@ func (c *Client) ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken,
// CreateAccessTokenOption options when create access token
type CreateAccessTokenOption struct {
Name string `json:"name"`
Name string `json:"name"`
Scopes []AccessTokenScope `json:"scopes"`
}
// CreateAccessToken create one access token with options
@ -71,13 +124,13 @@ func (c *Client) DeleteAccessToken(value interface{}) (*Response, error) {
return nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed")
}
var token = ""
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 {
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
return nil, err
}
token = value.(string)

View File

@ -19,7 +19,7 @@ func TestUserSettings(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, userConf)
assert.EqualValues(t, UserSettings{
Theme: "gitea",
Theme: "gitea-auto",
HideEmail: false,
HideActivity: false,
}, *userConf)
@ -33,7 +33,7 @@ func TestUserSettings(t *testing.T) {
assert.NotNil(t, userConf)
assert.EqualValues(t, UserSettings{
FullName: "Admin User on Test",
Theme: "gitea",
Theme: "gitea-auto",
Language: "de_de",
HideEmail: true,
HideActivity: false,

View File

@ -21,7 +21,7 @@ func TestMyUser(t *testing.T) {
assert.EqualValues(t, "test01", user.UserName)
assert.EqualValues(t, "test01@gitea.io", user.Email)
assert.EqualValues(t, "", user.FullName)
assert.EqualValues(t, getGiteaURL()+"/user/avatar/test01/-1", user.AvatarURL)
assert.EqualValues(t, "http://gitea:3000/avatars/d794373e882a68fb173cef817fb6180a", user.AvatarURL)
assert.True(t, user.IsAdmin)
}
@ -144,14 +144,14 @@ func TestUserEmail(t *testing.T) {
el, _, err := c.ListEmails(ListEmailsOptions{})
assert.NoError(t, err)
assert.Len(t, el, 1)
assert.EqualValues(t, "testuseremail@gitea.io", el[0].Email)
assert.EqualValues(t, "TestUserEmail@gitea.io", el[0].Email)
assert.True(t, el[0].Primary)
// AddEmail
mails := []string{"wow@mail.send", "speed@mail.me"}
el, _, err = c.AddEmail(CreateEmailOption{Emails: mails})
assert.NoError(t, err)
assert.Len(t, el, 2)
assert.Len(t, el, 3)
_, _, err = c.AddEmail(CreateEmailOption{Emails: []string{mails[1]}})
assert.Error(t, err)
el, _, err = c.ListEmails(ListEmailsOptions{})

View File

@ -6,13 +6,14 @@ package gitea
import (
"fmt"
"strings"
"github.com/hashicorp/go-version"
)
// ServerVersion returns the version of the server
func (c *Client) ServerVersion() (string, *Response, error) {
var v = struct {
v := struct {
Version string `json:"version"`
}{}
resp, err := c.getParsedResponse("GET", "/version", nil, nil, &v)
@ -39,17 +40,58 @@ func (c *Client) CheckServerVersionConstraint(constraint string) error {
return nil
}
// SetGiteaVersion configures the Client to assume the given version of the
// Gitea server, instead of querying the server for it when initializing.
// Use "" to skip all canonical ways in the SDK to check for versions
func SetGiteaVersion(v string) ClientOption {
if v == "" {
return func(c *Client) error {
c.ignoreVersion = true
return nil
}
}
return func(c *Client) (err error) {
c.getVersionOnce.Do(func() {
c.serverVersion, err = version.NewVersion(v)
})
return
}
}
// predefined versions only have to be parsed by library once
var (
version1_11_0, _ = version.NewVersion("1.11.0")
version1_12_0, _ = version.NewVersion("1.12.0")
version1_13_0, _ = version.NewVersion("1.13.0")
version1_14_0, _ = version.NewVersion("1.14.0")
version1_15_0, _ = version.NewVersion("1.15.0")
version1_11_0 = version.Must(version.NewVersion("1.11.0"))
version1_11_5 = version.Must(version.NewVersion("1.11.5"))
version1_12_0 = version.Must(version.NewVersion("1.12.0"))
version1_12_3 = version.Must(version.NewVersion("1.12.3"))
version1_13_0 = version.Must(version.NewVersion("1.13.0"))
version1_14_0 = version.Must(version.NewVersion("1.14.0"))
version1_15_0 = version.Must(version.NewVersion("1.15.0"))
version1_16_0 = version.Must(version.NewVersion("1.16.0"))
version1_17_0 = version.Must(version.NewVersion("1.17.0"))
version1_22_0 = version.Must(version.NewVersion("1.22.0"))
)
// checkServerVersionGreaterThanOrEqual is internally used to speed up things and ignore issues with prerelease
// ErrUnknownVersion is an unknown version from the API
type ErrUnknownVersion struct {
raw string
}
// Error fulfills error
func (e *ErrUnknownVersion) Error() string {
return fmt.Sprintf("unknown version: %s", e.raw)
}
func (*ErrUnknownVersion) Is(target error) bool {
_, ok := target.(*ErrUnknownVersion)
return ok
}
// checkServerVersionGreaterThanOrEqual is the canonical way in the SDK to check for versions for API compatibility reasons
func (c *Client) checkServerVersionGreaterThanOrEqual(v *version.Version) error {
if c.ignoreVersion {
return nil
}
if err := c.loadServerVersion(); err != nil {
return err
}
@ -72,6 +114,11 @@ func (c *Client) loadServerVersion() (err error) {
return
}
if c.serverVersion, err = version.NewVersion(raw); err != nil {
if strings.TrimSpace(raw) != "" {
// Version was something, just not recognized
c.serverVersion = version1_11_0
err = &ErrUnknownVersion{raw: raw}
}
return
}
})

View File

@ -18,6 +18,15 @@ func TestVersion(t *testing.T) {
assert.NoError(t, err)
assert.True(t, true, rawVersion != "")
assert.NoError(t, c.CheckServerVersionConstraint(">= 1.11.0"))
assert.NoError(t, c.checkServerVersionGreaterThanOrEqual(version1_11_0))
assert.Error(t, c.CheckServerVersionConstraint("< 1.11.0"))
c.serverVersion = version1_11_0
assert.Error(t, c.checkServerVersionGreaterThanOrEqual(version1_15_0))
c.ignoreVersion = true
assert.NoError(t, c.checkServerVersionGreaterThanOrEqual(version1_15_0))
c, err = NewClient(getGiteaURL(), newTestClientAuth(), SetGiteaVersion("1.12.123"))
assert.NoError(t, err)
assert.NoError(t, c.CheckServerVersionConstraint("=1.12.123"))
}