mirror of https://gitea.com/gitea/go-sdk.git
Compare commits
102 Commits
gitea/v0.1
...
main
Author | SHA1 | Date |
---|---|---|
appleboy | 8110db1e6d | |
Bo-Yi Wu | 83c73e79a5 | |
Bo-Yi Wu | 6c0938dc15 | |
tobiasbp | 0a99bda0cb | |
Bo-Yi Wu | 36723e11c8 | |
appleboy | a991e370c5 | |
appleboy | bb25c989a0 | |
appleboy | 18e5522f90 | |
Lunny Xiao | bad4de0196 | |
rjhaverkamp | 9ec1b82849 | |
6543 | bf71ec2fd9 | |
Martijn van der Kleijn | 001d5fad51 | |
diogo464 | 5d0143e4e7 | |
Simon Ser | 53f735b911 | |
Christian Groschupp | 98b424a8af | |
Bo-Yi Wu | 2abfdd7ba6 | |
Andreas Wachter | 8f4d3d8916 | |
ysicing | bde56fb9bb | |
ysicing | 228d6931f4 | |
appleboy | c8745e1599 | |
Lauris BH | 0e0a4691b6 | |
crapStone | e23e8aa300 | |
venc0r | d5e174e5b5 | |
appleboy | 0141b499c9 | |
Bo-Yi Wu | 091528835f | |
infinoid | ae1583a7be | |
John Olheiser | 93cfc320cf | |
b398f0fcac | 0fe2ace132 | |
Alessandro De Blasis | f4be505bf6 | |
JohnWalkerx | 315cf7aac8 | |
Zettat123 | 970776d1c1 | |
Norwin | 24a404e561 | |
jolheiser | 3c5cad657b | |
6543 | 6d1bcd107f | |
John Olheiser | 7511c6d3cd | |
Dmitry Afanasiev | 846d53e967 | |
John Olheiser | df1269f18d | |
John Olheiser | 2c35c11772 | |
edieruby | 2d9ee57af1 | |
qwerty287 | 1c9c71d84a | |
6543 | 8f846bdb9b | |
earl-warren | aef4e5e2bd | |
6543 | 7aeaaa45e1 | |
Norwin | e5b5b3447a | |
6543 | 5852fcc4a3 | |
6543 | a0127ed0e7 | |
6543 | cc14c63ccc | |
lafriks | de34275bb6 | |
Wim | e5f0c189f2 | |
6543 | 359c771ce3 | |
Gusted | 99a9de3172 | |
6543 | 23e1316337 | |
Gusted | f3ebdb8afe | |
arkamar | 319a978c6c | |
Gusted | adebf1cd11 | |
Gusted | 8fab37e740 | |
John Olheiser | ce9d46682d | |
John Olheiser | 89a4b0be6e | |
Gusted | ad3580e44d | |
Gusted | 559cc2fb2a | |
Gusted | 468d48c978 | |
Norwin | 223f0a75e0 | |
Gusted | 2e8bb53b30 | |
John Olheiser | 321bd56d93 | |
John Olheiser | 2616d10528 | |
Gusted | 603e4358f8 | |
Gusted | a56a62a4df | |
Gusted | f3162e5333 | |
Lunny Xiao | 22f2853429 | |
petergardfjall | f0663b3c13 | |
qwerty287 | 29e6eb37fe | |
Norwin | 36c7f8c8de | |
takirala | 73182f46eb | |
Norwin | 3ff2c60a86 | |
6543 | d19bc07721 | |
6543 | 635de1b821 | |
6543 | a87a2c7390 | |
spawn2kill | 5d54a04f8d | |
Norwin | 0fb32ac11f | |
Lunny Xiao | bf0e0883a8 | |
6543 | 4debc6ca4b | |
6543 | 747c78e835 | |
6543 | 71d2bf01d1 | |
6543 | b81847d03d | |
6543 | 63e97e127c | |
6543 | f5cc003900 | |
6543 | dee0475f01 | |
6543 | 79f379313c | |
6543 | 230fd25196 | |
6543 | 13d2d23dfc | |
6543 | cc08994d13 | |
6543 | ddc879e297 | |
6543 | a5ff184297 | |
6543 | fb42ca1c8d | |
6543 | 42fed7165c | |
J0Nes90 | e11a4f7f3b | |
J0Nes90 | 00ddb6b116 | |
J0Nes90 | 4e4d6dd731 | |
6543 | 7de882b744 | |
6543 | 0599915e88 | |
6543 | 120aa6234e | |
6543 | 4fac753f78 |
|
@ -10,11 +10,11 @@ base-url: https://gitea.com
|
|||
|
||||
# Changelog groups and which labeled PRs to add to each group
|
||||
groups:
|
||||
-
|
||||
-
|
||||
name: BREAKING
|
||||
labels:
|
||||
- kind/breaking
|
||||
-
|
||||
-
|
||||
name: FEATURES
|
||||
labels:
|
||||
- kind/feature
|
||||
|
@ -22,7 +22,7 @@ groups:
|
|||
name: BUGFIXES
|
||||
labels:
|
||||
- kind/bug
|
||||
-
|
||||
-
|
||||
name: ENHANCEMENTS
|
||||
labels:
|
||||
- kind/enhancement
|
||||
|
@ -32,26 +32,26 @@ groups:
|
|||
name: SECURITY
|
||||
labels:
|
||||
- kind/security
|
||||
-
|
||||
-
|
||||
name: TESTING
|
||||
labels:
|
||||
- kind/testing
|
||||
-
|
||||
-
|
||||
name: TRANSLATION
|
||||
labels:
|
||||
- kind/translation
|
||||
-
|
||||
-
|
||||
name: BUILD
|
||||
labels:
|
||||
- kind/build
|
||||
- kind/lint
|
||||
-
|
||||
-
|
||||
name: DOCS
|
||||
labels:
|
||||
- kind/docs
|
||||
-
|
||||
-
|
||||
name: MISC
|
||||
default: true
|
||||
|
||||
# regex indicating which labels to skip for the changelog
|
||||
skip-labels: skip-changelog|backport\/.+
|
||||
skip-labels: skip-changelog|backport\/.+|has/backport
|
||||
|
|
69
.drone.yml
69
.drone.yml
|
@ -1,69 +0,0 @@
|
|||
---
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.gitea.io/sdk
|
||||
|
||||
steps:
|
||||
- name: gitea
|
||||
image: gitea/gitea:latest
|
||||
detach: true
|
||||
commands:
|
||||
- mkdir -p /tmp/conf/
|
||||
- mkdir -p /tmp/data/
|
||||
- echo "[security]" > /tmp/conf/app.ini
|
||||
- echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> /tmp/conf/app.ini
|
||||
- echo "INSTALL_LOCK = true" >> /tmp/conf/app.ini
|
||||
- echo "SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo" >> /tmp/conf/app.ini
|
||||
- echo "PASSWORD_COMPLEXITY = off" >> /tmp/conf/app.ini
|
||||
- echo "[database]" >> /tmp/conf/app.ini
|
||||
- echo "DB_TYPE = sqlite3" >> /tmp/conf/app.ini
|
||||
- echo "[repository]" >> /tmp/conf/app.ini
|
||||
- echo "ROOT = /tmp/data/" >> /tmp/conf/app.ini
|
||||
- echo "[server]" >> /tmp/conf/app.ini
|
||||
- echo "ROOT_URL = http://gitea:3000" >> /tmp/conf/app.ini
|
||||
- gitea migrate -c /tmp/conf/app.ini
|
||||
- gitea admin 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.14
|
||||
environment:
|
||||
GOPROXY: "https://goproxy.cn"
|
||||
GO111MODULE: "on"
|
||||
HTTP_PROXY: ""
|
||||
GITEA_SDK_TEST_URL: "http://gitea:3000"
|
||||
GITEA_SDK_TEST_USERNAME: "test01"
|
||||
GITEA_SDK_TEST_PASSWORD: "test01"
|
||||
#GITEA_SDK_TEST_RUN_GITEA: "true"
|
||||
commands:
|
||||
- make clean
|
||||
- make vet
|
||||
- make revive
|
||||
- make build
|
||||
- curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance
|
||||
- make test
|
||||
|
||||
- name: discord
|
||||
pull: always
|
||||
image: appleboy/drone-discord:1.0.0
|
||||
environment:
|
||||
DISCORD_WEBHOOK_ID:
|
||||
from_secret: discord_webhook_id
|
||||
DISCORD_WEBHOOK_TOKEN:
|
||||
from_secret: discord_webhook_token
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
- pull_request
|
||||
status:
|
||||
- changed
|
||||
- failure
|
|
@ -0,0 +1,52 @@
|
|||
name: testing
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
- push
|
||||
|
||||
jobs:
|
||||
testing:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GOPROXY: "https://goproxy.io"
|
||||
GO111MODULE: "on"
|
||||
HTTP_PROXY: ""
|
||||
GITEA_SDK_TEST_URL: "http://gitea:3000"
|
||||
GITEA_SDK_TEST_USERNAME: "test01"
|
||||
GITEA_SDK_TEST_PASSWORD: "test01"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ">=1.21"
|
||||
check-latest: true
|
||||
- run: make clean
|
||||
- run: make vet
|
||||
- run: make ci-lint
|
||||
- run: make build
|
||||
- run: curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance
|
||||
- run: make test
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:1.22.0-rc1
|
||||
cmd:
|
||||
- bash
|
||||
- -c
|
||||
- >-
|
||||
mkdir -p /tmp/conf/
|
||||
&& mkdir -p /tmp/data/
|
||||
&& echo "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT = true" > /tmp/conf/app.ini
|
||||
&& echo "[security]" >> /tmp/conf/app.ini
|
||||
&& echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> /tmp/conf/app.ini
|
||||
&& echo "INSTALL_LOCK = true" >> /tmp/conf/app.ini
|
||||
&& echo "SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo" >> /tmp/conf/app.ini
|
||||
&& echo "PASSWORD_COMPLEXITY = off" >> /tmp/conf/app.ini
|
||||
&& echo "[database]" >> /tmp/conf/app.ini
|
||||
&& echo "DB_TYPE = sqlite3" >> /tmp/conf/app.ini
|
||||
&& echo "[repository]" >> /tmp/conf/app.ini
|
||||
&& echo "ROOT = /tmp/data/" >> /tmp/conf/app.ini
|
||||
&& echo "[server]" >> /tmp/conf/app.ini
|
||||
&& echo "ROOT_URL = http://gitea:3000" >> /tmp/conf/app.ini
|
||||
&& gitea migrate -c /tmp/conf/app.ini
|
||||
&& gitea admin user create --username=test01 --password=test01 --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c /tmp/conf/app.ini
|
||||
&& gitea web -c /tmp/conf/app.ini
|
|
@ -1,7 +1,7 @@
|
|||
Please check the following:
|
||||
|
||||
1. Make sure you are targeting the `master` branch, pull requests on release branches are only allowed for bug fixes.
|
||||
2. Read contributing guidelines: https://gitea.com/gitea/go-sdk/src/branch/master/CONTRIBUTING.md
|
||||
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for bug fixes.
|
||||
2. Read contributing guidelines: https://gitea.com/gitea/go-sdk/src/branch/main/CONTRIBUTING.md
|
||||
3. Describe what your pull request does and which issue you're targeting (if any)
|
||||
|
||||
**You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.**
|
||||
|
|
|
@ -18,6 +18,7 @@ _cgo_gotypes.go
|
|||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
gitea-vet
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
|
|
44
CHANGELOG.md
44
CHANGELOG.md
|
@ -1,6 +1,40 @@
|
|||
# Changelog
|
||||
|
||||
## [v0.14.0](https://gitea.com/gitea/go-sdk/releases/tag/v0.14.0) - 2021-03-21
|
||||
## [v0.15.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.15.1) - 2022-01-04
|
||||
|
||||
* FEATURES
|
||||
* Add ignoreVersion & manuall version set option (#560) (#562)
|
||||
* BUGFIXES
|
||||
* Fix version string for next release (#559)
|
||||
|
||||
|
||||
## [v0.15.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.15.0) - 2021-08-13
|
||||
|
||||
* BREAKING
|
||||
* Introduce NotifySubjectState (#520)
|
||||
* Drop deprecations (#503)
|
||||
* FEATURES
|
||||
* Add Repo Team Management Functions (#537)
|
||||
* Add CreateRepoFromTemplate (#536)
|
||||
* Add GetReviewers & GetAssignees (#534)
|
||||
* Add GetTag, GetAnnotatedTag & CreateTag (#533)
|
||||
* Add GetUserSettings & UpdateUserSettings (#531)
|
||||
* Add ListPullRequestCommits (#530)
|
||||
* Add GetUserByID (#513)
|
||||
* Add GetRepoByID (#511)
|
||||
* ENHANCEMENTS
|
||||
* Update List Options (#527)
|
||||
* Update Structs (#524)
|
||||
* ListFunctions: option to disable pagination (#509)
|
||||
|
||||
|
||||
## [v0.14.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.14.1) - 2021-06-30
|
||||
|
||||
* BUGFIXES
|
||||
* Fix setDefaults (#508) (#510)
|
||||
|
||||
|
||||
## [v0.14.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.14.0) - 2021-03-21
|
||||
|
||||
* BREAKING
|
||||
* Update Structs (#486)
|
||||
|
@ -38,6 +72,14 @@
|
|||
* Improve PullReview docs (#469)
|
||||
|
||||
|
||||
## [v0.13.3](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.3) - 2021-03-22
|
||||
|
||||
* BUGFIXES
|
||||
* Fix GetCombinedStatus() (#470) (#472)
|
||||
* ENHANCEMENTS
|
||||
* Add html_url field to Release struct (#477) (#478)
|
||||
|
||||
|
||||
## [v0.13.2](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.2) - 2020-12-07
|
||||
|
||||
* BUGFIXES
|
||||
|
|
61
Makefile
61
Makefile
|
@ -6,8 +6,14 @@ GITEA_SDK_TEST_URL ?= http://localhost:3000
|
|||
GITEA_SDK_TEST_USERNAME ?= test01
|
||||
GITEA_SDK_TEST_PASSWORD ?= test01
|
||||
|
||||
PACKAGE := code.gitea.io/sdk/gitea
|
||||
|
||||
GITEA_DL := https://dl.gitea.io/gitea/master/gitea-master-
|
||||
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-
|
||||
|
@ -27,7 +33,7 @@ ifeq ($(UNAME_S),Linux)
|
|||
endif
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
GITEA_DL := $(GITEA_DL)darwin-10.6-amd64
|
||||
GITEA_DL := $(GITEA_DL)darwin-10.12-amd64
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
|
@ -53,22 +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:
|
||||
cd gitea && $(GO) vet ./...
|
||||
# Default vet
|
||||
cd gitea && $(GO) vet $(PACKAGE)
|
||||
# Custom vet
|
||||
cd gitea && $(GO) get $(GITEA_VET_PACKAGE)
|
||||
cd gitea && $(GO) build code.gitea.io/gitea-vet
|
||||
cd gitea && $(GO) vet -vettool=gitea-vet $(PACKAGE)
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@echo 'make lint is depricated. Use "make revive" if you want to use the old lint tool, or "make golangci-lint" to run a complete code check.'
|
||||
|
||||
.PHONY: revive
|
||||
revive:
|
||||
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/mgechev/revive; \
|
||||
fi
|
||||
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
|
||||
.PHONY: ci-lint
|
||||
ci-lint:
|
||||
@cd gitea/; echo -n "gofumpt ...";\
|
||||
diff=$$($(GO) run $(GOFUMPT_PACKAGE) -extra -l .); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo; echo "Not gofumpt-ed"; \
|
||||
exit 1; \
|
||||
fi; echo " done"; echo -n "golangci-lint ...";\
|
||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run --timeout 5m; \
|
||||
if [ $$? -eq 1 ]; then \
|
||||
echo; echo "Doesn't pass golangci-lint"; \
|
||||
exit 1; \
|
||||
fi; echo " done"; \
|
||||
cd -; \
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
@ -81,8 +97,8 @@ test:
|
|||
test-instance:
|
||||
rm -f -r ${WORK_DIR}/test 2> /dev/null; \
|
||||
mkdir -p ${WORK_DIR}/test/conf/ ${WORK_DIR}/test/data/
|
||||
wget ${GITEA_DL} -O ${WORK_DIR}/test/gitea-master; \
|
||||
chmod +x ${WORK_DIR}/test/gitea-master; \
|
||||
wget ${GITEA_DL} -O ${WORK_DIR}/test/gitea-main; \
|
||||
chmod +x ${WORK_DIR}/test/gitea-main; \
|
||||
echo "[security]" > ${WORK_DIR}/test/conf/app.ini; \
|
||||
echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||
echo "INSTALL_LOCK = true" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||
|
@ -94,9 +110,9 @@ test-instance:
|
|||
echo "ROOT = ${WORK_DIR}/test/data/" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||
echo "[server]" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||
echo "ROOT_URL = ${GITEA_SDK_TEST_URL}" >> ${WORK_DIR}/test/conf/app.ini; \
|
||||
${WORK_DIR}/test/gitea-master migrate -c ${WORK_DIR}/test/conf/app.ini; \
|
||||
${WORK_DIR}/test/gitea-master admin user create --username=${GITEA_SDK_TEST_USERNAME} --password=${GITEA_SDK_TEST_PASSWORD} --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c ${WORK_DIR}/test/conf/app.ini; \
|
||||
${WORK_DIR}/test/gitea-master web -c ${WORK_DIR}/test/conf/app.ini
|
||||
${WORK_DIR}/test/gitea-main migrate -c ${WORK_DIR}/test/conf/app.ini; \
|
||||
${WORK_DIR}/test/gitea-main admin user create --username=${GITEA_SDK_TEST_USERNAME} --password=${GITEA_SDK_TEST_PASSWORD} --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c ${WORK_DIR}/test/conf/app.ini; \
|
||||
${WORK_DIR}/test/gitea-main web -c ${WORK_DIR}/test/conf/app.ini
|
||||
|
||||
.PHONY: bench
|
||||
bench:
|
||||
|
@ -106,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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# Migration Guide: v0.11 to v0.12
|
||||
|
||||
v0.12.0 introduces a number of breaking changes, throu it should not be hard to
|
||||
migrate.
|
||||
Just follow this guid and if issues still ocure ask for help on discord or
|
||||
feel free to create an issue.
|
||||
v0.12.0 introduces a number of breaking changes, through which it should not be difficult to migrate.
|
||||
Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue.
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Migration Guide: v0.12 to v0.13
|
||||
|
||||
v0.13.0 introduces a number of breaking changes, throu it should not be hard to migrate.
|
||||
Just follow this guid and if issues still ocure ask for help on discord or
|
||||
feel free to create an issue.
|
||||
v0.13.0 introduces a number of breaking changes, through which it should not be difficult to migrate.
|
||||
Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue.
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Migration Guide: v0.13 to v0.14
|
||||
|
||||
v0.14.0 introduces a number of breaking changes, throu it should not be hard to migrate.
|
||||
Just follow this guid and if issues still ocure ask for help on discord or
|
||||
feel free to create an issue.
|
||||
v0.14.0 introduces a number of breaking changes, through which it should not be difficult to migrate.
|
||||
Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue.
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# Migration Guide: v0.14 to v0.15
|
||||
|
||||
v0.15.0 introduces a number of API changes, which should be simple to migrate.
|
||||
Just follow this guide and if you still encounter problems, ask for help on Discord or feel free to create an issue.
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [Changed Struct Fields (#503) (#520)](#changed-struct-fields)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Changed Struct Fields
|
||||
|
||||
- The `State` field at **NotificationSubject** changed from **StateType** to **NotifySubjectState**, it also contains `"open"`, `"closed"` and add `"merged"`.
|
||||
- In **Issue**, **CreateIssueOption** and **EditIssueOption** structs, `Assignee` got removed. Use `Assignees`.
|
||||
- `Type` field at **CreateHookOption** now use **HookType** instead of pure string.
|
||||
|
||||
Pulls:
|
||||
- [#503 Drop deprecations](https://gitea.com/gitea/go-sdk/pulls/503)
|
||||
- [#520 Introduce NotifySubjectState](https://gitea.com/gitea/go-sdk/pulls/520)
|
|
@ -0,0 +1,28 @@
|
|||
# Migration Guide: v0.15 to v0.16
|
||||
|
||||
v0.16.0 introduces a number of API changes, which should be simple to migrate.
|
||||
Just follow this guide and if you still encounter problems, ask for help on Discord or feel free to create an issue.
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [Upstream API changes](#upstream-api-changes)
|
||||
- [GetPullRequestDiff: add PullRequestDiffOption parameter (#542)](#getpullrequestdiff)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Upstream API changes
|
||||
|
||||
As we aim to track API changes in Gitea 1.16 with this SDK release, you may find this [summary listing of changes](https://gitea.com/gitea/go-sdk/issues/558) helpful.
|
||||
|
||||
## GetPullRequestDiff
|
||||
Added new parameter `opts PullRequestDiffOption`. Gitea 1.16 will default to omit binary file changes in diffs; if you still need that information, set `opts.Binary = true`.
|
||||
Related PRs:
|
||||
- [go-sdk#542](https://gitea.com/gitea/go-sdk/pulls/542)
|
||||
- [gitea#17158](https://github.com/go-gitea/gitea/pull/17158)
|
||||
|
||||
## ReadNotification, ReadNotifications, ReadRepoNotifications
|
||||
The function now has a new return argument. The read notifications will now be returned by Gitea 1.16. If you don't require this information, use a blank identifier for the return variable.
|
||||
|
||||
Related PRs:
|
||||
- [go-sdk#590](https://gitea.com/gitea/go-sdk/pulls/590)
|
||||
- [gitea#17064](https://github.com/go-gitea/gitea/pull/17064)
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestLabels test label related func
|
||||
func TestLabels(t *testing.T) {
|
||||
log.Println("== TestLabels ==")
|
||||
c := newTestClient()
|
||||
repo, err := createTestRepo(t, "LabelTestsRepo", c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
createOpts := CreateLabelOption{
|
||||
Name: " ",
|
||||
Description: "",
|
||||
Color: "",
|
||||
}
|
||||
err = createOpts.Validate()
|
||||
assert.Error(t, err)
|
||||
assert.EqualValues(t, "invalid color format", err.Error())
|
||||
createOpts.Color = "12345f"
|
||||
err = createOpts.Validate()
|
||||
assert.Error(t, err)
|
||||
assert.EqualValues(t, "empty name not allowed", err.Error())
|
||||
createOpts.Name = "label one"
|
||||
|
||||
label1, _, err := c.CreateLabel(repo.Owner.UserName, repo.Name, createOpts)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, createOpts.Name, label1.Name)
|
||||
assert.EqualValues(t, createOpts.Color, label1.Color)
|
||||
|
||||
}
|
|
@ -45,7 +45,7 @@ func TestAdminCronTasks(t *testing.T) {
|
|||
|
||||
tasks, _, err := c.ListCronTasks(ListCronTaskOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, tasks, 17)
|
||||
assert.True(t, len(tasks) > 15)
|
||||
_, err = c.RunCronTasks(tasks[0].Name)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -26,14 +26,15 @@ func (c *Client) AdminListUsers(opt AdminListUsersOptions) ([]*User, *Response,
|
|||
|
||||
// CreateUserOption create user options
|
||||
type CreateUserOption struct {
|
||||
SourceID int64 `json:"source_id"`
|
||||
LoginName string `json:"login_name"`
|
||||
Username string `json:"username"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
MustChangePassword *bool `json:"must_change_password"`
|
||||
SendNotify bool `json:"send_notify"`
|
||||
SourceID int64 `json:"source_id"`
|
||||
LoginName string `json:"login_name"`
|
||||
Username string `json:"username"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
MustChangePassword *bool `json:"must_change_password"`
|
||||
SendNotify bool `json:"send_notify"`
|
||||
Visibility *VisibleType `json:"visibility"`
|
||||
}
|
||||
|
||||
// Validate the CreateUserOption struct
|
||||
|
@ -63,21 +64,24 @@ func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error)
|
|||
|
||||
// EditUserOption edit user options
|
||||
type EditUserOption struct {
|
||||
SourceID int64 `json:"source_id"`
|
||||
LoginName string `json:"login_name"`
|
||||
Email *string `json:"email"`
|
||||
FullName *string `json:"full_name"`
|
||||
Password string `json:"password"`
|
||||
MustChangePassword *bool `json:"must_change_password"`
|
||||
Website *string `json:"website"`
|
||||
Location *string `json:"location"`
|
||||
Active *bool `json:"active"`
|
||||
Admin *bool `json:"admin"`
|
||||
AllowGitHook *bool `json:"allow_git_hook"`
|
||||
AllowImportLocal *bool `json:"allow_import_local"`
|
||||
MaxRepoCreation *int `json:"max_repo_creation"`
|
||||
ProhibitLogin *bool `json:"prohibit_login"`
|
||||
AllowCreateOrganization *bool `json:"allow_create_organization"`
|
||||
SourceID int64 `json:"source_id"`
|
||||
LoginName string `json:"login_name"`
|
||||
Email *string `json:"email"`
|
||||
FullName *string `json:"full_name"`
|
||||
Password string `json:"password"`
|
||||
Description *string `json:"description"`
|
||||
MustChangePassword *bool `json:"must_change_password"`
|
||||
Website *string `json:"website"`
|
||||
Location *string `json:"location"`
|
||||
Active *bool `json:"active"`
|
||||
Admin *bool `json:"admin"`
|
||||
AllowGitHook *bool `json:"allow_git_hook"`
|
||||
AllowImportLocal *bool `json:"allow_import_local"`
|
||||
MaxRepoCreation *int `json:"max_repo_creation"`
|
||||
ProhibitLogin *bool `json:"prohibit_login"`
|
||||
AllowCreateOrganization *bool `json:"allow_create_organization"`
|
||||
Restricted *bool `json:"restricted"`
|
||||
Visibility *VisibleType `json:"visibility"`
|
||||
}
|
||||
|
||||
// AdminEditUser modify user informations
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
// hasAgent returns true if the ssh agent is available
|
||||
func hasAgent() bool {
|
||||
if _, err := os.Stat(os.Getenv("SSH_AUTH_SOCK")); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetAgent returns a ssh agent
|
||||
func GetAgent() (agent.Agent, error) {
|
||||
if !hasAgent() {
|
||||
return nil, fmt.Errorf("no ssh agent available")
|
||||
}
|
||||
|
||||
sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return agent.NewClient(sshAgent), nil
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/davidmz/go-pageant"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
// hasAgent returns true if pageant is available
|
||||
func hasAgent() bool {
|
||||
return pageant.Available()
|
||||
}
|
||||
|
||||
// GetAgent returns a ssh agent
|
||||
func GetAgent() (agent.Agent, error) {
|
||||
if !hasAgent() {
|
||||
return nil, fmt.Errorf("no pageant available")
|
||||
}
|
||||
|
||||
return pageant.New(), nil
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
250
gitea/client.go
250
gitea/client.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParsedPaging(t *testing.T) {
|
||||
resp := newResponse(&http.Response{
|
||||
Header: http.Header{
|
||||
"Link": []string{
|
||||
strings.Join(
|
||||
[]string{
|
||||
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=3>; rel="next"`,
|
||||
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=4>; rel="last"`,
|
||||
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=1>; rel="first"`,
|
||||
`<https://try.gitea.io/api/v1/repos/gitea/go-sdk/issues/1/comments?page=1>; rel="prev"`,
|
||||
}, ",",
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
assert.Equal(t, 1, resp.FirstPage)
|
||||
assert.Equal(t, 1, resp.PrevPage)
|
||||
assert.Equal(t, 3, resp.NextPage)
|
||||
assert.Equal(t, 4, resp.LastPage)
|
||||
}
|
|
@ -2,4 +2,8 @@
|
|||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package gitea implements a client for the Gitea API.
|
||||
// The version corresponds to the highest supported version
|
||||
// of the gitea API, but backwards-compatibility is mostly
|
||||
// given.
|
||||
package gitea // import "code.gitea.io/sdk/gitea"
|
||||
|
|
|
@ -16,7 +16,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
|
||||
|
|
16
gitea/go.mod
16
gitea/go.mod
|
@ -1,8 +1,18 @@
|
|||
module code.gitea.io/sdk/gitea
|
||||
|
||||
go 1.13
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
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
|
||||
)
|
||||
|
|
35
gitea/go.sum
35
gitea/go.sum
|
@ -1,15 +1,34 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/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/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -24,6 +24,28 @@ type Hook struct {
|
|||
Created time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// HookType represent all webhook types gitea currently offer
|
||||
type HookType string
|
||||
|
||||
const (
|
||||
// HookTypeDingtalk webhook that dingtalk understand
|
||||
HookTypeDingtalk HookType = "dingtalk"
|
||||
// HookTypeDiscord webhook that discord understand
|
||||
HookTypeDiscord HookType = "discord"
|
||||
// HookTypeGitea webhook that gitea understand
|
||||
HookTypeGitea HookType = "gitea"
|
||||
// HookTypeGogs webhook that gogs understand
|
||||
HookTypeGogs HookType = "gogs"
|
||||
// HookTypeMsteams webhook that msteams understand
|
||||
HookTypeMsteams HookType = "msteams"
|
||||
// HookTypeSlack webhook that slack understand
|
||||
HookTypeSlack HookType = "slack"
|
||||
// HookTypeTelegram webhook that telegram understand
|
||||
HookTypeTelegram HookType = "telegram"
|
||||
// HookTypeFeishu webhook that feishu understand
|
||||
HookTypeFeishu HookType = "feishu"
|
||||
)
|
||||
|
||||
// ListHooksOptions options for listing hooks
|
||||
type ListHooksOptions struct {
|
||||
ListOptions
|
||||
|
@ -40,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 {
|
||||
|
@ -61,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 {
|
||||
|
@ -73,11 +110,12 @@ func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, err
|
|||
|
||||
// CreateHookOption options when create a hook
|
||||
type CreateHookOption struct {
|
||||
Type string `json:"type"`
|
||||
Config map[string]string `json:"config"`
|
||||
Events []string `json:"events"`
|
||||
BranchFilter string `json:"branch_filter"`
|
||||
Active bool `json:"active"`
|
||||
Type HookType `json:"type"`
|
||||
Config map[string]string `json:"config"`
|
||||
Events []string `json:"events"`
|
||||
BranchFilter string `json:"branch_filter"`
|
||||
Active bool `json:"active"`
|
||||
AuthorizationHeader string `json:"authorization_header"`
|
||||
}
|
||||
|
||||
// Validate the CreateHookOption struct
|
||||
|
@ -105,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 {
|
||||
|
@ -121,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
|
||||
|
@ -140,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 {
|
||||
|
@ -162,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 {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// VerifyWebhookSignature verifies that a payload matches the X-Gitea-Signature based on a secret
|
||||
func VerifyWebhookSignature(secret, expected string, payload []byte) (bool, error) {
|
||||
hash := hmac.New(sha256.New, []byte(secret))
|
||||
if _, err := hash.Write(payload); err != nil {
|
||||
return false, err
|
||||
}
|
||||
expectedSum, err := hex.DecodeString(expected)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return hmac.Equal(hash.Sum(nil), expectedSum), nil
|
||||
}
|
||||
|
||||
// VerifyWebhookSignatureMiddleware is a http.Handler for verifying X-Gitea-Signature on incoming webhooks
|
||||
func VerifyWebhookSignatureMiddleware(secret string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var b bytes.Buffer
|
||||
if _, err := io.Copy(&b, r.Body); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
expected := r.Header.Get("X-Gitea-Signature")
|
||||
if expected == "" {
|
||||
http.Error(w, "no signature found", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ok, err := VerifyWebhookSignature(secret, expected, b.Bytes())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
http.Error(w, "invalid payload", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
r.Body = io.NopCloser(&b)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Hashers are based on https://github.com/go-gitea/gitea/blob/0dfc2e55ea258d2b1a3cd86e2b6f27a481e495ff/services/webhook/deliver.go#L105-L116
|
||||
|
||||
func TestVerifyWebhookSignature(t *testing.T) {
|
||||
secret := "s3cr3t"
|
||||
payload := []byte(`{"foo": "bar", "baz": true}`)
|
||||
|
||||
hasher := hmac.New(sha256.New, []byte(secret))
|
||||
hasher.Write(payload)
|
||||
sig := hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
tt := []struct {
|
||||
Name string
|
||||
Secret string
|
||||
Payload string
|
||||
Succeed bool
|
||||
}{
|
||||
{
|
||||
Name: "Correct secret and payload",
|
||||
Secret: "s3cr3t",
|
||||
Payload: `{"foo": "bar", "baz": true}`,
|
||||
Succeed: true,
|
||||
},
|
||||
{
|
||||
Name: "Correct secret bad payload",
|
||||
Secret: "s3cr3t",
|
||||
Payload: "{}",
|
||||
Succeed: false,
|
||||
},
|
||||
{
|
||||
Name: "Incorrect secret good payload",
|
||||
Secret: "secret",
|
||||
Payload: `{"foo": "bar", "baz": true}`,
|
||||
Succeed: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
ok, err := VerifyWebhookSignature(tc.Secret, sig, []byte(tc.Payload))
|
||||
assert.NoError(t, err, "verification should not error")
|
||||
assert.True(t, ok == tc.Succeed, "verification should be %t", tc.Succeed)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyWebhookSignatureHandler(t *testing.T) {
|
||||
secret := "s3cr3t"
|
||||
payload := []byte(`{"foo": "bar", "baz": true}`)
|
||||
|
||||
hasher := hmac.New(sha256.New, []byte(secret))
|
||||
hasher.Write(payload)
|
||||
sig := hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
tt := []struct {
|
||||
Name string
|
||||
Secret string
|
||||
Payload string
|
||||
Signature string
|
||||
Status int
|
||||
}{
|
||||
{
|
||||
Name: "Correct secret and payload",
|
||||
Secret: "s3cr3t",
|
||||
Payload: `{"foo": "bar", "baz": true}`,
|
||||
Signature: sig,
|
||||
Status: http.StatusOK,
|
||||
},
|
||||
{
|
||||
Name: "Correct secret bad payload",
|
||||
Secret: "s3cr3t",
|
||||
Payload: "{}",
|
||||
Signature: sig,
|
||||
Status: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
Name: "Incorrect secret good payload",
|
||||
Secret: "secret",
|
||||
Payload: `{"foo": "bar", "baz": true}`,
|
||||
Signature: sig,
|
||||
Status: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
Name: "No signature",
|
||||
Status: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
server := httptest.NewServer(VerifyWebhookSignatureMiddleware(tc.Secret)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write(nil)
|
||||
})))
|
||||
defer server.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, server.URL, strings.NewReader(tc.Payload))
|
||||
assert.NoError(t, err, "should create request")
|
||||
|
||||
if tc.Signature != "" {
|
||||
req.Header.Set("X-Gitea-Signature", tc.Signature)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
assert.NoError(t, err, "request should be delivered")
|
||||
assert.True(t, resp.StatusCode == tc.Status, "status should be %d, but got %d", tc.Status, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-fed/httpsig"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// HTTPSign contains the signer used for signing requests
|
||||
type HTTPSign struct {
|
||||
ssh.Signer
|
||||
cert bool
|
||||
}
|
||||
|
||||
// HTTPSignConfig contains the configuration for creating a HTTPSign
|
||||
type HTTPSignConfig struct {
|
||||
fingerprint string
|
||||
principal string
|
||||
pubkey bool
|
||||
cert bool
|
||||
sshKey string
|
||||
passphrase string
|
||||
}
|
||||
|
||||
// NewHTTPSignWithPubkey can be used to create a HTTPSign with a public key
|
||||
// if no fingerprint is specified it returns the first public key found
|
||||
func NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase string) (*HTTPSign, error) {
|
||||
return newHTTPSign(&HTTPSignConfig{
|
||||
fingerprint: fingerprint,
|
||||
pubkey: true,
|
||||
sshKey: sshKey,
|
||||
passphrase: passphrase,
|
||||
})
|
||||
}
|
||||
|
||||
// NewHTTPSignWithCert can be used to create a HTTPSign with a certificate
|
||||
// if no principal is specified it returns the first certificate found
|
||||
func NewHTTPSignWithCert(principal, sshKey, passphrase string) (*HTTPSign, error) {
|
||||
return newHTTPSign(&HTTPSignConfig{
|
||||
principal: principal,
|
||||
cert: true,
|
||||
sshKey: sshKey,
|
||||
passphrase: passphrase,
|
||||
})
|
||||
}
|
||||
|
||||
// NewHTTPSign returns a new HTTPSign
|
||||
// It will check the ssh-agent or a local file is config.sshKey is set.
|
||||
// Depending on the configuration it will either use a certificate or a public key
|
||||
func newHTTPSign(config *HTTPSignConfig) (*HTTPSign, error) {
|
||||
var signer ssh.Signer
|
||||
|
||||
if config.sshKey != "" {
|
||||
priv, err := os.ReadFile(config.sshKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.passphrase == "" {
|
||||
signer, err = ssh.ParsePrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
signer, err = ssh.ParsePrivateKeyWithPassphrase(priv, []byte(config.passphrase))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.cert {
|
||||
certbytes, err := os.ReadFile(config.sshKey + "-cert.pub")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pub, _, _, _, err := ssh.ParseAuthorizedKey(certbytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, ok := pub.(*ssh.Certificate)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to parse certificate")
|
||||
}
|
||||
|
||||
signer, err = ssh.NewCertSigner(cert, signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if no sshKey is specified, check if we have a ssh-agent and use it
|
||||
agent, err := GetAgent()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signers, err := agent.Signers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(signers) == 0 {
|
||||
return nil, fmt.Errorf("no signers found")
|
||||
}
|
||||
|
||||
if config.cert {
|
||||
signer = findCertSigner(signers, config.principal)
|
||||
if signer == nil {
|
||||
return nil, fmt.Errorf("no certificate found for %s", config.principal)
|
||||
}
|
||||
}
|
||||
|
||||
if config.pubkey {
|
||||
signer = findPubkeySigner(signers, config.fingerprint)
|
||||
if signer == nil {
|
||||
return nil, fmt.Errorf("no public key found for %s", config.fingerprint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &HTTPSign{
|
||||
Signer: signer,
|
||||
cert: config.cert,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignRequest signs a HTTP request
|
||||
func (c *Client) SignRequest(r *http.Request) error {
|
||||
var contents []byte
|
||||
|
||||
headersToSign := []string{httpsig.RequestTarget, "(created)", "(expires)"}
|
||||
|
||||
if c.httpsigner.cert {
|
||||
// add our certificate to the headers to sign
|
||||
pubkey, _ := ssh.ParsePublicKey(c.httpsigner.Signer.PublicKey().Marshal())
|
||||
if cert, ok := pubkey.(*ssh.Certificate); ok {
|
||||
certString := base64.RawStdEncoding.EncodeToString(cert.Marshal())
|
||||
r.Header.Add("x-ssh-certificate", certString)
|
||||
|
||||
headersToSign = append(headersToSign, "x-ssh-certificate")
|
||||
} else {
|
||||
return fmt.Errorf("no ssh certificate found")
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a body, the Digest header will be added and we'll include this also in
|
||||
// our signature.
|
||||
if r.Body != nil {
|
||||
body, err := r.GetBody()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getBody() failed: %s", err)
|
||||
}
|
||||
|
||||
contents, err = io.ReadAll(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed reading body: %s", err)
|
||||
}
|
||||
|
||||
headersToSign = append(headersToSign, "Digest")
|
||||
}
|
||||
|
||||
// create a signer for the request and headers, the signature will be valid for 10 seconds
|
||||
signer, _, err := httpsig.NewSSHSigner(c.httpsigner.Signer, httpsig.DigestSha512, headersToSign, httpsig.Signature, 10)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpsig.NewSSHSigner failed: %s", err)
|
||||
}
|
||||
|
||||
// sign the request, use the fingerprint if we don't have a certificate
|
||||
keyID := "gitea"
|
||||
if !c.httpsigner.cert {
|
||||
keyID = ssh.FingerprintSHA256(c.httpsigner.Signer.PublicKey())
|
||||
}
|
||||
|
||||
err = signer.SignRequest(keyID, r, contents)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpsig.Signrequest failed: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findCertSigner returns the Signer containing a valid certificate
|
||||
// if no principal is specified it returns the first certificate found
|
||||
func findCertSigner(sshsigners []ssh.Signer, principal string) ssh.Signer {
|
||||
for _, s := range sshsigners {
|
||||
// Check if the key is a certificate
|
||||
if !strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
|
||||
continue
|
||||
}
|
||||
|
||||
// convert the ssh.Signer to a ssh.Certificate
|
||||
mpubkey, _ := ssh.ParsePublicKey(s.PublicKey().Marshal())
|
||||
cryptopub := mpubkey.(crypto.PublicKey)
|
||||
cert := cryptopub.(*ssh.Certificate)
|
||||
t := time.Unix(int64(cert.ValidBefore), 0)
|
||||
|
||||
// make sure the certificate is at least 10 seconds valid
|
||||
if time.Until(t) <= time.Second*10 {
|
||||
continue
|
||||
}
|
||||
|
||||
if principal == "" {
|
||||
return s
|
||||
}
|
||||
|
||||
for _, p := range cert.ValidPrincipals {
|
||||
if p == principal {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findPubkeySigner returns the Signer containing a valid public key
|
||||
// if no fingerprint is specified it returns the first public key found
|
||||
func findPubkeySigner(sshsigners []ssh.Signer, fingerprint string) ssh.Signer {
|
||||
for _, s := range sshsigners {
|
||||
// Check if the key is a certificate
|
||||
if strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
|
||||
continue
|
||||
}
|
||||
|
||||
if fingerprint == "" {
|
||||
return s
|
||||
}
|
||||
|
||||
if strings.TrimSpace(string(ssh.MarshalAuthorizedKey(s.PublicKey()))) == fingerprint {
|
||||
return s
|
||||
}
|
||||
|
||||
if ssh.FingerprintSHA256(s.PublicKey()) == fingerprint {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -42,10 +42,7 @@ type Issue struct {
|
|||
Ref string `json:"ref"`
|
||||
Labels []*Label `json:"labels"`
|
||||
Milestone *Milestone `json:"milestone"`
|
||||
// deprecated
|
||||
// TODO: rm on sdk 0.15.0
|
||||
Assignee *User `json:"assignee"`
|
||||
Assignees []*User `json:"assignees"`
|
||||
Assignees []*User `json:"assignees"`
|
||||
// Whether the issue is open or closed
|
||||
State StateType `json:"state"`
|
||||
IsLocked bool `json:"is_locked"`
|
||||
|
@ -66,6 +63,18 @@ type ListIssueOption struct {
|
|||
Labels []string
|
||||
Milestones []string
|
||||
KeyWord string
|
||||
Since time.Time
|
||||
Before time.Time
|
||||
// filter by created by username
|
||||
CreatedBy string
|
||||
// filter by assigned to username
|
||||
AssignedBy string
|
||||
// filter by username mentioned
|
||||
MentionedBy string
|
||||
// filter by owner (only works on ListIssues on User)
|
||||
Owner string
|
||||
// filter by team (requires organization owner parameter to be provided and only works on ListIssues on User)
|
||||
Team string
|
||||
}
|
||||
|
||||
// StateType issue state type
|
||||
|
@ -114,6 +123,29 @@ func (opt *ListIssueOption) QueryEncode() string {
|
|||
query.Add("milestones", strings.Join(opt.Milestones, ","))
|
||||
}
|
||||
|
||||
if !opt.Since.IsZero() {
|
||||
query.Add("since", opt.Since.Format(time.RFC3339))
|
||||
}
|
||||
if !opt.Before.IsZero() {
|
||||
query.Add("before", opt.Before.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
if len(opt.CreatedBy) > 0 {
|
||||
query.Add("created_by", opt.CreatedBy)
|
||||
}
|
||||
if len(opt.AssignedBy) > 0 {
|
||||
query.Add("assigned_by", opt.AssignedBy)
|
||||
}
|
||||
if len(opt.MentionedBy) > 0 {
|
||||
query.Add("mentioned_by", opt.MentionedBy)
|
||||
}
|
||||
if len(opt.Owner) > 0 {
|
||||
query.Add("owner", opt.Owner)
|
||||
}
|
||||
if len(opt.Team) > 0 {
|
||||
query.Add("team", opt.MentionedBy)
|
||||
}
|
||||
|
||||
return query.Encode()
|
||||
}
|
||||
|
||||
|
@ -178,12 +210,9 @@ func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, e
|
|||
|
||||
// CreateIssueOption options to create one issue
|
||||
type CreateIssueOption struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Ref string `json:"ref"`
|
||||
// deprecated
|
||||
// TODO: rm on sdk 0.15.0
|
||||
Assignee string `json:"assignee"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Ref string `json:"ref"`
|
||||
Assignees []string `json:"assignees"`
|
||||
Deadline *time.Time `json:"due_date"`
|
||||
// milestone id
|
||||
|
@ -222,12 +251,9 @@ func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue,
|
|||
|
||||
// EditIssueOption options for editing an issue
|
||||
type EditIssueOption struct {
|
||||
Title string `json:"title"`
|
||||
Body *string `json:"body"`
|
||||
Ref *string `json:"ref"`
|
||||
// deprecated
|
||||
// TODO: rm on sdk 0.15.0
|
||||
Assignee *string `json:"assignee"`
|
||||
Title string `json:"title"`
|
||||
Body *string `json:"body"`
|
||||
Ref *string `json:"ref"`
|
||||
Assignees []string `json:"assignees"`
|
||||
Milestone *int64 `json:"milestone"`
|
||||
State *StateType `json:"state"`
|
||||
|
@ -263,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()
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestIssueComment creat a issue and test comment creation/edit/deletion on it
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// IssueTemplate provides metadata and content on an issue template.
|
||||
// There are two types of issue templates: .Markdown- and .Form-based.
|
||||
type IssueTemplate struct {
|
||||
Name string `json:"name"`
|
||||
About string `json:"about"`
|
||||
Filename string `json:"file_name"`
|
||||
IssueTitle string `json:"title"`
|
||||
IssueLabels []string `json:"labels"`
|
||||
IssueRef string `json:"ref"`
|
||||
// If non-nil, this is a form-based template
|
||||
Form []IssueFormElement `json:"body"`
|
||||
// Should only be used when .Form is nil.
|
||||
MarkdownContent string `json:"content"`
|
||||
}
|
||||
|
||||
// IssueFormElement describes a part of a IssueTemplate form
|
||||
type IssueFormElement struct {
|
||||
ID string `json:"id"`
|
||||
Type IssueFormElementType `json:"type"`
|
||||
Attributes IssueFormElementAttributes `json:"attributes"`
|
||||
Validations IssueFormElementValidations `json:"validations"`
|
||||
}
|
||||
|
||||
// IssueFormElementAttributes contains the combined set of attributes available on all element types.
|
||||
type IssueFormElementAttributes struct {
|
||||
// required for all element types.
|
||||
// A brief description of the expected user input, which is also displayed in the form.
|
||||
Label string `json:"label"`
|
||||
// required for element types "dropdown", "checkboxes"
|
||||
// for dropdown, contains the available options
|
||||
Options []string `json:"options"`
|
||||
// for element types "markdown", "textarea", "input"
|
||||
// Text that is pre-filled in the input
|
||||
Value string `json:"value"`
|
||||
// for element types "textarea", "input", "dropdown", "checkboxes"
|
||||
// A description of the text area to provide context or guidance, which is displayed in the form.
|
||||
Description string `json:"description"`
|
||||
// for element types "textarea", "input"
|
||||
// A semi-opaque placeholder that renders in the text area when empty.
|
||||
Placeholder string `json:"placeholder"`
|
||||
// for element types "textarea"
|
||||
// A language specifier. If set, the input is rendered as codeblock with syntax highlighting.
|
||||
SyntaxHighlighting string `json:"render"`
|
||||
// for element types "dropdown"
|
||||
Multiple bool `json:"multiple"`
|
||||
}
|
||||
|
||||
// IssueFormElementValidations contains the combined set of validations available on all element types.
|
||||
type IssueFormElementValidations struct {
|
||||
// for all element types
|
||||
Required bool `json:"required"`
|
||||
// for element types "input"
|
||||
IsNumber bool `json:"is_number"`
|
||||
// for element types "input"
|
||||
Regex string `json:"regex"`
|
||||
}
|
||||
|
||||
// IssueFormElementType is an enum
|
||||
type IssueFormElementType string
|
||||
|
||||
const (
|
||||
// IssueFormElementMarkdown is markdown rendered to the form for context, but omitted in the resulting issue
|
||||
IssueFormElementMarkdown IssueFormElementType = "markdown"
|
||||
// IssueFormElementTextarea is a multi line input
|
||||
IssueFormElementTextarea IssueFormElementType = "textarea"
|
||||
// IssueFormElementInput is a single line input
|
||||
IssueFormElementInput IssueFormElementType = "input"
|
||||
// IssueFormElementDropdown is a select form
|
||||
IssueFormElementDropdown IssueFormElementType = "dropdown"
|
||||
// IssueFormElementCheckboxes are a multi checkbox input
|
||||
IssueFormElementCheckboxes IssueFormElementType = "checkboxes"
|
||||
)
|
||||
|
||||
// GetIssueTemplates lists all issue templates of the repository
|
||||
func (c *Client) GetIssueTemplates(owner, repo string) ([]*IssueTemplate, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
templates := new([]*IssueTemplate)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issue_templates", owner, repo), nil, nil, templates)
|
||||
return *templates, resp, err
|
||||
}
|
||||
|
||||
// IsForm tells if this template is a form instead of a markdown-based template.
|
||||
func (t IssueTemplate) IsForm() bool {
|
||||
return t.Form != nil
|
||||
}
|
|
@ -19,9 +19,10 @@ func TestIssue(t *testing.T) {
|
|||
|
||||
createIssue(t, c)
|
||||
// Little sleep in order to give some time for gitea to properly store all information on database. Without this sleep, CI is a bit unstable
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
editIssues(t, c)
|
||||
listIssues(t, c)
|
||||
deleteIssue(t, c)
|
||||
}
|
||||
|
||||
func createIssue(t *testing.T, c *Client) {
|
||||
|
@ -47,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
|
||||
}
|
||||
|
|
|
@ -9,12 +9,13 @@ import (
|
|||
"net/url"
|
||||
)
|
||||
|
||||
const defaultPageSize = 10
|
||||
const maxPageSize = 50
|
||||
|
||||
// ListOptions options for using Gitea's API pagination
|
||||
type ListOptions struct {
|
||||
Page int
|
||||
// Setting Page to -1 disables pagination on endpoints that support it.
|
||||
// Page numbering starts at 1.
|
||||
Page int
|
||||
// The default value depends on the server config DEFAULT_PAGING_NUM
|
||||
// The highest valid value depends on the server config MAX_RESPONSE_ITEMS
|
||||
PageSize int
|
||||
}
|
||||
|
||||
|
@ -26,12 +27,14 @@ func (o ListOptions) getURLQuery() url.Values {
|
|||
return query
|
||||
}
|
||||
|
||||
func (o ListOptions) setDefaults() {
|
||||
if o.Page < 1 {
|
||||
// setDefaults applies default pagination options.
|
||||
// If .Page is set to -1, it will disable pagination.
|
||||
// WARNING: This function is not idempotent, make sure to never call this method twice!
|
||||
func (o *ListOptions) setDefaults() {
|
||||
if o.Page < 0 {
|
||||
o.Page, o.PageSize = 0, 0
|
||||
return
|
||||
} else if o.Page == 0 {
|
||||
o.Page = 1
|
||||
}
|
||||
|
||||
if o.PageSize < 0 || o.PageSize > maxPageSize {
|
||||
o.PageSize = defaultPageSize
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,14 +40,16 @@ func enableRunGitea() bool {
|
|||
}
|
||||
|
||||
func newTestClient() *Client {
|
||||
c, _ := NewClient(getGiteaURL(), newTestClientAuth())
|
||||
return c
|
||||
}
|
||||
|
||||
func newTestClientAuth() ClientOption {
|
||||
token := getGiteaToken()
|
||||
if token == "" {
|
||||
client := NewClientWithHTTP(getGiteaURL(), &http.Client{})
|
||||
client.SetBasicAuth(getGiteaUsername(), getGiteaPassword())
|
||||
return client
|
||||
return SetBasicAuth(getGiteaUsername(), getGiteaPassword())
|
||||
}
|
||||
c, _ := NewClient(getGiteaURL(), SetToken(getGiteaToken()))
|
||||
return c
|
||||
return SetToken(getGiteaToken())
|
||||
}
|
||||
|
||||
func giteaMasterPath() string {
|
||||
|
@ -80,7 +82,7 @@ func downGitea() (string, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
if err = os.Chmod(f.Name(), 700); err != nil {
|
||||
if err = os.Chmod(f.Name(), 0o700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
@ -99,7 +101,10 @@ func runGitea() (*os.Process, error) {
|
|||
|
||||
giteaDir := filepath.Dir(p)
|
||||
cfgDir := filepath.Join(giteaDir, "custom", "conf")
|
||||
os.MkdirAll(cfgDir, os.ModePerm)
|
||||
err = os.MkdirAll(cfgDir, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cfg, err := os.Create(filepath.Join(cfgDir, "app.ini"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -148,7 +153,9 @@ func TestMain(m *testing.M) {
|
|||
return
|
||||
}
|
||||
defer func() {
|
||||
p.Kill()
|
||||
if err := p.Kill(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
log.Printf("testing with %v, %v, %v\n", getGiteaURL(), getGiteaUsername(), getGiteaPassword())
|
||||
|
|
|
@ -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 string `json:"type"`
|
||||
State StateType `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
|
||||
|
@ -48,12 +44,39 @@ const (
|
|||
NotifyStatusPinned NotifyStatus = "pinned"
|
||||
)
|
||||
|
||||
// NotifySubjectType represent type of notification subject
|
||||
type NotifySubjectType string
|
||||
|
||||
const (
|
||||
// NotifySubjectIssue an issue is subject of an notification
|
||||
NotifySubjectIssue NotifySubjectType = "Issue"
|
||||
// NotifySubjectPull an pull is subject of an notification
|
||||
NotifySubjectPull NotifySubjectType = "Pull"
|
||||
// NotifySubjectCommit an commit is subject of an notification
|
||||
NotifySubjectCommit NotifySubjectType = "Commit"
|
||||
// NotifySubjectRepository an repository is subject of an notification
|
||||
NotifySubjectRepository NotifySubjectType = "Repository"
|
||||
)
|
||||
|
||||
// NotifySubjectState reflect state of notification subject
|
||||
type NotifySubjectState string
|
||||
|
||||
const (
|
||||
// NotifySubjectOpen if subject is a pull/issue and is open at the moment
|
||||
NotifySubjectOpen NotifySubjectState = "open"
|
||||
// NotifySubjectClosed if subject is a pull/issue and is closed at the moment
|
||||
NotifySubjectClosed NotifySubjectState = "closed"
|
||||
// NotifySubjectMerged if subject is a pull and got merged
|
||||
NotifySubjectMerged NotifySubjectState = "merged"
|
||||
)
|
||||
|
||||
// ListNotificationOptions represents the filter options
|
||||
type ListNotificationOptions struct {
|
||||
ListOptions
|
||||
Since time.Time
|
||||
Before time.Time
|
||||
Status []NotifyStatus
|
||||
Since time.Time
|
||||
Before time.Time
|
||||
Status []NotifyStatus
|
||||
SubjectTypes []NotifySubjectType
|
||||
}
|
||||
|
||||
// MarkNotificationOptions represents the filter & modify options
|
||||
|
@ -75,6 +98,9 @@ func (opt *ListNotificationOptions) QueryEncode() string {
|
|||
for _, s := range opt.Status {
|
||||
query.Add("status-types", string(s))
|
||||
}
|
||||
for _, s := range opt.SubjectTypes {
|
||||
query.Add("subject-type", string(s))
|
||||
}
|
||||
return query.Encode()
|
||||
}
|
||||
|
||||
|
@ -134,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
|
||||
|
@ -162,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
|
||||
|
@ -194,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
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ func TestNotifications(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
user2 := createTestUser(t, "notify2", c)
|
||||
|
||||
//create 2 repos
|
||||
// create 2 repos
|
||||
repoA, err := createTestRepo(t, "TestNotifications_A", c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -35,8 +35,9 @@ func TestNotifications(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
c.sudo = user2.UserName
|
||||
_, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
notifications, _, err := c.ReadNotifications(MarkNotificationOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, notifications, 0)
|
||||
count, _, err := c.CheckNotifications()
|
||||
assert.EqualValues(t, 0, count)
|
||||
assert.NoError(t, err)
|
||||
|
@ -45,7 +46,7 @@ func TestNotifications(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
issue, _, err := c.CreateIssue(repoB.Owner.UserName, repoB.Name, CreateIssueOption{Title: "B Issue", Closed: false})
|
||||
assert.NoError(t, err)
|
||||
time.Sleep(time.Second * 1)
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
// CheckNotifications of user2
|
||||
c.sudo = user2.UserName
|
||||
|
@ -60,6 +61,8 @@ func TestNotifications(t *testing.T) {
|
|||
for _, n := range nList {
|
||||
assert.EqualValues(t, true, n.Unread)
|
||||
assert.EqualValues(t, "Issue", n.Subject.Type)
|
||||
assert.EqualValues(t, NotifySubjectOpen, nList[0].Subject.State)
|
||||
assert.EqualValues(t, NotifySubjectOpen, nList[1].Subject.State)
|
||||
if n.Subject.Title == "A Issue" {
|
||||
assert.EqualValues(t, repoA.Name, n.Repository.Name)
|
||||
} else if n.Subject.Title == "B Issue" {
|
||||
|
@ -75,8 +78,9 @@ func TestNotifications(t *testing.T) {
|
|||
assert.Len(t, nList, 1)
|
||||
assert.EqualValues(t, "A Issue", nList[0].Subject.Title)
|
||||
// ReadRepoNotifications
|
||||
_, err = c.ReadRepoNotifications(repoA.Owner.UserName, repoA.Name, MarkNotificationOptions{})
|
||||
notifications, _, err = c.ReadRepoNotifications(repoA.Owner.UserName, repoA.Name, MarkNotificationOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, notifications, 1)
|
||||
|
||||
// GetThread
|
||||
n, _, err := c.GetNotification(nList[0].ID)
|
||||
|
@ -85,8 +89,9 @@ func TestNotifications(t *testing.T) {
|
|||
assert.EqualValues(t, "A Issue", n.Subject.Title)
|
||||
|
||||
// ReadNotifications
|
||||
_, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
notifications, _, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, notifications, 1)
|
||||
nList, _, err = c.ListNotifications(ListNotificationOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, nList, 0)
|
||||
|
@ -96,7 +101,7 @@ func TestNotifications(t *testing.T) {
|
|||
c.sudo = ""
|
||||
_, _, err = c.EditIssue(repoB.Owner.UserName, repoB.Name, issue.Index, EditIssueOption{State: &iState})
|
||||
assert.NoError(t, err)
|
||||
time.Sleep(time.Second * 1)
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
c.sudo = user2.UserName
|
||||
nList, _, err = c.ListNotifications(ListNotificationOptions{})
|
||||
|
@ -104,25 +109,32 @@ func TestNotifications(t *testing.T) {
|
|||
count, _, err = c.CheckNotifications()
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, count)
|
||||
assert.Len(t, nList, 1)
|
||||
if len(nList) > 0 {
|
||||
_, err = c.ReadNotification(nList[0].ID)
|
||||
if assert.Len(t, nList, 1) {
|
||||
assert.EqualValues(t, NotifySubjectClosed, nList[0].Subject.State)
|
||||
notification, _, err := c.ReadNotification(nList[0].ID)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, notification.ID, nList[0].ID)
|
||||
}
|
||||
|
||||
c.sudo = ""
|
||||
_, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
notifications, _, err = c.ReadNotifications(MarkNotificationOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, _ = 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)
|
||||
assert.Len(t, nList, 2)
|
||||
if assert.Len(t, nList, 2) {
|
||||
assert.EqualValues(t, NotifySubjectOpen, nList[0].Subject.State)
|
||||
assert.EqualValues(t, NotifySubjectOpen, nList[1].Subject.State)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,13 @@ import (
|
|||
|
||||
// Oauth2 represents an Oauth2 Application
|
||||
type Oauth2 struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
Created time.Time `json:"created"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
ConfidentialClient bool `json:"confidential_client"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
// ListOauth2Option for listing Oauth2 Applications
|
||||
|
@ -28,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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
17
gitea/org.go
17
gitea/org.go
|
@ -73,12 +73,13 @@ func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) {
|
|||
|
||||
// CreateOrgOption options for creating an organization
|
||||
type CreateOrgOption struct {
|
||||
Name string `json:"username"`
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
Visibility VisibleType `json:"visibility"`
|
||||
Name string `json:"username"`
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
Website string `json:"website"`
|
||||
Location string `json:"location"`
|
||||
Visibility VisibleType `json:"visibility"`
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
||||
}
|
||||
|
||||
// checkVisibilityOpt check if mode exist
|
||||
|
@ -92,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
|
||||
}
|
||||
|
@ -123,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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ListOrgActionSecretOption list OrgActionSecret options
|
||||
type ListOrgActionSecretOption struct {
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListOrgActionSecret list an organization's secrets
|
||||
func (c *Client) ListOrgActionSecret(org string, opt ListOrgActionSecretOption) ([]*Secret, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
secrets := make([]*Secret, 0, opt.PageSize)
|
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/actions/secrets", org))
|
||||
link.RawQuery = opt.getURLQuery().Encode()
|
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets)
|
||||
return secrets, resp, err
|
||||
}
|
||||
|
||||
// CreateSecretOption represents the options for creating a secret.
|
||||
type CreateSecretOption struct {
|
||||
Name string `json:"name"` // Name is the name of the secret.
|
||||
Data string `json:"data"` // Data is the data of the secret.
|
||||
}
|
||||
|
||||
// Validate checks if the CreateSecretOption is valid.
|
||||
// It returns an error if any of the validation checks fail.
|
||||
func (opt *CreateSecretOption) Validate() error {
|
||||
if len(opt.Name) == 0 {
|
||||
return fmt.Errorf("name required")
|
||||
}
|
||||
if len(opt.Name) > 30 {
|
||||
return fmt.Errorf("name to long")
|
||||
}
|
||||
if len(opt.Data) == 0 {
|
||||
return fmt.Errorf("data required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateOrgActionSecret creates a secret for the specified organization in the Gitea Actions.
|
||||
// It takes the organization name and the secret options as parameters.
|
||||
// The function returns the HTTP response and an error, if any.
|
||||
func (c *Client) CreateOrgActionSecret(org string, opt CreateSecretOption) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&org); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := (&opt).Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/actions/secrets/%s", org, opt.Name), jsonHeader, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch status {
|
||||
case http.StatusCreated:
|
||||
return resp, nil
|
||||
case http.StatusNoContent:
|
||||
return resp, nil
|
||||
case http.StatusNotFound:
|
||||
return resp, fmt.Errorf("forbidden")
|
||||
case http.StatusBadRequest:
|
||||
return resp, fmt.Errorf("bad request")
|
||||
default:
|
||||
return resp, fmt.Errorf("unexpected Status: %d", status)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateOrgActionSecret(t *testing.T) {
|
||||
log.Println("== TestCreateOrgActionSecret ==")
|
||||
c := newTestClient()
|
||||
|
||||
user := createTestUser(t, "org_action_user", c)
|
||||
c.SetSudo(user.UserName)
|
||||
newOrg, _, err := c.CreateOrg(CreateOrgOption{Name: "ActionOrg"})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, newOrg)
|
||||
|
||||
// create secret
|
||||
resp, err := c.CreateOrgActionSecret(newOrg.UserName, CreateSecretOption{Name: "test", Data: "test"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
|
||||
// update secret
|
||||
resp, err = c.CreateOrgActionSecret(newOrg.UserName, CreateSecretOption{Name: "test", Data: "test2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
|
||||
|
||||
// list secrets
|
||||
secrets, _, err := c.ListOrgActionSecret(newOrg.UserName, ListOrgActionSecretOption{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, secrets, 1)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -11,11 +11,9 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
/**
|
||||
/*
|
||||
// DeleteOrgMembership remove a member from an organization
|
||||
func (c *Client) DeleteOrgMembership(org, user string) error {}
|
||||
|
||||
|
||||
*/
|
||||
func TestOrgMembership(t *testing.T) {
|
||||
log.Println("== TestOrgMembership ==")
|
||||
|
@ -35,6 +33,11 @@ func TestOrgMembership(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.True(t, check)
|
||||
|
||||
perm, _, err := c.GetOrgPermissions(newOrg.UserName, user.UserName)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, perm)
|
||||
assert.True(t, perm.IsOwner)
|
||||
|
||||
_, err = c.SetPublicOrgMembership(newOrg.UserName, user.UserName, true)
|
||||
assert.NoError(t, err)
|
||||
check, _, err = c.CheckPublicOrgMembership(newOrg.UserName, user.UserName)
|
||||
|
|
|
@ -8,21 +8,47 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Team represents a team in an organization
|
||||
type Team struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||
Units []string `json:"units"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
Units []RepoUnitType `json:"units"`
|
||||
}
|
||||
|
||||
// RepoUnitType represent all unit types of a repo gitea currently offer
|
||||
type RepoUnitType string
|
||||
|
||||
const (
|
||||
// RepoUnitCode represent file view of a repository
|
||||
RepoUnitCode RepoUnitType = "repo.code"
|
||||
// RepoUnitIssues represent issues of a repository
|
||||
RepoUnitIssues RepoUnitType = "repo.issues"
|
||||
// RepoUnitPulls represent pulls of a repository
|
||||
RepoUnitPulls RepoUnitType = "repo.pulls"
|
||||
// RepoUnitExtIssues represent external issues of a repository
|
||||
RepoUnitExtIssues RepoUnitType = "repo.ext_issues"
|
||||
// RepoUnitWiki represent wiki of a repository
|
||||
RepoUnitWiki RepoUnitType = "repo.wiki"
|
||||
// RepoUnitExtWiki represent external wiki of a repository
|
||||
RepoUnitExtWiki RepoUnitType = "repo.ext_wiki"
|
||||
// RepoUnitReleases represent releases of a repository
|
||||
RepoUnitReleases RepoUnitType = "repo.releases"
|
||||
// RepoUnitProjects represent projects of a repository
|
||||
RepoUnitProjects RepoUnitType = "repo.projects"
|
||||
// RepoUnitPackages represents packages of a repository
|
||||
RepoUnitPackages RepoUnitType = "repo.packages"
|
||||
// RepoUnitActions represents actions of a repository
|
||||
RepoUnitActions RepoUnitType = "repo.actions"
|
||||
)
|
||||
|
||||
// ListTeamsOptions options for listing teams
|
||||
type ListTeamsOptions struct {
|
||||
ListOptions
|
||||
|
@ -54,19 +80,56 @@ func (c *Client) GetTeam(id int64) (*Team, *Response, error) {
|
|||
return t, resp, err
|
||||
}
|
||||
|
||||
// SearchTeamsOptions options for searching teams.
|
||||
type SearchTeamsOptions struct {
|
||||
ListOptions
|
||||
Query string
|
||||
IncludeDescription bool
|
||||
}
|
||||
|
||||
func (o SearchTeamsOptions) getURLQuery() url.Values {
|
||||
query := make(url.Values)
|
||||
query.Add("page", fmt.Sprintf("%d", o.Page))
|
||||
query.Add("limit", fmt.Sprintf("%d", o.PageSize))
|
||||
query.Add("q", o.Query)
|
||||
query.Add("include_desc", fmt.Sprintf("%t", o.IncludeDescription))
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
// TeamSearchResults is the JSON struct that is returned from Team search API.
|
||||
type TeamSearchResults struct {
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error"`
|
||||
Data []*Team `json:"data"`
|
||||
}
|
||||
|
||||
// SearchOrgTeams search for teams in a org.
|
||||
func (c *Client) SearchOrgTeams(org string, opt *SearchTeamsOptions) ([]*Team, *Response, error) {
|
||||
responseBody := TeamSearchResults{}
|
||||
opt.setDefaults()
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams/search?%s", org, opt.getURLQuery().Encode()), nil, nil, &responseBody)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if !responseBody.OK {
|
||||
return nil, resp, fmt.Errorf("gitea error: %v", responseBody.Error)
|
||||
}
|
||||
return responseBody.Data, resp, err
|
||||
}
|
||||
|
||||
// CreateTeamOption options for creating a team
|
||||
type CreateTeamOption struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||
Units []string `json:"units"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||
Units []RepoUnitType `json:"units"`
|
||||
}
|
||||
|
||||
// Validate the CreateTeamOption struct
|
||||
func (opt CreateTeamOption) Validate() error {
|
||||
func (opt *CreateTeamOption) Validate() error {
|
||||
if opt.Permission == AccessModeOwner {
|
||||
opt.Permission = AccessModeAdmin
|
||||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
|
||||
|
@ -89,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)
|
||||
|
@ -103,17 +166,16 @@ func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response,
|
|||
|
||||
// EditTeamOption options for editing a team
|
||||
type EditTeamOption struct {
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories *bool `json:"includes_all_repositories"`
|
||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||
Units []string `json:"units"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Permission AccessMode `json:"permission"`
|
||||
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
|
||||
IncludesAllRepositories *bool `json:"includes_all_repositories"`
|
||||
Units []RepoUnitType `json:"units"`
|
||||
}
|
||||
|
||||
// Validate the EditTeamOption struct
|
||||
func (opt EditTeamOption) Validate() error {
|
||||
func (opt *EditTeamOption) Validate() error {
|
||||
if opt.Permission == AccessModeOwner {
|
||||
opt.Permission = AccessModeAdmin
|
||||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
|
||||
|
@ -133,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)
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func createTestOrgTeams(t *testing.T, c *Client, org, name string, accessMode AccessMode, units []RepoUnitType) (*Team, error) {
|
||||
team, _, e := c.CreateTeam(org, CreateTeamOption{
|
||||
Name: name,
|
||||
Description: name + "'s team desc",
|
||||
Permission: accessMode,
|
||||
CanCreateOrgRepo: false,
|
||||
IncludesAllRepositories: false,
|
||||
Units: units,
|
||||
})
|
||||
assert.NoError(t, e)
|
||||
assert.NotNil(t, team)
|
||||
return team, e
|
||||
}
|
||||
|
||||
func TestTeamSearch(t *testing.T) {
|
||||
log.Println("== TestTeamSearch ==")
|
||||
c := newTestClient()
|
||||
|
||||
orgName := "TestTeamsOrg"
|
||||
// prepare for test
|
||||
_, _, err := c.CreateOrg(CreateOrgOption{
|
||||
Name: orgName,
|
||||
Visibility: VisibleTypePublic,
|
||||
RepoAdminChangeTeamAccess: true,
|
||||
})
|
||||
defer func() {
|
||||
_, _ = c.DeleteOrg(orgName)
|
||||
}()
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
if _, err = createTestOrgTeams(t, c, orgName, "Admins", AccessModeAdmin, []RepoUnitType{RepoUnitCode, RepoUnitIssues, RepoUnitPulls, RepoUnitReleases}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
teams, _, err := c.SearchOrgTeams(orgName, &SearchTeamsOptions{
|
||||
Query: "Admins",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, teams, 1) {
|
||||
assert.Equal(t, "Admins", teams[0].Name)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func createTestOrgRepo(t *testing.T, c *Client, name string) (func(), *Repository, error) {
|
||||
_, _, err := c.GetOrg(name)
|
||||
if err == nil {
|
||||
_, _ = c.DeleteOrg(name)
|
||||
}
|
||||
_, _, err = c.CreateOrg(CreateOrgOption{
|
||||
Name: name,
|
||||
Visibility: VisibleTypePublic,
|
||||
RepoAdminChangeTeamAccess: true,
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_, _, err = c.GetRepo(name, name)
|
||||
if err == nil {
|
||||
_, _ = c.DeleteRepo(name, name)
|
||||
}
|
||||
|
||||
repo, _, err := c.CreateOrgRepo(name, CreateRepoOption{
|
||||
Name: name,
|
||||
Description: "A test Repo: " + name,
|
||||
AutoInit: true,
|
||||
Gitignores: "C,C++",
|
||||
License: "MIT",
|
||||
Readme: "Default",
|
||||
IssueLabels: "Default",
|
||||
Private: false,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, repo)
|
||||
|
||||
return func() {
|
||||
_, _ = c.DeleteRepo(name, name)
|
||||
_, _ = c.DeleteOrg(name)
|
||||
}, repo, err
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Package represents a package
|
||||
type Package struct {
|
||||
// the package's id
|
||||
ID int64 `json:"id"`
|
||||
// the package's owner
|
||||
Owner User `json:"owner"`
|
||||
// the repo this package belongs to (if any)
|
||||
Repository *Repository `json:"repository"`
|
||||
// the package's creator
|
||||
Creator User `json:"creator"`
|
||||
// the type of package:
|
||||
Type string `json:"type"`
|
||||
// the name of the package
|
||||
Name string `json:"name"`
|
||||
// the version of the package
|
||||
Version string `json:"version"`
|
||||
// the date the package was uploaded
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// PackageFile represents a file from a package
|
||||
type PackageFile struct {
|
||||
// the file's ID
|
||||
ID int64 `json:"id"`
|
||||
// the size of the file in bytes
|
||||
Size int64 `json:"size"`
|
||||
// the name of the file
|
||||
Name string `json:"name"`
|
||||
// the md5 hash of the file
|
||||
MD5 string `json:"md5"`
|
||||
// the sha1 hash of the file
|
||||
SHA1 string `json:"sha1"`
|
||||
// the sha256 hash of the file
|
||||
SHA256 string `json:"sha256"`
|
||||
// the sha512 hash of the file
|
||||
SHA512 string `json:"sha512"`
|
||||
}
|
||||
|
||||
// ListPackagesOptions options for listing packages
|
||||
type ListPackagesOptions struct {
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListPackages lists all the packages owned by a given owner (user, organisation)
|
||||
func (c *Client) ListPackages(owner string, opt ListPackagesOptions) ([]*Package, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
packages := make([]*Package, 0, opt.PageSize)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s?%s", owner, opt.getURLQuery().Encode()), nil, nil, &packages)
|
||||
return packages, resp, err
|
||||
}
|
||||
|
||||
// GetPackage gets the details of a specific package version
|
||||
func (c *Client) GetPackage(owner, packageType, name, version string) (*Package, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
foundPackage := new(Package)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil, foundPackage)
|
||||
return foundPackage, resp, err
|
||||
}
|
||||
|
||||
// DeletePackage deletes a specific package version
|
||||
func (c *Client) DeletePackage(owner, packageType, name, version string) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// ListPackageFiles lists the files within a package
|
||||
func (c *Client) ListPackageFiles(owner, packageType, name, version string) ([]*PackageFile, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
packageFiles := make([]*PackageFile, 0)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s/files", owner, packageType, name, version), nil, nil, &packageFiles)
|
||||
return packageFiles, resp, err
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// create an org with a single package for testing purposes
|
||||
func createTestPackage(t *testing.T, c *Client) error {
|
||||
_, _ = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1")
|
||||
_, _ = c.DeleteOrg("PackageOrg")
|
||||
_, _, _ = c.CreateOrg(CreateOrgOption{Name: "PackageOrg"})
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 10,
|
||||
}
|
||||
|
||||
reader := bytes.NewReader([]byte("Hello world!"))
|
||||
|
||||
url := fmt.Sprintf("%s/api/packages/PackageOrg/generic/MyPackage/v1/file1.txt", os.Getenv("GITEA_SDK_TEST_URL"))
|
||||
req, err := http.NewRequest(http.MethodPut, url, reader)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(os.Getenv("GITEA_SDK_TEST_USERNAME"), os.Getenv("GITEA_SDK_TEST_PASSWORD"))
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestListPackages(t *testing.T) {
|
||||
log.Println("== TestListPackages ==")
|
||||
c := newTestClient()
|
||||
err := createTestPackage(t, c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{
|
||||
ListOptions{
|
||||
Page: 1,
|
||||
PageSize: 1000,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, packagesList, 1)
|
||||
}
|
||||
|
||||
func TestGetPackage(t *testing.T) {
|
||||
log.Println("== TestGetPackage ==")
|
||||
c := newTestClient()
|
||||
err := createTestPackage(t, c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pkg, _, err := c.GetPackage("PackageOrg", "generic", "MyPackage", "v1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pkg)
|
||||
assert.True(t, pkg.Name == "MyPackage")
|
||||
assert.True(t, pkg.Version == "v1")
|
||||
assert.NotEmpty(t, pkg.CreatedAt)
|
||||
}
|
||||
|
||||
func TestDeletePackage(t *testing.T) {
|
||||
log.Println("== TestDeletePackage ==")
|
||||
c := newTestClient()
|
||||
err := createTestPackage(t, c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// no packages should be listed following deletion
|
||||
packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{
|
||||
ListOptions{
|
||||
Page: 1,
|
||||
PageSize: 1000,
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, packagesList, 0)
|
||||
}
|
||||
|
||||
func TestListPackageFiles(t *testing.T) {
|
||||
log.Println("== TestListPackageFiles ==")
|
||||
c := newTestClient()
|
||||
err := createTestPackage(t, c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
packageFiles, _, err := c.ListPackageFiles("PackageOrg", "generic", "MyPackage", "v1")
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, packageFiles, 1)
|
||||
assert.True(t, packageFiles[0].Name == "file1.txt")
|
||||
}
|
131
gitea/pull.go
131
gitea/pull.go
|
@ -43,11 +43,12 @@ type PullRequest struct {
|
|||
DiffURL string `json:"diff_url"`
|
||||
PatchURL string `json:"patch_url"`
|
||||
|
||||
Mergeable bool `json:"mergeable"`
|
||||
HasMerged bool `json:"merged"`
|
||||
Merged *time.Time `json:"merged_at"`
|
||||
MergedCommitID *string `json:"merge_commit_sha"`
|
||||
MergedBy *User `json:"merged_by"`
|
||||
Mergeable bool `json:"mergeable"`
|
||||
HasMerged bool `json:"merged"`
|
||||
Merged *time.Time `json:"merged_at"`
|
||||
MergedCommitID *string `json:"merge_commit_sha"`
|
||||
MergedBy *User `json:"merged_by"`
|
||||
AllowMaintainerEdit bool `json:"allow_maintainer_edit"`
|
||||
|
||||
Base *PRBranchInfo `json:"base"`
|
||||
Head *PRBranchInfo `json:"head"`
|
||||
|
@ -59,6 +60,19 @@ type PullRequest struct {
|
|||
Closed *time.Time `json:"closed_at"`
|
||||
}
|
||||
|
||||
// ChangedFile is a changed file in a diff
|
||||
type ChangedFile struct {
|
||||
Filename string `json:"filename"`
|
||||
PreviousFilename string `json:"previous_filename"`
|
||||
Status string `json:"status"`
|
||||
Additions int `json:"additions"`
|
||||
Deletions int `json:"deletions"`
|
||||
Changes int `json:"changes"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
ContentsURL string `json:"contents_url"`
|
||||
RawURL string `json:"raw_url"`
|
||||
}
|
||||
|
||||
// ListPullRequestsOptions options for listing pull requests
|
||||
type ListPullRequestsOptions struct {
|
||||
ListOptions
|
||||
|
@ -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,38 @@ func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64
|
|||
if r.Private {
|
||||
return nil, nil, err
|
||||
}
|
||||
return c.getWebResponse("GET", fmt.Sprintf("/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil)
|
||||
url := fmt.Sprintf("/%s/%s/pulls/%d.%s?%s", owner, repo, index, kind, opts.QueryEncode())
|
||||
return c.getWebResponse("GET", url, nil)
|
||||
}
|
||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil)
|
||||
}
|
||||
|
||||
// GetPullRequestPatch gets the .patch file as bytes for a PR
|
||||
// GetPullRequestPatch gets the git patchset of a PR
|
||||
func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) {
|
||||
return c.getPullRequestDiffOrPatch(owner, repo, "patch", index)
|
||||
return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypePatch, index, PullRequestDiffOptions{})
|
||||
}
|
||||
|
||||
// GetPullRequestDiff gets the .diff file as bytes for a PR
|
||||
func (c *Client) GetPullRequestDiff(owner, repo string, index int64) ([]byte, *Response, error) {
|
||||
return c.getPullRequestDiffOrPatch(owner, repo, "diff", index)
|
||||
// GetPullRequestDiff gets the diff of a PR. For Gitea >= 1.16, you must set includeBinary to get an applicable diff
|
||||
func (c *Client) GetPullRequestDiff(owner, repo string, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) {
|
||||
return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypeDiff, index, opts)
|
||||
}
|
||||
|
||||
// ListPullRequestCommitsOptions options for listing pull requests
|
||||
type ListPullRequestCommitsOptions struct {
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListPullRequestCommits list commits for a pull request
|
||||
func (c *Client) ListPullRequestCommits(owner, repo string, index int64, opt ListPullRequestCommitsOptions) ([]*Commit, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/commits", owner, repo, index))
|
||||
opt.setDefaults()
|
||||
commits := make([]*Commit, 0, opt.PageSize)
|
||||
link.RawQuery = opt.getURLQuery().Encode()
|
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits)
|
||||
return commits, resp, err
|
||||
}
|
||||
|
||||
// fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675
|
||||
|
@ -304,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
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ type PullReviewComment struct {
|
|||
Body string `json:"body"`
|
||||
Reviewer *User `json:"user"`
|
||||
ReviewID int64 `json:"pull_request_review_id"`
|
||||
Resolver *User `json:"resolver"`
|
||||
|
||||
Created time.Time `json:"created_at"`
|
||||
Updated time.Time `json:"updated_at"`
|
||||
|
@ -115,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 {
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
|
|
|
@ -17,8 +17,8 @@ func TestPull(t *testing.T) {
|
|||
user, _, err := c.GetMyUserInfo()
|
||||
assert.NoError(t, err)
|
||||
|
||||
var repoName = "repo_pull_test"
|
||||
var forkOrg = "ForkOrg"
|
||||
repoName := "repo_pull_test"
|
||||
forkOrg := "ForkOrg"
|
||||
if !preparePullTest(t, c, repoName, forkOrg) {
|
||||
return
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ func TestPull(t *testing.T) {
|
|||
assert.Len(t, pulls, 0)
|
||||
|
||||
pullUpdateFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||
Base: "master",
|
||||
Base: "main",
|
||||
Head: forkOrg + ":overwrite_licence",
|
||||
Title: "overwrite a file",
|
||||
})
|
||||
|
@ -37,7 +37,7 @@ func TestPull(t *testing.T) {
|
|||
assert.NotNil(t, pullUpdateFile)
|
||||
|
||||
pullNewFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||
Base: "master",
|
||||
Base: "main",
|
||||
Head: forkOrg + ":new_file",
|
||||
Title: "create a file",
|
||||
})
|
||||
|
@ -45,7 +45,7 @@ func TestPull(t *testing.T) {
|
|||
assert.NotNil(t, pullNewFile)
|
||||
|
||||
pullConflict, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{
|
||||
Base: "master",
|
||||
Base: "main",
|
||||
Head: forkOrg + ":will_conflict",
|
||||
Title: "this pull will conflict",
|
||||
})
|
||||
|
@ -56,16 +56,35 @@ func TestPull(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, pulls, 3)
|
||||
|
||||
diff, _, err := c.GetPullRequestDiff(c.username, repoName, pullUpdateFile.Index)
|
||||
diff, _, err := c.GetPullRequestDiff(c.username, repoName, pullUpdateFile.Index, PullRequestDiffOptions{
|
||||
Binary: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(diff) > 1100 && len(diff) < 1300)
|
||||
patch, _, err := c.GetPullRequestPatch(c.username, repoName, pullUpdateFile.Index)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(patch) > len(diff))
|
||||
|
||||
commits, _, err := c.ListPullRequestCommits(c.username, repoName, pullUpdateFile.Index, ListPullRequestCommitsOptions{})
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, commits, 1) && assert.Len(t, commits[0].Files, 1) {
|
||||
assert.EqualValues(t, "LICENSE", commits[0].Files[0].Filename)
|
||||
}
|
||||
|
||||
files, _, err := c.ListPullRequestFiles(c.username, repoName, pullUpdateFile.Index, ListPullRequestFilesOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, files, 1)
|
||||
file := files[0]
|
||||
assert.EqualValues(t, "LICENSE", file.Filename)
|
||||
assert.EqualValues(t, "changed", file.Status)
|
||||
assert.EqualValues(t, 3, file.Additions)
|
||||
assert.EqualValues(t, 9, file.Deletions)
|
||||
assert.EqualValues(t, 12, file.Changes)
|
||||
|
||||
// test Update pull
|
||||
pr, _, err := c.GetPullRequest(user.UserName, repoName, pullUpdateFile.Index)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pr)
|
||||
assert.False(t, pullUpdateFile.HasMerged)
|
||||
assert.True(t, pullUpdateFile.Mergeable)
|
||||
merged, _, err := c.MergePullRequest(user.UserName, repoName, pullUpdateFile.Index, MergePullRequestOption{
|
||||
|
@ -89,8 +108,8 @@ func TestPull(t *testing.T) {
|
|||
// test conflict pull
|
||||
pr, _, err = c.GetPullRequest(user.UserName, repoName, pullConflict.Index)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, pullConflict.HasMerged)
|
||||
assert.False(t, pullConflict.Mergeable)
|
||||
assert.False(t, pr.HasMerged)
|
||||
assert.False(t, pr.Mergeable)
|
||||
merged, _, err = c.MergePullRequest(user.UserName, repoName, pullConflict.Index, MergePullRequestOption{
|
||||
Style: MergeStyleMerge,
|
||||
Title: "pullConflict",
|
||||
|
@ -137,18 +156,18 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
|||
assert.NoError(t, err)
|
||||
assert.NotNil(t, forkRepo)
|
||||
|
||||
masterLicence, _, err := c.GetContents(forkRepo.Owner.UserName, forkRepo.Name, "master", "LICENSE")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, masterLicence) {
|
||||
mainLicense, _, err := c.GetContents(forkRepo.Owner.UserName, forkRepo.Name, "main", "LICENSE")
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) {
|
||||
return false
|
||||
}
|
||||
|
||||
updatedFile, _, err := c.UpdateFile(forkRepo.Owner.UserName, forkRepo.Name, "LICENSE", UpdateFileOptions{
|
||||
FileOptions: FileOptions{
|
||||
Message: "Overwrite",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
NewBranchName: "overwrite_licence",
|
||||
},
|
||||
SHA: masterLicence.SHA,
|
||||
SHA: mainLicense.SHA,
|
||||
Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=",
|
||||
})
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) {
|
||||
|
@ -159,7 +178,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
|||
Content: "QSBuZXcgRmlsZQo=",
|
||||
FileOptions: FileOptions{
|
||||
Message: "creat a new file",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
NewBranchName: "new_file",
|
||||
},
|
||||
})
|
||||
|
@ -171,7 +190,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
|||
Content: "U3RhcnQgQ29uZmxpY3QK",
|
||||
FileOptions: FileOptions{
|
||||
Message: "Start Conflict",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
},
|
||||
})
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, conflictFile1) {
|
||||
|
@ -182,7 +201,7 @@ func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool {
|
|||
Content: "V2lsbEhhdmUgQ29uZmxpY3QK",
|
||||
FileOptions: FileOptions{
|
||||
Message: "creat a new file witch will conflict",
|
||||
BranchName: "master",
|
||||
BranchName: "main",
|
||||
NewBranchName: "will_conflict",
|
||||
},
|
||||
})
|
||||
|
|
|
@ -35,6 +35,22 @@ type Release struct {
|
|||
// ListReleasesOptions options for listing repository's releases
|
||||
type ListReleasesOptions struct {
|
||||
ListOptions
|
||||
IsDraft *bool
|
||||
IsPreRelease *bool
|
||||
}
|
||||
|
||||
// QueryEncode turns options into querystring argument
|
||||
func (opt *ListReleasesOptions) QueryEncode() string {
|
||||
query := opt.getURLQuery()
|
||||
|
||||
if opt.IsDraft != nil {
|
||||
query.Add("draft", fmt.Sprintf("%t", *opt.IsDraft))
|
||||
}
|
||||
if opt.IsPreRelease != nil {
|
||||
query.Add("pre-release", fmt.Sprintf("%t", *opt.IsPreRelease))
|
||||
}
|
||||
|
||||
return query.Encode()
|
||||
}
|
||||
|
||||
// ListReleases list releases of a repository
|
||||
|
@ -45,7 +61,7 @@ func (c *Client) ListReleases(owner, repo string, opt ListReleasesOptions) ([]*R
|
|||
opt.setDefaults()
|
||||
releases := make([]*Release, 0, opt.PageSize)
|
||||
resp, err := c.getParsedResponse("GET",
|
||||
fmt.Sprintf("/repos/%s/%s/releases?%s", owner, repo, opt.getURLQuery().Encode()),
|
||||
fmt.Sprintf("/repos/%s/%s/releases?%s", owner, repo, opt.QueryEncode()),
|
||||
nil, nil, &releases)
|
||||
return releases, resp, err
|
||||
}
|
||||
|
@ -62,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)
|
||||
}
|
||||
|
@ -152,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
|
||||
}
|
||||
|
@ -166,15 +194,15 @@ 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{Page: i}})
|
||||
rl, resp, err := c.ListReleases(owner, repo, ListReleasesOptions{ListOptions: ListOptions{Page: i}})
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
if len(rl) == 0 {
|
||||
return nil,
|
||||
&Response{&http.Response{StatusCode: 404}},
|
||||
newResponse(&http.Response{StatusCode: 404}),
|
||||
fmt.Errorf("release with tag '%s' not found", tag)
|
||||
}
|
||||
for _, r := range rl {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,8 @@ 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"`
|
||||
}
|
||||
|
||||
// RepoType represent repo type
|
||||
|
@ -285,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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,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
|
||||
}
|
||||
}
|
||||
|
@ -382,6 +389,13 @@ func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error)
|
|||
return repo, resp, err
|
||||
}
|
||||
|
||||
// GetRepoByID returns information of a repository by a giver repository ID.
|
||||
func (c *Client) GetRepoByID(id int64) (*Repository, *Response, error) {
|
||||
repo := new(Repository)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repositories/%d", id), nil, nil, repo)
|
||||
return repo, resp, err
|
||||
}
|
||||
|
||||
// EditRepoOption options when editing a repository's properties
|
||||
type EditRepoOption struct {
|
||||
// name of the repository
|
||||
|
@ -412,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`.
|
||||
|
@ -426,6 +446,13 @@ type EditRepoOption struct {
|
|||
Archived *bool `json:"archived,omitempty"`
|
||||
// set to a string like `8h30m0s` to set the mirror interval time
|
||||
MirrorInterval *string `json:"mirror_interval,omitempty"`
|
||||
// either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`.
|
||||
AllowManualMerge *bool `json:"allow_manual_merge,omitempty"`
|
||||
// either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur.
|
||||
AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"`
|
||||
// set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". `has_pull_requests` must be `true`.
|
||||
DefaultMergeStyle *MergeStyle `json:"default_merge_style,omitempty"`
|
||||
// set to `true` to archive this repository.
|
||||
}
|
||||
|
||||
// EditRepo edit the properties of a repository
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ListRepoActionSecretOption list RepoActionSecret options
|
||||
type ListRepoActionSecretOption struct {
|
||||
ListOptions
|
||||
}
|
||||
|
||||
// ListRepoActionSecret list a repository's secrets
|
||||
func (c *Client) ListRepoActionSecret(user, repo string, opt ListRepoActionSecretOption) ([]*Secret, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
opt.setDefaults()
|
||||
secrets := make([]*Secret, 0, opt.PageSize)
|
||||
|
||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/actions/secrets", user, repo))
|
||||
link.RawQuery = opt.getURLQuery().Encode()
|
||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets)
|
||||
return secrets, resp, err
|
||||
}
|
||||
|
||||
// CreateRepoActionSecret creates a secret for the specified repository in the Gitea Actions.
|
||||
// It takes the organization name and the secret options as parameters.
|
||||
// The function returns the HTTP response and an error, if any.
|
||||
func (c *Client) CreateRepoActionSecret(user, repo string, opt CreateSecretOption) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := (&opt).Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/actions/secrets/%s", user, repo, opt.Name), jsonHeader, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch status {
|
||||
case http.StatusCreated:
|
||||
return resp, nil
|
||||
case http.StatusNoContent:
|
||||
return resp, nil
|
||||
case http.StatusNotFound:
|
||||
return resp, fmt.Errorf("forbidden")
|
||||
case http.StatusBadRequest:
|
||||
return resp, fmt.Errorf("bad request")
|
||||
default:
|
||||
return resp, fmt.Errorf("unexpected Status: %d", status)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateRepoActionSecret(t *testing.T) {
|
||||
log.Println("== TestCreateRepoActionSecret ==")
|
||||
c := newTestClient()
|
||||
|
||||
user := createTestUser(t, "repo_action_user", c)
|
||||
c.SetSudo(user.UserName)
|
||||
newRepo, _, err := c.CreateRepo(CreateRepoOption{
|
||||
Name: "test",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, newRepo)
|
||||
|
||||
// create secret
|
||||
resp, err := c.CreateRepoActionSecret(newRepo.Owner.UserName, newRepo.Name, CreateSecretOption{Name: "test", Data: "test"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
|
||||
// update secret
|
||||
resp, err = c.CreateRepoActionSecret(newRepo.Owner.UserName, newRepo.Name, CreateSecretOption{Name: "test", Data: "test2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
|
||||
|
||||
// list secrets
|
||||
secrets, _, err := c.ListRepoActionSecret(newRepo.Owner.UserName, newRepo.Name, ListRepoActionSecretOption{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, secrets, 1)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
@ -15,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 {
|
||||
|
@ -43,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"`
|
||||
|
@ -65,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
|
||||
|
@ -87,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)
|
||||
|
@ -107,3 +135,29 @@ func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response,
|
|||
fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GetReviewers return all users that can be requested to review in this repo
|
||||
func (c *Client) GetReviewers(user, repo string) ([]*User, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
reviewers := make([]*User, 0, 5)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/reviewers", user, repo), nil, nil, &reviewers)
|
||||
return reviewers, resp, err
|
||||
}
|
||||
|
||||
// GetAssignees return all users that have write access and can be assigned to issues
|
||||
func (c *Client) GetAssignees(user, repo string) ([]*User, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
assignees := make([]*User, 0, 5)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/assignees", user, repo), nil, nil, &assignees)
|
||||
return assignees, resp, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRepoCollaborator(t *testing.T) {
|
||||
log.Println("== TestRepoCollaborator ==")
|
||||
c := newTestClient()
|
||||
|
||||
repo, _ := createTestRepo(t, "RepoCollaborators", c)
|
||||
createTestUser(t, "ping", c)
|
||||
createTestUser(t, "pong", c)
|
||||
defer func() {
|
||||
_, err := c.AdminDeleteUser("ping")
|
||||
assert.NoError(t, err)
|
||||
_, err = c.AdminDeleteUser("pong")
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
collaborators, _, err := c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, collaborators, 0)
|
||||
|
||||
mode := AccessModeAdmin
|
||||
resp, err := c.AddCollaborator(repo.Owner.UserName, repo.Name, "ping", AddCollaboratorOption{Permission: &mode})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 204, resp.StatusCode)
|
||||
|
||||
permissonPing, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "ping")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 200, resp.StatusCode)
|
||||
assert.EqualValues(t, AccessModeAdmin, permissonPing.Permission)
|
||||
assert.EqualValues(t, "ping", permissonPing.User.UserName)
|
||||
|
||||
mode = AccessModeRead
|
||||
_, err = c.AddCollaborator(repo.Owner.UserName, repo.Name, "pong", AddCollaboratorOption{Permission: &mode})
|
||||
assert.NoError(t, err)
|
||||
|
||||
permissonPong, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "pong")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 200, resp.StatusCode)
|
||||
assert.EqualValues(t, AccessModeRead, permissonPong.Permission)
|
||||
assert.EqualValues(t, "pong", permissonPong.User.UserName)
|
||||
|
||||
collaborators, _, err = c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, collaborators, 2)
|
||||
assert.EqualValues(t, []string{"ping", "pong"}, userToStringSlice(collaborators))
|
||||
|
||||
reviewers, _, err := c.GetReviewers(repo.Owner.UserName, repo.Name)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, reviewers, 3)
|
||||
assert.EqualValues(t, []string{"ping", "pong", "test01"}, userToStringSlice(reviewers))
|
||||
|
||||
assignees, _, err := c.GetAssignees(repo.Owner.UserName, repo.Name)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, assignees, 2)
|
||||
assert.EqualValues(t, []string{"ping", "test01"}, userToStringSlice(assignees))
|
||||
|
||||
resp, err = c.DeleteCollaborator(repo.Owner.UserName, repo.Name, "ping")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 204, resp.StatusCode)
|
||||
|
||||
collaborators, _, err = c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, collaborators, 1)
|
||||
|
||||
permissonNotExists, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "user_that_not_exists")
|
||||
assert.Error(t, err)
|
||||
assert.EqualValues(t, 404, resp.StatusCode)
|
||||
assert.Nil(t, permissonNotExists)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
|
@ -22,4 +23,38 @@ func TestListRepoCommits(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, l, 1)
|
||||
assert.EqualValues(t, "Initial commit\n", l[0].RepoCommit.Message)
|
||||
assert.EqualValues(t, "gpg.error.not_signed_commit", l[0].RepoCommit.Verification.Reason)
|
||||
assert.EqualValues(t, 100, l[0].Stats.Additions)
|
||||
}
|
||||
|
||||
func TestGetCommitDiffOrPatch(t *testing.T) {
|
||||
log.Println("== TestGetCommitDiffOrPatch ==")
|
||||
c := newTestClient()
|
||||
|
||||
repo, err := createTestRepo(t, "TestGetCommitDiffOrPatch", c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Add a new simple small commit to the repository.
|
||||
fileResponse, _, err := c.CreateFile(repo.Owner.UserName, repo.Name, "NOT_A_LICENSE", CreateFileOptions{
|
||||
Content: base64.StdEncoding.EncodeToString([]byte("But is it?\n")),
|
||||
FileOptions: FileOptions{
|
||||
Message: "Ensure people know it's not a license!",
|
||||
Committer: Identity{
|
||||
Name: "Sup3rCookie",
|
||||
Email: "Sup3rCookie@example.com",
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test the diff output.
|
||||
diffOutput, _, err := c.GetCommitDiff(repo.Owner.UserName, repo.Name, fileResponse.Commit.SHA)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "diff --git a/NOT_A_LICENSE b/NOT_A_LICENSE\nnew file mode 100644\nindex 0000000..f27a20a\n--- /dev/null\n+++ b/NOT_A_LICENSE\n@@ -0,0 +1 @@\n+But is it?\n", string(diffOutput))
|
||||
|
||||
// Test the patch output.
|
||||
patchOutput, _, err := c.GetCommitPatch(repo.Owner.UserName, repo.Name, fileResponse.Commit.SHA)
|
||||
assert.NoError(t, err)
|
||||
// Use contains, because we cannot include the first part, because of dates + non-static CommitID..
|
||||
assert.Contains(t, string(patchOutput), "Subject: [PATCH] Ensure people know it's not a license!\n\n---\n NOT_A_LICENSE | 1 +\n 1 file changed, 1 insertion(+)\n create mode 100644 NOT_A_LICENSE\n\ndiff --git a/NOT_A_LICENSE b/NOT_A_LICENSE\nnew file mode 100644\nindex 0000000..f27a20a\n--- /dev/null\n+++ b/NOT_A_LICENSE\n@@ -0,0 +1 @@\n+But is it?\n")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Compare represents a comparison between two commits.
|
||||
type Compare struct {
|
||||
TotalCommits int `json:"total_commits"` // Total number of commits in the comparison.
|
||||
Commits []*Commit `json:"commits"` // List of commits in the comparison.
|
||||
}
|
||||
|
||||
// CompareCommits compares two commits in a repository.
|
||||
func (c *Client) CompareCommits(user, repo, prev, current string) (*Compare, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_22_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo, &prev, ¤t); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
basehead := fmt.Sprintf("%s...%s", prev, current)
|
||||
|
||||
apiResp := new(Compare)
|
||||
resp, err := c.getParsedResponse(
|
||||
"GET",
|
||||
fmt.Sprintf("/repos/%s/%s/compare/%s", user, repo, basehead),
|
||||
nil, nil, apiResp,
|
||||
)
|
||||
return apiResp, resp, err
|
||||
}
|
|
@ -9,6 +9,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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
@ -47,6 +47,8 @@ type MigrateRepoOption struct {
|
|||
PullRequests bool `json:"pull_requests"`
|
||||
Releases bool `json:"releases"`
|
||||
MirrorInterval string `json:"mirror_interval"`
|
||||
LFS bool `json:"lfs"`
|
||||
LFSEndpoint string `json:"lfs_endpoint"`
|
||||
}
|
||||
|
||||
// Validate the MigrateRepoOption struct
|
||||
|
@ -60,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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -5,9 +5,10 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRepoStaring(t *testing.T) {
|
||||
|
|
|
@ -5,18 +5,39 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Tag represents a repository tag
|
||||
type Tag struct {
|
||||
Name string `json:"name"`
|
||||
Message string `json:"message"`
|
||||
ID string `json:"id"`
|
||||
Commit *CommitMeta `json:"commit"`
|
||||
ZipballURL string `json:"zipball_url"`
|
||||
TarballURL string `json:"tarball_url"`
|
||||
}
|
||||
|
||||
// AnnotatedTag represents an annotated tag
|
||||
type AnnotatedTag struct {
|
||||
Tag string `json:"tag"`
|
||||
SHA string `json:"sha"`
|
||||
URL string `json:"url"`
|
||||
Message string `json:"message"`
|
||||
Tagger *CommitUser `json:"tagger"`
|
||||
Object *AnnotatedTagObject `json:"object"`
|
||||
Verification *PayloadCommitVerification `json:"verification"`
|
||||
}
|
||||
|
||||
// AnnotatedTagObject contains meta information of the tag object
|
||||
type AnnotatedTagObject struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
SHA string `json:"sha"`
|
||||
}
|
||||
|
||||
// ListRepoTagsOptions options for listing a repository's tags
|
||||
type ListRepoTagsOptions struct {
|
||||
ListOptions
|
||||
|
@ -33,8 +54,69 @@ func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Ta
|
|||
return tags, resp, err
|
||||
}
|
||||
|
||||
// GetTag get the tag of a repository
|
||||
func (c *Client) GetTag(user, repo, tag string) (*Tag, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
t := new(Tag)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag), nil, nil, &t)
|
||||
return t, resp, err
|
||||
}
|
||||
|
||||
// GetAnnotatedTag get the tag object of an annotated tag (not lightweight tags) of a repository
|
||||
func (c *Client) GetAnnotatedTag(user, repo, sha string) (*AnnotatedTag, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
t := new(AnnotatedTag)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/tags/%s", user, repo, sha), nil, nil, &t)
|
||||
return t, resp, err
|
||||
}
|
||||
|
||||
// CreateTagOption options when creating a tag
|
||||
type CreateTagOption struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Message string `json:"message"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
// Validate validates CreateTagOption
|
||||
func (opt CreateTagOption) Validate() error {
|
||||
if len(opt.TagName) == 0 {
|
||||
return fmt.Errorf("TagName is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTag create a new git tag in a repository
|
||||
func (c *Client) CreateTag(user, repo string, opt CreateTagOption) (*Tag, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := opt.Validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
body, err := json.Marshal(opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
t := new(Tag)
|
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/tags", user, repo), jsonHeader, bytes.NewReader(body), &t)
|
||||
return t, resp, err
|
||||
}
|
||||
|
||||
// DeleteTag deletes a tag from a repository, if no release refers to it
|
||||
func (c *Client) DeleteTag(user, repo string, tag string) (*Response, error) {
|
||||
func (c *Client) DeleteTag(user, repo, tag string) (*Response, error) {
|
||||
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
|
@ -18,30 +19,40 @@ func TestTags(t *testing.T) {
|
|||
repo, _ := createTestRepo(t, "TestTags", c)
|
||||
|
||||
// Create Tags
|
||||
createTestTag(t, c, repo, "tag1")
|
||||
cTagMSG := "A tag message.\n\n:)"
|
||||
cTag, resp, err := c.CreateTag(repo.Owner.UserName, repo.Name, CreateTagOption{
|
||||
TagName: "tag1",
|
||||
Message: cTagMSG,
|
||||
Target: "main",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 201, resp.StatusCode)
|
||||
assert.EqualValues(t, cTagMSG, cTag.Message)
|
||||
assert.EqualValues(t, fmt.Sprintf("%s/%s/TestTags/archive/tag1.zip", c.url, c.username), cTag.ZipballURL)
|
||||
|
||||
tags, _, err := c.ListRepoTags(repo.Owner.UserName, repo.Name, ListRepoTagsOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, tags, 1)
|
||||
assert.EqualValues(t, cTag, tags[0])
|
||||
|
||||
// get tag
|
||||
gTag, _, err := c.GetTag(repo.Owner.UserName, repo.Name, cTag.Name)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, cTag, gTag)
|
||||
|
||||
aTag, _, err := c.GetAnnotatedTag(repo.Owner.UserName, repo.Name, cTag.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, cTag.Name, aTag.Tag)
|
||||
assert.EqualValues(t, cTag.ID, aTag.SHA)
|
||||
assert.EqualValues(t, fmt.Sprintf("%s/api/v1/repos/%s/TestTags/git/tags/%s", c.url, c.username, cTag.ID), aTag.URL)
|
||||
assert.EqualValues(t, cTag.Message+"\n", aTag.Message)
|
||||
assert.EqualValues(t, "commit", aTag.Object.Type)
|
||||
|
||||
// DeleteReleaseTag
|
||||
resp, err := c.DeleteTag(repo.Owner.UserName, repo.Name, "tag1")
|
||||
resp, err = c.DeleteTag(repo.Owner.UserName, repo.Name, "tag1")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 204, resp.StatusCode)
|
||||
tags, _, err = c.ListRepoTags(repo.Owner.UserName, repo.Name, ListRepoTagsOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, tags, 0)
|
||||
}
|
||||
|
||||
// createTestTag use create release api since there exist no api to create tag only
|
||||
// https://github.com/go-gitea/gitea/issues/14669
|
||||
func createTestTag(t *testing.T, c *Client, repo *Repository, name string) {
|
||||
rel, _, err := c.CreateRelease(repo.Owner.UserName, repo.Name, CreateReleaseOption{
|
||||
TagName: name,
|
||||
Target: "master",
|
||||
Title: "TMP Release",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
_, err = c.DeleteRelease(repo.Owner.UserName, repo.Name, rel.ID)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GetRepoTeams return teams from a repository
|
||||
func (c *Client) GetRepoTeams(user, repo string) ([]*Team, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
teams := make([]*Team, 0, 5)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams", user, repo), nil, nil, &teams)
|
||||
return teams, resp, err
|
||||
}
|
||||
|
||||
// AddRepoTeam add a team to a repository
|
||||
func (c *Client) AddRepoTeam(user, repo, team string) (*Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo, &team); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// RemoveRepoTeam delete a team from a repository
|
||||
func (c *Client) RemoveRepoTeam(user, repo, team string) (*Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo, &team); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// CheckRepoTeam check if team is assigned to repo by name and return it.
|
||||
// If not assigned, it will return nil.
|
||||
func (c *Client) CheckRepoTeam(user, repo, team string) (*Team, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := escapeValidatePathSegments(&user, &repo, &team); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
t := new(Team)
|
||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/teams/%s", user, repo, team), nil, nil, &t)
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
// if not found it's not an error, it indicates it's not assigned
|
||||
return nil, resp, nil
|
||||
}
|
||||
return t, resp, err
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRepoTeamManagement(t *testing.T) {
|
||||
log.Println("== TestRepoTeamManagement ==")
|
||||
c := newTestClient()
|
||||
|
||||
// prepare for test
|
||||
clean, repo, err := createTestOrgRepo(t, c, "RepoTeamManagement")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer clean()
|
||||
if _, err = createTestOrgTeams(t, c, repo.Owner.UserName, "Admins", AccessModeAdmin, []RepoUnitType{RepoUnitCode, RepoUnitIssues, RepoUnitPulls, RepoUnitReleases}); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = createTestOrgTeams(t, c, repo.Owner.UserName, "CodeManager", AccessModeWrite, []RepoUnitType{RepoUnitCode}); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = createTestOrgTeams(t, c, repo.Owner.UserName, "IssueManager", AccessModeWrite, []RepoUnitType{RepoUnitIssues, RepoUnitPulls}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// test
|
||||
teams, _, err := c.GetRepoTeams(repo.Owner.UserName, repo.Name)
|
||||
assert.NoError(t, err)
|
||||
if !assert.Len(t, teams, 1) {
|
||||
return
|
||||
}
|
||||
assert.EqualValues(t, AccessModeOwner, teams[0].Permission)
|
||||
|
||||
team, _, err := c.CheckRepoTeam(repo.Owner.UserName, repo.Name, "Admins")
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, team)
|
||||
|
||||
resp, err := c.AddRepoTeam(repo.Owner.UserName, repo.Name, "Admins")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 204, resp.StatusCode)
|
||||
resp, err = c.AddRepoTeam(repo.Owner.UserName, repo.Name, "CodeManager")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 204, resp.StatusCode)
|
||||
resp, err = c.AddRepoTeam(repo.Owner.UserName, repo.Name, "IssueManager")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 204, resp.StatusCode)
|
||||
|
||||
team, _, err = c.CheckRepoTeam(repo.Owner.UserName, repo.Name, "Admins")
|
||||
assert.NoError(t, err)
|
||||
if assert.NotNil(t, team) {
|
||||
assert.EqualValues(t, "Admins", team.Name)
|
||||
assert.EqualValues(t, AccessModeAdmin, team.Permission)
|
||||
}
|
||||
|
||||
teams, _, err = c.GetRepoTeams(repo.Owner.UserName, repo.Name)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, teams, 4)
|
||||
|
||||
resp, err = c.RemoveRepoTeam(repo.Owner.UserName, repo.Name, "IssueManager")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 204, resp.StatusCode)
|
||||
|
||||
team, _, err = c.CheckRepoTeam(repo.Owner.UserName, repo.Name, "IssueManager")
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, team)
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CreateRepoFromTemplateOption options when creating repository using a template
|
||||
type CreateRepoFromTemplateOption struct {
|
||||
// Owner is the organization or person who will own the new repository
|
||||
Owner string `json:"owner"`
|
||||
// Name of the repository to create
|
||||
Name string `json:"name"`
|
||||
// Description of the repository to create
|
||||
Description string `json:"description"`
|
||||
// Private is whether the repository is private
|
||||
Private bool `json:"private"`
|
||||
// GitContent include git content of default branch in template repo
|
||||
GitContent bool `json:"git_content"`
|
||||
// Topics include topics of template repo
|
||||
Topics bool `json:"topics"`
|
||||
// GitHooks include git hooks of template repo
|
||||
GitHooks bool `json:"git_hooks"`
|
||||
// Webhooks include webhooks of template repo
|
||||
Webhooks bool `json:"webhooks"`
|
||||
// Avatar include avatar of the template repo
|
||||
Avatar bool `json:"avatar"`
|
||||
// Labels include labels of template repo
|
||||
Labels bool `json:"labels"`
|
||||
}
|
||||
|
||||
// Validate validates CreateRepoFromTemplateOption
|
||||
func (opt CreateRepoFromTemplateOption) Validate() error {
|
||||
if len(opt.Owner) == 0 {
|
||||
return fmt.Errorf("field Owner is required")
|
||||
}
|
||||
if len(opt.Name) == 0 {
|
||||
return fmt.Errorf("field Name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateRepoFromTemplate create a repository using a template
|
||||
func (c *Client) CreateRepoFromTemplate(templateOwner, templateRepo string, opt CreateRepoFromTemplateOption) (*Repository, *Response, error) {
|
||||
if err := escapeValidatePathSegments(&templateOwner, &templateRepo); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := opt.Validate(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
repo := new(Repository)
|
||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/generate", templateOwner, templateRepo), jsonHeader, bytes.NewReader(body), &repo)
|
||||
return repo, resp, err
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRepoFromTemplate(t *testing.T) {
|
||||
log.Println("== TestRepoFromTemplate ==")
|
||||
c := newTestClient()
|
||||
repo, err := createTestRepo(t, "TemplateRepo", c)
|
||||
assert.NoError(t, err)
|
||||
repo, _, err = c.EditRepo(repo.Owner.UserName, repo.Name, EditRepoOption{Template: OptionalBool(true)})
|
||||
assert.NoError(t, err)
|
||||
_, err = c.SetRepoTopics(repo.Owner.UserName, repo.Name, []string{"abc", "def", "ghi"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
newRepo, resp, err := c.CreateRepoFromTemplate(repo.Owner.UserName, repo.Name, CreateRepoFromTemplateOption{
|
||||
Owner: repo.Owner.UserName,
|
||||
Name: "repoFromTemplate",
|
||||
Description: "",
|
||||
Topics: true,
|
||||
Labels: true,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 201, resp.StatusCode)
|
||||
assert.False(t, newRepo.Template)
|
||||
|
||||
labels, _, err := c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, labels, 7)
|
||||
|
||||
topics, _, _ := c.ListRepoTopics(repo.Owner.UserName, repo.Name, ListRepoTopicsOptions{})
|
||||
assert.EqualValues(t, []string{"abc", "def", "ghi"}, topics)
|
||||
|
||||
_, err = c.DeleteRepo(repo.Owner.UserName, "repoFromTemplate")
|
||||
assert.NoError(t, err)
|
||||
}
|
|
@ -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()
|
||||
|
||||
|
@ -157,15 +159,33 @@ func TestGetArchiveReader(t *testing.T) {
|
|||
assert.EqualValues(t, nBytes, len(archive.Bytes()))
|
||||
}
|
||||
|
||||
func TestGetRepoByID(t *testing.T) {
|
||||
log.Println("== TestGetRepoByID ==")
|
||||
c := newTestClient()
|
||||
testrepo, _ := createTestRepo(t, "TestGetRepoByID", c)
|
||||
|
||||
repo, _, err := c.GetRepoByID(testrepo.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, repo)
|
||||
assert.EqualValues(t, testrepo.ID, repo.ID)
|
||||
|
||||
_, err = c.DeleteRepo(repo.Owner.UserName, repo.Name)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// standard func to create a init repo for test routines
|
||||
func createTestRepo(t *testing.T, name string, c *Client) (*Repository, error) {
|
||||
user, _, uErr := c.GetMyUserInfo()
|
||||
assert.NoError(t, uErr)
|
||||
_, _, err := c.GetRepo(user.UserName, name)
|
||||
if err == nil {
|
||||
repo, _, err := c.GetRepo(user.UserName, name)
|
||||
// We need to check that the received repo is not a
|
||||
// redirected one, it could be the case that gitea redirect us
|
||||
// to a new repo(because it e.g. was transferred or renamed).
|
||||
if err == nil && repo.Owner.UserName == user.UserName {
|
||||
_, _ = c.DeleteRepo(user.UserName, name)
|
||||
}
|
||||
repo, _, err := c.CreateRepo(CreateRepoOption{
|
||||
|
||||
repo, _, err = c.CreateRepo(CreateRepoOption{
|
||||
Name: name,
|
||||
Description: "A test Repo: " + name,
|
||||
AutoInit: true,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -8,13 +8,17 @@ package gitea
|
|||
type GlobalUISettings struct {
|
||||
DefaultTheme string `json:"default_theme"`
|
||||
AllowedReactions []string `json:"allowed_reactions"`
|
||||
CustomEmojis []string `json:"custom_emojis"`
|
||||
}
|
||||
|
||||
// GlobalRepoSettings represent the global repository settings of a gitea instance witch is exposed by API
|
||||
type GlobalRepoSettings struct {
|
||||
MirrorsDisabled bool `json:"mirrors_disabled"`
|
||||
HTTPGitDisabled bool `json:"http_git_disabled"`
|
||||
MigrationsDisabled bool `json:"migrations_disabled"`
|
||||
MirrorsDisabled bool `json:"mirrors_disabled"`
|
||||
HTTPGitDisabled bool `json:"http_git_disabled"`
|
||||
MigrationsDisabled bool `json:"migrations_disabled"`
|
||||
StarsDisabled bool `json:"stars_disabled"`
|
||||
TimeTrackingDisabled bool `json:"time_tracking_disabled"`
|
||||
LFSDisabled bool `json:"lfs_disabled"`
|
||||
}
|
||||
|
||||
// GlobalAPISettings contains global api settings exposed by it
|
||||
|
|
|
@ -25,6 +25,7 @@ func TestGetGlobalSettings(t *testing.T) {
|
|||
assert.EqualValues(t, &GlobalRepoSettings{
|
||||
HTTPGitDisabled: false,
|
||||
MirrorsDisabled: false,
|
||||
LFSDisabled: true,
|
||||
}, repoSettings)
|
||||
|
||||
apiSettings, _, err := c.GetGlobalAPISettings()
|
||||
|
@ -38,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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,6 +6,8 @@ package gitea
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -15,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"`
|
||||
|
@ -23,9 +29,30 @@ type User struct {
|
|||
// User locale
|
||||
Language string `json:"language"`
|
||||
// Is the user an administrator
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
LastLogin time.Time `json:"last_login,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
// Date and Time of last login
|
||||
LastLogin time.Time `json:"last_login"`
|
||||
// Date and Time of user creation
|
||||
Created time.Time `json:"created"`
|
||||
// Is user restricted
|
||||
Restricted bool `json:"restricted"`
|
||||
// Is user active
|
||||
IsActive bool `json:"active"`
|
||||
// Is user login prohibited
|
||||
ProhibitLogin bool `json:"prohibit_login"`
|
||||
// the user's location
|
||||
Location string `json:"location"`
|
||||
// the user's website
|
||||
Website string `json:"website"`
|
||||
// the user's description
|
||||
Description string `json:"description"`
|
||||
// User visibility level option
|
||||
Visibility VisibleType `json:"visibility"`
|
||||
|
||||
// user counts
|
||||
FollowerCount int `json:"followers_count"`
|
||||
FollowingCount int `json:"following_count"`
|
||||
StarredRepoCount int `json:"starred_repos_count"`
|
||||
}
|
||||
|
||||
// GetUserInfo get user info by user's name
|
||||
|
@ -44,3 +71,23 @@ func (c *Client) GetMyUserInfo() (*User, *Response, error) {
|
|||
resp, err := c.getParsedResponse("GET", "/user", nil, nil, u)
|
||||
return u, resp, err
|
||||
}
|
||||
|
||||
// GetUserByID returns user by a given user ID
|
||||
func (c *Client) GetUserByID(id int64) (*User, *Response, error) {
|
||||
if id < 0 {
|
||||
return nil, nil, fmt.Errorf("invalid user id %d", id)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if len(users) == 1 {
|
||||
return users[0], resp, err
|
||||
}
|
||||
|
||||
return nil, resp, fmt.Errorf("user not found with id %d", id)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -34,11 +34,15 @@ func (opt *SearchUsersOption) QueryEncode() string {
|
|||
return query.Encode()
|
||||
}
|
||||
|
||||
// SearchUsers finds users by query
|
||||
func (c *Client) SearchUsers(opt SearchUsersOption) ([]*User, *Response, error) {
|
||||
func (c *Client) searchUsers(rawQuery string) ([]*User, *Response, error) {
|
||||
link, _ := url.Parse("/users/search")
|
||||
link.RawQuery = opt.QueryEncode()
|
||||
link.RawQuery = rawQuery
|
||||
userResp := new(searchUsersResponse)
|
||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &userResp)
|
||||
return userResp.Users, resp, err
|
||||
}
|
||||
|
||||
// SearchUsers finds users by query
|
||||
func (c *Client) SearchUsers(opt SearchUsersOption) ([]*User, *Response, error) {
|
||||
return c.searchUsers(opt.QueryEncode())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// UserSettings represents user settings
|
||||
type UserSettings struct {
|
||||
FullName string `json:"full_name"`
|
||||
Website string `json:"website"`
|
||||
Description string `json:"description"`
|
||||
Location string `json:"location"`
|
||||
Language string `json:"language"`
|
||||
Theme string `json:"theme"`
|
||||
DiffViewStyle string `json:"diff_view_style"`
|
||||
// Privacy
|
||||
HideEmail bool `json:"hide_email"`
|
||||
HideActivity bool `json:"hide_activity"`
|
||||
}
|
||||
|
||||
// UserSettingsOptions represents options to change user settings
|
||||
type UserSettingsOptions struct {
|
||||
FullName *string `json:"full_name,omitempty"`
|
||||
Website *string `json:"website,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Location *string `json:"location,omitempty"`
|
||||
Language *string `json:"language,omitempty"`
|
||||
Theme *string `json:"theme,omitempty"`
|
||||
DiffViewStyle *string `json:"diff_view_style,omitempty"`
|
||||
// Privacy
|
||||
HideEmail *bool `json:"hide_email,omitempty"`
|
||||
HideActivity *bool `json:"hide_activity,omitempty"`
|
||||
}
|
||||
|
||||
// GetUserSettings returns user settings
|
||||
func (c *Client) GetUserSettings() (*UserSettings, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
userConfig := new(UserSettings)
|
||||
resp, err := c.getParsedResponse("GET", "/user/settings", nil, nil, userConfig)
|
||||
return userConfig, resp, err
|
||||
}
|
||||
|
||||
// UpdateUserSettings returns user settings
|
||||
func (c *Client) UpdateUserSettings(opt UserSettingsOptions) (*UserSettings, *Response, error) {
|
||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
body, err := json.Marshal(&opt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
userConfig := new(UserSettings)
|
||||
resp, err := c.getParsedResponse("PATCH", "/user/settings", jsonHeader, bytes.NewReader(body), userConfig)
|
||||
return userConfig, resp, err
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUserSettings(t *testing.T) {
|
||||
log.Println("== TestUserSettings ==")
|
||||
c := newTestClient()
|
||||
|
||||
userConf, _, err := c.GetUserSettings()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, userConf)
|
||||
assert.EqualValues(t, UserSettings{
|
||||
Theme: "gitea-auto",
|
||||
HideEmail: false,
|
||||
HideActivity: false,
|
||||
}, *userConf)
|
||||
|
||||
userConf, _, err = c.UpdateUserSettings(UserSettingsOptions{
|
||||
FullName: OptionalString("Admin User on Test"),
|
||||
Language: OptionalString("de_de"),
|
||||
HideEmail: OptionalBool(true),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, userConf)
|
||||
assert.EqualValues(t, UserSettings{
|
||||
FullName: "Admin User on Test",
|
||||
Theme: "gitea-auto",
|
||||
Language: "de_de",
|
||||
HideEmail: true,
|
||||
HideActivity: false,
|
||||
}, *userConf)
|
||||
|
||||
_, _, err = c.UpdateUserSettings(UserSettingsOptions{
|
||||
FullName: OptionalString(""),
|
||||
Language: OptionalString(""),
|
||||
HideEmail: OptionalBool(false),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
|
@ -21,8 +21,8 @@ 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.Equal(t, true, user.IsAdmin)
|
||||
assert.EqualValues(t, "http://gitea:3000/avatars/d794373e882a68fb173cef817fb6180a", user.AvatarURL)
|
||||
assert.True(t, user.IsAdmin)
|
||||
}
|
||||
|
||||
func TestUserApp(t *testing.T) {
|
||||
|
@ -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{})
|
||||
|
@ -171,6 +171,32 @@ func TestUserEmail(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetUserByID(t *testing.T) {
|
||||
log.Println("== TestGetUserByID ==")
|
||||
c := newTestClient()
|
||||
|
||||
user1 := createTestUser(t, "user1", c)
|
||||
user2 := createTestUser(t, "user2", c)
|
||||
|
||||
r1, _, err := c.GetUserByID(user1.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, r1)
|
||||
assert.Equal(t, user1.UserName, r1.UserName)
|
||||
|
||||
r2, _, err := c.GetUserByID(user2.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, r2)
|
||||
assert.Equal(t, user2.UserName, r2.UserName)
|
||||
|
||||
r3, _, err := c.GetUserByID(42)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, r3)
|
||||
|
||||
r4, _, err := c.GetUserByID(-1)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, r4)
|
||||
}
|
||||
|
||||
func createTestUser(t *testing.T, username string, client *Client) *User {
|
||||
user, _, _ := client.GetUserInfo(username)
|
||||
if user.ID != 0 {
|
||||
|
@ -180,3 +206,12 @@ func createTestUser(t *testing.T, username string, client *Client) *User {
|
|||
assert.NoError(t, err)
|
||||
return user
|
||||
}
|
||||
|
||||
// userToStringSlice return string slice based on UserName of users
|
||||
func userToStringSlice(users []*User) []string {
|
||||
result := make([]string, 0, len(users))
|
||||
for i := range users {
|
||||
result = append(result, users[i].UserName)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -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,16 +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_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
|
||||
}
|
||||
|
@ -71,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
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue