Use golang v1.18 and drop vendor folder (#478)
* remove vendor folder * use golang v1.18 in ci * use "go install" * use vendor folder as cache Reviewed-on: https://gitea.com/gitea/tea/pulls/478 Reviewed-by: techknowlogick <techknowlogick@gitea.io> Reviewed-by: John Olheiser <john.olheiser@gmail.com>
This commit is contained in:
parent
9ab36c55fa
commit
d06f35482e
19
.drone.yml
19
.drone.yml
|
@ -7,9 +7,17 @@ platform:
|
||||||
arch: amd64
|
arch: amd64
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: vendor
|
||||||
|
pull: always
|
||||||
|
image: golang:1.18
|
||||||
|
environment:
|
||||||
|
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
|
||||||
|
commands:
|
||||||
|
- make vendor # use vendor folder as cache
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
pull: always
|
pull: always
|
||||||
image: golang:1.17
|
image: golang:1.18
|
||||||
environment:
|
environment:
|
||||||
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
|
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
|
||||||
commands:
|
commands:
|
||||||
|
@ -18,7 +26,6 @@ steps:
|
||||||
- make lint
|
- make lint
|
||||||
- make fmt-check
|
- make fmt-check
|
||||||
- make misspell-check
|
- make misspell-check
|
||||||
- make test-vendor
|
|
||||||
- make build
|
- make build
|
||||||
when:
|
when:
|
||||||
event:
|
event:
|
||||||
|
@ -27,7 +34,7 @@ steps:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: unit-test
|
- name: unit-test
|
||||||
image: golang:1.17
|
image: golang:1.18
|
||||||
commands:
|
commands:
|
||||||
- make unit-test-coverage
|
- make unit-test-coverage
|
||||||
settings:
|
settings:
|
||||||
|
@ -42,7 +49,7 @@ steps:
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
- name: release-test
|
- name: release-test
|
||||||
image: golang:1.17
|
image: golang:1.18
|
||||||
commands:
|
commands:
|
||||||
- make test
|
- make test
|
||||||
settings:
|
settings:
|
||||||
|
@ -58,7 +65,7 @@ steps:
|
||||||
|
|
||||||
- name: tag-test
|
- name: tag-test
|
||||||
pull: always
|
pull: always
|
||||||
image: golang:1.17
|
image: golang:1.18
|
||||||
commands:
|
commands:
|
||||||
- make test
|
- make test
|
||||||
settings:
|
settings:
|
||||||
|
@ -70,7 +77,7 @@ steps:
|
||||||
- tag
|
- tag
|
||||||
|
|
||||||
- name: static
|
- name: static
|
||||||
image: golang:1.17
|
image: golang:1.18
|
||||||
environment:
|
environment:
|
||||||
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
|
GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not
|
||||||
commands:
|
commands:
|
||||||
|
|
|
@ -6,3 +6,4 @@ tea
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
vendor/
|
||||||
|
|
51
Makefile
51
Makefile
|
@ -63,24 +63,15 @@ vet:
|
||||||
$(GO) vet -vettool=gitea-vet $(PACKAGES)
|
$(GO) vet -vettool=gitea-vet $(PACKAGES)
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint: install-lint-tools
|
||||||
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
cd /tmp && $(GO) get -u github.com/mgechev/revive; \
|
|
||||||
fi
|
|
||||||
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
|
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
|
||||||
|
|
||||||
.PHONY: misspell-check
|
.PHONY: misspell-check
|
||||||
misspell-check:
|
misspell-check: install-lint-tools
|
||||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
cd /tmp && $(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
|
||||||
fi
|
|
||||||
misspell -error -i unknwon,destory $(GOFILES)
|
misspell -error -i unknwon,destory $(GOFILES)
|
||||||
|
|
||||||
.PHONY: misspell
|
.PHONY: misspell
|
||||||
misspell:
|
misspell: install-lint-tools
|
||||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
cd /tmp && $(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
|
||||||
fi
|
|
||||||
misspell -w -i unknwon $(GOFILES)
|
misspell -w -i unknwon $(GOFILES)
|
||||||
|
|
||||||
.PHONY: fmt-check
|
.PHONY: fmt-check
|
||||||
|
@ -105,15 +96,6 @@ unit-test-coverage:
|
||||||
vendor:
|
vendor:
|
||||||
$(GO) mod tidy && $(GO) mod vendor
|
$(GO) mod tidy && $(GO) mod vendor
|
||||||
|
|
||||||
.PHONY: test-vendor
|
|
||||||
test-vendor: vendor
|
|
||||||
@diff=$$(git diff vendor/); \
|
|
||||||
if [ -n "$$diff" ]; then \
|
|
||||||
echo "Please run 'make vendor' and commit the result:"; \
|
|
||||||
echo "$${diff}"; \
|
|
||||||
exit 1; \
|
|
||||||
fi;
|
|
||||||
|
|
||||||
.PHONY: check
|
.PHONY: check
|
||||||
check: test
|
check: test
|
||||||
|
|
||||||
|
@ -141,18 +123,29 @@ release-dirs:
|
||||||
|
|
||||||
.PHONY: release-os
|
.PHONY: release-os
|
||||||
release-os:
|
release-os:
|
||||||
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
cd /tmp && $(GO) get -u github.com/mitchellh/gox; \
|
|
||||||
fi
|
|
||||||
CGO_ENABLED=0 gox -verbose -cgo=false $(GOFLAGS) -osarch='!darwin/386 !darwin/arm' -os="windows linux darwin" -arch="386 amd64 arm arm64" -output="$(DIST)/release/tea-$(VERSION)-{{.OS}}-{{.Arch}}"
|
CGO_ENABLED=0 gox -verbose -cgo=false $(GOFLAGS) -osarch='!darwin/386 !darwin/arm' -os="windows linux darwin" -arch="386 amd64 arm arm64" -output="$(DIST)/release/tea-$(VERSION)-{{.OS}}-{{.Arch}}"
|
||||||
|
|
||||||
.PHONY: release-compress
|
.PHONY: release-compress
|
||||||
release-compress:
|
release-compress: install-release-tools
|
||||||
@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
GO111MODULE=off $(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
|
|
||||||
fi
|
|
||||||
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
|
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
|
||||||
|
|
||||||
.PHONY: release-check
|
.PHONY: release-check
|
||||||
release-check:
|
release-check: install-release-tools
|
||||||
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "checksumming $${file}" && $(SHASUM) `echo $${file} | sed 's/^..//'` > $${file}.sha256; done;
|
cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "checksumming $${file}" && $(SHASUM) `echo $${file} | sed 's/^..//'` > $${file}.sha256; done;
|
||||||
|
|
||||||
|
### tools
|
||||||
|
install-release-tools:
|
||||||
|
@hash gox > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) install github.com/mitchellh/gox@latest; \
|
||||||
|
fi
|
||||||
|
@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) install github.com/ulikunitz/xz/cmd/gxz@latest; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
install-lint-tools:
|
||||||
|
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) install github.com/mgechev/revive@latest; \
|
||||||
|
fi
|
||||||
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
|
||||||
|
fi
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
# The full repository name
|
|
||||||
repo: gitea/gitea-vet
|
|
||||||
|
|
||||||
# Service type (gitea or github)
|
|
||||||
service: gitea
|
|
||||||
|
|
||||||
# Base URL for Gitea instance if using gitea service type (optional)
|
|
||||||
base-url: https://gitea.com
|
|
||||||
|
|
||||||
# Changelog groups and which labeled PRs to add to each group
|
|
||||||
groups:
|
|
||||||
-
|
|
||||||
name: BREAKING
|
|
||||||
labels:
|
|
||||||
- breaking
|
|
||||||
-
|
|
||||||
name: FEATURES
|
|
||||||
labels:
|
|
||||||
- feature
|
|
||||||
-
|
|
||||||
name: BUGFIXES
|
|
||||||
labels:
|
|
||||||
- bug
|
|
||||||
-
|
|
||||||
name: ENHANCEMENTS
|
|
||||||
labels:
|
|
||||||
- enhancement
|
|
||||||
|
|
||||||
# regex indicating which labels to skip for the changelog
|
|
||||||
skip-labels: skip-changelog|backport\/.+
|
|
|
@ -1,45 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: compliance
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: check
|
|
||||||
pull: always
|
|
||||||
image: golang:1.14
|
|
||||||
environment:
|
|
||||||
GOPROXY: https://goproxy.cn
|
|
||||||
commands:
|
|
||||||
- make build
|
|
||||||
- make lint
|
|
||||||
- make vet
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: build-master
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
pull: always
|
|
||||||
image: techknowlogick/xgo:latest
|
|
||||||
environment:
|
|
||||||
GOPROXY: https://goproxy.cn
|
|
||||||
commands:
|
|
||||||
- make build
|
|
|
@ -1,5 +0,0 @@
|
||||||
# GoLand
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# Binaries
|
|
||||||
/gitea-vet*
|
|
|
@ -1,23 +0,0 @@
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- deadcode
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
|
||||||
- errcheck
|
|
||||||
- gocognit
|
|
||||||
- goconst
|
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
|
||||||
- gofmt
|
|
||||||
- golint
|
|
||||||
- gosimple
|
|
||||||
- govet
|
|
||||||
- maligned
|
|
||||||
- misspell
|
|
||||||
- prealloc
|
|
||||||
- staticcheck
|
|
||||||
- structcheck
|
|
||||||
- typecheck
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
- varcheck
|
|
|
@ -1,11 +0,0 @@
|
||||||
## [v0.2.1](https://gitea.com/gitea/gitea-vet/releases/tag/v0.2.1) - 2020-08-15
|
|
||||||
|
|
||||||
* BUGFIXES
|
|
||||||
* Split migration check to Deps and Imports (#9)
|
|
||||||
|
|
||||||
## [0.2.0](https://gitea.com/gitea/gitea-vet/pulls?q=&type=all&state=closed&milestone=1272) - 2020-07-20
|
|
||||||
|
|
||||||
* FEATURES
|
|
||||||
* Add migrations check (#5)
|
|
||||||
* BUGFIXES
|
|
||||||
* Correct Import Paths (#6)
|
|
|
@ -1,19 +0,0 @@
|
||||||
Copyright (c) 2020 The Gitea Authors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
|
@ -1,22 +0,0 @@
|
||||||
GO ?= go
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
$(GO) build
|
|
||||||
|
|
||||||
.PHONY: fmt
|
|
||||||
fmt:
|
|
||||||
$(GO) fmt ./...
|
|
||||||
|
|
||||||
.PHONY: vet
|
|
||||||
vet: build
|
|
||||||
$(GO) vet ./...
|
|
||||||
$(GO) vet -vettool=gitea-vet ./...
|
|
||||||
|
|
||||||
.PHONY: lint
|
|
||||||
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 $(shell $(GO) env GOPATH)/bin v1.24.0; \
|
|
||||||
fi
|
|
||||||
golangci-lint run --timeout 5m
|
|
|
@ -1,11 +0,0 @@
|
||||||
# gitea-vet
|
|
||||||
|
|
||||||
[![Build Status](https://drone.gitea.com/api/badges/gitea/gitea-vet/status.svg)](https://drone.gitea.com/gitea/gitea-vet)
|
|
||||||
|
|
||||||
`go vet` tool for Gitea
|
|
||||||
|
|
||||||
| Analyzer | Description |
|
|
||||||
|------------|-----------------------------------------------------------------------------|
|
|
||||||
| Imports | Checks for import sorting. stdlib->code.gitea.io->other |
|
|
||||||
| License | Checks file headers for some form of `Copyright...YYYY...Gitea/Gogs` |
|
|
||||||
| Migrations | Checks for black-listed packages in `code.gitea.io/gitea/models/migrations` |
|
|
|
@ -1,46 +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 checks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Imports = &analysis.Analyzer{
|
|
||||||
Name: "imports",
|
|
||||||
Doc: "check for import order",
|
|
||||||
Run: runImports,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runImports(pass *analysis.Pass) (interface{}, error) {
|
|
||||||
for _, file := range pass.Files {
|
|
||||||
level := 0
|
|
||||||
for _, im := range file.Imports {
|
|
||||||
var lvl int
|
|
||||||
val := im.Path.Value
|
|
||||||
switch {
|
|
||||||
case importHasPrefix(val, "code.gitea.io"):
|
|
||||||
lvl = 2
|
|
||||||
case strings.Contains(val, "."):
|
|
||||||
lvl = 3
|
|
||||||
default:
|
|
||||||
lvl = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if lvl < level {
|
|
||||||
pass.Reportf(file.Pos(), "Imports are sorted wrong")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
level = lvl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func importHasPrefix(s, p string) bool {
|
|
||||||
return strings.HasPrefix(s, "\""+p)
|
|
||||||
}
|
|
|
@ -1,73 +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 checks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
header = regexp.MustCompile(`.*Copyright.*\d{4}.*(Gitea|Gogs)`)
|
|
||||||
goGenerate = "//go:generate"
|
|
||||||
buildTag = "// +build"
|
|
||||||
)
|
|
||||||
|
|
||||||
var License = &analysis.Analyzer{
|
|
||||||
Name: "license",
|
|
||||||
Doc: "check for a copyright header",
|
|
||||||
Run: runLicense,
|
|
||||||
}
|
|
||||||
|
|
||||||
func runLicense(pass *analysis.Pass) (interface{}, error) {
|
|
||||||
for _, file := range pass.Files {
|
|
||||||
if len(file.Comments) == 0 {
|
|
||||||
pass.Reportf(file.Pos(), "Copyright not found")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(file.Comments[0].List) == 0 {
|
|
||||||
pass.Reportf(file.Pos(), "Copyright not found or wrong")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
commentGroup := 0
|
|
||||||
if strings.HasPrefix(file.Comments[0].List[0].Text, goGenerate) {
|
|
||||||
if len(file.Comments[0].List) > 1 {
|
|
||||||
pass.Reportf(file.Pos(), "Must be an empty line between the go:generate and the Copyright")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
commentGroup++
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(file.Comments[0].List[0].Text, buildTag) {
|
|
||||||
commentGroup++
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(file.Comments) < commentGroup+1 {
|
|
||||||
pass.Reportf(file.Pos(), "Copyright not found")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(file.Comments[commentGroup].List) < 1 {
|
|
||||||
pass.Reportf(file.Pos(), "Copyright not found or wrong")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var check bool
|
|
||||||
for _, comment := range file.Comments[commentGroup].List {
|
|
||||||
if header.MatchString(comment.Text) {
|
|
||||||
check = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !check {
|
|
||||||
pass.Reportf(file.Pos(), "Copyright did not match check")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
|
@ -1,77 +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 checks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Migrations = &analysis.Analyzer{
|
|
||||||
Name: "migrations",
|
|
||||||
Doc: "check migrations for black-listed packages.",
|
|
||||||
Run: checkMigrations,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
migrationDepBlockList = []string{
|
|
||||||
"code.gitea.io/gitea/models",
|
|
||||||
}
|
|
||||||
migrationImpBlockList = []string{
|
|
||||||
"code.gitea.io/gitea/modules/structs",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkMigrations(pass *analysis.Pass) (interface{}, error) {
|
|
||||||
if !strings.EqualFold(pass.Pkg.Path(), "code.gitea.io/gitea/models/migrations") {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := exec.LookPath("go"); err != nil {
|
|
||||||
return nil, errors.New("go was not found in the PATH")
|
|
||||||
}
|
|
||||||
|
|
||||||
depsCmd := exec.Command("go", "list", "-f", `{{join .Deps "\n"}}`, "code.gitea.io/gitea/models/migrations")
|
|
||||||
depsOut, err := depsCmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
deps := strings.Split(string(depsOut), "\n")
|
|
||||||
for _, dep := range deps {
|
|
||||||
if stringInSlice(dep, migrationDepBlockList) {
|
|
||||||
pass.Reportf(0, "code.gitea.io/gitea/models/migrations cannot depend on the following packages: %s", migrationDepBlockList)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impsCmd := exec.Command("go", "list", "-f", `{{join .Imports "\n"}}`, "code.gitea.io/gitea/models/migrations")
|
|
||||||
impsOut, err := impsCmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
imps := strings.Split(string(impsOut), "\n")
|
|
||||||
for _, imp := range imps {
|
|
||||||
if stringInSlice(imp, migrationImpBlockList) {
|
|
||||||
pass.Reportf(0, "code.gitea.io/gitea/models/migrations cannot import the following packages: %s", migrationImpBlockList)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringInSlice(needle string, haystack []string) bool {
|
|
||||||
for _, h := range haystack {
|
|
||||||
if strings.EqualFold(needle, h) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
module code.gitea.io/gitea-vet
|
|
||||||
|
|
||||||
go 1.14
|
|
||||||
|
|
||||||
require golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224
|
|
|
@ -1,20 +0,0 @@
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI=
|
|
||||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
|
@ -1,19 +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 main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.gitea.io/gitea-vet/checks"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis/unitchecker"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
unitchecker.Main(
|
|
||||||
checks.Imports,
|
|
||||||
checks.License,
|
|
||||||
checks.Migrations,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
Copyright (c) 2016 The Gitea Authors
|
|
||||||
Copyright (c) 2014 The Gogs Authors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
|
@ -1,47 +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 (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CronTask represents a Cron task
|
|
||||||
type CronTask struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Schedule string `json:"schedule"`
|
|
||||||
Next time.Time `json:"next"`
|
|
||||||
Prev time.Time `json:"prev"`
|
|
||||||
ExecTimes int64 `json:"exec_times"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListCronTaskOptions list options for ListCronTasks
|
|
||||||
type ListCronTaskOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListCronTasks list available cron tasks
|
|
||||||
func (c *Client) ListCronTasks(opt ListCronTaskOptions) ([]*CronTask, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
ct := make([]*CronTask, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/cron?%s", opt.getURLQuery().Encode()), jsonHeader, nil, &ct)
|
|
||||||
return ct, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunCronTasks run a cron task
|
|
||||||
func (c *Client) RunCronTasks(task string) (*Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&task); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/admin/cron/%s", task), jsonHeader, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AdminListOrgsOptions options for listing admin's organizations
|
|
||||||
type AdminListOrgsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdminListOrgs lists all orgs
|
|
||||||
func (c *Client) AdminListOrgs(opt AdminListOrgsOptions) ([]*Organization, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
orgs := make([]*Organization, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs)
|
|
||||||
return orgs, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdminCreateOrg create an organization
|
|
||||||
func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
org := new(Organization)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/orgs", user), jsonHeader, bytes.NewReader(body), org)
|
|
||||||
return org, resp, err
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AdminCreateRepo create a repo
|
|
||||||
func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); 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("/admin/users/%s/repos", user), jsonHeader, bytes.NewReader(body), repo)
|
|
||||||
return repo, resp, err
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AdminListUsersOptions options for listing admin users
|
|
||||||
type AdminListUsersOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdminListUsers lists all users
|
|
||||||
func (c *Client) AdminListUsers(opt AdminListUsersOptions) ([]*User, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
users := make([]*User, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/admin/users?%s", opt.getURLQuery().Encode()), nil, nil, &users)
|
|
||||||
return users, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUserOption create user options
|
|
||||||
type CreateUserOption struct {
|
|
||||||
SourceID int64 `json:"source_id"`
|
|
||||||
LoginName string `json:"login_name"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
FullName string `json:"full_name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
MustChangePassword *bool `json:"must_change_password"`
|
|
||||||
SendNotify bool `json:"send_notify"`
|
|
||||||
Visibility *VisibleType `json:"visibility"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateUserOption struct
|
|
||||||
func (opt CreateUserOption) Validate() error {
|
|
||||||
if len(opt.Email) == 0 {
|
|
||||||
return fmt.Errorf("email is empty")
|
|
||||||
}
|
|
||||||
if len(opt.Username) == 0 {
|
|
||||||
return fmt.Errorf("username is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdminCreateUser create a user
|
|
||||||
func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, *Response, error) {
|
|
||||||
if err := opt.Validate(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
user := new(User)
|
|
||||||
resp, err := c.getParsedResponse("POST", "/admin/users", jsonHeader, bytes.NewReader(body), user)
|
|
||||||
return user, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditUserOption edit user options
|
|
||||||
type EditUserOption struct {
|
|
||||||
SourceID int64 `json:"source_id"`
|
|
||||||
LoginName string `json:"login_name"`
|
|
||||||
Email *string `json:"email"`
|
|
||||||
FullName *string `json:"full_name"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
MustChangePassword *bool `json:"must_change_password"`
|
|
||||||
Website *string `json:"website"`
|
|
||||||
Location *string `json:"location"`
|
|
||||||
Active *bool `json:"active"`
|
|
||||||
Admin *bool `json:"admin"`
|
|
||||||
AllowGitHook *bool `json:"allow_git_hook"`
|
|
||||||
AllowImportLocal *bool `json:"allow_import_local"`
|
|
||||||
MaxRepoCreation *int `json:"max_repo_creation"`
|
|
||||||
ProhibitLogin *bool `json:"prohibit_login"`
|
|
||||||
AllowCreateOrganization *bool `json:"allow_create_organization"`
|
|
||||||
Restricted *bool `json:"restricted"`
|
|
||||||
Visibility *VisibleType `json:"visibility"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdminEditUser modify user informations
|
|
||||||
func (c *Client) AdminEditUser(user string, opt EditUserOption) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/admin/users/%s", user), jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdminDeleteUser delete one user according name
|
|
||||||
func (c *Client) AdminDeleteUser(user string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s", user), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdminCreateUserPublicKey adds a public key for the user
|
|
||||||
func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*PublicKey, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
key := new(PublicKey)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/keys", user), jsonHeader, bytes.NewReader(body), key)
|
|
||||||
return key, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AdminDeleteUserPublicKey deletes a user's public key
|
|
||||||
func (c *Client) AdminDeleteUserPublicKey(user string, keyID int) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s/keys/%d", user, keyID), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea // import "code.gitea.io/sdk/gitea"
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Attachment a generic attachment
|
|
||||||
type Attachment struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
DownloadCount int64 `json:"download_count"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
UUID string `json:"uuid"`
|
|
||||||
DownloadURL string `json:"browser_download_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListReleaseAttachmentsOptions options for listing release's attachments
|
|
||||||
type ListReleaseAttachmentsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListReleaseAttachments list release's attachments
|
|
||||||
func (c *Client) ListReleaseAttachments(user, repo string, release int64, opt ListReleaseAttachmentsOptions) ([]*Attachment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
attachments := make([]*Attachment, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets?%s", user, repo, release, opt.getURLQuery().Encode()),
|
|
||||||
nil, nil, &attachments)
|
|
||||||
return attachments, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetReleaseAttachment returns the requested attachment
|
|
||||||
func (c *Client) GetReleaseAttachment(user, repo string, release int64, id int64) (*Attachment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
a := new(Attachment)
|
|
||||||
resp, err := c.getParsedResponse("GET",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id),
|
|
||||||
nil, nil, &a)
|
|
||||||
return a, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateReleaseAttachment creates an attachment for the given release
|
|
||||||
func (c *Client) CreateReleaseAttachment(user, repo string, release int64, file io.Reader, filename string) (*Attachment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// Write file to body
|
|
||||||
body := new(bytes.Buffer)
|
|
||||||
writer := multipart.NewWriter(body)
|
|
||||||
part, err := writer.CreateFormFile("attachment", filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = io.Copy(part, file); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err = writer.Close(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
attachment := new(Attachment)
|
|
||||||
resp, err := c.getParsedResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d/assets", user, repo, release),
|
|
||||||
http.Header{"Content-Type": {writer.FormDataContentType()}}, body, &attachment)
|
|
||||||
return attachment, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditAttachmentOptions options for editing attachments
|
|
||||||
type EditAttachmentOptions struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditReleaseAttachment updates the given attachment with the given options
|
|
||||||
func (c *Client) EditReleaseAttachment(user, repo string, release int64, attachment int64, form EditAttachmentOptions) (*Attachment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&form)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
attach := new(Attachment)
|
|
||||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, attachment), jsonHeader, bytes.NewReader(body), attach)
|
|
||||||
return attach, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteReleaseAttachment deletes the given attachment including the uploaded file
|
|
||||||
func (c *Client) DeleteReleaseAttachment(user, repo string, release int64, id int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/releases/%d/assets/%d", user, repo, release, id), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,333 +0,0 @@
|
||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var jsonHeader = http.Header{"content-type": []string{"application/json"}}
|
|
||||||
|
|
||||||
// Version return the library version
|
|
||||||
func Version() string {
|
|
||||||
return "0.14.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client represents a thread-safe Gitea API client.
|
|
||||||
type Client struct {
|
|
||||||
url string
|
|
||||||
accessToken string
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
otp string
|
|
||||||
sudo string
|
|
||||||
debug bool
|
|
||||||
client *http.Client
|
|
||||||
ctx context.Context
|
|
||||||
mutex sync.RWMutex
|
|
||||||
serverVersion *version.Version
|
|
||||||
getVersionOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response represents the gitea response
|
|
||||||
type Response struct {
|
|
||||||
*http.Response
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
client := &Client{
|
|
||||||
url: strings.TrimSuffix(url, "/"),
|
|
||||||
client: &http.Client{},
|
|
||||||
ctx: context.Background(),
|
|
||||||
}
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(client)
|
|
||||||
}
|
|
||||||
if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientWithHTTP creates an API client with a custom http client
|
|
||||||
// Deprecated use SetHTTPClient option
|
|
||||||
func NewClientWithHTTP(url string, httpClient *http.Client) *Client {
|
|
||||||
client, _ := NewClient(url, SetHTTPClient(httpClient))
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHTTPClient is an option for NewClient to set custom http client
|
|
||||||
func SetHTTPClient(httpClient *http.Client) func(client *Client) {
|
|
||||||
return func(client *Client) {
|
|
||||||
client.SetHTTPClient(httpClient)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHTTPClient replaces default http.Client with user given one.
|
|
||||||
func (c *Client) SetHTTPClient(client *http.Client) {
|
|
||||||
c.mutex.Lock()
|
|
||||||
c.client = client
|
|
||||||
c.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetToken is an option for NewClient to set token
|
|
||||||
func SetToken(token string) func(client *Client) {
|
|
||||||
return func(client *Client) {
|
|
||||||
client.mutex.Lock()
|
|
||||||
client.accessToken = token
|
|
||||||
client.mutex.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBasicAuth is an option for NewClient to set username and password
|
|
||||||
func SetBasicAuth(username, password string) func(client *Client) {
|
|
||||||
return func(client *Client) {
|
|
||||||
client.SetBasicAuth(username, password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBasicAuth sets username and password
|
|
||||||
func (c *Client) SetBasicAuth(username, password string) {
|
|
||||||
c.mutex.Lock()
|
|
||||||
c.username, c.password = username, password
|
|
||||||
c.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOTP is an option for NewClient to set OTP for 2FA
|
|
||||||
func SetOTP(otp string) func(client *Client) {
|
|
||||||
return func(client *Client) {
|
|
||||||
client.SetOTP(otp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOTP sets OTP for 2FA
|
|
||||||
func (c *Client) SetOTP(otp string) {
|
|
||||||
c.mutex.Lock()
|
|
||||||
c.otp = otp
|
|
||||||
c.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetContext is an option for NewClient to set context
|
|
||||||
func SetContext(ctx context.Context) func(client *Client) {
|
|
||||||
return func(client *Client) {
|
|
||||||
client.SetContext(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetContext set context witch is used for http requests
|
|
||||||
func (c *Client) SetContext(ctx context.Context) {
|
|
||||||
c.mutex.Lock()
|
|
||||||
c.ctx = ctx
|
|
||||||
c.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSudo is an option for NewClient to set sudo header
|
|
||||||
func SetSudo(sudo string) func(client *Client) {
|
|
||||||
return func(client *Client) {
|
|
||||||
client.SetSudo(sudo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSudo sets username to impersonate.
|
|
||||||
func (c *Client) SetSudo(sudo string) {
|
|
||||||
c.mutex.Lock()
|
|
||||||
c.sudo = sudo
|
|
||||||
c.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDebugMode is an option for NewClient to enable debug mode
|
|
||||||
func SetDebugMode() func(client *Client) {
|
|
||||||
return func(client *Client) {
|
|
||||||
client.mutex.Lock()
|
|
||||||
client.debug = true
|
|
||||||
client.mutex.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) {
|
|
||||||
c.mutex.RLock()
|
|
||||||
debug := c.debug
|
|
||||||
if debug {
|
|
||||||
fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body)
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body)
|
|
||||||
|
|
||||||
client := c.client // client ref can change from this point on so safe it
|
|
||||||
c.mutex.RUnlock()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if debug {
|
|
||||||
fmt.Printf("Response: %v\n\n", resp)
|
|
||||||
}
|
|
||||||
return data, &Response{resp}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body)
|
|
||||||
if err != nil {
|
|
||||||
c.mutex.RUnlock()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(c.accessToken) != 0 {
|
|
||||||
req.Header.Set("Authorization", "token "+c.accessToken)
|
|
||||||
}
|
|
||||||
if len(c.otp) != 0 {
|
|
||||||
req.Header.Set("X-GITEA-OTP", c.otp)
|
|
||||||
}
|
|
||||||
if len(c.username) != 0 {
|
|
||||||
req.SetBasicAuth(c.username, c.password)
|
|
||||||
}
|
|
||||||
if len(c.sudo) != 0 {
|
|
||||||
req.Header.Set("Sudo", c.sudo)
|
|
||||||
}
|
|
||||||
|
|
||||||
client := c.client // client ref can change from this point on so safe it
|
|
||||||
c.mutex.RUnlock()
|
|
||||||
|
|
||||||
for k, v := range header {
|
|
||||||
req.Header[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
fmt.Printf("Response: %v\n\n", resp)
|
|
||||||
}
|
|
||||||
return &Response{resp}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a response for a HTTP status code indicating an error condition
|
|
||||||
// (non-2XX) to a well-known error value and response body. For non-problematic
|
|
||||||
// (2XX) status codes nil will be returned. Note that on a non-2XX response, the
|
|
||||||
// response body stream will have been read and, hence, is closed on return.
|
|
||||||
func statusCodeToErr(resp *Response) (body []byte, err error) {
|
|
||||||
// no error
|
|
||||||
if resp.StatusCode/100 == 2 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// error: body will be read for details
|
|
||||||
//
|
|
||||||
defer resp.Body.Close()
|
|
||||||
data, err := ioutil.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
|
|
||||||
errMap := make(map[string]interface{})
|
|
||||||
if err = json.Unmarshal(data, &errMap); err != nil {
|
|
||||||
// when the JSON can't be parsed, data was probably empty or a
|
|
||||||
// plain string, so we try to return a helpful error anyway
|
|
||||||
return data, 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) {
|
|
||||||
resp, err := c.doRequest(method, path, header, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// check for errors
|
|
||||||
data, err := statusCodeToErr(resp)
|
|
||||||
if err != nil {
|
|
||||||
return data, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// success (2XX), read body
|
|
||||||
data, err = ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) (*Response, error) {
|
|
||||||
data, resp, err := c.getResponse(method, path, header, body)
|
|
||||||
if err != nil {
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
return resp, json.Unmarshal(data, obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) getStatusCode(method, path string, header http.Header, body io.Reader) (int, *Response, error) {
|
|
||||||
resp, err := c.doRequest(method, path, header, body)
|
|
||||||
if err != nil {
|
|
||||||
return -1, resp, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return resp.StatusCode, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pathEscapeSegments escapes segments of a path while not escaping forward slash
|
|
||||||
func pathEscapeSegments(path string) string {
|
|
||||||
slice := strings.Split(path, "/")
|
|
||||||
for index := range slice {
|
|
||||||
slice[index] = url.PathEscape(slice[index])
|
|
||||||
}
|
|
||||||
escapedPath := strings.Join(slice, "/")
|
|
||||||
return escapedPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// escapeValidatePathSegments is a help function to validate and encode url path segments
|
|
||||||
func escapeValidatePathSegments(seg ...*string) error {
|
|
||||||
for i := range seg {
|
|
||||||
if seg[i] == nil || len(*seg[i]) == 0 {
|
|
||||||
return fmt.Errorf("path segment [%d] is empty", i)
|
|
||||||
}
|
|
||||||
*seg[i] = url.PathEscape(*seg[i])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea // import "code.gitea.io/sdk/gitea"
|
|
|
@ -1,49 +0,0 @@
|
||||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListForksOptions options for listing repository's forks
|
|
||||||
type ListForksOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListForks list a repository's forks
|
|
||||||
func (c *Client) ListForks(user string, repo string, opt ListForksOptions) ([]*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
forks := make([]*Repository, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/forks?%s", user, repo, opt.getURLQuery().Encode()),
|
|
||||||
nil, nil, &forks)
|
|
||||||
return forks, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateForkOption options for creating a fork
|
|
||||||
type CreateForkOption struct {
|
|
||||||
// organization name, if forking into an organization
|
|
||||||
Organization *string `json:"organization"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFork create a fork of a repository
|
|
||||||
func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(form)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
fork := new(Repository)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/forks", user, repo), jsonHeader, bytes.NewReader(body), &fork)
|
|
||||||
return fork, resp, err
|
|
||||||
}
|
|
|
@ -1,28 +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 (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GitBlobResponse represents a git blob
|
|
||||||
type GitBlobResponse struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
Encoding string `json:"encoding"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
SHA string `json:"sha"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBlob get the blob of a repository file
|
|
||||||
func (c *Client) GetBlob(user, repo, sha string) (*GitBlobResponse, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
blob := new(GitBlobResponse)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/blobs/%s", user, repo, sha), nil, nil, blob)
|
|
||||||
return blob, resp, err
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GitHook represents a Git repository hook
|
|
||||||
type GitHook struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
IsActive bool `json:"is_active"`
|
|
||||||
Content string `json:"content,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoGitHooksOptions options for listing repository's githooks
|
|
||||||
type ListRepoGitHooksOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoGitHooks list all the Git hooks of one repository
|
|
||||||
func (c *Client) ListRepoGitHooks(user, repo string, opt ListRepoGitHooksOptions) ([]*GitHook, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
hooks := make([]*GitHook, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
|
||||||
return hooks, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepoGitHook get a Git hook of a repository
|
|
||||||
func (c *Client) GetRepoGitHook(user, repo, id string) (*GitHook, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
h := new(GitHook)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil, h)
|
|
||||||
return h, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditGitHookOption options when modifying one Git hook
|
|
||||||
type EditGitHookOption struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditRepoGitHook modify one Git hook of a repository
|
|
||||||
func (c *Client) EditRepoGitHook(user, repo, id string, opt EditGitHookOption) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRepoGitHook delete one Git hook from a repository
|
|
||||||
func (c *Client) DeleteRepoGitHook(user, repo, id string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &id); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/git/%s", user, repo, id), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
module code.gitea.io/sdk/gitea
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
||||||
require (
|
|
||||||
code.gitea.io/gitea-vet v0.2.1 // indirect
|
|
||||||
github.com/hashicorp/go-version v1.2.1
|
|
||||||
github.com/stretchr/testify v1.4.0
|
|
||||||
)
|
|
|
@ -1,33 +0,0 @@
|
||||||
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s=
|
|
||||||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
|
||||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI=
|
|
||||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
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=
|
|
|
@ -1,20 +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
|
|
||||||
|
|
||||||
// OptionalBool convert a bool to a bool reference
|
|
||||||
func OptionalBool(v bool) *bool {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionalString convert a string to a string reference
|
|
||||||
func OptionalString(v string) *string {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionalInt64 convert a int64 to a int64 reference
|
|
||||||
func OptionalInt64(v int64) *int64 {
|
|
||||||
return &v
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Hook a hook is a web hook when one repository changed
|
|
||||||
type Hook struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
URL string `json:"-"`
|
|
||||||
Config map[string]string `json:"config"`
|
|
||||||
Events []string `json:"events"`
|
|
||||||
Active bool `json:"active"`
|
|
||||||
Updated time.Time `json:"updated_at"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOrgHooks list all the hooks of one organization
|
|
||||||
func (c *Client) ListOrgHooks(org string, opt ListHooksOptions) ([]*Hook, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
hooks := make([]*Hook, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks?%s", org, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
|
||||||
return hooks, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoHooks list all the hooks of one repository
|
|
||||||
func (c *Client) ListRepoHooks(user, repo string, opt ListHooksOptions) ([]*Hook, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
hooks := make([]*Hook, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &hooks)
|
|
||||||
return hooks, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrgHook get a hook of an organization
|
|
||||||
func (c *Client) GetOrgHook(org string, id int64) (*Hook, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
h := new(Hook)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h)
|
|
||||||
return h, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepoHook get a hook of a repository
|
|
||||||
func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
h := new(Hook)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h)
|
|
||||||
return h, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateHookOption options when create a hook
|
|
||||||
type CreateHookOption struct {
|
|
||||||
Type HookType `json:"type"`
|
|
||||||
Config map[string]string `json:"config"`
|
|
||||||
Events []string `json:"events"`
|
|
||||||
BranchFilter string `json:"branch_filter"`
|
|
||||||
Active bool `json:"active"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateHookOption struct
|
|
||||||
func (opt CreateHookOption) Validate() error {
|
|
||||||
if len(opt.Type) == 0 {
|
|
||||||
return fmt.Errorf("hook type needed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOrgHook create one hook for an organization, with options
|
|
||||||
func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); 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
|
|
||||||
}
|
|
||||||
h := new(Hook)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/hooks", org), jsonHeader, bytes.NewReader(body), h)
|
|
||||||
return h, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRepoHook create one hook for a repository, with options
|
|
||||||
func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
h := new(Hook)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/hooks", user, repo), jsonHeader, bytes.NewReader(body), h)
|
|
||||||
return h, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditHookOption options when modify one hook
|
|
||||||
type EditHookOption struct {
|
|
||||||
Config map[string]string `json:"config"`
|
|
||||||
Events []string `json:"events"`
|
|
||||||
BranchFilter string `json:"branch_filter"`
|
|
||||||
Active *bool `json:"active"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditOrgHook modify one hook of an organization, with hook id and options
|
|
||||||
func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditRepoHook modify one hook of a repository, with hook id and options
|
|
||||||
func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteOrgHook delete one hook from an organization, with hook id
|
|
||||||
func (c *Client) DeleteOrgHook(org string, id int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRepoHook delete one hook from a repository, with hook id
|
|
||||||
func (c *Client) DeleteRepoHook(user, repo string, id int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,288 +0,0 @@
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PullRequestMeta PR info if an issue is a PR
|
|
||||||
type PullRequestMeta struct {
|
|
||||||
HasMerged bool `json:"merged"`
|
|
||||||
Merged *time.Time `json:"merged_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepositoryMeta basic repository information
|
|
||||||
type RepositoryMeta struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Owner string `json:"owner"`
|
|
||||||
FullName string `json:"full_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue represents an issue in a repository
|
|
||||||
type Issue struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
Index int64 `json:"number"`
|
|
||||||
Poster *User `json:"user"`
|
|
||||||
OriginalAuthor string `json:"original_author"`
|
|
||||||
OriginalAuthorID int64 `json:"original_author_id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
Ref string `json:"ref"`
|
|
||||||
Labels []*Label `json:"labels"`
|
|
||||||
Milestone *Milestone `json:"milestone"`
|
|
||||||
Assignees []*User `json:"assignees"`
|
|
||||||
// Whether the issue is open or closed
|
|
||||||
State StateType `json:"state"`
|
|
||||||
IsLocked bool `json:"is_locked"`
|
|
||||||
Comments int `json:"comments"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
Updated time.Time `json:"updated_at"`
|
|
||||||
Closed *time.Time `json:"closed_at"`
|
|
||||||
Deadline *time.Time `json:"due_date"`
|
|
||||||
PullRequest *PullRequestMeta `json:"pull_request"`
|
|
||||||
Repository *RepositoryMeta `json:"repository"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListIssueOption list issue options
|
|
||||||
type ListIssueOption struct {
|
|
||||||
ListOptions
|
|
||||||
State StateType
|
|
||||||
Type IssueType
|
|
||||||
Labels []string
|
|
||||||
Milestones []string
|
|
||||||
KeyWord string
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateType issue state type
|
|
||||||
type StateType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StateOpen pr/issue is opend
|
|
||||||
StateOpen StateType = "open"
|
|
||||||
// StateClosed pr/issue is closed
|
|
||||||
StateClosed StateType = "closed"
|
|
||||||
// StateAll is all
|
|
||||||
StateAll StateType = "all"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IssueType is issue a pull or only an issue
|
|
||||||
type IssueType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// IssueTypeAll pr and issue
|
|
||||||
IssueTypeAll IssueType = ""
|
|
||||||
// IssueTypeIssue only issues
|
|
||||||
IssueTypeIssue IssueType = "issues"
|
|
||||||
// IssueTypePull only pulls
|
|
||||||
IssueTypePull IssueType = "pulls"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
|
||||||
func (opt *ListIssueOption) QueryEncode() string {
|
|
||||||
query := opt.getURLQuery()
|
|
||||||
|
|
||||||
if len(opt.State) > 0 {
|
|
||||||
query.Add("state", string(opt.State))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opt.Labels) > 0 {
|
|
||||||
query.Add("labels", strings.Join(opt.Labels, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opt.KeyWord) > 0 {
|
|
||||||
query.Add("q", opt.KeyWord)
|
|
||||||
}
|
|
||||||
|
|
||||||
query.Add("type", string(opt.Type))
|
|
||||||
|
|
||||||
if len(opt.Milestones) > 0 {
|
|
||||||
query.Add("milestones", strings.Join(opt.Milestones, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListIssues returns all issues assigned the authenticated user
|
|
||||||
func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
issues := make([]*Issue, 0, opt.PageSize)
|
|
||||||
|
|
||||||
link, _ := url.Parse("/repos/issues/search")
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
|
|
||||||
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil {
|
|
||||||
for i := 0; i < len(issues); i++ {
|
|
||||||
if issues[i].Repository != nil {
|
|
||||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := range issues {
|
|
||||||
c.issueBackwardsCompatibility(issues[i])
|
|
||||||
}
|
|
||||||
return issues, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoIssues returns all issues for a given repository
|
|
||||||
func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
issues := make([]*Issue, 0, opt.PageSize)
|
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues", owner, repo))
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &issues)
|
|
||||||
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil {
|
|
||||||
for i := 0; i < len(issues); i++ {
|
|
||||||
if issues[i].Repository != nil {
|
|
||||||
issues[i].Repository.Owner = strings.Split(issues[i].Repository.FullName, "/")[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := range issues {
|
|
||||||
c.issueBackwardsCompatibility(issues[i])
|
|
||||||
}
|
|
||||||
return issues, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIssue returns a single issue for a given repository
|
|
||||||
func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
issue := new(Issue)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue)
|
|
||||||
if e := c.checkServerVersionGreaterThanOrEqual(version1_12_0); e != nil && issue.Repository != nil {
|
|
||||||
issue.Repository.Owner = strings.Split(issue.Repository.FullName, "/")[0]
|
|
||||||
}
|
|
||||||
c.issueBackwardsCompatibility(issue)
|
|
||||||
return issue, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIssueOption options to create one issue
|
|
||||||
type CreateIssueOption struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
Ref string `json:"ref"`
|
|
||||||
Assignees []string `json:"assignees"`
|
|
||||||
Deadline *time.Time `json:"due_date"`
|
|
||||||
// milestone id
|
|
||||||
Milestone int64 `json:"milestone"`
|
|
||||||
// list of label ids
|
|
||||||
Labels []int64 `json:"labels"`
|
|
||||||
Closed bool `json:"closed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateIssueOption struct
|
|
||||||
func (opt CreateIssueOption) Validate() error {
|
|
||||||
if len(strings.TrimSpace(opt.Title)) == 0 {
|
|
||||||
return fmt.Errorf("title is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIssue create a new issue for a given repository
|
|
||||||
func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &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
|
|
||||||
}
|
|
||||||
issue := new(Issue)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo),
|
|
||||||
jsonHeader, bytes.NewReader(body), issue)
|
|
||||||
c.issueBackwardsCompatibility(issue)
|
|
||||||
return issue, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditIssueOption options for editing an issue
|
|
||||||
type EditIssueOption struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Body *string `json:"body"`
|
|
||||||
Ref *string `json:"ref"`
|
|
||||||
Assignees []string `json:"assignees"`
|
|
||||||
Milestone *int64 `json:"milestone"`
|
|
||||||
State *StateType `json:"state"`
|
|
||||||
Deadline *time.Time `json:"due_date"`
|
|
||||||
RemoveDeadline *bool `json:"unset_due_date"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the EditIssueOption struct
|
|
||||||
func (opt EditIssueOption) Validate() error {
|
|
||||||
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 {
|
|
||||||
return fmt.Errorf("title is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditIssue modify an existing issue for a given repository
|
|
||||||
func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) (*Issue, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &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
|
|
||||||
}
|
|
||||||
issue := new(Issue)
|
|
||||||
resp, err := c.getParsedResponse("PATCH",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index),
|
|
||||||
jsonHeader, bytes.NewReader(body), issue)
|
|
||||||
c.issueBackwardsCompatibility(issue)
|
|
||||||
return issue, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) issueBackwardsCompatibility(issue *Issue) {
|
|
||||||
if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil {
|
|
||||||
c.mutex.RLock()
|
|
||||||
issue.HTMLURL = fmt.Sprintf("%s/%s/issues/%d", c.url, issue.Repository.FullName, issue.Index)
|
|
||||||
c.mutex.RUnlock()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Comment represents a comment on a commit or issue
|
|
||||||
type Comment struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
PRURL string `json:"pull_request_url"`
|
|
||||||
IssueURL string `json:"issue_url"`
|
|
||||||
Poster *User `json:"user"`
|
|
||||||
OriginalAuthor string `json:"original_author"`
|
|
||||||
OriginalAuthorID int64 `json:"original_author_id"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
Updated time.Time `json:"updated_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListIssueCommentOptions list comment options
|
|
||||||
type ListIssueCommentOptions struct {
|
|
||||||
ListOptions
|
|
||||||
Since time.Time
|
|
||||||
Before time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
|
||||||
func (opt *ListIssueCommentOptions) QueryEncode() string {
|
|
||||||
query := opt.getURLQuery()
|
|
||||||
if !opt.Since.IsZero() {
|
|
||||||
query.Add("since", opt.Since.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
if !opt.Before.IsZero() {
|
|
||||||
query.Add("before", opt.Before.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListIssueComments list comments on an issue.
|
|
||||||
func (c *Client) ListIssueComments(owner, repo string, index int64, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index))
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
comments := make([]*Comment, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments)
|
|
||||||
return comments, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoIssueComments list comments for a given repo.
|
|
||||||
func (c *Client) ListRepoIssueComments(owner, repo string, opt ListIssueCommentOptions) ([]*Comment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo))
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
comments := make([]*Comment, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &comments)
|
|
||||||
return comments, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIssueComment get a comment for a given repo by id.
|
|
||||||
func (c *Client) GetIssueComment(owner, repo string, id int64) (*Comment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
comment := new(Comment)
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return comment, nil, err
|
|
||||||
}
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, id), nil, nil, &comment)
|
|
||||||
return comment, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIssueCommentOption options for creating a comment on an issue
|
|
||||||
type CreateIssueCommentOption struct {
|
|
||||||
Body string `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateIssueCommentOption struct
|
|
||||||
func (opt CreateIssueCommentOption) Validate() error {
|
|
||||||
if len(opt.Body) == 0 {
|
|
||||||
return fmt.Errorf("body is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIssueComment create comment on an issue.
|
|
||||||
func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &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
|
|
||||||
}
|
|
||||||
comment := new(Comment)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment)
|
|
||||||
return comment, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditIssueCommentOption options for editing a comment
|
|
||||||
type EditIssueCommentOption struct {
|
|
||||||
Body string `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the EditIssueCommentOption struct
|
|
||||||
func (opt EditIssueCommentOption) Validate() error {
|
|
||||||
if len(opt.Body) == 0 {
|
|
||||||
return fmt.Errorf("body is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditIssueComment edits an issue comment.
|
|
||||||
func (c *Client) EditIssueComment(owner, repo string, commentID int64, opt EditIssueCommentOption) (*Comment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &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
|
|
||||||
}
|
|
||||||
comment := new(Comment)
|
|
||||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), jsonHeader, bytes.NewReader(body), comment)
|
|
||||||
return comment, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteIssueComment deletes an issue comment.
|
|
||||||
func (c *Client) DeleteIssueComment(owner, repo string, commentID int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/comments/%d", owner, repo, commentID), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Label a label to an issue or a pr
|
|
||||||
type Label struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
// example: 00aabb
|
|
||||||
Color string `json:"color"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListLabelsOptions options for listing repository's labels
|
|
||||||
type ListLabelsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoLabels list labels of one repository
|
|
||||||
func (c *Client) ListRepoLabels(owner, repo string, opt ListLabelsOptions) ([]*Label, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
labels := make([]*Label, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels?%s", owner, repo, opt.getURLQuery().Encode()), nil, nil, &labels)
|
|
||||||
return labels, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepoLabel get one label of repository by repo it
|
|
||||||
func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
label := new(Label)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label)
|
|
||||||
return label, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateLabelOption options for creating a label
|
|
||||||
type CreateLabelOption struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
// example: #00aabb
|
|
||||||
Color string `json:"color"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateLabelOption struct
|
|
||||||
func (opt CreateLabelOption) Validate() error {
|
|
||||||
aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", opt.Color)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !aw {
|
|
||||||
return fmt.Errorf("invalid color format")
|
|
||||||
}
|
|
||||||
if len(strings.TrimSpace(opt.Name)) == 0 {
|
|
||||||
return fmt.Errorf("empty name not allowed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateLabel create one label of repository
|
|
||||||
func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if len(opt.Color) == 6 {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
opt.Color = "#" + opt.Color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
label := new(Label)
|
|
||||||
resp, err := c.getParsedResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/labels", owner, repo),
|
|
||||||
jsonHeader, bytes.NewReader(body), label)
|
|
||||||
return label, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditLabelOption options for editing a label
|
|
||||||
type EditLabelOption struct {
|
|
||||||
Name *string `json:"name"`
|
|
||||||
Color *string `json:"color"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the EditLabelOption struct
|
|
||||||
func (opt EditLabelOption) Validate() error {
|
|
||||||
if opt.Color != nil {
|
|
||||||
aw, err := regexp.MatchString("^#?[0-9,a-f,A-F]{6}$", *opt.Color)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !aw {
|
|
||||||
return fmt.Errorf("invalid color format")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opt.Name != nil {
|
|
||||||
if len(strings.TrimSpace(*opt.Name)) == 0 {
|
|
||||||
return fmt.Errorf("empty name not allowed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditLabel modify one label with options
|
|
||||||
func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &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
|
|
||||||
}
|
|
||||||
label := new(Label)
|
|
||||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), label)
|
|
||||||
return label, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteLabel delete one label of repository by id
|
|
||||||
func (c *Client) DeleteLabel(owner, repo string, id int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIssueLabels get labels of one issue via issue id
|
|
||||||
func (c *Client) GetIssueLabels(owner, repo string, index int64, opts ListLabelsOptions) ([]*Label, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
labels := make([]*Label, 0, 5)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/labels?%s", owner, repo, index, opts.getURLQuery().Encode()), nil, nil, &labels)
|
|
||||||
return labels, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssueLabelsOption a collection of labels
|
|
||||||
type IssueLabelsOption struct {
|
|
||||||
// list of label IDs
|
|
||||||
Labels []int64 `json:"labels"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddIssueLabels add one or more labels to one issue
|
|
||||||
func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
var labels []*Label
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels)
|
|
||||||
return labels, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceIssueLabels replace old labels of issue with new labels
|
|
||||||
func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
var labels []*Label
|
|
||||||
resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels)
|
|
||||||
return labels, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteIssueLabel delete one label of one issue by issue id and label id
|
|
||||||
// TODO: maybe we need delete by label name and issue id
|
|
||||||
func (c *Client) DeleteIssueLabel(owner, repo string, index, label int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, label), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearIssueLabels delete all the labels of one issue.
|
|
||||||
func (c *Client) ClearIssueLabels(owner, repo string, index int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,237 +0,0 @@
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Milestone milestone is a collection of issues on one repository
|
|
||||||
type Milestone struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
State StateType `json:"state"`
|
|
||||||
OpenIssues int `json:"open_issues"`
|
|
||||||
ClosedIssues int `json:"closed_issues"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
Updated *time.Time `json:"updated_at"`
|
|
||||||
Closed *time.Time `json:"closed_at"`
|
|
||||||
Deadline *time.Time `json:"due_on"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMilestoneOption list milestone options
|
|
||||||
type ListMilestoneOption struct {
|
|
||||||
ListOptions
|
|
||||||
// open, closed, all
|
|
||||||
State StateType
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
|
||||||
func (opt *ListMilestoneOption) QueryEncode() string {
|
|
||||||
query := opt.getURLQuery()
|
|
||||||
if opt.State != "" {
|
|
||||||
query.Add("state", string(opt.State))
|
|
||||||
}
|
|
||||||
if len(opt.Name) != 0 {
|
|
||||||
query.Add("name", opt.Name)
|
|
||||||
}
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoMilestones list all the milestones of one repository
|
|
||||||
func (c *Client) ListRepoMilestones(owner, repo string, opt ListMilestoneOption) ([]*Milestone, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
milestones := make([]*Milestone, 0, opt.PageSize)
|
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/milestones", owner, repo))
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &milestones)
|
|
||||||
return milestones, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMilestone get one milestone by repo name and milestone id
|
|
||||||
func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
milestone := new(Milestone)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone)
|
|
||||||
return milestone, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMilestoneByName get one milestone by repo and milestone name
|
|
||||||
func (c *Client) GetMilestoneByName(owner, repo string, name string) (*Milestone, *Response, error) {
|
|
||||||
if c.CheckServerVersionConstraint(">=1.13") != nil {
|
|
||||||
// backwards compatibility mode
|
|
||||||
m, resp, err := c.resolveMilestoneByName(owner, repo, name)
|
|
||||||
return m, resp, err
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
milestone := new(Milestone)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone)
|
|
||||||
return milestone, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMilestoneOption options for creating a milestone
|
|
||||||
type CreateMilestoneOption struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
State StateType `json:"state"`
|
|
||||||
Deadline *time.Time `json:"due_on"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateMilestoneOption struct
|
|
||||||
func (opt CreateMilestoneOption) Validate() error {
|
|
||||||
if len(strings.TrimSpace(opt.Title)) == 0 {
|
|
||||||
return fmt.Errorf("title is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMilestone create one milestone with options
|
|
||||||
func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &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
|
|
||||||
}
|
|
||||||
milestone := new(Milestone)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/milestones", owner, repo), jsonHeader, bytes.NewReader(body), milestone)
|
|
||||||
|
|
||||||
// make creating closed milestones need gitea >= v1.13.0
|
|
||||||
// this make it backwards compatible
|
|
||||||
if err == nil && opt.State == StateClosed && milestone.State != StateClosed {
|
|
||||||
closed := StateClosed
|
|
||||||
return c.EditMilestone(owner, repo, milestone.ID, EditMilestoneOption{
|
|
||||||
State: &closed,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return milestone, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditMilestoneOption options for editing a milestone
|
|
||||||
type EditMilestoneOption struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
State *StateType `json:"state"`
|
|
||||||
Deadline *time.Time `json:"due_on"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the EditMilestoneOption struct
|
|
||||||
func (opt EditMilestoneOption) Validate() error {
|
|
||||||
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 {
|
|
||||||
return fmt.Errorf("title is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditMilestone modify milestone with options
|
|
||||||
func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &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
|
|
||||||
}
|
|
||||||
milestone := new(Milestone)
|
|
||||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), milestone)
|
|
||||||
return milestone, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditMilestoneByName modify milestone with options
|
|
||||||
func (c *Client) EditMilestoneByName(owner, repo string, name string, opt EditMilestoneOption) (*Milestone, *Response, error) {
|
|
||||||
if c.CheckServerVersionConstraint(">=1.13") != nil {
|
|
||||||
// backwards compatibility mode
|
|
||||||
m, _, err := c.resolveMilestoneByName(owner, repo, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return c.EditMilestone(owner, repo, m.ID, opt)
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &name); 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
|
|
||||||
}
|
|
||||||
milestone := new(Milestone)
|
|
||||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), milestone)
|
|
||||||
return milestone, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMilestone delete one milestone by id
|
|
||||||
func (c *Client) DeleteMilestone(owner, repo string, id int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMilestoneByName delete one milestone by name
|
|
||||||
func (c *Client) DeleteMilestoneByName(owner, repo string, name string) (*Response, error) {
|
|
||||||
if c.CheckServerVersionConstraint(">=1.13") != nil {
|
|
||||||
// backwards compatibility mode
|
|
||||||
m, _, err := c.resolveMilestoneByName(owner, repo, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.DeleteMilestone(owner, repo, m.ID)
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveMilestoneByName is a fallback method to find milestone id by name
|
|
||||||
func (c *Client) resolveMilestoneByName(owner, repo, name string) (*Milestone, *Response, error) {
|
|
||||||
for i := 1; ; i++ {
|
|
||||||
miles, resp, err := c.ListRepoMilestones(owner, repo, ListMilestoneOption{
|
|
||||||
ListOptions: ListOptions{
|
|
||||||
Page: i,
|
|
||||||
},
|
|
||||||
State: "all",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if len(miles) == 0 {
|
|
||||||
return nil, nil, fmt.Errorf("milestone '%s' do not exist", name)
|
|
||||||
}
|
|
||||||
for _, m := range miles {
|
|
||||||
if strings.ToLower(strings.TrimSpace(m.Title)) == strings.ToLower(strings.TrimSpace(name)) {
|
|
||||||
return m, resp, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +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 (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reaction contain one reaction
|
|
||||||
type Reaction struct {
|
|
||||||
User *User `json:"user"`
|
|
||||||
Reaction string `json:"content"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIssueReactions get a list reactions of an issue
|
|
||||||
func (c *Client) GetIssueReactions(owner, repo string, index int64) ([]*Reaction, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
reactions := make([]*Reaction, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), nil, nil, &reactions)
|
|
||||||
return reactions, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIssueCommentReactions get a list of reactions from a comment of an issue
|
|
||||||
func (c *Client) GetIssueCommentReactions(owner, repo string, commentID int64) ([]*Reaction, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
reactions := make([]*Reaction, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID), nil, nil, &reactions)
|
|
||||||
return reactions, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// editReactionOption contain the reaction type
|
|
||||||
type editReactionOption struct {
|
|
||||||
Reaction string `json:"content"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostIssueReaction add a reaction to an issue
|
|
||||||
func (c *Client) PostIssueReaction(owner, repo string, index int64, reaction string) (*Reaction, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
reactionResponse := new(Reaction)
|
|
||||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
resp, err := c.getParsedResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index),
|
|
||||||
jsonHeader, bytes.NewReader(body), reactionResponse)
|
|
||||||
return reactionResponse, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteIssueReaction remove a reaction from an issue
|
|
||||||
func (c *Client) DeleteIssueReaction(owner, repo string, index int64, reaction string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/reactions", owner, repo, index), jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostIssueCommentReaction add a reaction to a comment of an issue
|
|
||||||
func (c *Client) PostIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Reaction, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
reactionResponse := new(Reaction)
|
|
||||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
resp, err := c.getParsedResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID),
|
|
||||||
jsonHeader, bytes.NewReader(body), reactionResponse)
|
|
||||||
return reactionResponse, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteIssueCommentReaction remove a reaction from a comment of an issue
|
|
||||||
func (c *Client) DeleteIssueCommentReaction(owner, repo string, commentID int64, reaction string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&editReactionOption{Reaction: reaction})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/issues/comments/%d/reactions", owner, repo, commentID),
|
|
||||||
jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,57 +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 (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StopWatch represents a running stopwatch of an issue / pr
|
|
||||||
type StopWatch struct {
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
Seconds int64 `json:"seconds"`
|
|
||||||
Duration string `json:"duration"`
|
|
||||||
IssueIndex int64 `json:"issue_index"`
|
|
||||||
IssueTitle string `json:"issue_title"`
|
|
||||||
RepoOwnerName string `json:"repo_owner_name"`
|
|
||||||
RepoName string `json:"repo_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMyStopwatches list all stopwatches
|
|
||||||
func (c *Client) GetMyStopwatches() ([]*StopWatch, *Response, error) {
|
|
||||||
stopwatches := make([]*StopWatch, 0, 1)
|
|
||||||
resp, err := c.getParsedResponse("GET", "/user/stopwatches", nil, nil, &stopwatches)
|
|
||||||
return stopwatches, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteIssueStopwatch delete / cancel a specific stopwatch
|
|
||||||
func (c *Client) DeleteIssueStopwatch(owner, repo string, index int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/delete", owner, repo, index), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartIssueStopWatch starts a stopwatch for an existing issue for a given
|
|
||||||
// repository
|
|
||||||
func (c *Client) StartIssueStopWatch(owner, repo string, index int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StopIssueStopWatch stops an existing stopwatch for an issue in a given
|
|
||||||
// repository
|
|
||||||
func (c *Client) StopIssueStopWatch(owner, repo string, index int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,87 +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 (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetIssueSubscribers get list of users who subscribed on an issue
|
|
||||||
func (c *Client) GetIssueSubscribers(owner, repo string, index int64) ([]*User, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
subscribers := make([]*User, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions", owner, repo, index), nil, nil, &subscribers)
|
|
||||||
return subscribers, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddIssueSubscription Subscribe user to issue
|
|
||||||
func (c *Client) AddIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
if status == http.StatusCreated {
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
if status == http.StatusOK {
|
|
||||||
return resp, fmt.Errorf("already subscribed")
|
|
||||||
}
|
|
||||||
return resp, fmt.Errorf("unexpected Status: %d", status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteIssueSubscription unsubscribe user from issue
|
|
||||||
func (c *Client) DeleteIssueSubscription(owner, repo string, index int64, user string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/%s", owner, repo, index, user), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
if status == http.StatusCreated {
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
if status == http.StatusOK {
|
|
||||||
return resp, fmt.Errorf("already unsubscribed")
|
|
||||||
}
|
|
||||||
return resp, fmt.Errorf("unexpected Status: %d", status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckIssueSubscription check if current user is subscribed to an issue
|
|
||||||
func (c *Client) CheckIssueSubscription(owner, repo string, index int64) (*WatchInfo, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
wi := new(WatchInfo)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/subscriptions/check", owner, repo, index), nil, nil, wi)
|
|
||||||
return wi, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssueSubscribe subscribe current user to an issue
|
|
||||||
func (c *Client) IssueSubscribe(owner, repo string, index int64) (*Response, error) {
|
|
||||||
u, _, err := c.GetMyUserInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.AddIssueSubscription(owner, repo, index, u.UserName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssueUnSubscribe unsubscribe current user from an issue
|
|
||||||
func (c *Client) IssueUnSubscribe(owner, repo string, index int64) (*Response, error) {
|
|
||||||
u, _, err := c.GetMyUserInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.DeleteIssueSubscription(owner, repo, index, u.UserName)
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TrackedTime worked time for an issue / pr
|
|
||||||
type TrackedTime struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
// Time in seconds
|
|
||||||
Time int64 `json:"time"`
|
|
||||||
// deprecated (only for backwards compatibility)
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
UserName string `json:"user_name"`
|
|
||||||
// deprecated (only for backwards compatibility)
|
|
||||||
IssueID int64 `json:"issue_id"`
|
|
||||||
Issue *Issue `json:"issue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListTrackedTimesOptions options for listing repository's tracked times
|
|
||||||
type ListTrackedTimesOptions struct {
|
|
||||||
ListOptions
|
|
||||||
Since time.Time
|
|
||||||
Before time.Time
|
|
||||||
// User filter is only used by ListRepoTrackedTimes !!!
|
|
||||||
User string
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
|
||||||
func (opt *ListTrackedTimesOptions) QueryEncode() string {
|
|
||||||
query := opt.getURLQuery()
|
|
||||||
|
|
||||||
if !opt.Since.IsZero() {
|
|
||||||
query.Add("since", opt.Since.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
if !opt.Before.IsZero() {
|
|
||||||
query.Add("before", opt.Before.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opt.User) != 0 {
|
|
||||||
query.Add("user", opt.User)
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoTrackedTimes list tracked times of a repository
|
|
||||||
func (c *Client) ListRepoTrackedTimes(owner, repo string, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/times", owner, repo))
|
|
||||||
opt.setDefaults()
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
times := make([]*TrackedTime, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ×)
|
|
||||||
return times, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMyTrackedTimes list tracked times of the current user
|
|
||||||
func (c *Client) GetMyTrackedTimes() ([]*TrackedTime, *Response, error) {
|
|
||||||
times := make([]*TrackedTime, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", "/user/times", jsonHeader, nil, ×)
|
|
||||||
return times, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTimeOption options for adding time to an issue
|
|
||||||
type AddTimeOption struct {
|
|
||||||
// time in seconds
|
|
||||||
Time int64 `json:"time"`
|
|
||||||
// optional
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
// optional
|
|
||||||
User string `json:"user_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the AddTimeOption struct
|
|
||||||
func (opt AddTimeOption) Validate() error {
|
|
||||||
if opt.Time == 0 {
|
|
||||||
return fmt.Errorf("no time to add")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTime adds time to issue with the given index
|
|
||||||
func (c *Client) AddTime(owner, repo string, index int64, opt AddTimeOption) (*TrackedTime, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &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(TrackedTime)
|
|
||||||
resp, err := c.getParsedResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index),
|
|
||||||
jsonHeader, bytes.NewReader(body), t)
|
|
||||||
return t, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListIssueTrackedTimes list tracked times of a single issue for a given repository
|
|
||||||
func (c *Client) ListIssueTrackedTimes(owner, repo string, index int64, opt ListTrackedTimesOptions) ([]*TrackedTime, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index))
|
|
||||||
opt.setDefaults()
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
times := make([]*TrackedTime, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, ×)
|
|
||||||
return times, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetIssueTime reset tracked time of a single issue for a given repository
|
|
||||||
func (c *Client) ResetIssueTime(owner, repo string, index int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times", owner, repo, index), jsonHeader, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTime delete a specific tracked time by id of a single issue for a given repository
|
|
||||||
func (c *Client) DeleteTime(owner, repo string, index, timeID int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/times/%d", owner, repo, index, timeID), jsonHeader, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,42 +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 (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultPageSize = 10
|
|
||||||
const maxPageSize = 50
|
|
||||||
|
|
||||||
// ListOptions options for using Gitea's API pagination
|
|
||||||
type ListOptions struct {
|
|
||||||
Page int
|
|
||||||
PageSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o ListOptions) getURLQuery() url.Values {
|
|
||||||
query := make(url.Values)
|
|
||||||
query.Add("page", fmt.Sprintf("%d", o.Page))
|
|
||||||
query.Add("limit", fmt.Sprintf("%d", o.PageSize))
|
|
||||||
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
|
|
||||||
// setDefaults set default pagination options if none or wrong are set
|
|
||||||
// if you set -1 as page it will set all to 0
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,241 +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 (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
version1_12_3, _ = version.NewVersion("1.12.3")
|
|
||||||
)
|
|
||||||
|
|
||||||
// NotificationThread expose Notification on API
|
|
||||||
type NotificationThread struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Repository *Repository `json:"repository"`
|
|
||||||
Subject *NotificationSubject `json:"subject"`
|
|
||||||
Unread bool `json:"unread"`
|
|
||||||
Pinned bool `json:"pinned"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotificationSubject contains the notification subject (Issue/Pull/Commit)
|
|
||||||
type NotificationSubject struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
LatestCommentURL string `json:"latest_comment_url"`
|
|
||||||
Type NotifySubjectType `json:"type"`
|
|
||||||
State NotifySubjectState `json:"state"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotifyStatus notification status type
|
|
||||||
type NotifyStatus string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NotifyStatusUnread was not read
|
|
||||||
NotifyStatusUnread NotifyStatus = "unread"
|
|
||||||
// NotifyStatusRead was already read by user
|
|
||||||
NotifyStatusRead NotifyStatus = "read"
|
|
||||||
// NotifyStatusPinned notification is pinned by user
|
|
||||||
NotifyStatusPinned NotifyStatus = "pinned"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
SubjectTypes []NotifySubjectType
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkNotificationOptions represents the filter & modify options
|
|
||||||
type MarkNotificationOptions struct {
|
|
||||||
LastReadAt time.Time
|
|
||||||
Status []NotifyStatus
|
|
||||||
ToStatus NotifyStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEncode encode options to url query
|
|
||||||
func (opt *ListNotificationOptions) QueryEncode() string {
|
|
||||||
query := opt.getURLQuery()
|
|
||||||
if !opt.Since.IsZero() {
|
|
||||||
query.Add("since", opt.Since.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
if !opt.Before.IsZero() {
|
|
||||||
query.Add("before", opt.Before.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
for _, s := range opt.Status {
|
|
||||||
query.Add("status-types", string(s))
|
|
||||||
}
|
|
||||||
for _, s := range opt.SubjectTypes {
|
|
||||||
query.Add("subject-type", string(s))
|
|
||||||
}
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateUserOption struct
|
|
||||||
func (opt ListNotificationOptions) Validate(c *Client) error {
|
|
||||||
if len(opt.Status) != 0 {
|
|
||||||
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEncode encode options to url query
|
|
||||||
func (opt *MarkNotificationOptions) QueryEncode() string {
|
|
||||||
query := make(url.Values)
|
|
||||||
if !opt.LastReadAt.IsZero() {
|
|
||||||
query.Add("last_read_at", opt.LastReadAt.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
for _, s := range opt.Status {
|
|
||||||
query.Add("status-types", string(s))
|
|
||||||
}
|
|
||||||
if len(opt.ToStatus) != 0 {
|
|
||||||
query.Add("to-status", string(opt.ToStatus))
|
|
||||||
}
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateUserOption struct
|
|
||||||
func (opt MarkNotificationOptions) Validate(c *Client) error {
|
|
||||||
if len(opt.Status) != 0 || len(opt.ToStatus) != 0 {
|
|
||||||
return c.checkServerVersionGreaterThanOrEqual(version1_12_3)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckNotifications list users's notification threads
|
|
||||||
func (c *Client) CheckNotifications() (int64, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
new := struct {
|
|
||||||
New int64 `json:"new"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
resp, err := c.getParsedResponse("GET", "/notifications/new", jsonHeader, nil, &new)
|
|
||||||
return new.New, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNotification get notification thread by ID
|
|
||||||
func (c *Client) GetNotification(id int64) (*NotificationThread, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
thread := new(NotificationThread)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/notifications/threads/%d", id), nil, nil, thread)
|
|
||||||
return thread, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadNotification mark notification thread as read by ID
|
|
||||||
// It optionally takes a second argument if status has to be set other than 'read'
|
|
||||||
func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
link := fmt.Sprintf("/notifications/threads/%d", id)
|
|
||||||
if len(status) != 0 {
|
|
||||||
link += fmt.Sprintf("?to-status=%s", status[0])
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PATCH", link, nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListNotifications list users's notification threads
|
|
||||||
func (c *Client) ListNotifications(opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(c); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
link, _ := url.Parse("/notifications")
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
threads := make([]*NotificationThread, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads)
|
|
||||||
return threads, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadNotifications mark notification threads as read
|
|
||||||
func (c *Client) ReadNotifications(opt MarkNotificationOptions) (*Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(c); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
link, _ := url.Parse("/notifications")
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoNotifications list users's notification threads on a specific repo
|
|
||||||
func (c *Client) ListRepoNotifications(owner, repo string, opt ListNotificationOptions) ([]*NotificationThread, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(c); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo))
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
threads := make([]*NotificationThread, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &threads)
|
|
||||||
return threads, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadRepoNotifications mark notification threads as read on a specific repo
|
|
||||||
func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(c); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo))
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
_, resp, err := c.getResponse("PUT", link.String(), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,91 +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 (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Oauth2 represents an Oauth2 Application
|
|
||||||
type Oauth2 struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
ClientID string `json:"client_id"`
|
|
||||||
ClientSecret string `json:"client_secret"`
|
|
||||||
RedirectURIs []string `json:"redirect_uris"`
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOauth2Option for listing Oauth2 Applications
|
|
||||||
type ListOauth2Option struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOauth2Option required options for creating an Application
|
|
||||||
type CreateOauth2Option struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
RedirectURIs []string `json:"redirect_uris"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object.
|
|
||||||
func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
oauth := new(Oauth2)
|
|
||||||
resp, err := c.getParsedResponse("POST", "/user/applications/oauth2", jsonHeader, bytes.NewReader(body), oauth)
|
|
||||||
return oauth, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOauth2 a specific Oauth2 Application by ID and return a completed Oauth2 object.
|
|
||||||
func (c *Client) UpdateOauth2(oauth2id int64, opt CreateOauth2Option) (*Oauth2, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
oauth := new(Oauth2)
|
|
||||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), jsonHeader, bytes.NewReader(body), oauth)
|
|
||||||
return oauth, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOauth2 a specific Oauth2 Application by ID.
|
|
||||||
func (c *Client) GetOauth2(oauth2id int64) (*Oauth2, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
oauth2s := &Oauth2{}
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil, &oauth2s)
|
|
||||||
return oauth2s, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOauth2 all of your Oauth2 Applications.
|
|
||||||
func (c *Client) ListOauth2(opt ListOauth2Option) ([]*Oauth2, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
oauth2s := make([]*Oauth2, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/applications/oauth2?%s", opt.getURLQuery().Encode()), nil, nil, &oauth2s)
|
|
||||||
return oauth2s, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteOauth2 delete an Oauth2 application by ID
|
|
||||||
func (c *Client) DeleteOauth2(oauth2id int64) (*Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/applications/oauth2/%d", oauth2id), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,155 +0,0 @@
|
||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Organization represents an organization
|
|
||||||
type Organization struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
UserName string `json:"username"`
|
|
||||||
FullName string `json:"full_name"`
|
|
||||||
AvatarURL string `json:"avatar_url"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Website string `json:"website"`
|
|
||||||
Location string `json:"location"`
|
|
||||||
Visibility string `json:"visibility"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleType defines the visibility
|
|
||||||
type VisibleType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// VisibleTypePublic Visible for everyone
|
|
||||||
VisibleTypePublic VisibleType = "public"
|
|
||||||
|
|
||||||
// VisibleTypeLimited Visible for every connected user
|
|
||||||
VisibleTypeLimited VisibleType = "limited"
|
|
||||||
|
|
||||||
// VisibleTypePrivate Visible only for organization's members
|
|
||||||
VisibleTypePrivate VisibleType = "private"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOrgsOptions options for listing organizations
|
|
||||||
type ListOrgsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMyOrgs list all of current user's organizations
|
|
||||||
func (c *Client) ListMyOrgs(opt ListOrgsOptions) ([]*Organization, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
orgs := make([]*Organization, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/orgs?%s", opt.getURLQuery().Encode()), nil, nil, &orgs)
|
|
||||||
return orgs, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListUserOrgs list all of some user's organizations
|
|
||||||
func (c *Client) ListUserOrgs(user string, opt ListOrgsOptions) ([]*Organization, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
orgs := make([]*Organization, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs?%s", user, opt.getURLQuery().Encode()), nil, nil, &orgs)
|
|
||||||
return orgs, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrg get one organization by name
|
|
||||||
func (c *Client) GetOrg(orgname string) (*Organization, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&orgname); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
org := new(Organization)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org)
|
|
||||||
return org, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"`
|
|
||||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkVisibilityOpt check if mode exist
|
|
||||||
func checkVisibilityOpt(v VisibleType) bool {
|
|
||||||
return v == VisibleTypePublic || v == VisibleTypeLimited || v == VisibleTypePrivate
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateOrgOption struct
|
|
||||||
func (opt CreateOrgOption) Validate() error {
|
|
||||||
if len(opt.Name) == 0 {
|
|
||||||
return fmt.Errorf("empty org name")
|
|
||||||
}
|
|
||||||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
|
|
||||||
return fmt.Errorf("infalid bisibility option")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOrg creates an organization
|
|
||||||
func (c *Client) CreateOrg(opt CreateOrgOption) (*Organization, *Response, error) {
|
|
||||||
if err := opt.Validate(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
org := new(Organization)
|
|
||||||
resp, err := c.getParsedResponse("POST", "/orgs", jsonHeader, bytes.NewReader(body), org)
|
|
||||||
return org, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditOrgOption options for editing an organization
|
|
||||||
type EditOrgOption struct {
|
|
||||||
FullName string `json:"full_name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Website string `json:"website"`
|
|
||||||
Location string `json:"location"`
|
|
||||||
Visibility VisibleType `json:"visibility"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the EditOrgOption struct
|
|
||||||
func (opt EditOrgOption) Validate() error {
|
|
||||||
if len(opt.Visibility) != 0 && !checkVisibilityOpt(opt.Visibility) {
|
|
||||||
return fmt.Errorf("infalid bisibility option")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditOrg modify one organization via options
|
|
||||||
func (c *Client) EditOrg(orgname string, opt EditOrgOption) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&orgname); 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
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteOrg deletes an organization
|
|
||||||
func (c *Client) DeleteOrg(orgname string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&orgname); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,119 +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 (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeleteOrgMembership remove a member from an organization
|
|
||||||
func (c *Client) DeleteOrgMembership(org, user string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOrgMembershipOption list OrgMembership options
|
|
||||||
type ListOrgMembershipOption struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOrgMembership list an organization's members
|
|
||||||
func (c *Client) ListOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
users := make([]*User, 0, opt.PageSize)
|
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/members", org))
|
|
||||||
link.RawQuery = opt.getURLQuery().Encode()
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
|
|
||||||
return users, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPublicOrgMembership list an organization's members
|
|
||||||
func (c *Client) ListPublicOrgMembership(org string, opt ListOrgMembershipOption) ([]*User, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
users := make([]*User, 0, opt.PageSize)
|
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/orgs/%s/public_members", org))
|
|
||||||
link.RawQuery = opt.getURLQuery().Encode()
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &users)
|
|
||||||
return users, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckOrgMembership Check if a user is a member of an organization
|
|
||||||
func (c *Client) CheckOrgMembership(org, user string) (bool, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/members/%s", org, user), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, resp, err
|
|
||||||
}
|
|
||||||
switch status {
|
|
||||||
case http.StatusNoContent:
|
|
||||||
return true, resp, nil
|
|
||||||
case http.StatusNotFound:
|
|
||||||
return false, resp, nil
|
|
||||||
default:
|
|
||||||
return false, resp, fmt.Errorf("unexpected Status: %d", status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckPublicOrgMembership Check if a user is a member of an organization
|
|
||||||
func (c *Client) CheckPublicOrgMembership(org, user string) (bool, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, resp, err
|
|
||||||
}
|
|
||||||
switch status {
|
|
||||||
case http.StatusNoContent:
|
|
||||||
return true, resp, nil
|
|
||||||
case http.StatusNotFound:
|
|
||||||
return false, resp, nil
|
|
||||||
default:
|
|
||||||
return false, resp, fmt.Errorf("unexpected Status: %d", status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPublicOrgMembership publicize/conceal a user's membership
|
|
||||||
func (c *Client) SetPublicOrgMembership(org, user string, visible bool) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org, &user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
status int
|
|
||||||
err error
|
|
||||||
resp *Response
|
|
||||||
)
|
|
||||||
if visible {
|
|
||||||
status, resp, err = c.getStatusCode("PUT", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
|
|
||||||
} else {
|
|
||||||
status, resp, err = c.getStatusCode("DELETE", fmt.Sprintf("/orgs/%s/public_members/%s", org, user), nil, nil)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
switch status {
|
|
||||||
case http.StatusNoContent:
|
|
||||||
return resp, nil
|
|
||||||
case http.StatusNotFound:
|
|
||||||
return resp, fmt.Errorf("forbidden")
|
|
||||||
default:
|
|
||||||
return resp, fmt.Errorf("unexpected Status: %d", status)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,242 +0,0 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Team represents a team in an organization
|
|
||||||
type Team struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Organization *Organization `json:"organization"`
|
|
||||||
Permission AccessMode `json:"permission"`
|
|
||||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
|
||||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListTeamsOptions options for listing teams
|
|
||||||
type ListTeamsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOrgTeams lists all teams of an organization
|
|
||||||
func (c *Client) ListOrgTeams(org string, opt ListTeamsOptions) ([]*Team, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
teams := make([]*Team, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams?%s", org, opt.getURLQuery().Encode()), nil, nil, &teams)
|
|
||||||
return teams, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMyTeams lists all the teams of the current user
|
|
||||||
func (c *Client) ListMyTeams(opt *ListTeamsOptions) ([]*Team, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
teams := make([]*Team, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/teams?%s", opt.getURLQuery().Encode()), nil, nil, &teams)
|
|
||||||
return teams, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTeam gets a team by ID
|
|
||||||
func (c *Client) GetTeam(id int64) (*Team, *Response, error) {
|
|
||||||
t := new(Team)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d", id), nil, nil, t)
|
|
||||||
return t, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTeamOption options for creating a team
|
|
||||||
type CreateTeamOption struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Permission AccessMode `json:"permission"`
|
|
||||||
CanCreateOrgRepo bool `json:"can_create_org_repo"`
|
|
||||||
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
|
||||||
Units []RepoUnitType `json:"units"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateTeamOption struct
|
|
||||||
func (opt CreateTeamOption) Validate() error {
|
|
||||||
if opt.Permission == AccessModeOwner {
|
|
||||||
opt.Permission = AccessModeAdmin
|
|
||||||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
|
|
||||||
return fmt.Errorf("permission mode invalid")
|
|
||||||
}
|
|
||||||
if len(opt.Name) == 0 {
|
|
||||||
return fmt.Errorf("name required")
|
|
||||||
}
|
|
||||||
if len(opt.Name) > 30 {
|
|
||||||
return fmt.Errorf("name to long")
|
|
||||||
}
|
|
||||||
if len(opt.Description) > 255 {
|
|
||||||
return fmt.Errorf("description to long")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTeam creates a team for an organization
|
|
||||||
func (c *Client) CreateTeam(org string, opt CreateTeamOption) (*Team, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); 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(Team)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/teams", org), jsonHeader, bytes.NewReader(body), t)
|
|
||||||
return t, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditTeamOption options for editing a team
|
|
||||||
type EditTeamOption struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description *string `json:"description"`
|
|
||||||
Permission AccessMode `json:"permission"`
|
|
||||||
CanCreateOrgRepo *bool `json:"can_create_org_repo"`
|
|
||||||
IncludesAllRepositories *bool `json:"includes_all_repositories"`
|
|
||||||
Units []RepoUnitType `json:"units"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the EditTeamOption struct
|
|
||||||
func (opt EditTeamOption) Validate() error {
|
|
||||||
if opt.Permission == AccessModeOwner {
|
|
||||||
opt.Permission = AccessModeAdmin
|
|
||||||
} else if opt.Permission != AccessModeRead && opt.Permission != AccessModeWrite && opt.Permission != AccessModeAdmin {
|
|
||||||
return fmt.Errorf("permission mode invalid")
|
|
||||||
}
|
|
||||||
if len(opt.Name) == 0 {
|
|
||||||
return fmt.Errorf("name required")
|
|
||||||
}
|
|
||||||
if len(opt.Name) > 30 {
|
|
||||||
return fmt.Errorf("name to long")
|
|
||||||
}
|
|
||||||
if opt.Description != nil && len(*opt.Description) > 255 {
|
|
||||||
return fmt.Errorf("description to long")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditTeam edits a team of an organization
|
|
||||||
func (c *Client) EditTeam(id int64, opt EditTeamOption) (*Response, error) {
|
|
||||||
if err := opt.Validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PATCH", fmt.Sprintf("/teams/%d", id), jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTeam deletes a team of an organization
|
|
||||||
func (c *Client) DeleteTeam(id int64) (*Response, error) {
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d", id), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListTeamMembersOptions options for listing team's members
|
|
||||||
type ListTeamMembersOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListTeamMembers lists all members of a team
|
|
||||||
func (c *Client) ListTeamMembers(id int64, opt ListTeamMembersOptions) ([]*User, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
members := make([]*User, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members?%s", id, opt.getURLQuery().Encode()), nil, nil, &members)
|
|
||||||
return members, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTeamMember gets a member of a team
|
|
||||||
func (c *Client) GetTeamMember(id int64, user string) (*User, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
m := new(User)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil, m)
|
|
||||||
return m, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTeamMember adds a member to a team
|
|
||||||
func (c *Client) AddTeamMember(id int64, user string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveTeamMember removes a member from a team
|
|
||||||
func (c *Client) RemoveTeamMember(id int64, user string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/members/%s", id, user), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListTeamRepositoriesOptions options for listing team's repositories
|
|
||||||
type ListTeamRepositoriesOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListTeamRepositories lists all repositories of a team
|
|
||||||
func (c *Client) ListTeamRepositories(id int64, opt ListTeamRepositoriesOptions) ([]*Repository, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
repos := make([]*Repository, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/teams/%d/repos?%s", id, opt.getURLQuery().Encode()), nil, nil, &repos)
|
|
||||||
return repos, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTeamRepository adds a repository to a team
|
|
||||||
func (c *Client) AddTeamRepository(id int64, org, repo string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveTeamRepository removes a repository from a team
|
|
||||||
func (c *Client) RemoveTeamRepository(id int64, org, repo string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/teams/%d/repos/%s/%s", id, org, repo), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,324 +0,0 @@
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PRBranchInfo information about a branch
|
|
||||||
type PRBranchInfo struct {
|
|
||||||
Name string `json:"label"`
|
|
||||||
Ref string `json:"ref"`
|
|
||||||
Sha string `json:"sha"`
|
|
||||||
RepoID int64 `json:"repo_id"`
|
|
||||||
Repository *Repository `json:"repo"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PullRequest represents a pull request
|
|
||||||
type PullRequest struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Index int64 `json:"number"`
|
|
||||||
Poster *User `json:"user"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
Labels []*Label `json:"labels"`
|
|
||||||
Milestone *Milestone `json:"milestone"`
|
|
||||||
Assignee *User `json:"assignee"`
|
|
||||||
Assignees []*User `json:"assignees"`
|
|
||||||
State StateType `json:"state"`
|
|
||||||
IsLocked bool `json:"is_locked"`
|
|
||||||
Comments int `json:"comments"`
|
|
||||||
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
DiffURL string `json:"diff_url"`
|
|
||||||
PatchURL string `json:"patch_url"`
|
|
||||||
|
|
||||||
Mergeable bool `json:"mergeable"`
|
|
||||||
HasMerged bool `json:"merged"`
|
|
||||||
Merged *time.Time `json:"merged_at"`
|
|
||||||
MergedCommitID *string `json:"merge_commit_sha"`
|
|
||||||
MergedBy *User `json:"merged_by"`
|
|
||||||
|
|
||||||
Base *PRBranchInfo `json:"base"`
|
|
||||||
Head *PRBranchInfo `json:"head"`
|
|
||||||
MergeBase string `json:"merge_base"`
|
|
||||||
|
|
||||||
Deadline *time.Time `json:"due_date"`
|
|
||||||
Created *time.Time `json:"created_at"`
|
|
||||||
Updated *time.Time `json:"updated_at"`
|
|
||||||
Closed *time.Time `json:"closed_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPullRequestsOptions options for listing pull requests
|
|
||||||
type ListPullRequestsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
State StateType `json:"state"`
|
|
||||||
// oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority
|
|
||||||
Sort string
|
|
||||||
Milestone int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeStyle is used specify how a pull is merged
|
|
||||||
type MergeStyle string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// MergeStyleMerge merge pull as usual
|
|
||||||
MergeStyleMerge MergeStyle = "merge"
|
|
||||||
// MergeStyleRebase rebase pull
|
|
||||||
MergeStyleRebase MergeStyle = "rebase"
|
|
||||||
// MergeStyleRebaseMerge rebase and merge pull
|
|
||||||
MergeStyleRebaseMerge MergeStyle = "rebase-merge"
|
|
||||||
// MergeStyleSquash squash and merge pull
|
|
||||||
MergeStyleSquash MergeStyle = "squash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
|
||||||
func (opt *ListPullRequestsOptions) QueryEncode() string {
|
|
||||||
query := opt.getURLQuery()
|
|
||||||
if len(opt.State) > 0 {
|
|
||||||
query.Add("state", string(opt.State))
|
|
||||||
}
|
|
||||||
if len(opt.Sort) > 0 {
|
|
||||||
query.Add("sort", opt.Sort)
|
|
||||||
}
|
|
||||||
if opt.Milestone > 0 {
|
|
||||||
query.Add("milestone", fmt.Sprintf("%d", opt.Milestone))
|
|
||||||
}
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoPullRequests list PRs of one repository
|
|
||||||
func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
prs := make([]*PullRequest, 0, opt.PageSize)
|
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls", owner, repo))
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &prs)
|
|
||||||
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
|
|
||||||
for i := range prs {
|
|
||||||
if err := fixPullHeadSha(c, prs[i]); err != nil {
|
|
||||||
return prs, resp, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prs, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPullRequest get information of one PR
|
|
||||||
func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
pr := new(PullRequest)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr)
|
|
||||||
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
|
|
||||||
if err := fixPullHeadSha(c, pr); err != nil {
|
|
||||||
return pr, resp, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pr, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePullRequestOption options when creating a pull request
|
|
||||||
type CreatePullRequestOption struct {
|
|
||||||
Head string `json:"head"`
|
|
||||||
Base string `json:"base"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
Assignee string `json:"assignee"`
|
|
||||||
Assignees []string `json:"assignees"`
|
|
||||||
Milestone int64 `json:"milestone"`
|
|
||||||
Labels []int64 `json:"labels"`
|
|
||||||
Deadline *time.Time `json:"due_date"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePullRequest create pull request with options
|
|
||||||
func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
pr := new(PullRequest)
|
|
||||||
resp, err := c.getParsedResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/pulls", owner, repo),
|
|
||||||
jsonHeader, bytes.NewReader(body), pr)
|
|
||||||
return pr, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditPullRequestOption options when modify pull request
|
|
||||||
type EditPullRequestOption struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
Base string `json:"base"`
|
|
||||||
Assignee string `json:"assignee"`
|
|
||||||
Assignees []string `json:"assignees"`
|
|
||||||
Milestone int64 `json:"milestone"`
|
|
||||||
Labels []int64 `json:"labels"`
|
|
||||||
State *StateType `json:"state"`
|
|
||||||
Deadline *time.Time `json:"due_date"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the EditPullRequestOption struct
|
|
||||||
func (opt EditPullRequestOption) Validate(c *Client) error {
|
|
||||||
if len(opt.Title) != 0 && len(strings.TrimSpace(opt.Title)) == 0 {
|
|
||||||
return fmt.Errorf("title is empty")
|
|
||||||
}
|
|
||||||
if len(opt.Base) != 0 {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return fmt.Errorf("can not change base gitea to old")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditPullRequest modify pull request with PR id and options
|
|
||||||
func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRequestOption) (*PullRequest, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(c); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
pr := new(PullRequest)
|
|
||||||
resp, err := c.getParsedResponse("PATCH",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index),
|
|
||||||
jsonHeader, bytes.NewReader(body), pr)
|
|
||||||
return pr, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergePullRequestOption options when merging a pull request
|
|
||||||
type MergePullRequestOption struct {
|
|
||||||
Style MergeStyle `json:"Do"`
|
|
||||||
Title string `json:"MergeTitleField"`
|
|
||||||
Message string `json:"MergeMessageField"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the MergePullRequestOption struct
|
|
||||||
func (opt MergePullRequestOption) Validate(c *Client) error {
|
|
||||||
if opt.Style == MergeStyleSquash {
|
|
||||||
if err := c.CheckServerVersionConstraint(">=1.11.5"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergePullRequest merge a PR to repository by PR id
|
|
||||||
func (c *Client) MergePullRequest(owner, repo string, index int64, opt MergePullRequestOption) (bool, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(c); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("POST", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), jsonHeader, bytes.NewReader(body))
|
|
||||||
if err != nil {
|
|
||||||
return false, resp, err
|
|
||||||
}
|
|
||||||
return status == 200, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPullRequestMerged test if one PR is merged to one repository
|
|
||||||
func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return false, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return status == 204, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR
|
|
||||||
func (c *Client) getPullRequestDiffOrPatch(owner, repo, kind string, index int64) ([]byte, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &kind); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
|
||||||
r, _, err2 := c.GetRepo(owner, repo)
|
|
||||||
if err2 != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if r.Private {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return c.getWebResponse("GET", fmt.Sprintf("/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil)
|
|
||||||
}
|
|
||||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPullRequestPatch gets the .patch file as bytes for a PR
|
|
||||||
func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) {
|
|
||||||
return c.getPullRequestDiffOrPatch(owner, repo, "patch", index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPullRequestDiff gets the .diff file as bytes for a PR
|
|
||||||
func (c *Client) GetPullRequestDiff(owner, repo string, index int64) ([]byte, *Response, error) {
|
|
||||||
return c.getPullRequestDiffOrPatch(owner, repo, "diff", index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPullRequestCommitsOptions options for listing pull requests
|
|
||||||
type ListPullRequestCommitsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPullRequestCommits list commits for a pull request
|
|
||||||
func (c *Client) ListPullRequestCommits(owner, repo string, index int64, opt ListPullRequestCommitsOptions) ([]*Commit, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/commits", owner, repo, index))
|
|
||||||
opt.setDefaults()
|
|
||||||
commits := make([]*Commit, 0, opt.PageSize)
|
|
||||||
link.RawQuery = opt.getURLQuery().Encode()
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits)
|
|
||||||
return commits, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675
|
|
||||||
// When no head sha is available, this is because the branch got deleted in the base repo.
|
|
||||||
// pr.Head.Ref points in this case not to the head repo branch name, but the base repo ref,
|
|
||||||
// which stays available to resolve the commit sha. This is fixed for gitea >= 1.14.0
|
|
||||||
func fixPullHeadSha(client *Client, pr *PullRequest) error {
|
|
||||||
if pr.Base != nil && pr.Base.Repository != nil && pr.Base.Repository.Owner != nil &&
|
|
||||||
pr.Head != nil && pr.Head.Ref != "" && pr.Head.Sha == "" {
|
|
||||||
owner := pr.Base.Repository.Owner.UserName
|
|
||||||
repo := pr.Base.Repository.Name
|
|
||||||
refs, _, err := client.GetRepoRefs(owner, repo, pr.Head.Ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(refs) == 0 {
|
|
||||||
return fmt.Errorf("unable to resolve PR ref '%s'", pr.Head.Ref)
|
|
||||||
}
|
|
||||||
pr.Head.Sha = refs[0].Object.SHA
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,325 +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 (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReviewStateType review state type
|
|
||||||
type ReviewStateType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ReviewStateApproved pr is approved
|
|
||||||
ReviewStateApproved ReviewStateType = "APPROVED"
|
|
||||||
// ReviewStatePending pr state is pending
|
|
||||||
ReviewStatePending ReviewStateType = "PENDING"
|
|
||||||
// ReviewStateComment is a comment review
|
|
||||||
ReviewStateComment ReviewStateType = "COMMENT"
|
|
||||||
// ReviewStateRequestChanges changes for pr are requested
|
|
||||||
ReviewStateRequestChanges ReviewStateType = "REQUEST_CHANGES"
|
|
||||||
// ReviewStateRequestReview review is requested from user
|
|
||||||
ReviewStateRequestReview ReviewStateType = "REQUEST_REVIEW"
|
|
||||||
// ReviewStateUnknown state of pr is unknown
|
|
||||||
ReviewStateUnknown ReviewStateType = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// PullReview represents a pull request review
|
|
||||||
type PullReview struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Reviewer *User `json:"user"`
|
|
||||||
ReviewerTeam *Team `json:"team"`
|
|
||||||
State ReviewStateType `json:"state"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
CommitID string `json:"commit_id"`
|
|
||||||
// Stale indicates if the pull has changed since the review
|
|
||||||
Stale bool `json:"stale"`
|
|
||||||
// Official indicates if the review counts towards the required approval limit, if PR base is a protected branch
|
|
||||||
Official bool `json:"official"`
|
|
||||||
Dismissed bool `json:"dismissed"`
|
|
||||||
CodeCommentsCount int `json:"comments_count"`
|
|
||||||
Submitted time.Time `json:"submitted_at"`
|
|
||||||
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
HTMLPullURL string `json:"pull_request_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PullReviewComment represents a comment on a pull request review
|
|
||||||
type PullReviewComment struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
Reviewer *User `json:"user"`
|
|
||||||
ReviewID int64 `json:"pull_request_review_id"`
|
|
||||||
Resolver *User `json:"resolver"`
|
|
||||||
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
Updated time.Time `json:"updated_at"`
|
|
||||||
|
|
||||||
Path string `json:"path"`
|
|
||||||
CommitID string `json:"commit_id"`
|
|
||||||
OrigCommitID string `json:"original_commit_id"`
|
|
||||||
DiffHunk string `json:"diff_hunk"`
|
|
||||||
LineNum uint64 `json:"position"`
|
|
||||||
OldLineNum uint64 `json:"original_position"`
|
|
||||||
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
HTMLPullURL string `json:"pull_request_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePullReviewOptions are options to create a pull review
|
|
||||||
type CreatePullReviewOptions struct {
|
|
||||||
State ReviewStateType `json:"event"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
CommitID string `json:"commit_id"`
|
|
||||||
Comments []CreatePullReviewComment `json:"comments"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePullReviewComment represent a review comment for creation api
|
|
||||||
type CreatePullReviewComment struct {
|
|
||||||
// the tree path
|
|
||||||
Path string `json:"path"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
// if comment to old file line or 0
|
|
||||||
OldLineNum int64 `json:"old_position"`
|
|
||||||
// if comment to new file line or 0
|
|
||||||
NewLineNum int64 `json:"new_position"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitPullReviewOptions are options to submit a pending pull review
|
|
||||||
type SubmitPullReviewOptions struct {
|
|
||||||
State ReviewStateType `json:"event"`
|
|
||||||
Body string `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DismissPullReviewOptions are options to dismiss a pull review
|
|
||||||
type DismissPullReviewOptions struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PullReviewRequestOptions are options to add or remove pull review requests
|
|
||||||
type PullReviewRequestOptions struct {
|
|
||||||
Reviewers []string `json:"reviewers"`
|
|
||||||
TeamReviewers []string `json:"team_reviewers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPullReviewsOptions options for listing PullReviews
|
|
||||||
type ListPullReviewsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreatePullReviewOptions struct
|
|
||||||
func (opt CreatePullReviewOptions) Validate() error {
|
|
||||||
if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 {
|
|
||||||
return fmt.Errorf("body is empty")
|
|
||||||
}
|
|
||||||
for i := range opt.Comments {
|
|
||||||
if err := opt.Comments[i].Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the SubmitPullReviewOptions struct
|
|
||||||
func (opt SubmitPullReviewOptions) Validate() error {
|
|
||||||
if opt.State != ReviewStateApproved && len(strings.TrimSpace(opt.Body)) == 0 {
|
|
||||||
return fmt.Errorf("body is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreatePullReviewComment struct
|
|
||||||
func (opt CreatePullReviewComment) Validate() error {
|
|
||||||
if len(strings.TrimSpace(opt.Body)) == 0 {
|
|
||||||
return fmt.Errorf("body is empty")
|
|
||||||
}
|
|
||||||
if opt.NewLineNum != 0 && opt.OldLineNum != 0 {
|
|
||||||
return fmt.Errorf("old and new line num are set, cant identify the code comment position")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPullReviews lists all reviews of a pull request
|
|
||||||
func (c *Client) ListPullReviews(owner, repo string, index int64, opt ListPullReviewsOptions) ([]*PullReview, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
rs := make([]*PullReview, 0, opt.PageSize)
|
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index))
|
|
||||||
link.RawQuery = opt.ListOptions.getURLQuery().Encode()
|
|
||||||
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rs)
|
|
||||||
return rs, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPullReview gets a specific review of a pull request
|
|
||||||
func (c *Client) GetPullReview(owner, repo string, index, id int64) (*PullReview, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := new(PullReview)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil, &r)
|
|
||||||
return r, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPullReviewComments lists all comments of a pull request review
|
|
||||||
func (c *Client) ListPullReviewComments(owner, repo string, index, id int64) ([]*PullReviewComment, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
rcl := make([]*PullReviewComment, 0, 4)
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/comments", owner, repo, index, id))
|
|
||||||
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &rcl)
|
|
||||||
return rcl, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePullReview delete a specific review from a pull request
|
|
||||||
func (c *Client) DeletePullReview(owner, repo string, index, id int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id), jsonHeader, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePullReview create a review to an pull request
|
|
||||||
func (c *Client) CreatePullReview(owner, repo string, index int64, opt CreatePullReviewOptions) (*PullReview, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := new(PullReview)
|
|
||||||
resp, err := c.getParsedResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews", owner, repo, index),
|
|
||||||
jsonHeader, bytes.NewReader(body), r)
|
|
||||||
return r, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitPullReview submit a pending review to an pull request
|
|
||||||
func (c *Client) SubmitPullReview(owner, repo string, index, id int64, opt SubmitPullReviewOptions) (*PullReview, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := new(PullReview)
|
|
||||||
resp, err := c.getParsedResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d", owner, repo, index, id),
|
|
||||||
jsonHeader, bytes.NewReader(body), r)
|
|
||||||
return r, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateReviewRequests create review requests to an pull request
|
|
||||||
func (c *Client) CreateReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp, err := c.getResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index),
|
|
||||||
jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteReviewRequests delete review requests to an pull request
|
|
||||||
func (c *Client) DeleteReviewRequests(owner, repo string, index int64, opt PullReviewRequestOptions) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp, err := c.getResponse("DELETE",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/pulls/%d/requested_reviewers", owner, repo, index),
|
|
||||||
jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DismissPullReview dismiss a review for a pull request
|
|
||||||
func (c *Client) DismissPullReview(owner, repo string, index, id int64, opt DismissPullReviewOptions) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp, err := c.getResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/dismissals", owner, repo, index, id),
|
|
||||||
jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnDismissPullReview cancel to dismiss a review for a pull request
|
|
||||||
func (c *Client) UnDismissPullReview(owner, repo string, index, id int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp, err := c.getResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, id),
|
|
||||||
jsonHeader, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Release represents a repository release
|
|
||||||
type Release struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
TagName string `json:"tag_name"`
|
|
||||||
Target string `json:"target_commitish"`
|
|
||||||
Title string `json:"name"`
|
|
||||||
Note string `json:"body"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
TarURL string `json:"tarball_url"`
|
|
||||||
ZipURL string `json:"zipball_url"`
|
|
||||||
IsDraft bool `json:"draft"`
|
|
||||||
IsPrerelease bool `json:"prerelease"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
PublishedAt time.Time `json:"published_at"`
|
|
||||||
Publisher *User `json:"author"`
|
|
||||||
Attachments []*Attachment `json:"assets"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListReleasesOptions options for listing repository's releases
|
|
||||||
type ListReleasesOptions struct {
|
|
||||||
ListOptions
|
|
||||||
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("draft", fmt.Sprintf("%t", *opt.IsPreRelease))
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListReleases list releases of a repository
|
|
||||||
func (c *Client) ListReleases(owner, repo string, opt ListReleasesOptions) ([]*Release, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
releases := make([]*Release, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases?%s", owner, repo, opt.QueryEncode()),
|
|
||||||
nil, nil, &releases)
|
|
||||||
return releases, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRelease get a release of a repository by id
|
|
||||||
func (c *Client) GetRelease(owner, repo string, id int64) (*Release, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
r := new(Release)
|
|
||||||
resp, err := c.getParsedResponse("GET",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id),
|
|
||||||
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) {
|
|
||||||
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
|
||||||
return c.fallbackGetReleaseByTag(owner, repo, tag)
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &tag); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
r := new(Release)
|
|
||||||
resp, err := c.getParsedResponse("GET",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/tags/%s", owner, repo, tag),
|
|
||||||
nil, nil, &r)
|
|
||||||
return r, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateReleaseOption options when creating a release
|
|
||||||
type CreateReleaseOption struct {
|
|
||||||
TagName string `json:"tag_name"`
|
|
||||||
Target string `json:"target_commitish"`
|
|
||||||
Title string `json:"name"`
|
|
||||||
Note string `json:"body"`
|
|
||||||
IsDraft bool `json:"draft"`
|
|
||||||
IsPrerelease bool `json:"prerelease"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateReleaseOption struct
|
|
||||||
func (opt CreateReleaseOption) Validate() error {
|
|
||||||
if len(strings.TrimSpace(opt.Title)) == 0 {
|
|
||||||
return fmt.Errorf("title is empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRelease create a release
|
|
||||||
func (c *Client) CreateRelease(owner, repo string, opt CreateReleaseOption) (*Release, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
r := new(Release)
|
|
||||||
resp, err := c.getParsedResponse("POST",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases", owner, repo),
|
|
||||||
jsonHeader, bytes.NewReader(body), r)
|
|
||||||
return r, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditReleaseOption options when editing a release
|
|
||||||
type EditReleaseOption struct {
|
|
||||||
TagName string `json:"tag_name"`
|
|
||||||
Target string `json:"target_commitish"`
|
|
||||||
Title string `json:"name"`
|
|
||||||
Note string `json:"body"`
|
|
||||||
IsDraft *bool `json:"draft"`
|
|
||||||
IsPrerelease *bool `json:"prerelease"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditRelease edit a release
|
|
||||||
func (c *Client) EditRelease(owner, repo string, id int64, form EditReleaseOption) (*Release, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(form)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
r := new(Release)
|
|
||||||
resp, err := c.getParsedResponse("PATCH",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d", owner, repo, id),
|
|
||||||
jsonHeader, bytes.NewReader(body), r)
|
|
||||||
return r, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRelease delete a release from a repository, keeping its tag
|
|
||||||
func (c *Client) DeleteRelease(user, repo string, id int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id),
|
|
||||||
nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteReleaseByTag deletes a release frm a repository by tag
|
|
||||||
func (c *Client) DeleteReleaseByTag(user, repo string, tag string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag),
|
|
||||||
nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 )
|
|
||||||
func (c *Client) fallbackGetReleaseByTag(owner, repo string, tag string) (*Release, *Response, error) {
|
|
||||||
for i := 1; ; i++ {
|
|
||||||
rl, resp, err := c.ListReleases(owner, repo, ListReleasesOptions{ListOptions: ListOptions{Page: i}})
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
if len(rl) == 0 {
|
|
||||||
return nil,
|
|
||||||
&Response{&http.Response{StatusCode: 404}},
|
|
||||||
fmt.Errorf("release with tag '%s' not found", tag)
|
|
||||||
}
|
|
||||||
for _, r := range rl {
|
|
||||||
if r.TagName == tag {
|
|
||||||
return r, resp, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,534 +0,0 @@
|
||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Permission represents a set of permissions
|
|
||||||
type Permission struct {
|
|
||||||
Admin bool `json:"admin"`
|
|
||||||
Push bool `json:"push"`
|
|
||||||
Pull bool `json:"pull"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// InternalTracker represents settings for internal tracker
|
|
||||||
type InternalTracker struct {
|
|
||||||
// Enable time tracking (Built-in issue tracker)
|
|
||||||
EnableTimeTracker bool `json:"enable_time_tracker"`
|
|
||||||
// Let only contributors track time (Built-in issue tracker)
|
|
||||||
AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"`
|
|
||||||
// Enable dependencies for issues and pull requests (Built-in issue tracker)
|
|
||||||
EnableIssueDependencies bool `json:"enable_issue_dependencies"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExternalTracker represents settings for external tracker
|
|
||||||
type ExternalTracker struct {
|
|
||||||
// URL of external issue tracker.
|
|
||||||
ExternalTrackerURL string `json:"external_tracker_url"`
|
|
||||||
// External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.
|
|
||||||
ExternalTrackerFormat string `json:"external_tracker_format"`
|
|
||||||
// External Issue Tracker Number Format, either `numeric` or `alphanumeric`
|
|
||||||
ExternalTrackerStyle string `json:"external_tracker_style"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExternalWiki represents setting for external wiki
|
|
||||||
type ExternalWiki struct {
|
|
||||||
// URL of external wiki.
|
|
||||||
ExternalWikiURL string `json:"external_wiki_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repository represents a repository
|
|
||||||
type Repository struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Owner *User `json:"owner"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
FullName string `json:"full_name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Empty bool `json:"empty"`
|
|
||||||
Private bool `json:"private"`
|
|
||||||
Fork bool `json:"fork"`
|
|
||||||
Template bool `json:"template"`
|
|
||||||
Parent *Repository `json:"parent"`
|
|
||||||
Mirror bool `json:"mirror"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
SSHURL string `json:"ssh_url"`
|
|
||||||
CloneURL string `json:"clone_url"`
|
|
||||||
OriginalURL string `json:"original_url"`
|
|
||||||
Website string `json:"website"`
|
|
||||||
Stars int `json:"stars_count"`
|
|
||||||
Forks int `json:"forks_count"`
|
|
||||||
Watchers int `json:"watchers_count"`
|
|
||||||
OpenIssues int `json:"open_issues_count"`
|
|
||||||
OpenPulls int `json:"open_pr_counter"`
|
|
||||||
Releases int `json:"release_counter"`
|
|
||||||
DefaultBranch string `json:"default_branch"`
|
|
||||||
Archived bool `json:"archived"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
Updated time.Time `json:"updated_at"`
|
|
||||||
Permissions *Permission `json:"permissions,omitempty"`
|
|
||||||
HasIssues bool `json:"has_issues"`
|
|
||||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
|
||||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
|
||||||
HasWiki bool `json:"has_wiki"`
|
|
||||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
|
||||||
HasPullRequests bool `json:"has_pull_requests"`
|
|
||||||
HasProjects bool `json:"has_projects"`
|
|
||||||
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts"`
|
|
||||||
AllowMerge bool `json:"allow_merge_commits"`
|
|
||||||
AllowRebase bool `json:"allow_rebase"`
|
|
||||||
AllowRebaseMerge bool `json:"allow_rebase_explicit"`
|
|
||||||
AllowSquash bool `json:"allow_squash_merge"`
|
|
||||||
AvatarURL string `json:"avatar_url"`
|
|
||||||
Internal bool `json:"internal"`
|
|
||||||
MirrorInterval string `json:"mirror_interval"`
|
|
||||||
DefaultMergeStyle MergeStyle `json:"default_merge_style"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepoType represent repo type
|
|
||||||
type RepoType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// RepoTypeNone dont specify a type
|
|
||||||
RepoTypeNone RepoType = ""
|
|
||||||
// RepoTypeSource is the default repo type
|
|
||||||
RepoTypeSource RepoType = "source"
|
|
||||||
// RepoTypeFork is a repo witch was forked from an other one
|
|
||||||
RepoTypeFork RepoType = "fork"
|
|
||||||
// RepoTypeMirror represents an mirror repo
|
|
||||||
RepoTypeMirror RepoType = "mirror"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TrustModel represent how git signatures are handled in a repository
|
|
||||||
type TrustModel string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// TrustModelDefault use TM set by global config
|
|
||||||
TrustModelDefault TrustModel = "default"
|
|
||||||
// TrustModelCollaborator gpg signature has to be owned by a repo collaborator
|
|
||||||
TrustModelCollaborator TrustModel = "collaborator"
|
|
||||||
// TrustModelCommitter gpg signature has to match committer
|
|
||||||
TrustModelCommitter TrustModel = "committer"
|
|
||||||
// TrustModelCollaboratorCommitter gpg signature has to match committer and owned by a repo collaborator
|
|
||||||
TrustModelCollaboratorCommitter TrustModel = "collaboratorcommitter"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListReposOptions options for listing repositories
|
|
||||||
type ListReposOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMyRepos lists all repositories for the authenticated user that has access to.
|
|
||||||
func (c *Client) ListMyRepos(opt ListReposOptions) ([]*Repository, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
repos := make([]*Repository, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/repos?%s", opt.getURLQuery().Encode()), nil, nil, &repos)
|
|
||||||
return repos, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListUserRepos list all repositories of one user by user's name
|
|
||||||
func (c *Client) ListUserRepos(user string, opt ListReposOptions) ([]*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
repos := make([]*Repository, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos?%s", user, opt.getURLQuery().Encode()), nil, nil, &repos)
|
|
||||||
return repos, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOrgReposOptions options for a organization's repositories
|
|
||||||
type ListOrgReposOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOrgRepos list all repositories of one organization by organization's name
|
|
||||||
func (c *Client) ListOrgRepos(org string, opt ListOrgReposOptions) ([]*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
repos := make([]*Repository, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos?%s", org, opt.getURLQuery().Encode()), nil, nil, &repos)
|
|
||||||
return repos, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchRepoOptions options for searching repositories
|
|
||||||
type SearchRepoOptions struct {
|
|
||||||
ListOptions
|
|
||||||
|
|
||||||
// The keyword to query
|
|
||||||
Keyword string
|
|
||||||
// Limit search to repositories with keyword as topic
|
|
||||||
KeywordIsTopic bool
|
|
||||||
// Include search of keyword within repository description
|
|
||||||
KeywordInDescription bool
|
|
||||||
|
|
||||||
/*
|
|
||||||
User Filter
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Repo Owner
|
|
||||||
OwnerID int64
|
|
||||||
// Stared By UserID
|
|
||||||
StarredByUserID int64
|
|
||||||
|
|
||||||
/*
|
|
||||||
Repo Attributes
|
|
||||||
*/
|
|
||||||
|
|
||||||
// pubic, private or all repositories (defaults to all)
|
|
||||||
IsPrivate *bool
|
|
||||||
// archived, non-archived or all repositories (defaults to all)
|
|
||||||
IsArchived *bool
|
|
||||||
// Exclude template repos from search
|
|
||||||
ExcludeTemplate bool
|
|
||||||
// Filter by "fork", "source", "mirror"
|
|
||||||
Type RepoType
|
|
||||||
|
|
||||||
/*
|
|
||||||
Sort Filters
|
|
||||||
*/
|
|
||||||
|
|
||||||
// sort repos by attribute. Supported values are "alpha", "created", "updated", "size", and "id". Default is "alpha"
|
|
||||||
Sort string
|
|
||||||
// sort order, either "asc" (ascending) or "desc" (descending). Default is "asc", ignored if "sort" is not specified.
|
|
||||||
Order string
|
|
||||||
// Repo owner to prioritize in the results
|
|
||||||
PrioritizedByOwnerID int64
|
|
||||||
|
|
||||||
/*
|
|
||||||
Cover EdgeCases
|
|
||||||
*/
|
|
||||||
// if set all other options are ignored and this string is used as query
|
|
||||||
RawQuery string
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
|
||||||
func (opt *SearchRepoOptions) QueryEncode() string {
|
|
||||||
query := opt.getURLQuery()
|
|
||||||
if opt.Keyword != "" {
|
|
||||||
query.Add("q", opt.Keyword)
|
|
||||||
}
|
|
||||||
if opt.KeywordIsTopic {
|
|
||||||
query.Add("topic", "true")
|
|
||||||
}
|
|
||||||
if opt.KeywordInDescription {
|
|
||||||
query.Add("includeDesc", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
// User Filter
|
|
||||||
if opt.OwnerID > 0 {
|
|
||||||
query.Add("uid", fmt.Sprintf("%d", opt.OwnerID))
|
|
||||||
query.Add("exclusive", "true")
|
|
||||||
}
|
|
||||||
if opt.StarredByUserID > 0 {
|
|
||||||
query.Add("starredBy", fmt.Sprintf("%d", opt.StarredByUserID))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repo Attributes
|
|
||||||
if opt.IsPrivate != nil {
|
|
||||||
query.Add("is_private", fmt.Sprintf("%v", opt.IsPrivate))
|
|
||||||
}
|
|
||||||
if opt.IsArchived != nil {
|
|
||||||
query.Add("archived", fmt.Sprintf("%v", opt.IsArchived))
|
|
||||||
}
|
|
||||||
if opt.ExcludeTemplate {
|
|
||||||
query.Add("template", "false")
|
|
||||||
}
|
|
||||||
if len(opt.Type) != 0 {
|
|
||||||
query.Add("mode", string(opt.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort Filters
|
|
||||||
if opt.Sort != "" {
|
|
||||||
query.Add("sort", opt.Sort)
|
|
||||||
}
|
|
||||||
if opt.PrioritizedByOwnerID > 0 {
|
|
||||||
query.Add("priority_owner_id", fmt.Sprintf("%d", opt.PrioritizedByOwnerID))
|
|
||||||
}
|
|
||||||
if opt.Order != "" {
|
|
||||||
query.Add("order", opt.Order)
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
type searchRepoResponse struct {
|
|
||||||
Repos []*Repository `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchRepos searches for repositories matching the given filters
|
|
||||||
func (c *Client) SearchRepos(opt SearchRepoOptions) ([]*Repository, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
repos := new(searchRepoResponse)
|
|
||||||
|
|
||||||
link, _ := url.Parse("/repos/search")
|
|
||||||
|
|
||||||
if len(opt.RawQuery) != 0 {
|
|
||||||
link.RawQuery = opt.RawQuery
|
|
||||||
} else {
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
// IsPrivate only works on gitea >= 1.12.0
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil && opt.IsPrivate != nil {
|
|
||||||
if *opt.IsPrivate {
|
|
||||||
// private repos only not supported on gitea <= 1.11.x
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
link.Query().Add("private", "false")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &repos)
|
|
||||||
return repos.Repos, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRepoOption options when creating repository
|
|
||||||
type CreateRepoOption struct {
|
|
||||||
// Name of the repository to create
|
|
||||||
Name string `json:"name"`
|
|
||||||
// Description of the repository to create
|
|
||||||
Description string `json:"description"`
|
|
||||||
// Whether the repository is private
|
|
||||||
Private bool `json:"private"`
|
|
||||||
// Issue Label set to use
|
|
||||||
IssueLabels string `json:"issue_labels"`
|
|
||||||
// Whether the repository should be auto-intialized?
|
|
||||||
AutoInit bool `json:"auto_init"`
|
|
||||||
// Whether the repository is template
|
|
||||||
Template bool `json:"template"`
|
|
||||||
// Gitignores to use
|
|
||||||
Gitignores string `json:"gitignores"`
|
|
||||||
// License to use
|
|
||||||
License string `json:"license"`
|
|
||||||
// Readme of the repository to create
|
|
||||||
Readme string `json:"readme"`
|
|
||||||
// DefaultBranch of the repository (used when initializes and in template)
|
|
||||||
DefaultBranch string `json:"default_branch"`
|
|
||||||
// TrustModel of the repository
|
|
||||||
TrustModel TrustModel `json:"trust_model"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateRepoOption struct
|
|
||||||
func (opt CreateRepoOption) Validate(c *Client) error {
|
|
||||||
if len(strings.TrimSpace(opt.Name)) == 0 {
|
|
||||||
return fmt.Errorf("name is empty")
|
|
||||||
}
|
|
||||||
if len(opt.Name) > 100 {
|
|
||||||
return fmt.Errorf("name has more than 100 chars")
|
|
||||||
}
|
|
||||||
if len(opt.Description) > 255 {
|
|
||||||
return fmt.Errorf("name has more than 255 chars")
|
|
||||||
}
|
|
||||||
if len(opt.DefaultBranch) > 100 {
|
|
||||||
return fmt.Errorf("name has more than 100 chars")
|
|
||||||
}
|
|
||||||
if len(opt.TrustModel) != 0 {
|
|
||||||
if err := c.CheckServerVersionConstraint(">=1.13.0"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRepo creates a repository for authenticated user.
|
|
||||||
func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) {
|
|
||||||
if err := opt.Validate(c); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
repo := new(Repository)
|
|
||||||
resp, err := c.getParsedResponse("POST", "/user/repos", jsonHeader, bytes.NewReader(body), repo)
|
|
||||||
return repo, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOrgRepo creates an organization repository for authenticated user.
|
|
||||||
func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&org); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(c); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
repo := new(Repository)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/org/%s/repos", org), jsonHeader, bytes.NewReader(body), repo)
|
|
||||||
return repo, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepo returns information of a repository of given owner.
|
|
||||||
func (c *Client) GetRepo(owner, reponame string) (*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
repo := new(Repository)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo)
|
|
||||||
return repo, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepoByID returns information of a repository by a giver repository ID.
|
|
||||||
func (c *Client) GetRepoByID(id int64) (*Repository, *Response, error) {
|
|
||||||
repo := new(Repository)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repositories/%d", id), nil, nil, repo)
|
|
||||||
return repo, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditRepoOption options when editing a repository's properties
|
|
||||||
type EditRepoOption struct {
|
|
||||||
// name of the repository
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
// a short description of the repository.
|
|
||||||
Description *string `json:"description,omitempty"`
|
|
||||||
// a URL with more information about the repository.
|
|
||||||
Website *string `json:"website,omitempty"`
|
|
||||||
// either `true` to make the repository private or `false` to make it public.
|
|
||||||
// Note: you will get a 422 error if the organization restricts changing repository visibility to organization
|
|
||||||
// owners and a non-owner tries to change the value of private.
|
|
||||||
Private *bool `json:"private,omitempty"`
|
|
||||||
// either `true` to make this repository a template or `false` to make it a normal repository
|
|
||||||
Template *bool `json:"template,omitempty"`
|
|
||||||
// either `true` to enable issues for this repository or `false` to disable them.
|
|
||||||
HasIssues *bool `json:"has_issues,omitempty"`
|
|
||||||
// set this structure to configure internal issue tracker (requires has_issues)
|
|
||||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
|
||||||
// set this structure to use external issue tracker (requires has_issues)
|
|
||||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
|
||||||
// either `true` to enable the wiki for this repository or `false` to disable it.
|
|
||||||
HasWiki *bool `json:"has_wiki,omitempty"`
|
|
||||||
// set this structure to use external wiki instead of internal (requires has_wiki)
|
|
||||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
|
||||||
// sets the default branch for this repository.
|
|
||||||
DefaultBranch *string `json:"default_branch,omitempty"`
|
|
||||||
// either `true` to allow pull requests, or `false` to prevent pull request.
|
|
||||||
HasPullRequests *bool `json:"has_pull_requests,omitempty"`
|
|
||||||
// either `true` to enable project unit, or `false` to disable them.
|
|
||||||
HasProjects *bool `json:"has_projects,omitempty"`
|
|
||||||
// either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace. `has_pull_requests` must be `true`.
|
|
||||||
IgnoreWhitespaceConflicts *bool `json:"ignore_whitespace_conflicts,omitempty"`
|
|
||||||
// either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits. `has_pull_requests` must be `true`.
|
|
||||||
AllowMerge *bool `json:"allow_merge_commits,omitempty"`
|
|
||||||
// either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging. `has_pull_requests` must be `true`.
|
|
||||||
AllowRebase *bool `json:"allow_rebase,omitempty"`
|
|
||||||
// either `true` to allow rebase with explicit merge commits (--no-ff), or `false` to prevent rebase with explicit merge commits. `has_pull_requests` must be `true`.
|
|
||||||
AllowRebaseMerge *bool `json:"allow_rebase_explicit,omitempty"`
|
|
||||||
// either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging. `has_pull_requests` must be `true`.
|
|
||||||
AllowSquash *bool `json:"allow_squash_merge,omitempty"`
|
|
||||||
// set to `true` to archive this repository.
|
|
||||||
Archived *bool `json:"archived,omitempty"`
|
|
||||||
// set to a string like `8h30m0s` to set the mirror interval time
|
|
||||||
MirrorInterval *string `json:"mirror_interval,omitempty"`
|
|
||||||
// either `true` to allow mark pr as merged manually, or `false` to prevent it. `has_pull_requests` must be `true`.
|
|
||||||
AllowManualMerge *bool `json:"allow_manual_merge,omitempty"`
|
|
||||||
// either `true` to enable AutodetectManualMerge, or `false` to prevent it. `has_pull_requests` must be `true`, Note: In some special cases, misjudgments can occur.
|
|
||||||
AutodetectManualMerge *bool `json:"autodetect_manual_merge,omitempty"`
|
|
||||||
// set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", or "squash". `has_pull_requests` must be `true`.
|
|
||||||
DefaultMergeStyle *MergeStyle `json:"default_merge_style,omitempty"`
|
|
||||||
// set to `true` to archive this repository.
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditRepo edit the properties of a repository
|
|
||||||
func (c *Client) EditRepo(owner, reponame string, opt EditRepoOption) (*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
repo := new(Repository)
|
|
||||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s", owner, reponame), jsonHeader, bytes.NewReader(body), repo)
|
|
||||||
return repo, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRepo deletes a repository of user or organization.
|
|
||||||
func (c *Client) DeleteRepo(owner, repo string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MirrorSync adds a mirrored repository to the mirror sync queue.
|
|
||||||
func (c *Client) MirrorSync(owner, repo string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepoLanguages return language stats of a repo
|
|
||||||
func (c *Client) GetRepoLanguages(owner, repo string) (map[string]int64, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
langMap := make(map[string]int64)
|
|
||||||
|
|
||||||
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/languages", owner, repo), jsonHeader, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal(data, &langMap); err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
return langMap, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArchiveType represent supported archive formats by gitea
|
|
||||||
type ArchiveType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ZipArchive represent zip format
|
|
||||||
ZipArchive ArchiveType = ".zip"
|
|
||||||
// TarGZArchive represent tar.gz format
|
|
||||||
TarGZArchive ArchiveType = ".tar.gz"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetArchive get an archive of a repository by git reference
|
|
||||||
// e.g.: ref -> master, 70b7c74b33, v1.2.1, ...
|
|
||||||
func (c *Client) GetArchive(owner, repo, ref string, ext ArchiveType) ([]byte, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
ref = pathEscapeSegments(ref)
|
|
||||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetArchiveReader gets a `git archive` for a particular tree-ish git reference
|
|
||||||
// such as a branch name (`master`), a commit hash (`70b7c74b33`), a tag
|
|
||||||
// (`v1.2.1`). The archive is returned as a byte stream in a ReadCloser. It is
|
|
||||||
// the responsibility of the client to close the reader.
|
|
||||||
func (c *Client) GetArchiveReader(owner, repo, ref string, ext ArchiveType) (io.ReadCloser, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
ref = pathEscapeSegments(ref)
|
|
||||||
resp, err := c.doRequest("GET", fmt.Sprintf("/repos/%s/%s/archive/%s%s", owner, repo, ref, ext), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := statusCodeToErr(resp); err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.Body, resp, nil
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PayloadUser represents the author or committer of a commit
|
|
||||||
type PayloadUser struct {
|
|
||||||
// Full name of the commit author
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
UserName string `json:"username"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PayloadCommit represents a commit
|
|
||||||
type PayloadCommit struct {
|
|
||||||
// sha1 hash of the commit
|
|
||||||
ID string `json:"id"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Author *PayloadUser `json:"author"`
|
|
||||||
Committer *PayloadUser `json:"committer"`
|
|
||||||
Verification *PayloadCommitVerification `json:"verification"`
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
Added []string `json:"added"`
|
|
||||||
Removed []string `json:"removed"`
|
|
||||||
Modified []string `json:"modified"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PayloadCommitVerification represents the GPG verification of a commit
|
|
||||||
type PayloadCommitVerification struct {
|
|
||||||
Verified bool `json:"verified"`
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Branch represents a repository branch
|
|
||||||
type Branch struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Commit *PayloadCommit `json:"commit"`
|
|
||||||
Protected bool `json:"protected"`
|
|
||||||
RequiredApprovals int64 `json:"required_approvals"`
|
|
||||||
EnableStatusCheck bool `json:"enable_status_check"`
|
|
||||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
|
||||||
UserCanPush bool `json:"user_can_push"`
|
|
||||||
UserCanMerge bool `json:"user_can_merge"`
|
|
||||||
EffectiveBranchProtectionName string `json:"effective_branch_protection_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoBranchesOptions options for listing a repository's branches
|
|
||||||
type ListRepoBranchesOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoBranches list all the branches of one repository
|
|
||||||
func (c *Client) ListRepoBranches(user, repo string, opt ListRepoBranchesOptions) ([]*Branch, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
branches := make([]*Branch, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &branches)
|
|
||||||
return branches, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepoBranch get one branch's information of one repository
|
|
||||||
func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
b := new(Branch)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
return b, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRepoBranch delete a branch in a repository
|
|
||||||
func (c *Client) DeleteRepoBranch(user, repo, branch string) (bool, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &branch); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, resp, err
|
|
||||||
}
|
|
||||||
return status == 204, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBranchOption options when creating a branch in a repository
|
|
||||||
type CreateBranchOption struct {
|
|
||||||
// Name of the branch to create
|
|
||||||
BranchName string `json:"new_branch_name"`
|
|
||||||
// Name of the old branch to create from (optional)
|
|
||||||
OldBranchName string `json:"old_branch_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the CreateBranchOption struct
|
|
||||||
func (opt CreateBranchOption) Validate() error {
|
|
||||||
if len(opt.BranchName) == 0 {
|
|
||||||
return fmt.Errorf("BranchName is empty")
|
|
||||||
}
|
|
||||||
if len(opt.BranchName) > 100 {
|
|
||||||
return fmt.Errorf("BranchName to long")
|
|
||||||
}
|
|
||||||
if len(opt.OldBranchName) > 100 {
|
|
||||||
return fmt.Errorf("OldBranchName to long")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBranch creates a branch for a user's repository
|
|
||||||
func (c *Client) CreateBranch(owner, repo string, opt CreateBranchOption) (*Branch, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
branch := new(Branch)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branches", owner, repo), jsonHeader, bytes.NewReader(body), branch)
|
|
||||||
return branch, resp, err
|
|
||||||
}
|
|
|
@ -1,168 +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 (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BranchProtection represents a branch protection for a repository
|
|
||||||
type BranchProtection struct {
|
|
||||||
BranchName string `json:"branch_name"`
|
|
||||||
EnablePush bool `json:"enable_push"`
|
|
||||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
|
||||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
|
||||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
|
||||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
|
||||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
|
||||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
|
||||||
EnableStatusCheck bool `json:"enable_status_check"`
|
|
||||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
|
||||||
RequiredApprovals int64 `json:"required_approvals"`
|
|
||||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
|
||||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
|
||||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
|
||||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
|
||||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
|
|
||||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
|
||||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
|
||||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
|
||||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
Updated time.Time `json:"updated_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBranchProtectionOption options for creating a branch protection
|
|
||||||
type CreateBranchProtectionOption struct {
|
|
||||||
BranchName string `json:"branch_name"`
|
|
||||||
EnablePush bool `json:"enable_push"`
|
|
||||||
EnablePushWhitelist bool `json:"enable_push_whitelist"`
|
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
|
||||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
|
||||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
|
|
||||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
|
|
||||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
|
||||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
|
||||||
EnableStatusCheck bool `json:"enable_status_check"`
|
|
||||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
|
||||||
RequiredApprovals int64 `json:"required_approvals"`
|
|
||||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"`
|
|
||||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
|
||||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
|
||||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"`
|
|
||||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"`
|
|
||||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"`
|
|
||||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals"`
|
|
||||||
RequireSignedCommits bool `json:"require_signed_commits"`
|
|
||||||
ProtectedFilePatterns string `json:"protected_file_patterns"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditBranchProtectionOption options for editing a branch protection
|
|
||||||
type EditBranchProtectionOption struct {
|
|
||||||
EnablePush *bool `json:"enable_push"`
|
|
||||||
EnablePushWhitelist *bool `json:"enable_push_whitelist"`
|
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
|
|
||||||
PushWhitelistTeams []string `json:"push_whitelist_teams"`
|
|
||||||
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"`
|
|
||||||
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
|
|
||||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
|
|
||||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
|
|
||||||
EnableStatusCheck *bool `json:"enable_status_check"`
|
|
||||||
StatusCheckContexts []string `json:"status_check_contexts"`
|
|
||||||
RequiredApprovals *int64 `json:"required_approvals"`
|
|
||||||
EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"`
|
|
||||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"`
|
|
||||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"`
|
|
||||||
BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"`
|
|
||||||
BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"`
|
|
||||||
BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"`
|
|
||||||
DismissStaleApprovals *bool `json:"dismiss_stale_approvals"`
|
|
||||||
RequireSignedCommits *bool `json:"require_signed_commits"`
|
|
||||||
ProtectedFilePatterns *string `json:"protected_file_patterns"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListBranchProtectionsOptions list branch protection options
|
|
||||||
type ListBranchProtectionsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListBranchProtections list branch protections for a repo
|
|
||||||
func (c *Client) ListBranchProtections(owner, repo string, opt ListBranchProtectionsOptions) ([]*BranchProtection, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
bps := make([]*BranchProtection, 0, opt.PageSize)
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo))
|
|
||||||
link.RawQuery = opt.getURLQuery().Encode()
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &bps)
|
|
||||||
return bps, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBranchProtection gets a branch protection
|
|
||||||
func (c *Client) GetBranchProtection(owner, repo, name string) (*BranchProtection, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
bp := new(BranchProtection)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil, bp)
|
|
||||||
return bp, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBranchProtection creates a branch protection for a repo
|
|
||||||
func (c *Client) CreateBranchProtection(owner, repo string, opt CreateBranchProtectionOption) (*BranchProtection, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
bp := new(BranchProtection)
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/branch_protections", owner, repo), jsonHeader, bytes.NewReader(body), bp)
|
|
||||||
return bp, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EditBranchProtection edits a branch protection for a repo
|
|
||||||
func (c *Client) EditBranchProtection(owner, repo, name string, opt EditBranchProtectionOption) (*BranchProtection, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
bp := new(BranchProtection)
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
resp, err := c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, bytes.NewReader(body), bp)
|
|
||||||
return bp, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteBranchProtection deletes a branch protection for a repo
|
|
||||||
func (c *Client) DeleteBranchProtection(owner, repo, name string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/branch_protections/%s", owner, repo, name), jsonHeader, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListCollaboratorsOptions options for listing a repository's collaborators
|
|
||||||
type ListCollaboratorsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListCollaborators list a repository's collaborators
|
|
||||||
func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
collaborators := make([]*User, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/collaborators?%s", user, repo, opt.getURLQuery().Encode()),
|
|
||||||
nil, nil, &collaborators)
|
|
||||||
return collaborators, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCollaborator check if a user is a collaborator of a repository
|
|
||||||
func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, resp, err
|
|
||||||
}
|
|
||||||
if status == 204 {
|
|
||||||
return true, resp, nil
|
|
||||||
}
|
|
||||||
return false, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCollaboratorOption options when adding a user as a collaborator of a repository
|
|
||||||
type AddCollaboratorOption struct {
|
|
||||||
Permission *AccessMode `json:"permission"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccessMode represent the grade of access you have to something
|
|
||||||
type AccessMode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AccessModeNone no access
|
|
||||||
AccessModeNone AccessMode = "none"
|
|
||||||
// AccessModeRead read access
|
|
||||||
AccessModeRead AccessMode = "read"
|
|
||||||
// AccessModeWrite write access
|
|
||||||
AccessModeWrite AccessMode = "write"
|
|
||||||
// AccessModeAdmin admin access
|
|
||||||
AccessModeAdmin AccessMode = "admin"
|
|
||||||
// AccessModeOwner owner
|
|
||||||
AccessModeOwner AccessMode = "owner"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Validate the AddCollaboratorOption struct
|
|
||||||
func (opt AddCollaboratorOption) Validate() error {
|
|
||||||
if opt.Permission != nil {
|
|
||||||
if *opt.Permission == AccessModeOwner {
|
|
||||||
*opt.Permission = AccessModeAdmin
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if *opt.Permission == AccessModeNone {
|
|
||||||
opt.Permission = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if *opt.Permission != AccessModeRead && *opt.Permission != AccessModeWrite && *opt.Permission != AccessModeAdmin {
|
|
||||||
return fmt.Errorf("permission mode invalid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCollaborator add some user as a collaborator of a repository
|
|
||||||
func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollaboratorOption) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &collaborator); 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
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteCollaborator remove a collaborator from a repository
|
|
||||||
func (c *Client) DeleteCollaborator(user, repo, collaborator string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetReviewers return all users that can be requested to review in this repo
|
|
||||||
func (c *Client) GetReviewers(user, repo string) ([]*User, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
reviewers := make([]*User, 0, 5)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/reviewers", user, repo), nil, nil, &reviewers)
|
|
||||||
return reviewers, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAssignees return all users that have write access and can be assigned to issues
|
|
||||||
func (c *Client) GetAssignees(user, repo string) ([]*User, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
assignees := make([]*User, 0, 5)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/assignees", user, repo), nil, nil, &assignees)
|
|
||||||
return assignees, resp, err
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
// Copyright 2018 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Identity for a person's identity like an author or committer
|
|
||||||
type Identity struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommitMeta contains meta information of a commit in terms of API.
|
|
||||||
type CommitMeta struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
SHA string `json:"sha"`
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommitUser contains information of a user in the context of a commit.
|
|
||||||
type CommitUser struct {
|
|
||||||
Identity
|
|
||||||
Date string `json:"date"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RepoCommit contains information of a commit in the context of a repository.
|
|
||||||
type RepoCommit struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Author *CommitUser `json:"author"`
|
|
||||||
Committer *CommitUser `json:"committer"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Tree *CommitMeta `json:"tree"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit contains information generated from a Git commit.
|
|
||||||
type Commit struct {
|
|
||||||
*CommitMeta
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
RepoCommit *RepoCommit `json:"commit"`
|
|
||||||
Author *User `json:"author"`
|
|
||||||
Committer *User `json:"committer"`
|
|
||||||
Parents []*CommitMeta `json:"parents"`
|
|
||||||
Files []*CommitAffectedFiles `json:"files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
|
|
||||||
type CommitDateOptions struct {
|
|
||||||
Author time.Time `json:"author"`
|
|
||||||
Committer time.Time `json:"committer"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommitAffectedFiles store information about files affected by the commit
|
|
||||||
type CommitAffectedFiles struct {
|
|
||||||
Filename string `json:"filename"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSingleCommit returns a single commit
|
|
||||||
func (c *Client) GetSingleCommit(user, repo, commitID string) (*Commit, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &commitID); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
commit := new(Commit)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s", user, repo, commitID), nil, nil, &commit)
|
|
||||||
return commit, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListCommitOptions list commit options
|
|
||||||
type ListCommitOptions struct {
|
|
||||||
ListOptions
|
|
||||||
//SHA or branch to start listing commits from (usually 'master')
|
|
||||||
SHA string
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
|
||||||
func (opt *ListCommitOptions) QueryEncode() string {
|
|
||||||
query := opt.ListOptions.getURLQuery()
|
|
||||||
if opt.SHA != "" {
|
|
||||||
query.Add("sha", opt.SHA)
|
|
||||||
}
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoCommits return list of commits from a repo
|
|
||||||
func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo))
|
|
||||||
opt.setDefaults()
|
|
||||||
commits := make([]*Commit, 0, opt.PageSize)
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits)
|
|
||||||
return commits, resp, err
|
|
||||||
}
|
|
|
@ -1,247 +0,0 @@
|
||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileOptions options for all file APIs
|
|
||||||
type FileOptions struct {
|
|
||||||
// message (optional) for the commit of this file. if not supplied, a default message will be used
|
|
||||||
Message string `json:"message"`
|
|
||||||
// branch (optional) to base this file from. if not given, the default branch is used
|
|
||||||
BranchName string `json:"branch"`
|
|
||||||
// new_branch (optional) will make a new branch from `branch` before creating the file
|
|
||||||
NewBranchName string `json:"new_branch"`
|
|
||||||
// `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
|
||||||
Author Identity `json:"author"`
|
|
||||||
Committer Identity `json:"committer"`
|
|
||||||
Dates CommitDateOptions `json:"dates"`
|
|
||||||
// Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
|
||||||
Signoff bool `json:"signoff"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFileOptions options for creating files
|
|
||||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
|
||||||
type CreateFileOptions struct {
|
|
||||||
FileOptions
|
|
||||||
// content must be base64 encoded
|
|
||||||
// required: true
|
|
||||||
Content string `json:"content"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFileOptions options for deleting files (used for other File structs below)
|
|
||||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
|
||||||
type DeleteFileOptions struct {
|
|
||||||
FileOptions
|
|
||||||
// sha is the SHA for the file that already exists
|
|
||||||
// required: true
|
|
||||||
SHA string `json:"sha"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFileOptions options for updating files
|
|
||||||
// Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
|
||||||
type UpdateFileOptions struct {
|
|
||||||
FileOptions
|
|
||||||
// sha is the SHA for the file that already exists
|
|
||||||
// required: true
|
|
||||||
SHA string `json:"sha"`
|
|
||||||
// content must be base64 encoded
|
|
||||||
// required: true
|
|
||||||
Content string `json:"content"`
|
|
||||||
// from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL
|
|
||||||
FromPath string `json:"from_path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileLinksResponse contains the links for a repo's file
|
|
||||||
type FileLinksResponse struct {
|
|
||||||
Self *string `json:"self"`
|
|
||||||
GitURL *string `json:"git"`
|
|
||||||
HTMLURL *string `json:"html"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content
|
|
||||||
type ContentsResponse struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
SHA string `json:"sha"`
|
|
||||||
// `type` will be `file`, `dir`, `symlink`, or `submodule`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
// `encoding` is populated when `type` is `file`, otherwise null
|
|
||||||
Encoding *string `json:"encoding"`
|
|
||||||
// `content` is populated when `type` is `file`, otherwise null
|
|
||||||
Content *string `json:"content"`
|
|
||||||
// `target` is populated when `type` is `symlink`, otherwise null
|
|
||||||
Target *string `json:"target"`
|
|
||||||
URL *string `json:"url"`
|
|
||||||
HTMLURL *string `json:"html_url"`
|
|
||||||
GitURL *string `json:"git_url"`
|
|
||||||
DownloadURL *string `json:"download_url"`
|
|
||||||
// `submodule_git_url` is populated when `type` is `submodule`, otherwise null
|
|
||||||
SubmoduleGitURL *string `json:"submodule_git_url"`
|
|
||||||
Links *FileLinksResponse `json:"_links"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileCommitResponse contains information generated from a Git commit for a repo's file.
|
|
||||||
type FileCommitResponse struct {
|
|
||||||
CommitMeta
|
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
Author *CommitUser `json:"author"`
|
|
||||||
Committer *CommitUser `json:"committer"`
|
|
||||||
Parents []*CommitMeta `json:"parents"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Tree *CommitMeta `json:"tree"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileResponse contains information about a repo's file
|
|
||||||
type FileResponse struct {
|
|
||||||
Content *ContentsResponse `json:"content"`
|
|
||||||
Commit *FileCommitResponse `json:"commit"`
|
|
||||||
Verification *PayloadCommitVerification `json:"verification"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileDeleteResponse contains information about a repo's file that was deleted
|
|
||||||
type FileDeleteResponse struct {
|
|
||||||
Content interface{} `json:"content"` // to be set to nil
|
|
||||||
Commit *FileCommitResponse `json:"commit"`
|
|
||||||
Verification *PayloadCommitVerification `json:"verification"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFile downloads a file of repository, ref can be branch/tag/commit.
|
|
||||||
// e.g.: ref -> master, filepath -> README.md (no leading slash)
|
|
||||||
func (c *Client) GetFile(owner, repo, ref, filepath string) ([]byte, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
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.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContents get the metadata and contents of a file in a repository
|
|
||||||
// ref is optional
|
|
||||||
func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) {
|
|
||||||
data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
cr := new(ContentsResponse)
|
|
||||||
if json.Unmarshal(data, &cr) != nil {
|
|
||||||
return nil, resp, fmt.Errorf("expect file, got directory")
|
|
||||||
}
|
|
||||||
return cr, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListContents gets a list of entries in a dir
|
|
||||||
// ref is optional
|
|
||||||
func (c *Client) ListContents(owner, repo, ref, filepath string) ([]*ContentsResponse, *Response, error) {
|
|
||||||
data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
crl := make([]*ContentsResponse, 0)
|
|
||||||
if json.Unmarshal(data, &crl) != nil {
|
|
||||||
return nil, resp, fmt.Errorf("expect directory, got file")
|
|
||||||
}
|
|
||||||
return crl, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) getDirOrFileContents(owner, repo, ref, filepath string) ([]byte, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
filepath = pathEscapeSegments(strings.TrimPrefix(filepath, "/"))
|
|
||||||
return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/contents/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), jsonHeader, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateFile create a file in a repository
|
|
||||||
func (c *Client) CreateFile(owner, repo, filepath string, opt CreateFileOptions) (*FileResponse, *Response, error) {
|
|
||||||
var err error
|
|
||||||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
filepath = pathEscapeSegments(filepath)
|
|
||||||
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
fr := new(FileResponse)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr)
|
|
||||||
return fr, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFile update a file in a repository
|
|
||||||
func (c *Client) UpdateFile(owner, repo, filepath string, opt UpdateFileOptions) (*FileResponse, *Response, error) {
|
|
||||||
var err error
|
|
||||||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
filepath = pathEscapeSegments(filepath)
|
|
||||||
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
fr := new(FileResponse)
|
|
||||||
resp, err := c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body), fr)
|
|
||||||
return fr, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFile delete a file from repository
|
|
||||||
func (c *Client) DeleteFile(owner, repo, filepath string, opt DeleteFileOptions) (*Response, error) {
|
|
||||||
var err error
|
|
||||||
if opt.BranchName, err = c.setDefaultBranchForOldVersions(owner, repo, opt.BranchName); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
filepath = pathEscapeSegments(filepath)
|
|
||||||
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, filepath), jsonHeader, bytes.NewReader(body))
|
|
||||||
if err != nil {
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
if status != 200 && status != 204 {
|
|
||||||
return resp, fmt.Errorf("unexpected Status: %d", status)
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) setDefaultBranchForOldVersions(owner, repo, branch string) (string, error) {
|
|
||||||
if len(branch) == 0 {
|
|
||||||
// Gitea >= 1.12.0 Use DefaultBranch on "", mimic this for older versions
|
|
||||||
if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil {
|
|
||||||
r, _, err := c.GetRepo(owner, repo)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return r.DefaultBranch, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return branch, nil
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeployKey a deploy key
|
|
||||||
type DeployKey struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
KeyID int64 `json:"key_id"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Fingerprint string `json:"fingerprint"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
ReadOnly bool `json:"read_only"`
|
|
||||||
Repository *Repository `json:"repository,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListDeployKeysOptions options for listing a repository's deploy keys
|
|
||||||
type ListDeployKeysOptions struct {
|
|
||||||
ListOptions
|
|
||||||
KeyID int64
|
|
||||||
Fingerprint string
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
|
||||||
func (opt *ListDeployKeysOptions) QueryEncode() string {
|
|
||||||
query := opt.getURLQuery()
|
|
||||||
if opt.KeyID > 0 {
|
|
||||||
query.Add("key_id", fmt.Sprintf("%d", opt.KeyID))
|
|
||||||
}
|
|
||||||
if len(opt.Fingerprint) > 0 {
|
|
||||||
query.Add("fingerprint", opt.Fingerprint)
|
|
||||||
}
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListDeployKeys list all the deploy keys of one repository
|
|
||||||
func (c *Client) ListDeployKeys(user, repo string, opt ListDeployKeysOptions) ([]*DeployKey, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/keys", user, repo))
|
|
||||||
opt.setDefaults()
|
|
||||||
link.RawQuery = opt.QueryEncode()
|
|
||||||
keys := make([]*DeployKey, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &keys)
|
|
||||||
return keys, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDeployKey get one deploy key with key id
|
|
||||||
func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
key := new(DeployKey)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key)
|
|
||||||
return key, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDeployKey options when create one deploy key
|
|
||||||
func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
key := new(DeployKey)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/keys", user, repo), jsonHeader, bytes.NewReader(body), key)
|
|
||||||
return key, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteDeployKey delete deploy key with key id
|
|
||||||
func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,132 +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 (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GitServiceType represents a git service
|
|
||||||
type GitServiceType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// GitServicePlain represents a plain git service
|
|
||||||
GitServicePlain GitServiceType = "git"
|
|
||||||
//GitServiceGithub represents github.com
|
|
||||||
GitServiceGithub GitServiceType = "github"
|
|
||||||
// GitServiceGitlab represents a gitlab service
|
|
||||||
GitServiceGitlab GitServiceType = "gitlab"
|
|
||||||
// GitServiceGitea represents a gitea service
|
|
||||||
GitServiceGitea GitServiceType = "gitea"
|
|
||||||
// GitServiceGogs represents a gogs service
|
|
||||||
GitServiceGogs GitServiceType = "gogs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MigrateRepoOption options for migrating a repository from an external service
|
|
||||||
type MigrateRepoOption struct {
|
|
||||||
RepoName string `json:"repo_name"`
|
|
||||||
RepoOwner string `json:"repo_owner"`
|
|
||||||
// deprecated use RepoOwner
|
|
||||||
RepoOwnerID int64 `json:"uid"`
|
|
||||||
CloneAddr string `json:"clone_addr"`
|
|
||||||
Service GitServiceType `json:"service"`
|
|
||||||
AuthUsername string `json:"auth_username"`
|
|
||||||
AuthPassword string `json:"auth_password"`
|
|
||||||
AuthToken string `json:"auth_token"`
|
|
||||||
Mirror bool `json:"mirror"`
|
|
||||||
Private bool `json:"private"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Wiki bool `json:"wiki"`
|
|
||||||
Milestones bool `json:"milestones"`
|
|
||||||
Labels bool `json:"labels"`
|
|
||||||
Issues bool `json:"issues"`
|
|
||||||
PullRequests bool `json:"pull_requests"`
|
|
||||||
Releases bool `json:"releases"`
|
|
||||||
MirrorInterval string `json:"mirror_interval"`
|
|
||||||
LFS bool `json:"lfs"`
|
|
||||||
LFSEndpoint string `json:"lfs_endpoint"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the MigrateRepoOption struct
|
|
||||||
func (opt *MigrateRepoOption) Validate(c *Client) error {
|
|
||||||
// check user options
|
|
||||||
if len(opt.CloneAddr) == 0 {
|
|
||||||
return fmt.Errorf("CloneAddr required")
|
|
||||||
}
|
|
||||||
if len(opt.RepoName) == 0 {
|
|
||||||
return fmt.Errorf("RepoName required")
|
|
||||||
} else if len(opt.RepoName) > 100 {
|
|
||||||
return fmt.Errorf("RepoName to long")
|
|
||||||
}
|
|
||||||
if len(opt.Description) > 255 {
|
|
||||||
return fmt.Errorf("Description to long")
|
|
||||||
}
|
|
||||||
switch opt.Service {
|
|
||||||
case GitServiceGithub:
|
|
||||||
if len(opt.AuthToken) == 0 {
|
|
||||||
return fmt.Errorf("github requires token authentication")
|
|
||||||
}
|
|
||||||
case GitServiceGitlab, GitServiceGitea:
|
|
||||||
if len(opt.AuthToken) == 0 {
|
|
||||||
return fmt.Errorf("%s requires token authentication", opt.Service)
|
|
||||||
}
|
|
||||||
// Gitlab is supported since 1.12.0 but api cant handle it until 1.13.0
|
|
||||||
// https://github.com/go-gitea/gitea/pull/12672
|
|
||||||
if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil {
|
|
||||||
return fmt.Errorf("migrate from service %s need gitea >= 1.13.0", opt.Service)
|
|
||||||
}
|
|
||||||
case GitServiceGogs:
|
|
||||||
if len(opt.AuthToken) == 0 {
|
|
||||||
return fmt.Errorf("gogs requires token authentication")
|
|
||||||
}
|
|
||||||
if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil {
|
|
||||||
return fmt.Errorf("migrate from service gogs need gitea >= 1.14.0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MigrateRepo migrates a repository from other Git hosting sources for the authenticated user.
|
|
||||||
//
|
|
||||||
// To migrate a repository for a organization, the authenticated user must be a
|
|
||||||
// owner of the specified organization.
|
|
||||||
func (c *Client) MigrateRepo(opt MigrateRepoOption) (*Repository, *Response, error) {
|
|
||||||
if err := opt.Validate(c); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
|
||||||
if len(opt.AuthToken) != 0 {
|
|
||||||
// gitea <= 1.12 dont understand AuthToken
|
|
||||||
opt.AuthUsername = opt.AuthToken
|
|
||||||
opt.AuthPassword, opt.AuthToken = "", ""
|
|
||||||
}
|
|
||||||
if len(opt.RepoOwner) != 0 {
|
|
||||||
// gitea <= 1.12 dont understand RepoOwner
|
|
||||||
u, _, err := c.GetUserInfo(opt.RepoOwner)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.RepoOwnerID = u.ID
|
|
||||||
} else if opt.RepoOwnerID == 0 {
|
|
||||||
// gitea <= 1.12 require RepoOwnerID
|
|
||||||
u, _, err := c.GetMyUserInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.RepoOwnerID = u.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
repo := new(Repository)
|
|
||||||
resp, err := c.getParsedResponse("POST", "/repos/migrate", jsonHeader, bytes.NewReader(body), repo)
|
|
||||||
return repo, resp, err
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reference represents a Git reference.
|
|
||||||
type Reference struct {
|
|
||||||
Ref string `json:"ref"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Object *GitObject `json:"object"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GitObject represents a Git object.
|
|
||||||
type GitObject struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
SHA string `json:"sha"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepoRef get one ref's information of one repository
|
|
||||||
func (c *Client) GetRepoRef(user, repo, ref string) (*Reference, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
ref = strings.TrimPrefix(ref, "refs/")
|
|
||||||
ref = pathEscapeSegments(ref)
|
|
||||||
r := new(Reference)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil, &r)
|
|
||||||
if _, ok := err.(*json.UnmarshalTypeError); ok {
|
|
||||||
// Multiple refs
|
|
||||||
return nil, resp, errors.New("no exact match found for this ref")
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepoRefs get list of ref's information of one repository
|
|
||||||
func (c *Client) GetRepoRefs(user, repo, ref string) ([]*Reference, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
ref = strings.TrimPrefix(ref, "refs/")
|
|
||||||
ref = pathEscapeSegments(ref)
|
|
||||||
|
|
||||||
data, resp, err := c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/refs/%s", user, repo, ref), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to unmarshal single returned ref.
|
|
||||||
r := new(Reference)
|
|
||||||
refErr := json.Unmarshal(data, r)
|
|
||||||
if refErr == nil {
|
|
||||||
return []*Reference{r}, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to unmarshal multiple refs.
|
|
||||||
var rs []*Reference
|
|
||||||
refsErr := json.Unmarshal(data, &rs)
|
|
||||||
if refsErr == nil {
|
|
||||||
if len(rs) == 0 {
|
|
||||||
return nil, resp, errors.New("unexpected response: an array of refs with length 0")
|
|
||||||
}
|
|
||||||
return rs, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, resp, fmt.Errorf("unmarshalling failed for both single and multiple refs: %s and %s", refErr, refsErr)
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListStargazersOptions options for listing a repository's stargazers
|
|
||||||
type ListStargazersOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoStargazers list a repository's stargazers
|
|
||||||
func (c *Client) ListRepoStargazers(user, repo string, opt ListStargazersOptions) ([]*User, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
stargazers := make([]*User, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/stargazers?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &stargazers)
|
|
||||||
return stargazers, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStarredRepos returns the repos that the given user has starred
|
|
||||||
func (c *Client) GetStarredRepos(user string) ([]*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
repos := make([]*Repository, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/starred", user), jsonHeader, nil, &repos)
|
|
||||||
return repos, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMyStarredRepos returns the repos that the authenticated user has starred
|
|
||||||
func (c *Client) GetMyStarredRepos() ([]*Repository, *Response, error) {
|
|
||||||
repos := make([]*Repository, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", "/user/starred", jsonHeader, nil, &repos)
|
|
||||||
return repos, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRepoStarring returns whether the authenticated user has starred the repo or not
|
|
||||||
func (c *Client) IsRepoStarring(user, repo string) (bool, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("GET", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
|
|
||||||
if resp != nil {
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusNotFound:
|
|
||||||
return false, resp, nil
|
|
||||||
case http.StatusNoContent:
|
|
||||||
return true, resp, nil
|
|
||||||
default:
|
|
||||||
return false, resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StarRepo star specified repo as the authenticated user
|
|
||||||
func (c *Client) StarRepo(user, repo string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
|
|
||||||
if resp != nil {
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusNoContent:
|
|
||||||
return resp, nil
|
|
||||||
default:
|
|
||||||
return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnStarRepo remove star to specified repo as the authenticated user
|
|
||||||
func (c *Client) UnStarRepo(user, repo string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/starred/%s/%s", user, repo), jsonHeader, nil)
|
|
||||||
if resp != nil {
|
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusNoContent:
|
|
||||||
return resp, nil
|
|
||||||
default:
|
|
||||||
return resp, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoTags list all the branches of one repository
|
|
||||||
func (c *Client) ListRepoTags(user, repo string, opt ListRepoTagsOptions) ([]*Tag, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
tags := make([]*Tag, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, &tags)
|
|
||||||
return tags, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTag get the tag of a repository
|
|
||||||
func (c *Client) GetTag(user, repo, tag string) (*Tag, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
t := new(Tag)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag), nil, nil, &t)
|
|
||||||
return t, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAnnotatedTag get the tag object of an annotated tag (not lightweight tags) of a repository
|
|
||||||
func (c *Client) GetAnnotatedTag(user, repo, sha string) (*AnnotatedTag, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &sha); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
t := new(AnnotatedTag)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/git/tags/%s", user, repo, sha), nil, nil, &t)
|
|
||||||
return t, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTagOption options when creating a tag
|
|
||||||
type CreateTagOption struct {
|
|
||||||
TagName string `json:"tag_name"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Target string `json:"target"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates CreateTagOption
|
|
||||||
func (opt CreateTagOption) Validate() error {
|
|
||||||
if len(opt.TagName) == 0 {
|
|
||||||
return fmt.Errorf("TagName is required")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTag create a new git tag in a repository
|
|
||||||
func (c *Client) CreateTag(user, repo string, opt CreateTagOption) (*Tag, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_15_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := opt.Validate(); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
t := new(Tag)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/tags", user, repo), jsonHeader, bytes.NewReader(body), &t)
|
|
||||||
return t, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTag deletes a tag from a repository, if no release refers to it
|
|
||||||
func (c *Client) DeleteTag(user, repo, tag string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE",
|
|
||||||
fmt.Sprintf("/repos/%s/%s/tags/%s", user, repo, tag),
|
|
||||||
nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListRepoTopicsOptions options for listing repo's topics
|
|
||||||
type ListRepoTopicsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// topicsList represents a list of repo's topics
|
|
||||||
type topicsList struct {
|
|
||||||
Topics []string `json:"topics"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRepoTopics list all repository's topics
|
|
||||||
func (c *Client) ListRepoTopics(user, repo string, opt ListRepoTopicsOptions) ([]string, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
|
|
||||||
list := new(topicsList)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/topics?%s", user, repo, opt.getURLQuery().Encode()), nil, nil, list)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
return list.Topics, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRepoTopics replaces the list of repo's topics
|
|
||||||
func (c *Client) SetRepoTopics(user, repo string, list []string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
l := topicsList{Topics: list}
|
|
||||||
body, err := json.Marshal(&l)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics", user, repo), jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRepoTopic adds a topic to a repo's topics list
|
|
||||||
func (c *Client) AddRepoTopic(user, repo, topic string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRepoTopic deletes a topic from repo's topics list
|
|
||||||
func (c *Client) DeleteRepoTopic(user, repo, topic string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &topic); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/topics/%s", user, repo, topic), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,36 +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 (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TransferRepoOption options when transfer a repository's ownership
|
|
||||||
type TransferRepoOption struct {
|
|
||||||
// required: true
|
|
||||||
NewOwner string `json:"new_owner"`
|
|
||||||
// ID of the team or teams to add to the repository. Teams can only be added to organization-owned repositories.
|
|
||||||
TeamIDs *[]int64 `json:"team_ids"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransferRepo transfers the ownership of a repository
|
|
||||||
func (c *Client) TransferRepo(owner, reponame string, opt TransferRepoOption) (*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &reponame); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
repo := new(Repository)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer", owner, reponame), jsonHeader, bytes.NewReader(body), repo)
|
|
||||||
return repo, resp, err
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GitEntry represents a git tree
|
|
||||||
type GitEntry struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
SHA string `json:"sha"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GitTreeResponse returns a git tree
|
|
||||||
type GitTreeResponse struct {
|
|
||||||
SHA string `json:"sha"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Entries []GitEntry `json:"tree"`
|
|
||||||
Truncated bool `json:"truncated"`
|
|
||||||
Page int `json:"page"`
|
|
||||||
TotalCount int `json:"total_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTrees downloads a file of repository, ref can be branch/tag/commit.
|
|
||||||
// e.g.: ref -> master, tree -> macaron.go(no leading slash)
|
|
||||||
func (c *Client) GetTrees(user, repo, ref string, recursive bool) (*GitTreeResponse, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &repo, &ref); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
trees := new(GitTreeResponse)
|
|
||||||
var path = fmt.Sprintf("/repos/%s/%s/git/trees/%s", user, repo, ref)
|
|
||||||
if recursive {
|
|
||||||
path += "?recursive=1"
|
|
||||||
}
|
|
||||||
resp, err := c.getParsedResponse("GET", path, nil, nil, trees)
|
|
||||||
return trees, resp, err
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WatchInfo represents an API watch status of one repository
|
|
||||||
type WatchInfo struct {
|
|
||||||
Subscribed bool `json:"subscribed"`
|
|
||||||
Ignored bool `json:"ignored"`
|
|
||||||
Reason interface{} `json:"reason"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
RepositoryURL string `json:"repository_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWatchedRepos list all the watched repos of user
|
|
||||||
func (c *Client) GetWatchedRepos(user string) ([]*Repository, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
repos := make([]*Repository, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/subscriptions", user), nil, nil, &repos)
|
|
||||||
return repos, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMyWatchedRepos list repositories watched by the authenticated user
|
|
||||||
func (c *Client) GetMyWatchedRepos() ([]*Repository, *Response, error) {
|
|
||||||
repos := make([]*Repository, 0, 10)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/subscriptions"), nil, nil, &repos)
|
|
||||||
return repos, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckRepoWatch check if the current user is watching a repo
|
|
||||||
func (c *Client) CheckRepoWatch(owner, repo string) (bool, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, resp, err
|
|
||||||
}
|
|
||||||
switch status {
|
|
||||||
case http.StatusNotFound:
|
|
||||||
return false, resp, nil
|
|
||||||
case http.StatusOK:
|
|
||||||
return true, resp, nil
|
|
||||||
default:
|
|
||||||
return false, resp, fmt.Errorf("unexpected Status: %d", status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchRepo start to watch a repository
|
|
||||||
func (c *Client) WatchRepo(owner, repo string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("PUT", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
if status == http.StatusOK {
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
return resp, fmt.Errorf("unexpected Status: %d", status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnWatchRepo stop to watch a repository
|
|
||||||
func (c *Client) UnWatchRepo(owner, repo string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
status, resp, err := c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/subscription", owner, repo), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
if status == http.StatusNoContent {
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
return resp, fmt.Errorf("unexpected Status: %d", status)
|
|
||||||
}
|
|
|
@ -1,78 +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
|
|
||||||
|
|
||||||
// GlobalUISettings represent the global ui settings of a gitea instance witch is exposed by API
|
|
||||||
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"`
|
|
||||||
StarsDisabled bool `json:"stars_disabled"`
|
|
||||||
TimeTrackingDisabled bool `json:"time_tracking_disabled"`
|
|
||||||
LFSDisabled bool `json:"lfs_disabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalAPISettings contains global api settings exposed by it
|
|
||||||
type GlobalAPISettings struct {
|
|
||||||
MaxResponseItems int `json:"max_response_items"`
|
|
||||||
DefaultPagingNum int `json:"default_paging_num"`
|
|
||||||
DefaultGitTreesPerPage int `json:"default_git_trees_per_page"`
|
|
||||||
DefaultMaxBlobSize int64 `json:"default_max_blob_size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlobalAttachmentSettings contains global Attachment settings exposed by API
|
|
||||||
type GlobalAttachmentSettings struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
AllowedTypes string `json:"allowed_types"`
|
|
||||||
MaxSize int64 `json:"max_size"`
|
|
||||||
MaxFiles int `json:"max_files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGlobalUISettings get global ui settings witch are exposed by API
|
|
||||||
func (c *Client) GetGlobalUISettings() (*GlobalUISettings, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
conf := new(GlobalUISettings)
|
|
||||||
resp, err := c.getParsedResponse("GET", "/settings/ui", jsonHeader, nil, &conf)
|
|
||||||
return conf, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGlobalRepoSettings get global repository settings witch are exposed by API
|
|
||||||
func (c *Client) GetGlobalRepoSettings() (*GlobalRepoSettings, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
conf := new(GlobalRepoSettings)
|
|
||||||
resp, err := c.getParsedResponse("GET", "/settings/repository", jsonHeader, nil, &conf)
|
|
||||||
return conf, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGlobalAPISettings get global api settings witch are exposed by it
|
|
||||||
func (c *Client) GetGlobalAPISettings() (*GlobalAPISettings, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
conf := new(GlobalAPISettings)
|
|
||||||
resp, err := c.getParsedResponse("GET", "/settings/api", jsonHeader, nil, &conf)
|
|
||||||
return conf, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGlobalAttachmentSettings get global repository settings witch are exposed by API
|
|
||||||
func (c *Client) GetGlobalAttachmentSettings() (*GlobalAttachmentSettings, *Response, error) {
|
|
||||||
if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
conf := new(GlobalAttachmentSettings)
|
|
||||||
resp, err := c.getParsedResponse("GET", "/settings/attachment", jsonHeader, nil, &conf)
|
|
||||||
return conf, resp, err
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StatusState holds the state of a Status
|
|
||||||
// It can be "pending", "success", "error", "failure", and "warning"
|
|
||||||
type StatusState string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StatusPending is for when the Status is Pending
|
|
||||||
StatusPending StatusState = "pending"
|
|
||||||
// StatusSuccess is for when the Status is Success
|
|
||||||
StatusSuccess StatusState = "success"
|
|
||||||
// StatusError is for when the Status is Error
|
|
||||||
StatusError StatusState = "error"
|
|
||||||
// StatusFailure is for when the Status is Failure
|
|
||||||
StatusFailure StatusState = "failure"
|
|
||||||
// StatusWarning is for when the Status is Warning
|
|
||||||
StatusWarning StatusState = "warning"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Status holds a single Status of a single Commit
|
|
||||||
type Status struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
State StatusState `json:"status"`
|
|
||||||
TargetURL string `json:"target_url"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Context string `json:"context"`
|
|
||||||
Creator *User `json:"creator"`
|
|
||||||
Created time.Time `json:"created_at"`
|
|
||||||
Updated time.Time `json:"updated_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateStatusOption holds the information needed to create a new Status for a Commit
|
|
||||||
type CreateStatusOption struct {
|
|
||||||
State StatusState `json:"state"`
|
|
||||||
TargetURL string `json:"target_url"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Context string `json:"context"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateStatus creates a new Status for a given Commit
|
|
||||||
func (c *Client) CreateStatus(owner, repo, sha string, opts CreateStatusOption) (*Status, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
status := new(Status)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/statuses/%s", owner, repo, url.QueryEscape(sha)), jsonHeader, bytes.NewReader(body), status)
|
|
||||||
return status, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListStatusesOption options for listing a repository's commit's statuses
|
|
||||||
type ListStatusesOption struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListStatuses returns all statuses for a given Commit by ref
|
|
||||||
func (c *Client) ListStatuses(owner, repo, ref string, opt ListStatusesOption) ([]*Status, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &ref); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
statuses := make([]*Status, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/statuses?%s", owner, repo, ref, opt.getURLQuery().Encode()), jsonHeader, nil, &statuses)
|
|
||||||
return statuses, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CombinedStatus holds the combined state of several statuses for a single commit
|
|
||||||
type CombinedStatus struct {
|
|
||||||
State StatusState `json:"state"`
|
|
||||||
SHA string `json:"sha"`
|
|
||||||
TotalCount int `json:"total_count"`
|
|
||||||
Statuses []*Status `json:"statuses"`
|
|
||||||
Repository *Repository `json:"repository"`
|
|
||||||
CommitURL string `json:"commit_url"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCombinedStatus returns the CombinedStatus for a given Commit
|
|
||||||
func (c *Client) GetCombinedStatus(owner, repo, ref string) (*CombinedStatus, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&owner, &repo, &ref); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
status := new(CombinedStatus)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/status", owner, repo, ref), jsonHeader, nil, status)
|
|
||||||
|
|
||||||
// gitea api return empty body if nothing here jet
|
|
||||||
if resp != nil && resp.StatusCode == 200 && err != nil {
|
|
||||||
return status, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return status, resp, err
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// User represents a user
|
|
||||||
type User struct {
|
|
||||||
// the user's id
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
// the user's username
|
|
||||||
UserName string `json:"login"`
|
|
||||||
// the user's full name
|
|
||||||
FullName string `json:"full_name"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
// URL to the user's avatar
|
|
||||||
AvatarURL string `json:"avatar_url"`
|
|
||||||
// User locale
|
|
||||||
Language string `json:"language"`
|
|
||||||
// Is the user an administrator
|
|
||||||
IsAdmin bool `json:"is_admin"`
|
|
||||||
// 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
|
|
||||||
func (c *Client) GetUserInfo(user string) (*User, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
u := new(User)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s", user), nil, nil, u)
|
|
||||||
return u, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMyUserInfo get user info of current user
|
|
||||||
func (c *Client) GetMyUserInfo() (*User, *Response, error) {
|
|
||||||
u := new(User)
|
|
||||||
resp, err := c.getParsedResponse("GET", "/user", nil, nil, u)
|
|
||||||
return u, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AccessToken represents an API access token.
|
|
||||||
type AccessToken struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Token string `json:"sha1"`
|
|
||||||
TokenLastEight string `json:"token_last_eight"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAccessTokensOptions options for listing a users's access tokens
|
|
||||||
type ListAccessTokensOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAccessTokens lists all the access tokens of user
|
|
||||||
func (c *Client) ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, *Response, error) {
|
|
||||||
c.mutex.RLock()
|
|
||||||
username := c.username
|
|
||||||
c.mutex.RUnlock()
|
|
||||||
if len(username) == 0 {
|
|
||||||
return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed")
|
|
||||||
}
|
|
||||||
opts.setDefaults()
|
|
||||||
tokens := make([]*AccessToken, 0, opts.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/tokens?%s", url.PathEscape(username), opts.getURLQuery().Encode()), jsonHeader, nil, &tokens)
|
|
||||||
return tokens, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAccessTokenOption options when create access token
|
|
||||||
type CreateAccessTokenOption struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAccessToken create one access token with options
|
|
||||||
func (c *Client) CreateAccessToken(opt CreateAccessTokenOption) (*AccessToken, *Response, error) {
|
|
||||||
c.mutex.RLock()
|
|
||||||
username := c.username
|
|
||||||
c.mutex.RUnlock()
|
|
||||||
if len(username) == 0 {
|
|
||||||
return nil, nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed")
|
|
||||||
}
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
t := new(AccessToken)
|
|
||||||
resp, err := c.getParsedResponse("POST", fmt.Sprintf("/users/%s/tokens", url.PathEscape(username)), jsonHeader, bytes.NewReader(body), t)
|
|
||||||
return t, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteAccessToken delete token, identified by ID and if not available by name
|
|
||||||
func (c *Client) DeleteAccessToken(value interface{}) (*Response, error) {
|
|
||||||
c.mutex.RLock()
|
|
||||||
username := c.username
|
|
||||||
c.mutex.RUnlock()
|
|
||||||
if len(username) == 0 {
|
|
||||||
return nil, fmt.Errorf("\"username\" not set: only BasicAuth allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
var token = ""
|
|
||||||
|
|
||||||
switch reflect.ValueOf(value).Kind() {
|
|
||||||
case reflect.Int64:
|
|
||||||
token = fmt.Sprintf("%d", value.(int64))
|
|
||||||
case reflect.String:
|
|
||||||
if err := c.CheckServerVersionConstraint(">= 1.13.0"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
token = value.(string)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("only string and int64 supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/users/%s/tokens/%s", url.PathEscape(username), url.PathEscape(token)), jsonHeader, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Email an email address belonging to a user
|
|
||||||
type Email struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
Verified bool `json:"verified"`
|
|
||||||
Primary bool `json:"primary"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListEmailsOptions options for listing current's user emails
|
|
||||||
type ListEmailsOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListEmails all the email addresses of user
|
|
||||||
func (c *Client) ListEmails(opt ListEmailsOptions) ([]*Email, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
emails := make([]*Email, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/emails?%s", opt.getURLQuery().Encode()), nil, nil, &emails)
|
|
||||||
return emails, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateEmailOption options when creating email addresses
|
|
||||||
type CreateEmailOption struct {
|
|
||||||
// email addresses to add
|
|
||||||
Emails []string `json:"emails"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddEmail add one email to current user with options
|
|
||||||
func (c *Client) AddEmail(opt CreateEmailOption) ([]*Email, *Response, error) {
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
emails := make([]*Email, 0, 3)
|
|
||||||
resp, err := c.getParsedResponse("POST", "/user/emails", jsonHeader, bytes.NewReader(body), &emails)
|
|
||||||
return emails, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteEmailOption options when deleting email addresses
|
|
||||||
type DeleteEmailOption struct {
|
|
||||||
// email addresses to delete
|
|
||||||
Emails []string `json:"emails"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteEmail delete one email of current users'
|
|
||||||
func (c *Client) DeleteEmail(opt DeleteEmailOption) (*Response, error) {
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", "/user/emails", jsonHeader, bytes.NewReader(body))
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// ListFollowersOptions options for listing followers
|
|
||||||
type ListFollowersOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMyFollowers list all the followers of current user
|
|
||||||
func (c *Client) ListMyFollowers(opt ListFollowersOptions) ([]*User, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
users := make([]*User, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/followers?%s", opt.getURLQuery().Encode()), nil, nil, &users)
|
|
||||||
return users, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListFollowers list all the followers of one user
|
|
||||||
func (c *Client) ListFollowers(user string, opt ListFollowersOptions) ([]*User, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
users := make([]*User, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/followers?%s", user, opt.getURLQuery().Encode()), nil, nil, &users)
|
|
||||||
return users, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListFollowingOptions options for listing a user's users being followed
|
|
||||||
type ListFollowingOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMyFollowing list all the users current user followed
|
|
||||||
func (c *Client) ListMyFollowing(opt ListFollowingOptions) ([]*User, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
users := make([]*User, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/following?%s", opt.getURLQuery().Encode()), nil, nil, &users)
|
|
||||||
return users, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListFollowing list all the users the user followed
|
|
||||||
func (c *Client) ListFollowing(user string, opt ListFollowingOptions) ([]*User, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
users := make([]*User, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/following?%s", user, opt.getURLQuery().Encode()), nil, nil, &users)
|
|
||||||
return users, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFollowing if current user followed the target
|
|
||||||
func (c *Client) IsFollowing(target string) (bool, *Response) {
|
|
||||||
if err := escapeValidatePathSegments(&target); err != nil {
|
|
||||||
// ToDo return err
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("GET", fmt.Sprintf("/user/following/%s", target), nil, nil)
|
|
||||||
return err == nil, resp
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsUserFollowing if the user followed the target
|
|
||||||
func (c *Client) IsUserFollowing(user, target string) (bool, *Response) {
|
|
||||||
if err := escapeValidatePathSegments(&user, &target); err != nil {
|
|
||||||
// ToDo return err
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("GET", fmt.Sprintf("/users/%s/following/%s", user, target), nil, nil)
|
|
||||||
return err == nil, resp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Follow set current user follow the target
|
|
||||||
func (c *Client) Follow(target string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&target); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("PUT", fmt.Sprintf("/user/following/%s", target), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unfollow set current user unfollow the target
|
|
||||||
func (c *Client) Unfollow(target string) (*Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&target); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/following/%s", target), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
// Copyright 2017 Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GPGKey a user GPG key to sign commit and tag in repository
|
|
||||||
type GPGKey struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
PrimaryKeyID string `json:"primary_key_id"`
|
|
||||||
KeyID string `json:"key_id"`
|
|
||||||
PublicKey string `json:"public_key"`
|
|
||||||
Emails []*GPGKeyEmail `json:"emails"`
|
|
||||||
SubsKey []*GPGKey `json:"subkeys"`
|
|
||||||
CanSign bool `json:"can_sign"`
|
|
||||||
CanEncryptComms bool `json:"can_encrypt_comms"`
|
|
||||||
CanEncryptStorage bool `json:"can_encrypt_storage"`
|
|
||||||
CanCertify bool `json:"can_certify"`
|
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
|
||||||
Expires time.Time `json:"expires_at,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GPGKeyEmail an email attached to a GPGKey
|
|
||||||
type GPGKeyEmail struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
Verified bool `json:"verified"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListGPGKeysOptions options for listing a user's GPGKeys
|
|
||||||
type ListGPGKeysOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListGPGKeys list all the GPG keys of the user
|
|
||||||
func (c *Client) ListGPGKeys(user string, opt ListGPGKeysOptions) ([]*GPGKey, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
keys := make([]*GPGKey, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/gpg_keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys)
|
|
||||||
return keys, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMyGPGKeys list all the GPG keys of current user
|
|
||||||
func (c *Client) ListMyGPGKeys(opt *ListGPGKeysOptions) ([]*GPGKey, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
keys := make([]*GPGKey, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys)
|
|
||||||
return keys, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGPGKey get current user's GPG key by key id
|
|
||||||
func (c *Client) GetGPGKey(keyID int64) (*GPGKey, *Response, error) {
|
|
||||||
key := new(GPGKey)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil, &key)
|
|
||||||
return key, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateGPGKeyOption options create user GPG key
|
|
||||||
type CreateGPGKeyOption struct {
|
|
||||||
// An armored GPG key to add
|
|
||||||
//
|
|
||||||
ArmoredKey string `json:"armored_public_key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateGPGKey create GPG key with options
|
|
||||||
func (c *Client) CreateGPGKey(opt CreateGPGKeyOption) (*GPGKey, *Response, error) {
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
key := new(GPGKey)
|
|
||||||
resp, err := c.getParsedResponse("POST", "/user/gpg_keys", jsonHeader, bytes.NewReader(body), key)
|
|
||||||
return key, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteGPGKey delete GPG key with key id
|
|
||||||
func (c *Client) DeleteGPGKey(keyID int64) (*Response, error) {
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gitea
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKey publickey is a user key to push code to repository
|
|
||||||
type PublicKey struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
|
||||||
Owner *User `json:"user,omitempty"`
|
|
||||||
ReadOnly bool `json:"read_only,omitempty"`
|
|
||||||
KeyType string `json:"key_type,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPublicKeysOptions options for listing a user's PublicKeys
|
|
||||||
type ListPublicKeysOptions struct {
|
|
||||||
ListOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListPublicKeys list all the public keys of the user
|
|
||||||
func (c *Client) ListPublicKeys(user string, opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) {
|
|
||||||
if err := escapeValidatePathSegments(&user); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
opt.setDefaults()
|
|
||||||
keys := make([]*PublicKey, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/keys?%s", user, opt.getURLQuery().Encode()), nil, nil, &keys)
|
|
||||||
return keys, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListMyPublicKeys list all the public keys of current user
|
|
||||||
func (c *Client) ListMyPublicKeys(opt ListPublicKeysOptions) ([]*PublicKey, *Response, error) {
|
|
||||||
opt.setDefaults()
|
|
||||||
keys := make([]*PublicKey, 0, opt.PageSize)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys?%s", opt.getURLQuery().Encode()), nil, nil, &keys)
|
|
||||||
return keys, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPublicKey get current user's public key by key id
|
|
||||||
func (c *Client) GetPublicKey(keyID int64) (*PublicKey, *Response, error) {
|
|
||||||
key := new(PublicKey)
|
|
||||||
resp, err := c.getParsedResponse("GET", fmt.Sprintf("/user/keys/%d", keyID), nil, nil, &key)
|
|
||||||
return key, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateKeyOption options when creating a key
|
|
||||||
type CreateKeyOption struct {
|
|
||||||
// Title of the key to add
|
|
||||||
Title string `json:"title"`
|
|
||||||
// An armored SSH key to add
|
|
||||||
Key string `json:"key"`
|
|
||||||
// Describe if the key has only read access or read/write
|
|
||||||
ReadOnly bool `json:"read_only"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePublicKey create public key with options
|
|
||||||
func (c *Client) CreatePublicKey(opt CreateKeyOption) (*PublicKey, *Response, error) {
|
|
||||||
body, err := json.Marshal(&opt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
key := new(PublicKey)
|
|
||||||
resp, err := c.getParsedResponse("POST", "/user/keys", jsonHeader, bytes.NewReader(body), key)
|
|
||||||
return key, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeletePublicKey delete public key with key id
|
|
||||||
func (c *Client) DeletePublicKey(keyID int64) (*Response, error) {
|
|
||||||
_, resp, err := c.getResponse("DELETE", fmt.Sprintf("/user/keys/%d", keyID), nil, nil)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
|
@ -1,48 +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 (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
type searchUsersResponse struct {
|
|
||||||
Users []*User `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchUsersOption options for SearchUsers
|
|
||||||
type SearchUsersOption struct {
|
|
||||||
ListOptions
|
|
||||||
KeyWord string
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryEncode turns options into querystring argument
|
|
||||||
func (opt *SearchUsersOption) QueryEncode() string {
|
|
||||||
query := make(url.Values)
|
|
||||||
if opt.Page > 0 {
|
|
||||||
query.Add("page", fmt.Sprintf("%d", opt.Page))
|
|
||||||
}
|
|
||||||
if opt.PageSize > 0 {
|
|
||||||
query.Add("limit", fmt.Sprintf("%d", opt.PageSize))
|
|
||||||
}
|
|
||||||
if len(opt.KeyWord) > 0 {
|
|
||||||
query.Add("q", opt.KeyWord)
|
|
||||||
}
|
|
||||||
return query.Encode()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) searchUsers(rawQuery string) ([]*User, *Response, error) {
|
|
||||||
link, _ := url.Parse("/users/search")
|
|
||||||
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())
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,79 +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 (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ServerVersion returns the version of the server
|
|
||||||
func (c *Client) ServerVersion() (string, *Response, error) {
|
|
||||||
var v = struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
}{}
|
|
||||||
resp, err := c.getParsedResponse("GET", "/version", nil, nil, &v)
|
|
||||||
return v.Version, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckServerVersionConstraint validates that the login's server satisfies a
|
|
||||||
// given version constraint such as ">= 1.11.0+dev"
|
|
||||||
func (c *Client) CheckServerVersionConstraint(constraint string) error {
|
|
||||||
if err := c.loadServerVersion(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
check, err := version.NewConstraint(constraint)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !check.Check(c.serverVersion) {
|
|
||||||
c.mutex.RLock()
|
|
||||||
url := c.url
|
|
||||||
c.mutex.RUnlock()
|
|
||||||
return fmt.Errorf("gitea server at %s does not satisfy version constraint %s", url, constraint)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// predefined versions only have to be parsed by library once
|
|
||||||
var (
|
|
||||||
version1_11_0, _ = version.NewVersion("1.11.0")
|
|
||||||
version1_12_0, _ = version.NewVersion("1.12.0")
|
|
||||||
version1_13_0, _ = version.NewVersion("1.13.0")
|
|
||||||
version1_14_0, _ = version.NewVersion("1.14.0")
|
|
||||||
version1_15_0, _ = version.NewVersion("1.15.0")
|
|
||||||
)
|
|
||||||
|
|
||||||
// checkServerVersionGreaterThanOrEqual is internally used to speed up things and ignore issues with prerelease
|
|
||||||
func (c *Client) checkServerVersionGreaterThanOrEqual(v *version.Version) error {
|
|
||||||
if err := c.loadServerVersion(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.serverVersion.GreaterThanOrEqual(v) {
|
|
||||||
c.mutex.RLock()
|
|
||||||
url := c.url
|
|
||||||
c.mutex.RUnlock()
|
|
||||||
return fmt.Errorf("gitea server at %s is older than %s", url, v.Original())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadServerVersion init the serverVersion variable
|
|
||||||
func (c *Client) loadServerVersion() (err error) {
|
|
||||||
c.getVersionOnce.Do(func() {
|
|
||||||
raw, _, err2 := c.ServerVersion()
|
|
||||||
if err2 != nil {
|
|
||||||
err = err2
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.serverVersion, err = version.NewVersion(raw); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,459 +0,0 @@
|
||||||
package unidiff
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.com/noerw/unidiff-comments/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
stateStartOfDiff = "stateStartOfDiff"
|
|
||||||
stateDiffHeader = "stateDiffHeader"
|
|
||||||
stateHunkHeader = "stateHunkHeader"
|
|
||||||
stateHunkBody = "stateHunkBody"
|
|
||||||
stateComment = "stateComment"
|
|
||||||
stateCommentDelim = "stateCommentDelim"
|
|
||||||
stateCommentHeader = "stateCommentHeader"
|
|
||||||
stateDiffComment = "stateDiffComment"
|
|
||||||
stateDiffCommentDelim = "stateDiffCommentDelim"
|
|
||||||
stateDiffCommentHeader = "stateDiffCommentHeader"
|
|
||||||
|
|
||||||
ignorePrefix = "###"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
reDiffHeader = regexp.MustCompile(
|
|
||||||
`^--- |^\+\+\+ `)
|
|
||||||
|
|
||||||
reGitDiffHeader = regexp.MustCompile(
|
|
||||||
`^diff |^index `)
|
|
||||||
|
|
||||||
reFromFile = regexp.MustCompile(
|
|
||||||
`^--- (\S+)(\s+(.*))`)
|
|
||||||
|
|
||||||
reToFile = regexp.MustCompile(
|
|
||||||
`^\+\+\+ (\S+)(\s+(.*))`)
|
|
||||||
|
|
||||||
reHunk = regexp.MustCompile(
|
|
||||||
`^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@`)
|
|
||||||
|
|
||||||
reSegmentContext = regexp.MustCompile(
|
|
||||||
`^ `)
|
|
||||||
|
|
||||||
reSegmentAdded = regexp.MustCompile(
|
|
||||||
`^\+`)
|
|
||||||
|
|
||||||
reSegmentRemoved = regexp.MustCompile(
|
|
||||||
`^-`)
|
|
||||||
|
|
||||||
reCommentDelim = regexp.MustCompile(
|
|
||||||
`^#\s+---`)
|
|
||||||
|
|
||||||
reCommentHeader = regexp.MustCompile(
|
|
||||||
`^#\s+\[(\d+)@(\d+)\]\s+\|([^|]+)\|(.*)`)
|
|
||||||
|
|
||||||
reCommentText = regexp.MustCompile(
|
|
||||||
`^#(\s*)(.*)\s*`)
|
|
||||||
|
|
||||||
reIndent = regexp.MustCompile(
|
|
||||||
`^#(\s+)`)
|
|
||||||
|
|
||||||
reEmptyLine = regexp.MustCompile(
|
|
||||||
`^\n$`)
|
|
||||||
|
|
||||||
reIgnoredLine = regexp.MustCompile(
|
|
||||||
`^` + ignorePrefix)
|
|
||||||
)
|
|
||||||
|
|
||||||
type parser struct {
|
|
||||||
state string
|
|
||||||
changeset types.Changeset
|
|
||||||
diff *types.Diff
|
|
||||||
hunk *types.Hunk
|
|
||||||
segment *types.Segment
|
|
||||||
comment *types.Comment
|
|
||||||
line *types.Line
|
|
||||||
lineNumber int
|
|
||||||
|
|
||||||
segmentType string
|
|
||||||
commentsList []*types.Comment
|
|
||||||
}
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
LineNumber int
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err Error) Error() string {
|
|
||||||
return fmt.Sprintf("line %d: %s", err.LineNumber, err.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadChangeset(r io.Reader) (types.Changeset, error) {
|
|
||||||
buffer := bufio.NewReader(r)
|
|
||||||
|
|
||||||
current := parser{}
|
|
||||||
current.state = stateStartOfDiff
|
|
||||||
|
|
||||||
for {
|
|
||||||
current.lineNumber++
|
|
||||||
|
|
||||||
line, err := buffer.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if reIgnoredLine.MatchString(line) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = current.switchState(line)
|
|
||||||
if err != nil {
|
|
||||||
return current.changeset, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = current.createNodes(line)
|
|
||||||
if err != nil {
|
|
||||||
return current.changeset, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = current.locateNodes(line)
|
|
||||||
if err != nil {
|
|
||||||
return current.changeset, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = current.parseLine(line)
|
|
||||||
if err != nil {
|
|
||||||
return current.changeset, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, comment := range current.commentsList {
|
|
||||||
comment.Text = strings.TrimSpace(comment.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
return current.changeset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) switchState(line string) error {
|
|
||||||
inComment := false
|
|
||||||
|
|
||||||
switch current.state {
|
|
||||||
case stateStartOfDiff:
|
|
||||||
switch {
|
|
||||||
case reDiffHeader.MatchString(line), reGitDiffHeader.MatchString(line):
|
|
||||||
current.state = stateDiffHeader
|
|
||||||
case reCommentText.MatchString(line):
|
|
||||||
inComment = true
|
|
||||||
case reEmptyLine.MatchString(line):
|
|
||||||
// body intentionally left empty
|
|
||||||
default:
|
|
||||||
return Error{
|
|
||||||
current.lineNumber,
|
|
||||||
"expected diff header, but none found",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case stateDiffHeader:
|
|
||||||
switch {
|
|
||||||
case reHunk.MatchString(line):
|
|
||||||
current.state = stateHunkHeader
|
|
||||||
}
|
|
||||||
case stateDiffComment, stateDiffCommentDelim, stateDiffCommentHeader:
|
|
||||||
switch {
|
|
||||||
case reDiffHeader.MatchString(line), reGitDiffHeader.MatchString(line):
|
|
||||||
current.state = stateDiffHeader
|
|
||||||
case reCommentText.MatchString(line):
|
|
||||||
inComment = true
|
|
||||||
case reEmptyLine.MatchString(line):
|
|
||||||
current.state = stateStartOfDiff
|
|
||||||
}
|
|
||||||
case stateHunkHeader:
|
|
||||||
current.state = stateHunkBody
|
|
||||||
fallthrough
|
|
||||||
case stateHunkBody, stateComment, stateCommentDelim, stateCommentHeader:
|
|
||||||
switch {
|
|
||||||
case reSegmentContext.MatchString(line):
|
|
||||||
current.state = stateHunkBody
|
|
||||||
current.segmentType = types.SegmentTypeContext
|
|
||||||
case reSegmentRemoved.MatchString(line):
|
|
||||||
current.state = stateHunkBody
|
|
||||||
current.segmentType = types.SegmentTypeRemoved
|
|
||||||
case reSegmentAdded.MatchString(line):
|
|
||||||
current.state = stateHunkBody
|
|
||||||
current.segmentType = types.SegmentTypeAdded
|
|
||||||
case reHunk.MatchString(line):
|
|
||||||
current.state = stateHunkHeader
|
|
||||||
case reCommentText.MatchString(line):
|
|
||||||
inComment = true
|
|
||||||
case reGitDiffHeader.MatchString(line):
|
|
||||||
current.state = stateDiffHeader
|
|
||||||
current.diff = nil
|
|
||||||
current.hunk = nil
|
|
||||||
current.segment = nil
|
|
||||||
current.line = nil
|
|
||||||
case reEmptyLine.MatchString(line):
|
|
||||||
current.state = stateStartOfDiff
|
|
||||||
current.diff = nil
|
|
||||||
current.hunk = nil
|
|
||||||
current.segment = nil
|
|
||||||
current.line = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !inComment {
|
|
||||||
current.comment = nil
|
|
||||||
} else {
|
|
||||||
switch current.state {
|
|
||||||
case stateStartOfDiff:
|
|
||||||
fallthrough
|
|
||||||
case stateDiffComment, stateDiffCommentDelim, stateDiffCommentHeader:
|
|
||||||
switch {
|
|
||||||
case reCommentDelim.MatchString(line):
|
|
||||||
current.state = stateDiffCommentDelim
|
|
||||||
case reCommentHeader.MatchString(line):
|
|
||||||
current.state = stateDiffCommentHeader
|
|
||||||
case reCommentText.MatchString(line):
|
|
||||||
current.state = stateDiffComment
|
|
||||||
}
|
|
||||||
case stateHunkBody:
|
|
||||||
fallthrough
|
|
||||||
case stateComment, stateCommentDelim, stateCommentHeader:
|
|
||||||
switch {
|
|
||||||
case reCommentDelim.MatchString(line):
|
|
||||||
current.state = stateCommentDelim
|
|
||||||
case reCommentHeader.MatchString(line):
|
|
||||||
current.state = stateCommentHeader
|
|
||||||
case reCommentText.MatchString(line):
|
|
||||||
current.state = stateComment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uncomment for debug state switching
|
|
||||||
// fmt.Printf("%20s : %#v\n", current.state, line)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) createNodes(line string) error {
|
|
||||||
switch current.state {
|
|
||||||
case stateDiffComment:
|
|
||||||
if current.comment != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case stateDiffCommentDelim, stateDiffCommentHeader:
|
|
||||||
current.comment = &types.Comment{}
|
|
||||||
fallthrough
|
|
||||||
case stateDiffHeader:
|
|
||||||
if current.diff == nil {
|
|
||||||
current.diff = &types.Diff{}
|
|
||||||
current.changeset.Diffs = append(current.changeset.Diffs,
|
|
||||||
current.diff)
|
|
||||||
}
|
|
||||||
case stateHunkHeader:
|
|
||||||
current.hunk = &types.Hunk{}
|
|
||||||
current.segment = &types.Segment{}
|
|
||||||
case stateCommentDelim, stateCommentHeader:
|
|
||||||
current.comment = &types.Comment{}
|
|
||||||
case stateComment:
|
|
||||||
if current.comment == nil {
|
|
||||||
current.comment = &types.Comment{}
|
|
||||||
}
|
|
||||||
case stateHunkBody:
|
|
||||||
if current.segment.Type != current.segmentType {
|
|
||||||
current.segment = &types.Segment{Type: current.segmentType}
|
|
||||||
current.hunk.Segments = append(current.hunk.Segments,
|
|
||||||
current.segment)
|
|
||||||
}
|
|
||||||
|
|
||||||
current.line = &types.Line{}
|
|
||||||
current.segment.Lines = append(current.segment.Lines, current.line)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) locateNodes(line string) error {
|
|
||||||
switch current.state {
|
|
||||||
case stateComment, stateDiffComment:
|
|
||||||
current.locateComment(line)
|
|
||||||
case stateHunkBody:
|
|
||||||
current.locateLine(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) locateComment(line string) error {
|
|
||||||
if current.comment.Parented || strings.TrimSpace(line) == "#" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
current.commentsList = append(current.commentsList, current.comment)
|
|
||||||
current.comment.Parented = true
|
|
||||||
|
|
||||||
if current.hunk != nil {
|
|
||||||
current.comment.Anchor.LineType = current.segment.Type
|
|
||||||
current.comment.Anchor.Line = current.segment.GetLineNum(current.line)
|
|
||||||
current.comment.Anchor.Path = current.diff.Destination.ToString
|
|
||||||
current.comment.Anchor.SrcPath = current.diff.Source.ToString
|
|
||||||
}
|
|
||||||
|
|
||||||
current.comment.Indent = getIndentSize(line)
|
|
||||||
|
|
||||||
parent := current.findParentComment(current.comment)
|
|
||||||
if parent != nil {
|
|
||||||
parent.Comments = append(parent.Comments, current.comment)
|
|
||||||
} else {
|
|
||||||
if current.line != nil {
|
|
||||||
current.diff.LineComments = append(current.diff.LineComments,
|
|
||||||
current.comment)
|
|
||||||
current.line.Comments = append(current.line.Comments,
|
|
||||||
current.comment)
|
|
||||||
} else {
|
|
||||||
current.diff.FileComments = append(current.diff.FileComments,
|
|
||||||
current.comment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) locateLine(line string) error {
|
|
||||||
sourceOffset := current.hunk.SourceLine - 1
|
|
||||||
destinationOffset := current.hunk.DestinationLine - 1
|
|
||||||
if len(current.hunk.Segments) > 1 {
|
|
||||||
prevSegment := current.hunk.Segments[len(current.hunk.Segments)-2]
|
|
||||||
lastLine := prevSegment.Lines[len(prevSegment.Lines)-1]
|
|
||||||
sourceOffset = lastLine.Source
|
|
||||||
destinationOffset = lastLine.Destination
|
|
||||||
}
|
|
||||||
hunkLength := int64(len(current.segment.Lines))
|
|
||||||
switch current.segment.Type {
|
|
||||||
case types.SegmentTypeContext:
|
|
||||||
current.line.Source = sourceOffset + hunkLength
|
|
||||||
current.line.Destination = destinationOffset + hunkLength
|
|
||||||
case types.SegmentTypeAdded:
|
|
||||||
current.line.Source = sourceOffset
|
|
||||||
current.line.Destination = destinationOffset + hunkLength
|
|
||||||
case types.SegmentTypeRemoved:
|
|
||||||
current.line.Source = sourceOffset + hunkLength
|
|
||||||
current.line.Destination = destinationOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) parseLine(line string) error {
|
|
||||||
switch current.state {
|
|
||||||
case stateDiffHeader:
|
|
||||||
current.parseDiffHeader(line)
|
|
||||||
case stateHunkHeader:
|
|
||||||
current.parseHunkHeader(line)
|
|
||||||
case stateHunkBody:
|
|
||||||
current.parseHunkBody(line)
|
|
||||||
case stateComment, stateDiffComment:
|
|
||||||
current.parseComment(line)
|
|
||||||
case stateCommentHeader, stateDiffCommentHeader:
|
|
||||||
current.parseCommentHeader(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) parseDiffHeader(line string) error {
|
|
||||||
switch {
|
|
||||||
case reFromFile.MatchString(line):
|
|
||||||
matches := reFromFile.FindStringSubmatch(line)
|
|
||||||
current.changeset.Path = matches[1]
|
|
||||||
current.diff.Source.ToString = matches[1]
|
|
||||||
current.changeset.FromHash = matches[3]
|
|
||||||
current.diff.Attributes.FromHash = []string{matches[3]}
|
|
||||||
case reToFile.MatchString(line):
|
|
||||||
matches := reToFile.FindStringSubmatch(line)
|
|
||||||
current.diff.Destination.ToString = matches[1]
|
|
||||||
current.changeset.ToHash = matches[3]
|
|
||||||
current.diff.Attributes.ToHash = []string{matches[3]}
|
|
||||||
default:
|
|
||||||
return Error{
|
|
||||||
current.lineNumber,
|
|
||||||
"expected diff header, but not found",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) parseHunkHeader(line string) error {
|
|
||||||
matches := reHunk.FindStringSubmatch(line)
|
|
||||||
current.hunk.SourceLine, _ = strconv.ParseInt(matches[1], 10, 64)
|
|
||||||
current.hunk.SourceSpan, _ = strconv.ParseInt(matches[3], 10, 64)
|
|
||||||
current.hunk.DestinationLine, _ = strconv.ParseInt(matches[4], 10, 64)
|
|
||||||
current.hunk.DestinationSpan, _ = strconv.ParseInt(matches[6], 10, 64)
|
|
||||||
current.diff.Hunks = append(current.diff.Hunks, current.hunk)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) parseHunkBody(line string) error {
|
|
||||||
current.line.Line = line[1 : len(line)-1]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) parseCommentHeader(line string) error {
|
|
||||||
matches := reCommentHeader.FindStringSubmatch(line)
|
|
||||||
current.comment.Author.DisplayName = strings.TrimSpace(matches[3])
|
|
||||||
current.comment.Id, _ = strconv.ParseInt(matches[1], 10, 64)
|
|
||||||
updatedDate, _ := time.ParseInLocation(time.ANSIC,
|
|
||||||
strings.TrimSpace(matches[4]),
|
|
||||||
time.Local)
|
|
||||||
current.comment.UpdatedDate = types.UnixTimestamp(updatedDate.Unix() * 1000)
|
|
||||||
|
|
||||||
version, _ := strconv.ParseInt(matches[2], 10, 64)
|
|
||||||
current.comment.Version = int(version)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) parseComment(line string) error {
|
|
||||||
matches := reCommentText.FindStringSubmatch(line)
|
|
||||||
if len(matches[1]) < current.comment.Indent {
|
|
||||||
return Error{
|
|
||||||
LineNumber: current.lineNumber,
|
|
||||||
Message: fmt.Sprintf(
|
|
||||||
"unexpected indent, should be at least: %d",
|
|
||||||
current.comment.Indent,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
indentedLine := matches[1][current.comment.Indent:] + matches[2]
|
|
||||||
current.comment.Text += "\n" + indentedLine
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (current *parser) findParentComment(comment *types.Comment) *types.Comment {
|
|
||||||
for i := len(current.commentsList) - 1; i >= 0; i-- {
|
|
||||||
c := current.commentsList[i]
|
|
||||||
if comment.Indent > c.Indent {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIndentSize(line string) int {
|
|
||||||
matches := reIndent.FindStringSubmatch(line)
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(matches[1])
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
module gitea.com/noerw/unidiff-comments
|
|
||||||
|
|
||||||
go 1.15
|
|
||||||
|
|
||||||
require github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597
|
|
|
@ -1,2 +0,0 @@
|
||||||
github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597 h1:nZY1S2jo+VtDrUfjO9XYI137O41hhRkxZNV5Fb5ixCA=
|
|
||||||
github.com/seletskiy/tplutil v0.0.0-20200921103632-f880f6245597/go.mod h1:F8CBHSOjnzjx9EeXyWJTAzJyVxN+Y8JH2WjLMn4utiw=
|
|
|
@ -1,47 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
type Changeset struct {
|
|
||||||
FromHash string
|
|
||||||
ToHash string
|
|
||||||
Path string
|
|
||||||
Whitespace string
|
|
||||||
Diffs []*Diff
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Changeset) ForEachComment(callback func(*Diff, *Comment, *Comment)) {
|
|
||||||
for _, diff := range r.Diffs {
|
|
||||||
stack := make([]*Comment, 0)
|
|
||||||
parents := make(map[*Comment]*Comment)
|
|
||||||
stack = append(stack, diff.FileComments...)
|
|
||||||
stack = append(stack, diff.LineComments...)
|
|
||||||
pos := 0
|
|
||||||
|
|
||||||
for pos < len(stack) {
|
|
||||||
comment := stack[pos]
|
|
||||||
|
|
||||||
if comment.Comments != nil {
|
|
||||||
stack = append(stack, comment.Comments...)
|
|
||||||
for _, c := range comment.Comments {
|
|
||||||
parents[c] = comment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(diff, comment, parents[comment])
|
|
||||||
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Changeset) ForEachLine(
|
|
||||||
callback func(*Diff, *Hunk, *Segment, *Line) error,
|
|
||||||
) error {
|
|
||||||
for _, diff := range r.Diffs {
|
|
||||||
err := diff.ForEachLine(callback)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UnixTimestamp int
|
|
||||||
|
|
||||||
func (u UnixTimestamp) String() string {
|
|
||||||
return time.Unix(int64(u/1000), 0).Format(time.ANSIC)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Comment struct {
|
|
||||||
Id int64
|
|
||||||
Version int
|
|
||||||
Text string
|
|
||||||
CreatedDate UnixTimestamp
|
|
||||||
UpdatedDate UnixTimestamp
|
|
||||||
Comments CommentsTree
|
|
||||||
Author struct {
|
|
||||||
Name string
|
|
||||||
EmailAddress string
|
|
||||||
Id int
|
|
||||||
DisplayName string
|
|
||||||
Active bool
|
|
||||||
Slug string
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
Anchor CommentAnchor
|
|
||||||
|
|
||||||
PermittedOperations struct {
|
|
||||||
Editable bool
|
|
||||||
Deletable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
Indent int
|
|
||||||
Parented bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommentAnchor struct {
|
|
||||||
FromHash string
|
|
||||||
ToHash string
|
|
||||||
Line int64 `json:"line"`
|
|
||||||
LineType string `json:"lineType"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
SrcPath string `json:"srcPath"`
|
|
||||||
FileType string `json:"fileType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommentsTree []*Comment
|
|
||||||
|
|
||||||
//const replyIndent = " "
|
|
||||||
|
|
||||||
var begOfLineRe = regexp.MustCompile("(?m)^")
|
|
||||||
|
|
||||||
//func (c Comment) String() string {
|
|
||||||
// comments, _ := commentTpl.Execute(c)
|
|
||||||
|
|
||||||
// for _, reply := range c.Comments {
|
|
||||||
// comments += reply.AsReply()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return comments
|
|
||||||
//}
|
|
||||||
|
|
||||||
//func (c Comment) AsReply() string {
|
|
||||||
// return begOfLineRe.ReplaceAllString(
|
|
||||||
// commentSpacing+c.String(),
|
|
||||||
// replyIndent,
|
|
||||||
// )
|
|
||||||
//}
|
|
||||||
|
|
||||||
var reWhiteSpace = regexp.MustCompile(`\s+`)
|
|
||||||
|
|
||||||
func (c Comment) Short(length int) string {
|
|
||||||
sticked := []rune(reWhiteSpace.ReplaceAllString(c.Text, " "))
|
|
||||||
|
|
||||||
if len(sticked) > length {
|
|
||||||
return string(sticked[:length]) + "..."
|
|
||||||
} else {
|
|
||||||
return string(sticked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ignorePrefix = "###"
|
|
||||||
|
|
||||||
var reBeginningOfLine = regexp.MustCompile(`(?m)^`)
|
|
||||||
var reIgnorePrefixSpace = regexp.MustCompile("(?m)^" + ignorePrefix + " $")
|
|
||||||
|
|
||||||
func Note(String string) string {
|
|
||||||
return reIgnorePrefixSpace.ReplaceAllString(
|
|
||||||
reBeginningOfLine.ReplaceAllString(String, ignorePrefix+" "),
|
|
||||||
ignorePrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
//const commentSpacing = "\n\n"
|
|
||||||
//const commentPrefix = "# "
|
|
||||||
|
|
||||||
//func (comments CommentsTree) String() string {
|
|
||||||
// res := ""
|
|
||||||
|
|
||||||
// if len(comments) > 0 {
|
|
||||||
// res = "---" + commentSpacing
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for i, comment := range comments {
|
|
||||||
// res += comment.String()
|
|
||||||
// if i < len(comments)-1 {
|
|
||||||
// res += commentSpacing
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(comments) > 0 {
|
|
||||||
// return danglingSpacesRe.ReplaceAllString(
|
|
||||||
// begOfLineRe.ReplaceAllString(res, "# "), "")
|
|
||||||
// } else {
|
|
||||||
// return ""
|
|
||||||
// }
|
|
||||||
//}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
type Diff struct {
|
|
||||||
Truncated bool
|
|
||||||
Source struct {
|
|
||||||
Parent string
|
|
||||||
Name string
|
|
||||||
ToString string
|
|
||||||
}
|
|
||||||
Destination struct {
|
|
||||||
Parent string
|
|
||||||
Name string
|
|
||||||
ToString string
|
|
||||||
}
|
|
||||||
Hunks []*Hunk
|
|
||||||
|
|
||||||
FileComments CommentsTree
|
|
||||||
LineComments CommentsTree
|
|
||||||
|
|
||||||
Note string
|
|
||||||
|
|
||||||
// Lists made only for Stash API compatibility.
|
|
||||||
// TODO: move it to `ash`.
|
|
||||||
Attributes struct {
|
|
||||||
FromHash []string
|
|
||||||
ToHash []string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Diff) GetHashFrom() string {
|
|
||||||
if len(d.Attributes.FromHash) > 0 {
|
|
||||||
return d.Attributes.FromHash[0]
|
|
||||||
} else {
|
|
||||||
return "???"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Diff) GetHashTo() string {
|
|
||||||
if len(d.Attributes.ToHash) > 0 {
|
|
||||||
return d.Attributes.ToHash[0]
|
|
||||||
} else {
|
|
||||||
return "???"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Diff) ForEachLine(
|
|
||||||
callback func(*Diff, *Hunk, *Segment, *Line) error,
|
|
||||||
) error {
|
|
||||||
for _, hunk := range d.Hunks {
|
|
||||||
for _, segment := range hunk.Segments {
|
|
||||||
for _, line := range segment.Lines {
|
|
||||||
err := callback(&d, hunk, segment, line)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
type Hunk struct {
|
|
||||||
SourceLine int64
|
|
||||||
SourceSpan int64
|
|
||||||
DestinationLine int64
|
|
||||||
DestinationSpan int64
|
|
||||||
Truncated bool
|
|
||||||
Segments []*Segment
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
import "regexp"
|
|
||||||
|
|
||||||
type Line struct {
|
|
||||||
Destination int64
|
|
||||||
Source int64
|
|
||||||
Line string
|
|
||||||
Truncated bool
|
|
||||||
ConflictMarker string
|
|
||||||
CommentIds []int64
|
|
||||||
Comments CommentsTree
|
|
||||||
}
|
|
||||||
|
|
||||||
var danglingSpacesRe = regexp.MustCompile("(?m) +$")
|
|
||||||
|
|
||||||
//var lineTpl = tplutil.SparseTemplate("line", `
|
|
||||||
//{{.Line}}
|
|
||||||
|
|
||||||
//{{if .Comments}}
|
|
||||||
// {{"\n"}}
|
|
||||||
// {{.Comments}}
|
|
||||||
//{{end}}
|
|
||||||
//`)
|
|
||||||
|
|
||||||
//func (l Line) String() string {
|
|
||||||
// result, _ := lineTpl.Execute(l)
|
|
||||||
// return result
|
|
||||||
//}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
const (
|
|
||||||
SegmentTypeContext = "CONTEXT"
|
|
||||||
SegmentTypeRemoved = "REMOVED"
|
|
||||||
SegmentTypeAdded = "ADDED"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Segment struct {
|
|
||||||
Type string
|
|
||||||
Truncated bool
|
|
||||||
Lines []*Line
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Segment) TextPrefix() string {
|
|
||||||
switch s.Type {
|
|
||||||
case SegmentTypeAdded:
|
|
||||||
return "+"
|
|
||||||
case SegmentTypeRemoved:
|
|
||||||
return "-"
|
|
||||||
case SegmentTypeContext:
|
|
||||||
return " "
|
|
||||||
default:
|
|
||||||
return "?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Segment) GetLineNum(l *Line) int64 {
|
|
||||||
switch s.Type {
|
|
||||||
case SegmentTypeContext:
|
|
||||||
fallthrough
|
|
||||||
case SegmentTypeRemoved:
|
|
||||||
return l.Source
|
|
||||||
case SegmentTypeAdded:
|
|
||||||
return l.Destination
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.12
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- linux-ppc64le
|
|
||||||
- osx
|
|
||||||
- windows
|
|
||||||
|
|
||||||
go_import_path: github.com/AlecAivazis/survey/v2
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- go get github.com/alecaivazis/run
|
|
||||||
|
|
||||||
install:
|
|
||||||
- run install-deps
|
|
||||||
|
|
||||||
script:
|
|
||||||
- run tests
|
|
||||||
# - run autoplay-tests
|
|
|
@ -1,77 +0,0 @@
|
||||||
# Contributing to Survey
|
|
||||||
|
|
||||||
🎉🎉 First off, thanks for the interest in contributing to `survey`! 🎉🎉
|
|
||||||
|
|
||||||
The following is a set of guidelines to follow when contributing to this package. These are not hard rules, please use common sense and feel free to propose changes to this document in a pull request.
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
1. [Code of Conduct](#code-of-conduct)
|
|
||||||
1. [Getting Help](#getting-help)
|
|
||||||
1. [Filing a Bug Report](#how-to-file-a-bug-report)
|
|
||||||
1. [Suggesting an API change](#suggesting-an-api-change)
|
|
||||||
1. [Submitting a Contribution](#submitting-a-contribution)
|
|
||||||
1. [Writing and Running Tests](#writing-and-running-tests)
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
This project and its contibutors are expected to uphold the [Go Community Code of Conduct](https://golang.org/conduct). By participating, you are expected to follow these guidelines.
|
|
||||||
|
|
||||||
## Getting help
|
|
||||||
|
|
||||||
Feel free to [open up an issue](https://github.com/AlecAivazis/survey/v2/issues/new) on GitHub when asking a question so others will be able to find it. Please remember to tag the issue with the `Question` label so the maintainers can get to your question as soon as possible. If the question is urgent, feel free to reach out to `@AlecAivazis` directly in the gophers slack channel.
|
|
||||||
|
|
||||||
## How to file a bug report
|
|
||||||
|
|
||||||
Bugs are tracked using the Github Issue tracker. When filing a bug, please remember to label the issue as a `Bug` and answer/provide the following:
|
|
||||||
|
|
||||||
1. What operating system and terminal are you using?
|
|
||||||
1. An example that showcases the bug.
|
|
||||||
1. What did you expect to see?
|
|
||||||
1. What did you see instead?
|
|
||||||
|
|
||||||
## Suggesting an API change
|
|
||||||
|
|
||||||
If you have an idea, I'm more than happy to discuss it. Please open an issue and we can work through it. In order to maintain some sense of stability, additions to the top-level API are taken just as seriously as changes that break it. Adding stuff is much easier than removing it.
|
|
||||||
|
|
||||||
## Submitting a contribution
|
|
||||||
|
|
||||||
In order to maintain stability, most features get fully integrated in more than one PR. This allows for more opportunity to think through each API change without amassing large amounts of tech debt and API changes at once. If your feature can be broken into separate chunks, it will be able to be reviewed much quicker. For example, if the PR that implemented the `Validate` field was submitted in a PR separately from one that included `survey.Required`, it would be able to get merge without having to decide how many different `Validators` we want to provide as part of `survey`'s API.
|
|
||||||
|
|
||||||
When submitting a contribution,
|
|
||||||
|
|
||||||
- Provide a description of the feature or change
|
|
||||||
- Reference the ticket addressed by the PR if there is one
|
|
||||||
- Following community standards, add comments for all exported members so that all necessary information is available on godocs
|
|
||||||
- Remember to update the project README.md with changes to the high-level API
|
|
||||||
- Include both positive and negative unit tests (when applicable)
|
|
||||||
- Contributions with visual ramifications or interaction changes should be accompanied with the appropriate `go-expect` tests. For more information on writing these tests, see [Writing and Running Tests](#writing-and-running-tests)
|
|
||||||
|
|
||||||
## Writing and running tests
|
|
||||||
|
|
||||||
When submitting features, please add as many units tests as necessary to test both positive and negative cases.
|
|
||||||
|
|
||||||
Integration tests for survey uses [go-expect](https://github.com/Netflix/go-expect) to expect a match on stdout and respond on stdin. Since `os.Stdout` in a `go test` process is not a TTY, you need a way to interpret terminal / ANSI escape sequences for things like `CursorLocation`. The stdin/stdout handled by `go-expect` is also multiplexed to a [virtual terminal](https://github.com/hinshun/vt10x).
|
|
||||||
|
|
||||||
For example, you can extend the tests for Input by specifying the following test case:
|
|
||||||
|
|
||||||
```go
|
|
||||||
{
|
|
||||||
"Test Input prompt interaction", // Name of the test.
|
|
||||||
&Input{ // An implementation of the survey.Prompt interface.
|
|
||||||
Message: "What is your name?",
|
|
||||||
},
|
|
||||||
func(c *expect.Console) { // An expect procedure. You can expect strings / regexps and
|
|
||||||
c.ExpectString("What is your name?") // write back strings / bytes to its psuedoterminal for survey.
|
|
||||||
c.SendLine("Johnny Appleseed")
|
|
||||||
c.ExpectEOF() // Nothing is read from the tty without an expect, and once an
|
|
||||||
// expectation is met, no further bytes are read. End your
|
|
||||||
// procedure with `c.ExpectEOF()` to read until survey finishes.
|
|
||||||
},
|
|
||||||
"Johnny Appleseed", // The expected result.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to write your own `go-expect` test from scratch, you'll need to instantiate a virtual terminal,
|
|
||||||
multiplex it into an `*expect.Console`, and hook up its tty with survey's optional stdio. Please see `go-expect`
|
|
||||||
[documentation](https://godoc.org/github.com/Netflix/go-expect) for more detail.
|
|
|
@ -1,78 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/Netflix/go-expect"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "c93bf25de8e869da25cf26bcd2932b36141f61ae"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/davecgh/go-spew"
|
|
||||||
packages = ["spew"]
|
|
||||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/hinshun/vt10x"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "1954e646417484a2a687ea344edade2c2b6523c8"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/kballard/go-shellquote"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "95032a82bc518f77982ea72343cc1ade730072f0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/kr/pty"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "282ce0e5322c82529687d609ee670fac7c7d917c"
|
|
||||||
version = "v1.1.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/mattn/go-colorable"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
|
||||||
version = "v0.0.9"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/mattn/go-isatty"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
|
||||||
version = "v0.0.3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/mgutz/ansi"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "9520e82c474b0a04dd04f8a40959027271bab992"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pmezard/go-difflib"
|
|
||||||
packages = ["difflib"]
|
|
||||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
packages = [
|
|
||||||
"assert",
|
|
||||||
"require"
|
|
||||||
]
|
|
||||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
|
||||||
version = "v1.2.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = ["unix"]
|
|
||||||
revision = "9527bec2660bd847c050fda93a0f0c6dee0800bb"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "371508ebad4798adc38a118f858b5c17a65b58594203548f9feb74cb781dd907"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
|
@ -1,54 +0,0 @@
|
||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/Netflix/go-expect"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/hinshun/vt10x"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/mattn/go-isatty"
|
|
||||||
version = "0.0.3"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/mgutz/ansi"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
version = "1.2.1"
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/kballard/go-shellquote"
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2018 Alec Aivazis
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,480 +0,0 @@
|
||||||
# Survey
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/AlecAivazis/survey.svg?branch=feature%2Fpretty)](https://travis-ci.org/AlecAivazis/survey)
|
|
||||||
[![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg)](https://pkg.go.dev/github.com/AlecAivazis/survey/v2)
|
|
||||||
|
|
||||||
A library for building interactive and accessible prompts on terminals supporting ANSI escape sequences.
|
|
||||||
|
|
||||||
<img width="550" src="https://thumbs.gfycat.com/VillainousGraciousKouprey-size_restricted.gif"/>
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// the questions to ask
|
|
||||||
var qs = []*survey.Question{
|
|
||||||
{
|
|
||||||
Name: "name",
|
|
||||||
Prompt: &survey.Input{Message: "What is your name?"},
|
|
||||||
Validate: survey.Required,
|
|
||||||
Transform: survey.Title,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "color",
|
|
||||||
Prompt: &survey.Select{
|
|
||||||
Message: "Choose a color:",
|
|
||||||
Options: []string{"red", "blue", "green"},
|
|
||||||
Default: "red",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "age",
|
|
||||||
Prompt: &survey.Input{Message: "How old are you?"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// the answers will be written to this struct
|
|
||||||
answers := struct {
|
|
||||||
Name string // survey will match the question and field names
|
|
||||||
FavoriteColor string `survey:"color"` // or you can tag fields to match a specific name
|
|
||||||
Age int // if the types don't match, survey will convert it
|
|
||||||
}{}
|
|
||||||
|
|
||||||
// perform the questions
|
|
||||||
err := survey.Ask(qs, &answers)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%s chose %s.", answers.Name, answers.FavoriteColor)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
1. [Examples](#examples)
|
|
||||||
1. [Running the Prompts](#running-the-prompts)
|
|
||||||
1. [Prompts](#prompts)
|
|
||||||
1. [Input](#input)
|
|
||||||
1. [Suggestion Options](#suggestion-options)
|
|
||||||
1. [Multiline](#multiline)
|
|
||||||
1. [Password](#password)
|
|
||||||
1. [Confirm](#confirm)
|
|
||||||
1. [Select](#select)
|
|
||||||
1. [MultiSelect](#multiselect)
|
|
||||||
1. [Editor](#editor)
|
|
||||||
1. [Filtering Options](#filtering-options)
|
|
||||||
1. [Validation](#validation)
|
|
||||||
1. [Built-in Validators](#built-in-validators)
|
|
||||||
1. [Help Text](#help-text)
|
|
||||||
1. [Changing the input rune](#changing-the-input-rune)
|
|
||||||
1. [Changing the Icons ](#changing-the-icons)
|
|
||||||
1. [Custom Types](#custom-types)
|
|
||||||
1. [Testing](#testing)
|
|
||||||
1. [FAQ](#faq)
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Examples can be found in the `examples/` directory. Run them
|
|
||||||
to see basic behavior:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get github.com/AlecAivazis/survey/v2
|
|
||||||
|
|
||||||
cd $GOPATH/src/github.com/AlecAivazis/survey
|
|
||||||
|
|
||||||
go run examples/simple.go
|
|
||||||
go run examples/validation.go
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running the Prompts
|
|
||||||
|
|
||||||
There are two primary ways to execute prompts and start collecting information from your users: `Ask` and
|
|
||||||
`AskOne`. The primary difference is whether you are interested in collecting a single piece of information
|
|
||||||
or if you have a list of questions to ask whose answers should be collected in a single struct.
|
|
||||||
For most basic usecases, `Ask` should be enough. However, for surveys with complicated branching logic,
|
|
||||||
we recommend that you break out your questions into multiple calls to both of these functions to fit your needs.
|
|
||||||
|
|
||||||
### Configuring the Prompts
|
|
||||||
|
|
||||||
Most prompts take fine-grained configuration through fields on the structs you instantiate. It is also
|
|
||||||
possible to change survey's default behaviors by passing `AskOpts` to either `Ask` or `AskOne`. Examples
|
|
||||||
in this document will do both interchangeably:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
prompt := &Select{
|
|
||||||
Message: "Choose a color:",
|
|
||||||
Options: []string{"red", "blue", "green"},
|
|
||||||
// can pass a validator directly
|
|
||||||
Validate: survey.Required,
|
|
||||||
}
|
|
||||||
|
|
||||||
// or define a default for the single call to `AskOne`
|
|
||||||
// the answer will get written to the color variable
|
|
||||||
survey.AskOne(prompt, &color, survey.WithValidator(survey.Required))
|
|
||||||
|
|
||||||
// or define a default for every entry in a list of questions
|
|
||||||
// the answer will get copied into the matching field of the struct as shown above
|
|
||||||
survey.Ask(questions, &answers, survey.WithValidator(survey.Required))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Prompts
|
|
||||||
|
|
||||||
### Input
|
|
||||||
|
|
||||||
<img src="https://thumbs.gfycat.com/LankyBlindAmericanpainthorse-size_restricted.gif" width="400px"/>
|
|
||||||
|
|
||||||
```golang
|
|
||||||
name := ""
|
|
||||||
prompt := &survey.Input{
|
|
||||||
Message: "ping",
|
|
||||||
}
|
|
||||||
survey.AskOne(prompt, &name)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Suggestion Options
|
|
||||||
|
|
||||||
<img src="https://i.imgur.com/Q7POpA1.gif" width="800px"/>
|
|
||||||
|
|
||||||
```golang
|
|
||||||
file := ""
|
|
||||||
prompt := &survey.Input{
|
|
||||||
Message: "inform a file to save:",
|
|
||||||
Suggest: func (toComplete string) []string {
|
|
||||||
files, _ := filepath.Glob(toComplete + "*")
|
|
||||||
return files
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
survey.AskOne(prompt, &file)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multiline
|
|
||||||
|
|
||||||
<img src="https://thumbs.gfycat.com/ImperfectShimmeringBeagle-size_restricted.gif" width="400px"/>
|
|
||||||
|
|
||||||
```golang
|
|
||||||
text := ""
|
|
||||||
prompt := &survey.Multiline{
|
|
||||||
Message: "ping",
|
|
||||||
}
|
|
||||||
survey.AskOne(prompt, &text)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Password
|
|
||||||
|
|
||||||
<img src="https://thumbs.gfycat.com/CompassionateSevereHypacrosaurus-size_restricted.gif" width="400px" />
|
|
||||||
|
|
||||||
```golang
|
|
||||||
password := ""
|
|
||||||
prompt := &survey.Password{
|
|
||||||
Message: "Please type your password",
|
|
||||||
}
|
|
||||||
survey.AskOne(prompt, &password)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Confirm
|
|
||||||
|
|
||||||
<img src="https://thumbs.gfycat.com/UnkemptCarefulGermanpinscher-size_restricted.gif" width="400px"/>
|
|
||||||
|
|
||||||
```golang
|
|
||||||
name := false
|
|
||||||
prompt := &survey.Confirm{
|
|
||||||
Message: "Do you like pie?",
|
|
||||||
}
|
|
||||||
survey.AskOne(prompt, &name)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Select
|
|
||||||
|
|
||||||
<img src="https://thumbs.gfycat.com/GrimFilthyAmazonparrot-size_restricted.gif" width="450px"/>
|
|
||||||
|
|
||||||
```golang
|
|
||||||
color := ""
|
|
||||||
prompt := &survey.Select{
|
|
||||||
Message: "Choose a color:",
|
|
||||||
Options: []string{"red", "blue", "green"},
|
|
||||||
}
|
|
||||||
survey.AskOne(prompt, &color)
|
|
||||||
```
|
|
||||||
|
|
||||||
Fields and values that come from a `Select` prompt can be one of two different things. If you pass an `int`
|
|
||||||
the field will have the value of the selected index. If you instead pass a string, the string value selected
|
|
||||||
will be written to the field.
|
|
||||||
|
|
||||||
The user can also press `esc` to toggle the ability cycle through the options with the j and k keys to do down and up respectively.
|
|
||||||
|
|
||||||
By default, the select prompt is limited to showing 7 options at a time
|
|
||||||
and will paginate lists of options longer than that. This can be changed a number of ways:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
// as a field on a single select
|
|
||||||
prompt := &survey.MultiSelect{..., PageSize: 10}
|
|
||||||
|
|
||||||
// or as an option to Ask or AskOne
|
|
||||||
survey.AskOne(prompt, &days, survey.WithPageSize(10))
|
|
||||||
```
|
|
||||||
|
|
||||||
### MultiSelect
|
|
||||||
|
|
||||||
![Example](img/multi-select-all-none.gif)
|
|
||||||
|
|
||||||
```golang
|
|
||||||
days := []string{}
|
|
||||||
prompt := &survey.MultiSelect{
|
|
||||||
Message: "What days do you prefer:",
|
|
||||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
|
||||||
}
|
|
||||||
survey.AskOne(prompt, &days)
|
|
||||||
```
|
|
||||||
|
|
||||||
Fields and values that come from a `MultiSelect` prompt can be one of two different things. If you pass an `int`
|
|
||||||
the field will have a slice of the selected indices. If you instead pass a string, a slice of the string values
|
|
||||||
selected will be written to the field.
|
|
||||||
|
|
||||||
The user can also press `esc` to toggle the ability cycle through the options with the j and k keys to do down and up respectively.
|
|
||||||
|
|
||||||
By default, the MultiSelect prompt is limited to showing 7 options at a time
|
|
||||||
and will paginate lists of options longer than that. This can be changed a number of ways:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
// as a field on a single select
|
|
||||||
prompt := &survey.MultiSelect{..., PageSize: 10}
|
|
||||||
|
|
||||||
// or as an option to Ask or AskOne
|
|
||||||
survey.AskOne(prompt, &days, survey.WithPageSize(10))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Editor
|
|
||||||
|
|
||||||
Launches the user's preferred editor (defined by the \$VISUAL or \$EDITOR environment variables) on a
|
|
||||||
temporary file. Once the user exits their editor, the contents of the temporary file are read in as
|
|
||||||
the result. If neither of those are present, notepad (on Windows) or vim (Linux or Mac) is used.
|
|
||||||
|
|
||||||
You can also specify a [pattern](https://golang.org/pkg/io/ioutil/#TempFile) for the name of the temporary file. This
|
|
||||||
can be useful for ensuring syntax highlighting matches your usecase.
|
|
||||||
|
|
||||||
```golang
|
|
||||||
prompt := &survey.Editor{
|
|
||||||
Message: "Shell code snippet",
|
|
||||||
FileName: "*.sh",
|
|
||||||
}
|
|
||||||
|
|
||||||
survey.AskOne(prompt, &content)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Filtering Options
|
|
||||||
|
|
||||||
By default, the user can filter for options in Select and MultiSelects by typing while the prompt
|
|
||||||
is active. This will filter out all options that don't contain the typed string anywhere in their name, ignoring case.
|
|
||||||
|
|
||||||
A custom filter function can also be provided to change this behavior:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
func myFilter(filterValue string, optValue string, optIndex int) bool {
|
|
||||||
// only include the option if it includes the filter and has length greater than 5
|
|
||||||
return strings.Contains(optValue, filterValue) && len(optValue) >= 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure it for a specific prompt
|
|
||||||
&Select{
|
|
||||||
Message: "Choose a color:",
|
|
||||||
Options: []string{"red", "blue", "green"},
|
|
||||||
Filter: myFilter,
|
|
||||||
}
|
|
||||||
|
|
||||||
// or define a default for all of the questions
|
|
||||||
survey.AskOne(prompt, &color, survey.WithFilter(myFilter))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Keeping the filter active
|
|
||||||
|
|
||||||
By default the filter will disappear if the user selects one of the filtered elements. Once the user selects one element the filter setting is gone.
|
|
||||||
|
|
||||||
However the user can prevent this from happening and keep the filter active for multiple selections in a e.g. MultiSelect:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
// configure it for a specific prompt
|
|
||||||
&Select{
|
|
||||||
Message: "Choose a color:",
|
|
||||||
Options: []string{"light-green", "green", "dark-green", "red"},
|
|
||||||
KeepFilter: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// or define a default for all of the questions
|
|
||||||
survey.AskOne(prompt, &color, survey.WithKeepFilter(true))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Validating individual responses for a particular question can be done by defining a
|
|
||||||
`Validate` field on the `survey.Question` to be validated. This function takes an
|
|
||||||
`interface{}` type and returns an error to show to the user, prompting them for another
|
|
||||||
response. Like usual, validators can be provided directly to the prompt or with `survey.WithValidator`:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
q := &survey.Question{
|
|
||||||
Prompt: &survey.Input{Message: "Hello world validation"},
|
|
||||||
Validate: func (val interface{}) error {
|
|
||||||
// since we are validating an Input, the assertion will always succeed
|
|
||||||
if str, ok := val.(string) ; !ok || len(str) > 10 {
|
|
||||||
return errors.New("This response cannot be longer than 10 characters.")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
color := ""
|
|
||||||
prompt := &survey.Input{ Message: "Whats your name?" }
|
|
||||||
|
|
||||||
// you can pass multiple validators here and survey will make sure each one passes
|
|
||||||
survey.AskOne(prompt, &color, survey.WithValidator(survey.Required))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Built-in Validators
|
|
||||||
|
|
||||||
`survey` comes prepackaged with a few validators to fit common situations. Currently these
|
|
||||||
validators include:
|
|
||||||
|
|
||||||
| name | valid types | description | notes |
|
|
||||||
| ------------ | -------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
||||||
| Required | any | Rejects zero values of the response type | Boolean values pass straight through since the zero value (false) is a valid response |
|
|
||||||
| MinLength(n) | string | Enforces that a response is at least the given length | |
|
|
||||||
| MaxLength(n) | string | Enforces that a response is no longer than the given length | |
|
|
||||||
| MaxItems(n) | []OptionAnswer | Enforces that a response has no more selections of the indicated | |
|
|
||||||
| MinItems(n) | []OptionAnswer | Enforces that a response has no less selections of the indicated | |
|
|
||||||
|
|
||||||
## Help Text
|
|
||||||
|
|
||||||
All of the prompts have a `Help` field which can be defined to provide more information to your users:
|
|
||||||
|
|
||||||
<img src="https://thumbs.gfycat.com/CloudyRemorsefulFossa-size_restricted.gif" width="400px" style="margin-top: 8px"/>
|
|
||||||
|
|
||||||
```golang
|
|
||||||
&survey.Input{
|
|
||||||
Message: "What is your phone number:",
|
|
||||||
Help: "Phone number should include the area code",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Changing the input rune
|
|
||||||
|
|
||||||
In some situations, `?` is a perfectly valid response. To handle this, you can change the rune that survey
|
|
||||||
looks for with `WithHelpInput`:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
import (
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
number := ""
|
|
||||||
prompt := &survey.Input{
|
|
||||||
Message: "If you have this need, please give me a reasonable message.",
|
|
||||||
Help: "I couldn't come up with one.",
|
|
||||||
}
|
|
||||||
|
|
||||||
survey.AskOne(prompt, &number, survey.WithHelpInput('^'))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changing the Icons
|
|
||||||
|
|
||||||
Changing the icons and their color/format can be done by passing the `WithIcons` option. The format
|
|
||||||
follows the patterns outlined [here](https://github.com/mgutz/ansi#style-format). For example:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
import (
|
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
number := ""
|
|
||||||
prompt := &survey.Input{
|
|
||||||
Message: "If you have this need, please give me a reasonable message.",
|
|
||||||
Help: "I couldn't come up with one.",
|
|
||||||
}
|
|
||||||
|
|
||||||
survey.AskOne(prompt, &number, survey.WithIcons(func(icons *survey.IconSet) {
|
|
||||||
// you can set any icons
|
|
||||||
icons.Question.Text = "⁇"
|
|
||||||
// for more information on formatting the icons, see here: https://github.com/mgutz/ansi#style-format
|
|
||||||
icons.Question.Format = "yellow+hb"
|
|
||||||
}))
|
|
||||||
```
|
|
||||||
|
|
||||||
The icons and their default text and format are summarized below:
|
|
||||||
|
|
||||||
| name | text | format | description |
|
|
||||||
| -------------- | ---- | ---------- | ------------------------------------------------------------- |
|
|
||||||
| Error | X | red | Before an error |
|
|
||||||
| Help | i | cyan | Before help text |
|
|
||||||
| Question | ? | green+hb | Before the message of a prompt |
|
|
||||||
| SelectFocus | > | green | Marks the current focus in `Select` and `MultiSelect` prompts |
|
|
||||||
| UnmarkedOption | [ ] | default+hb | Marks an unselected option in a `MultiSelect` prompt |
|
|
||||||
| MarkedOption | [x] | cyan+b | Marks a chosen selection in a `MultiSelect` prompt |
|
|
||||||
|
|
||||||
## Custom Types
|
|
||||||
|
|
||||||
survey will assign prompt answers to your custom types if they implement this interface:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
type Settable interface {
|
|
||||||
WriteAnswer(field string, value interface{}) error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is an example how to use them:
|
|
||||||
|
|
||||||
```golang
|
|
||||||
type MyValue struct {
|
|
||||||
value string
|
|
||||||
}
|
|
||||||
func (my *MyValue) WriteAnswer(name string, value interface{}) error {
|
|
||||||
my.value = value.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
myval := MyValue{}
|
|
||||||
survey.AskOne(
|
|
||||||
&survey.Input{
|
|
||||||
Message: "Enter something:",
|
|
||||||
},
|
|
||||||
&myval
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
You can test your program's interactive prompts using [go-expect](https://github.com/Netflix/go-expect). The library
|
|
||||||
can be used to expect a match on stdout and respond on stdin. Since `os.Stdout` in a `go test` process is not a TTY,
|
|
||||||
if you are manipulating the cursor or using `survey`, you will need a way to interpret terminal / ANSI escape sequences
|
|
||||||
for things like `CursorLocation`. `vt10x.NewVT10XConsole` will create a `go-expect` console that also multiplexes
|
|
||||||
stdio to an in-memory [virtual terminal](https://github.com/hinshun/vt10x).
|
|
||||||
|
|
||||||
For some examples, you can see any of the tests in this repo.
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
### What kinds of IO are supported by `survey`?
|
|
||||||
|
|
||||||
survey aims to support most terminal emulators; it expects support for ANSI escape sequences.
|
|
||||||
This means that reading from piped stdin or writing to piped stdout is **not supported**,
|
|
||||||
and likely to break your application in these situations. See [#337](https://github.com/AlecAivazis/survey/pull/337#issue-581351617)
|
|
||||||
|
|
||||||
### Why isn't sending a SIGINT (aka. CTRL-C) signal working?
|
|
||||||
|
|
||||||
When you send an interrupt signal to the process, it only interrupts the current prompt instead of the entire process. This manifests in a `github.com/AlecAivazis/survey/v2/terminal.InterruptErr` being returned from `Ask` and `AskOne`. If you want to stop the process, handle the returned error in your code:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := survey.AskOne(prompt, &myVar)
|
|
||||||
if err == terminal.InterruptErr {
|
|
||||||
fmt.Println("interrupted")
|
|
||||||
|
|
||||||
os.Exit(0)
|
|
||||||
} else if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,19 +0,0 @@
|
||||||
task "install-deps" {
|
|
||||||
description = "Install all of package dependencies"
|
|
||||||
pipeline = [
|
|
||||||
"go get -v {{.files}}",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
task "tests" {
|
|
||||||
description = "Run the test suite"
|
|
||||||
command = "go test -v {{.files}}"
|
|
||||||
environment = {
|
|
||||||
GOFLAGS = "-mod=vendor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variables = {
|
|
||||||
files = "$(go list -v ./... | grep -iEv \"tests|examples\")"
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
package survey
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Confirm is a regular text input that accept yes/no answers. Response type is a bool.
|
|
||||||
type Confirm struct {
|
|
||||||
Renderer
|
|
||||||
Message string
|
|
||||||
Default bool
|
|
||||||
Help string
|
|
||||||
}
|
|
||||||
|
|
||||||
// data available to the templates when processing
|
|
||||||
type ConfirmTemplateData struct {
|
|
||||||
Confirm
|
|
||||||
Answer string
|
|
||||||
ShowHelp bool
|
|
||||||
Config *PromptConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
|
||||||
var ConfirmQuestionTemplate = `
|
|
||||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
|
||||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
|
||||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
|
||||||
{{- if .Answer}}
|
|
||||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
|
||||||
{{- else }}
|
|
||||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
|
||||||
{{- color "white"}}{{if .Default}}(Y/n) {{else}}(y/N) {{end}}{{color "reset"}}
|
|
||||||
{{- end}}`
|
|
||||||
|
|
||||||
// the regex for answers
|
|
||||||
var (
|
|
||||||
yesRx = regexp.MustCompile("^(?i:y(?:es)?)$")
|
|
||||||
noRx = regexp.MustCompile("^(?i:n(?:o)?)$")
|
|
||||||
)
|
|
||||||
|
|
||||||
func yesNo(t bool) string {
|
|
||||||
if t {
|
|
||||||
return "Yes"
|
|
||||||
}
|
|
||||||
return "No"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Confirm) getBool(showHelp bool, config *PromptConfig) (bool, error) {
|
|
||||||
cursor := c.NewCursor()
|
|
||||||
rr := c.NewRuneReader()
|
|
||||||
rr.SetTermMode()
|
|
||||||
defer rr.RestoreTermMode()
|
|
||||||
|
|
||||||
// start waiting for input
|
|
||||||
for {
|
|
||||||
line, err := rr.ReadLine(0)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
// move back up a line to compensate for the \n echoed from terminal
|
|
||||||
cursor.PreviousLine(1)
|
|
||||||
val := string(line)
|
|
||||||
|
|
||||||
// get the answer that matches the
|
|
||||||
var answer bool
|
|
||||||
switch {
|
|
||||||
case yesRx.Match([]byte(val)):
|
|
||||||
answer = true
|
|
||||||
case noRx.Match([]byte(val)):
|
|
||||||
answer = false
|
|
||||||
case val == "":
|
|
||||||
answer = c.Default
|
|
||||||
case val == config.HelpInput && c.Help != "":
|
|
||||||
err := c.Render(
|
|
||||||
ConfirmQuestionTemplate,
|
|
||||||
ConfirmTemplateData{
|
|
||||||
Confirm: *c,
|
|
||||||
ShowHelp: true,
|
|
||||||
Config: config,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
// use the default value and bubble up
|
|
||||||
return c.Default, err
|
|
||||||
}
|
|
||||||
showHelp = true
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
// we didnt get a valid answer, so print error and prompt again
|
|
||||||
if err := c.Error(config, fmt.Errorf("%q is not a valid answer, please try again.", val)); err != nil {
|
|
||||||
return c.Default, err
|
|
||||||
}
|
|
||||||
err := c.Render(
|
|
||||||
ConfirmQuestionTemplate,
|
|
||||||
ConfirmTemplateData{
|
|
||||||
Confirm: *c,
|
|
||||||
ShowHelp: showHelp,
|
|
||||||
Config: config,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
// use the default value and bubble up
|
|
||||||
return c.Default, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return answer, nil
|
|
||||||
}
|
|
||||||
// should not get here
|
|
||||||
return c.Default, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prompt prompts the user with a simple text field and expects a reply followed
|
|
||||||
by a carriage return.
|
|
||||||
|
|
||||||
likesPie := false
|
|
||||||
prompt := &survey.Confirm{ Message: "What is your name?" }
|
|
||||||
survey.AskOne(prompt, &likesPie)
|
|
||||||
*/
|
|
||||||
func (c *Confirm) Prompt(config *PromptConfig) (interface{}, error) {
|
|
||||||
// render the question template
|
|
||||||
err := c.Render(
|
|
||||||
ConfirmQuestionTemplate,
|
|
||||||
ConfirmTemplateData{
|
|
||||||
Confirm: *c,
|
|
||||||
Config: config,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get input and return
|
|
||||||
return c.getBool(false, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup overwrite the line with the finalized formatted version
|
|
||||||
func (c *Confirm) Cleanup(config *PromptConfig, val interface{}) error {
|
|
||||||
// if the value was previously true
|
|
||||||
ans := yesNo(val.(bool))
|
|
||||||
|
|
||||||
// render the template
|
|
||||||
return c.Render(
|
|
||||||
ConfirmQuestionTemplate,
|
|
||||||
ConfirmTemplateData{
|
|
||||||
Confirm: *c,
|
|
||||||
Answer: ans,
|
|
||||||
Config: config,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"sync"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/mgutz/ansi"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DisableColor can be used to make testing reliable
|
|
||||||
var DisableColor = false
|
|
||||||
|
|
||||||
var TemplateFuncsWithColor = map[string]interface{}{
|
|
||||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
|
||||||
"color": ansi.ColorCode,
|
|
||||||
}
|
|
||||||
|
|
||||||
var TemplateFuncsNoColor = map[string]interface{}{
|
|
||||||
// Templates without Color formatting. For layout/ testing.
|
|
||||||
"color": func(color string) string {
|
|
||||||
return ""
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
//RunTemplate returns two formatted strings given a template and
|
|
||||||
//the data it requires. The first string returned is generated for
|
|
||||||
//user-facing output and may or may not contain ANSI escape codes
|
|
||||||
//for colored output. The second string does not contain escape codes
|
|
||||||
//and can be used by the renderer for layout purposes.
|
|
||||||
func RunTemplate(tmpl string, data interface{}) (string, string, error) {
|
|
||||||
tPair, err := GetTemplatePair(tmpl)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
userBuf := bytes.NewBufferString("")
|
|
||||||
err = tPair[0].Execute(userBuf, data)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
layoutBuf := bytes.NewBufferString("")
|
|
||||||
err = tPair[1].Execute(layoutBuf, data)
|
|
||||||
if err != nil {
|
|
||||||
return userBuf.String(), "", err
|
|
||||||
}
|
|
||||||
return userBuf.String(), layoutBuf.String(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
memoizedGetTemplate = map[string][2]*template.Template{}
|
|
||||||
|
|
||||||
memoMutex = &sync.RWMutex{}
|
|
||||||
)
|
|
||||||
|
|
||||||
//GetTemplatePair returns a pair of compiled templates where the
|
|
||||||
//first template is generated for user-facing output and the
|
|
||||||
//second is generated for use by the renderer. The second
|
|
||||||
//template does not contain any color escape codes, whereas
|
|
||||||
//the first template may or may not depending on DisableColor.
|
|
||||||
func GetTemplatePair(tmpl string) ([2]*template.Template, error) {
|
|
||||||
memoMutex.RLock()
|
|
||||||
if t, ok := memoizedGetTemplate[tmpl]; ok {
|
|
||||||
memoMutex.RUnlock()
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
memoMutex.RUnlock()
|
|
||||||
|
|
||||||
templatePair := [2]*template.Template{nil, nil}
|
|
||||||
|
|
||||||
templateNoColor, err := template.New("prompt").Funcs(TemplateFuncsNoColor).Parse(tmpl)
|
|
||||||
if err != nil {
|
|
||||||
return [2]*template.Template{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
templatePair[1] = templateNoColor
|
|
||||||
|
|
||||||
if DisableColor {
|
|
||||||
templatePair[0] = templatePair[1]
|
|
||||||
} else {
|
|
||||||
templateWithColor, err := template.New("prompt").Funcs(TemplateFuncsWithColor).Parse(tmpl)
|
|
||||||
templatePair[0] = templateWithColor
|
|
||||||
if err != nil {
|
|
||||||
return [2]*template.Template{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memoMutex.Lock()
|
|
||||||
memoizedGetTemplate[tmpl] = templatePair
|
|
||||||
memoMutex.Unlock()
|
|
||||||
return templatePair, nil
|
|
||||||
}
|
|
|
@ -1,374 +0,0 @@
|
||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// the tag used to denote the name of the question
|
|
||||||
const tagName = "survey"
|
|
||||||
|
|
||||||
// Settable allow for configuration when assigning answers
|
|
||||||
type Settable interface {
|
|
||||||
WriteAnswer(field string, value interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionAnswer is the return type of Selects/MultiSelects that lets the appropriate information
|
|
||||||
// get copied to the user's struct
|
|
||||||
type OptionAnswer struct {
|
|
||||||
Value string
|
|
||||||
Index int
|
|
||||||
}
|
|
||||||
|
|
||||||
type reflectField struct {
|
|
||||||
value reflect.Value
|
|
||||||
fieldType reflect.StructField
|
|
||||||
}
|
|
||||||
|
|
||||||
func OptionAnswerList(incoming []string) []OptionAnswer {
|
|
||||||
list := []OptionAnswer{}
|
|
||||||
for i, opt := range incoming {
|
|
||||||
list = append(list, OptionAnswer{Value: opt, Index: i})
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
|
|
||||||
// if the field is a custom type
|
|
||||||
if s, ok := t.(Settable); ok {
|
|
||||||
// use the interface method
|
|
||||||
return s.WriteAnswer(name, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the target to write to
|
|
||||||
target := reflect.ValueOf(t)
|
|
||||||
// the value to write from
|
|
||||||
value := reflect.ValueOf(v)
|
|
||||||
|
|
||||||
// make sure we are writing to a pointer
|
|
||||||
if target.Kind() != reflect.Ptr {
|
|
||||||
return errors.New("you must pass a pointer as the target of a Write operation")
|
|
||||||
}
|
|
||||||
// the object "inside" of the target pointer
|
|
||||||
elem := target.Elem()
|
|
||||||
|
|
||||||
// handle the special types
|
|
||||||
switch elem.Kind() {
|
|
||||||
// if we are writing to a struct
|
|
||||||
case reflect.Struct:
|
|
||||||
// if we are writing to an option answer than we want to treat
|
|
||||||
// it like a single thing and not a place to deposit answers
|
|
||||||
if elem.Type().Name() == "OptionAnswer" {
|
|
||||||
// copy the value over to the normal struct
|
|
||||||
return copy(elem, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the name of the field that matches the string we were given
|
|
||||||
field, _, err := findField(elem, name)
|
|
||||||
// if something went wrong
|
|
||||||
if err != nil {
|
|
||||||
// bubble up
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// handle references to the Settable interface aswell
|
|
||||||
if s, ok := field.Interface().(Settable); ok {
|
|
||||||
// use the interface method
|
|
||||||
return s.WriteAnswer(name, v)
|
|
||||||
}
|
|
||||||
if field.CanAddr() {
|
|
||||||
if s, ok := field.Addr().Interface().(Settable); ok {
|
|
||||||
// use the interface method
|
|
||||||
return s.WriteAnswer(name, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the value over to the normal struct
|
|
||||||
return copy(field, value)
|
|
||||||
case reflect.Map:
|
|
||||||
mapType := reflect.TypeOf(t).Elem()
|
|
||||||
if mapType.Key().Kind() != reflect.String {
|
|
||||||
return errors.New("answer maps key must be of type string")
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy only string value/index value to map if,
|
|
||||||
// map is not of type interface and is 'OptionAnswer'
|
|
||||||
if value.Type().Name() == "OptionAnswer" {
|
|
||||||
if kval := mapType.Elem().Kind(); kval == reflect.String {
|
|
||||||
mt := *t.(*map[string]string)
|
|
||||||
mt[name] = value.FieldByName("Value").String()
|
|
||||||
return nil
|
|
||||||
} else if kval == reflect.Int {
|
|
||||||
mt := *t.(*map[string]int)
|
|
||||||
mt[name] = int(value.FieldByName("Index").Int())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mapType.Elem().Kind() != reflect.Interface {
|
|
||||||
return errors.New("answer maps must be of type map[string]interface")
|
|
||||||
}
|
|
||||||
mt := *t.(*map[string]interface{})
|
|
||||||
mt[name] = value.Interface()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// otherwise just copy the value to the target
|
|
||||||
return copy(elem, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
type errFieldNotMatch struct {
|
|
||||||
questionName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err errFieldNotMatch) Error() string {
|
|
||||||
return fmt.Sprintf("could not find field matching %v", err.questionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err errFieldNotMatch) Is(target error) bool { // implements the dynamic errors.Is interface.
|
|
||||||
if target != nil {
|
|
||||||
if name, ok := IsFieldNotMatch(target); ok {
|
|
||||||
// if have a filled questionName then perform "deeper" comparison.
|
|
||||||
return name == "" || err.questionName == "" || name == err.questionName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFieldNotMatch reports whether an "err" is caused by a non matching field.
|
|
||||||
// It returns the Question.Name that couldn't be matched with a destination field.
|
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
// err := survey.Ask(qs, &v);
|
|
||||||
// if err != nil {
|
|
||||||
// if name, ok := core.IsFieldNotMatch(err); ok {
|
|
||||||
// [...name is the not matched question name]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
func IsFieldNotMatch(err error) (string, bool) {
|
|
||||||
if err != nil {
|
|
||||||
if v, ok := err.(errFieldNotMatch); ok {
|
|
||||||
return v.questionName, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// BUG(AlecAivazis): the current implementation might cause weird conflicts if there are
|
|
||||||
// two fields with same name that only differ by casing.
|
|
||||||
func findField(s reflect.Value, name string) (reflect.Value, reflect.StructField, error) {
|
|
||||||
|
|
||||||
fields := flattenFields(s)
|
|
||||||
|
|
||||||
// first look for matching tags so we can overwrite matching field names
|
|
||||||
for _, f := range fields {
|
|
||||||
// the value of the survey tag
|
|
||||||
tag := f.fieldType.Tag.Get(tagName)
|
|
||||||
// if the tag matches the name we are looking for
|
|
||||||
if tag != "" && tag == name {
|
|
||||||
// then we found our index
|
|
||||||
return f.value, f.fieldType, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// then look for matching names
|
|
||||||
for _, f := range fields {
|
|
||||||
// if the name of the field matches what we're looking for
|
|
||||||
if strings.ToLower(f.fieldType.Name) == strings.ToLower(name) {
|
|
||||||
return f.value, f.fieldType, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we didn't find the field
|
|
||||||
return reflect.Value{}, reflect.StructField{}, errFieldNotMatch{name}
|
|
||||||
}
|
|
||||||
|
|
||||||
func flattenFields(s reflect.Value) []reflectField {
|
|
||||||
sType := s.Type()
|
|
||||||
numField := sType.NumField()
|
|
||||||
fields := make([]reflectField, 0, numField)
|
|
||||||
for i := 0; i < numField; i++ {
|
|
||||||
fieldType := sType.Field(i)
|
|
||||||
field := s.Field(i)
|
|
||||||
|
|
||||||
if field.Kind() == reflect.Struct && fieldType.Anonymous {
|
|
||||||
// field is a promoted structure
|
|
||||||
for _, f := range flattenFields(field) {
|
|
||||||
fields = append(fields, f)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields = append(fields, reflectField{field, fieldType})
|
|
||||||
}
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
|
|
||||||
// isList returns true if the element is something we can Len()
|
|
||||||
func isList(v reflect.Value) bool {
|
|
||||||
switch v.Type().Kind() {
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write takes a value and copies it to the target
|
|
||||||
func copy(t reflect.Value, v reflect.Value) (err error) {
|
|
||||||
// if something ends up panicing we need to catch it in a deferred func
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
// if we paniced with an error
|
|
||||||
if _, ok := r.(error); ok {
|
|
||||||
// cast the result to an error object
|
|
||||||
err = r.(error)
|
|
||||||
} else if _, ok := r.(string); ok {
|
|
||||||
// otherwise we could have paniced with a string so wrap it in an error
|
|
||||||
err = errors.New(r.(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// if we are copying from a string result to something else
|
|
||||||
if v.Kind() == reflect.String && v.Type() != t.Type() {
|
|
||||||
var castVal interface{}
|
|
||||||
var casterr error
|
|
||||||
vString := v.Interface().(string)
|
|
||||||
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
castVal, casterr = strconv.ParseBool(vString)
|
|
||||||
case reflect.Int:
|
|
||||||
castVal, casterr = strconv.Atoi(vString)
|
|
||||||
case reflect.Int8:
|
|
||||||
var val64 int64
|
|
||||||
val64, casterr = strconv.ParseInt(vString, 10, 8)
|
|
||||||
if casterr == nil {
|
|
||||||
castVal = int8(val64)
|
|
||||||
}
|
|
||||||
case reflect.Int16:
|
|
||||||
var val64 int64
|
|
||||||
val64, casterr = strconv.ParseInt(vString, 10, 16)
|
|
||||||
if casterr == nil {
|
|
||||||
castVal = int16(val64)
|
|
||||||
}
|
|
||||||
case reflect.Int32:
|
|
||||||
var val64 int64
|
|
||||||
val64, casterr = strconv.ParseInt(vString, 10, 32)
|
|
||||||
if casterr == nil {
|
|
||||||
castVal = int32(val64)
|
|
||||||
}
|
|
||||||
case reflect.Int64:
|
|
||||||
if t.Type() == reflect.TypeOf(time.Duration(0)) {
|
|
||||||
castVal, casterr = time.ParseDuration(vString)
|
|
||||||
} else {
|
|
||||||
castVal, casterr = strconv.ParseInt(vString, 10, 64)
|
|
||||||
}
|
|
||||||
case reflect.Uint:
|
|
||||||
var val64 uint64
|
|
||||||
val64, casterr = strconv.ParseUint(vString, 10, 8)
|
|
||||||
if casterr == nil {
|
|
||||||
castVal = uint(val64)
|
|
||||||
}
|
|
||||||
case reflect.Uint8:
|
|
||||||
var val64 uint64
|
|
||||||
val64, casterr = strconv.ParseUint(vString, 10, 8)
|
|
||||||
if casterr == nil {
|
|
||||||
castVal = uint8(val64)
|
|
||||||
}
|
|
||||||
case reflect.Uint16:
|
|
||||||
var val64 uint64
|
|
||||||
val64, casterr = strconv.ParseUint(vString, 10, 16)
|
|
||||||
if casterr == nil {
|
|
||||||
castVal = uint16(val64)
|
|
||||||
}
|
|
||||||
case reflect.Uint32:
|
|
||||||
var val64 uint64
|
|
||||||
val64, casterr = strconv.ParseUint(vString, 10, 32)
|
|
||||||
if casterr == nil {
|
|
||||||
castVal = uint32(val64)
|
|
||||||
}
|
|
||||||
case reflect.Uint64:
|
|
||||||
castVal, casterr = strconv.ParseUint(vString, 10, 64)
|
|
||||||
case reflect.Float32:
|
|
||||||
var val64 float64
|
|
||||||
val64, casterr = strconv.ParseFloat(vString, 32)
|
|
||||||
if casterr == nil {
|
|
||||||
castVal = float32(val64)
|
|
||||||
}
|
|
||||||
case reflect.Float64:
|
|
||||||
castVal, casterr = strconv.ParseFloat(vString, 64)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unable to convert from string to type %s", t.Kind())
|
|
||||||
}
|
|
||||||
|
|
||||||
if casterr != nil {
|
|
||||||
return casterr
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Set(reflect.ValueOf(castVal))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we are copying from an OptionAnswer to something
|
|
||||||
if v.Type().Name() == "OptionAnswer" {
|
|
||||||
// copying an option answer to a string
|
|
||||||
if t.Kind() == reflect.String {
|
|
||||||
// copies the Value field of the struct
|
|
||||||
t.Set(reflect.ValueOf(v.FieldByName("Value").Interface()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// copying an option answer to an int
|
|
||||||
if t.Kind() == reflect.Int {
|
|
||||||
// copies the Index field of the struct
|
|
||||||
t.Set(reflect.ValueOf(v.FieldByName("Index").Interface()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// copying an OptionAnswer to an OptionAnswer
|
|
||||||
if t.Type().Name() == "OptionAnswer" {
|
|
||||||
t.Set(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// we're copying an option answer to an incorrect type
|
|
||||||
return fmt.Errorf("Unable to convert from OptionAnswer to type %s", t.Kind())
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we are copying from one slice or array to another
|
|
||||||
if isList(v) && isList(t) {
|
|
||||||
// loop over every item in the desired value
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
// write to the target given its kind
|
|
||||||
switch t.Kind() {
|
|
||||||
// if its a slice
|
|
||||||
case reflect.Slice:
|
|
||||||
// an object of the correct type
|
|
||||||
obj := reflect.Indirect(reflect.New(t.Type().Elem()))
|
|
||||||
|
|
||||||
// write the appropriate value to the obj and catch any errors
|
|
||||||
if err := copy(obj, v.Index(i)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// just append the value to the end
|
|
||||||
t.Set(reflect.Append(t, obj))
|
|
||||||
// otherwise it could be an array
|
|
||||||
case reflect.Array:
|
|
||||||
// set the index to the appropriate value
|
|
||||||
copy(t.Slice(i, i+1).Index(0), v.Index(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// set the value to the target
|
|
||||||
t.Set(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we're done
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
package survey
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/AlecAivazis/survey/v2/terminal"
|
|
||||||
shellquote "github.com/kballard/go-shellquote"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Editor launches an instance of the users preferred editor on a temporary file.
|
|
||||||
The editor to use is determined by reading the $VISUAL or $EDITOR environment
|
|
||||||
variables. If neither of those are present, notepad (on Windows) or vim
|
|
||||||
(others) is used.
|
|
||||||
The launch of the editor is triggered by the enter key. Since the response may
|
|
||||||
be long, it will not be echoed as Input does, instead, it print <Received>.
|
|
||||||
Response type is a string.
|
|
||||||
|
|
||||||
message := ""
|
|
||||||
prompt := &survey.Editor{ Message: "What is your commit message?" }
|
|
||||||
survey.AskOne(prompt, &message)
|
|
||||||
*/
|
|
||||||
type Editor struct {
|
|
||||||
Renderer
|
|
||||||
Message string
|
|
||||||
Default string
|
|
||||||
Help string
|
|
||||||
Editor string
|
|
||||||
HideDefault bool
|
|
||||||
AppendDefault bool
|
|
||||||
FileName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// data available to the templates when processing
|
|
||||||
type EditorTemplateData struct {
|
|
||||||
Editor
|
|
||||||
Answer string
|
|
||||||
ShowAnswer bool
|
|
||||||
ShowHelp bool
|
|
||||||
Config *PromptConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
|
||||||
var EditorQuestionTemplate = `
|
|
||||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
|
||||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
|
||||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
|
||||||
{{- if .ShowAnswer}}
|
|
||||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
|
||||||
{{- else }}
|
|
||||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
|
||||||
{{- if and .Default (not .HideDefault)}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
|
||||||
{{- color "cyan"}}[Enter to launch editor] {{color "reset"}}
|
|
||||||
{{- end}}`
|
|
||||||
|
|
||||||
var (
|
|
||||||
bom = []byte{0xef, 0xbb, 0xbf}
|
|
||||||
editor = "vim"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
editor = "notepad"
|
|
||||||
}
|
|
||||||
if v := os.Getenv("VISUAL"); v != "" {
|
|
||||||
editor = v
|
|
||||||
} else if e := os.Getenv("EDITOR"); e != "" {
|
|
||||||
editor = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Editor) PromptAgain(config *PromptConfig, invalid interface{}, err error) (interface{}, error) {
|
|
||||||
initialValue := invalid.(string)
|
|
||||||
return e.prompt(initialValue, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Editor) Prompt(config *PromptConfig) (interface{}, error) {
|
|
||||||
initialValue := ""
|
|
||||||
if e.Default != "" && e.AppendDefault {
|
|
||||||
initialValue = e.Default
|
|
||||||
}
|
|
||||||
return e.prompt(initialValue, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Editor) prompt(initialValue string, config *PromptConfig) (interface{}, error) {
|
|
||||||
// render the template
|
|
||||||
err := e.Render(
|
|
||||||
EditorQuestionTemplate,
|
|
||||||
EditorTemplateData{
|
|
||||||
Editor: *e,
|
|
||||||
Config: config,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// start reading runes from the standard in
|
|
||||||
rr := e.NewRuneReader()
|
|
||||||
rr.SetTermMode()
|
|
||||||
defer rr.RestoreTermMode()
|
|
||||||
|
|
||||||
cursor := e.NewCursor()
|
|
||||||
cursor.Hide()
|
|
||||||
defer cursor.Show()
|
|
||||||
|
|
||||||
for {
|
|
||||||
r, _, err := rr.ReadRune()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if r == '\r' || r == '\n' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if r == terminal.KeyInterrupt {
|
|
||||||
return "", terminal.InterruptErr
|
|
||||||
}
|
|
||||||
if r == terminal.KeyEndTransmission {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if string(r) == config.HelpInput && e.Help != "" {
|
|
||||||
err = e.Render(
|
|
||||||
EditorQuestionTemplate,
|
|
||||||
EditorTemplateData{
|
|
||||||
Editor: *e,
|
|
||||||
ShowHelp: true,
|
|
||||||
Config: config,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare the temp file
|
|
||||||
pattern := e.FileName
|
|
||||||
if pattern == "" {
|
|
||||||
pattern = "survey*.txt"
|
|
||||||
}
|
|
||||||
f, err := ioutil.TempFile("", pattern)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer os.Remove(f.Name())
|
|
||||||
|
|
||||||
// write utf8 BOM header
|
|
||||||
// The reason why we do this is because notepad.exe on Windows determines the
|
|
||||||
// encoding of an "empty" text file by the locale, for example, GBK in China,
|
|
||||||
// while golang string only handles utf8 well. However, a text file with utf8
|
|
||||||
// BOM header is not considered "empty" on Windows, and the encoding will then
|
|
||||||
// be determined utf8 by notepad.exe, instead of GBK or other encodings.
|
|
||||||
if _, err := f.Write(bom); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// write initial value
|
|
||||||
if _, err := f.WriteString(initialValue); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// close the fd to prevent the editor unable to save file
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check is input editor exist
|
|
||||||
if e.Editor != "" {
|
|
||||||
editor = e.Editor
|
|
||||||
}
|
|
||||||
|
|
||||||
stdio := e.Stdio()
|
|
||||||
|
|
||||||
args, err := shellquote.Split(editor)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
args = append(args, f.Name())
|
|
||||||
|
|
||||||
// open the editor
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
|
||||||
cmd.Stdin = stdio.In
|
|
||||||
cmd.Stdout = stdio.Out
|
|
||||||
cmd.Stderr = stdio.Err
|
|
||||||
cursor.Show()
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// raw is a BOM-unstripped UTF8 byte slice
|
|
||||||
raw, err := ioutil.ReadFile(f.Name())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// strip BOM header
|
|
||||||
text := string(bytes.TrimPrefix(raw, bom))
|
|
||||||
|
|
||||||
// check length, return default value on empty
|
|
||||||
if len(text) == 0 && !e.AppendDefault {
|
|
||||||
return e.Default, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return text, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Editor) Cleanup(config *PromptConfig, val interface{}) error {
|
|
||||||
return e.Render(
|
|
||||||
EditorQuestionTemplate,
|
|
||||||
EditorTemplateData{
|
|
||||||
Editor: *e,
|
|
||||||
Answer: "<Received>",
|
|
||||||
ShowAnswer: true,
|
|
||||||
Config: config,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package survey
|
|
|
@ -1,19 +0,0 @@
|
||||||
module github.com/AlecAivazis/survey/v2
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
|
||||||
github.com/kr/pty v1.1.4
|
|
||||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.8
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/stretchr/testify v1.2.1
|
|
||||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 // indirect
|
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
|
||||||
golang.org/x/text v0.3.3
|
|
||||||
)
|
|
||||||
|
|
||||||
go 1.13
|
|
|
@ -1,35 +0,0 @@
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
|
||||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
|
||||||
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
|
|
||||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
|
||||||
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/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U=
|
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
|
|
||||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
|
||||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
|
||||||
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=
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue