pax_global_header00006660000000000000000000000064145333635100014515gustar00rootroot0000000000000052 comment=0e0a4691b626bb2537f03afb337a164aa8669261 go-sdk/000077500000000000000000000000001453336351000122455ustar00rootroot00000000000000go-sdk/.changelog.yml000077500000000000000000000017511453336351000150040ustar00rootroot00000000000000# The full repository name repo: gitea/go-sdk # Service type (gitea or github) service: gitea # Base URL for Gitea instance if using gitea service type (optional) # Default: https://gitea.com base-url: https://gitea.com # Changelog groups and which labeled PRs to add to each group groups: - name: BREAKING labels: - kind/breaking - name: FEATURES labels: - kind/feature - name: BUGFIXES labels: - kind/bug - name: ENHANCEMENTS labels: - kind/enhancement - kind/refactor - kind/ui - name: SECURITY labels: - kind/security - name: TESTING labels: - kind/testing - name: TRANSLATION labels: - kind/translation - name: BUILD labels: - kind/build - kind/lint - name: DOCS labels: - kind/docs - name: MISC default: true # regex indicating which labels to skip for the changelog skip-labels: skip-changelog|backport\/.+|has/backport go-sdk/.editorconfig000066400000000000000000000006551453336351000147300ustar00rootroot00000000000000# http://editorconfig.org root = true [*] charset = utf-8 insert_final_newline = true trim_trailing_whitespace = true [*.go] indent_style = tab indent_size = 8 [*.{tmpl,html}] indent_style = tab indent_size = 4 [*.{less}] indent_style = space indent_size = 4 [*.{yml}] indent_style = space indent_size = 2 [*.js] indent_style = space indent_size = 4 [Makefile] indent_style = tab [*.md] trim_trailing_whitespace = false go-sdk/.gitattributes000066400000000000000000000000231453336351000151330ustar00rootroot00000000000000* text=auto eol=lf go-sdk/.gitea/000077500000000000000000000000001453336351000134145ustar00rootroot00000000000000go-sdk/.gitea/workflows/000077500000000000000000000000001453336351000154515ustar00rootroot00000000000000go-sdk/.gitea/workflows/testing.yml000066400000000000000000000040041453336351000176470ustar00rootroot00000000000000name: testing on: - pull_request - push jobs: testing: runs-on: ubuntu-latest env: GOPROXY: "https://goproxy.io" GO111MODULE: "on" HTTP_PROXY: "" GITEA_SDK_TEST_URL: "http://gitea:3000" GITEA_SDK_TEST_USERNAME: "test01" GITEA_SDK_TEST_PASSWORD: "test01" steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: go-version: '>=1.20' check-latest: true - run: make clean - run: make vet - run: make ci-lint - run: make build - run: curl --noproxy "*" http://gitea:3000/api/v1/version # verify connection to instance - run: make test services: gitea: image: gitea/gitea:1.18 cmd: - bash - -c - >- mkdir -p /tmp/conf/ && mkdir -p /tmp/data/ && echo "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT = true" > /tmp/conf/app.ini && echo "[security]" >> /tmp/conf/app.ini && echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> /tmp/conf/app.ini && echo "INSTALL_LOCK = true" >> /tmp/conf/app.ini && echo "SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo" >> /tmp/conf/app.ini && echo "PASSWORD_COMPLEXITY = off" >> /tmp/conf/app.ini && echo "[database]" >> /tmp/conf/app.ini && echo "DB_TYPE = sqlite3" >> /tmp/conf/app.ini && echo "[repository]" >> /tmp/conf/app.ini && echo "ROOT = /tmp/data/" >> /tmp/conf/app.ini && echo "[server]" >> /tmp/conf/app.ini && echo "ROOT_URL = http://gitea:3000" >> /tmp/conf/app.ini && gitea migrate -c /tmp/conf/app.ini && gitea admin user create --username=test01 --password=test01 --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c /tmp/conf/app.ini && gitea web -c /tmp/conf/app.ini go-sdk/.github/000077500000000000000000000000001453336351000136055ustar00rootroot00000000000000go-sdk/.github/issue_template.md000066400000000000000000000007211453336351000171520ustar00rootroot00000000000000 go-sdk/.github/pull_request_template.md000066400000000000000000000006731453336351000205540ustar00rootroot00000000000000Please check the following: 1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for bug fixes. 2. Read contributing guidelines: https://gitea.com/gitea/go-sdk/src/branch/main/CONTRIBUTING.md 3. Describe what your pull request does and which issue you're targeting (if any) **You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.** go-sdk/.gitignore000066400000000000000000000005111453336351000142320ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go gitea-vet *.exe *.test *.prof coverage.out # JetBrains .idea #integrations test go-sdk/.revive.toml000066400000000000000000000007601453336351000145230ustar00rootroot00000000000000ignoreGeneratedHeader = false severity = "warning" confidence = 0.8 errorCode = 1 warningCode = 1 [rule.blank-imports] [rule.context-as-argument] [rule.context-keys-type] [rule.dot-imports] [rule.error-return] [rule.error-strings] [rule.error-naming] [rule.exported] [rule.if-return] [rule.increment-decrement] [rule.var-naming] [rule.var-declaration] [rule.package-comments] [rule.range] [rule.receiver-naming] [rule.time-naming] [rule.unexported-return] [rule.indent-error-flow] [rule.errorf] go-sdk/CHANGELOG.md000066400000000000000000000206461453336351000140660ustar00rootroot00000000000000# Changelog ## [v0.15.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.15.1) - 2022-01-04 * FEATURES * Add ignoreVersion & manuall version set option (#560) (#562) * BUGFIXES * Fix version string for next release (#559) ## [v0.15.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.15.0) - 2021-08-13 * BREAKING * Introduce NotifySubjectState (#520) * Drop deprecations (#503) * FEATURES * Add Repo Team Management Functions (#537) * Add CreateRepoFromTemplate (#536) * Add GetReviewers & GetAssignees (#534) * Add GetTag, GetAnnotatedTag & CreateTag (#533) * Add GetUserSettings & UpdateUserSettings (#531) * Add ListPullRequestCommits (#530) * Add GetUserByID (#513) * Add GetRepoByID (#511) * ENHANCEMENTS * Update List Options (#527) * Update Structs (#524) * ListFunctions: option to disable pagination (#509) ## [v0.14.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.14.1) - 2021-06-30 * BUGFIXES * Fix setDefaults (#508) (#510) ## [v0.14.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.14.0) - 2021-03-21 * BREAKING * Update Structs (#486) * Added repo ListContents and changed GetContents doc to talk about a single file (#485) * Remove & Rename TrackedTimes list functions (#467) * UrlEscape Function Arguments used in UrlPath (#273) * FEATURES * Add Create/Delete ReviewRequests (#493) * Add Un-/DismissPullReview funcs (#489) * Add Repo Un-Star Functions (#483) * introduce Client.GetArchiveReader (#476) * Add DeleteRepoTag function (#461) * Add GetReleaseByTag (#427) * BUGFIXES * Handle Contents Edge-Case (#492) * Fix GetCombinedStatus() (#470) * Use Predefind Versions & Compare Function (#442) * Return resp on NotFound too (#428) * ENHANCEMENTS * Add workaround to get head branch sha of pulls with deleted head branch (#498) * GetFile: Use "ref" in-query if posible (#491) * Add DeleteTag & Correct DeleteReleaseByTag (#488) * Add html_url field to Release struct (#477) * Add Ref to Issue structs (#466) * Update Issue Struct (#458) * Use sync.Once for loading ServerVersion (#456) * Add Gitea2Gitea Migration Support (#454) * Add Helper for Optional Values (#448) * Update CreateRepoOption struct (#445) * Update CommitMeta Struct (#434) * Update Struct NotificationSubject (#424) * Add Debug Mode (#422) * DOCS * Make Client thread-safe & add docs (#495) * Improve PullReview docs (#469) ## [v0.13.3](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.3) - 2021-03-22 * BUGFIXES * Fix GetCombinedStatus() (#470) (#472) * ENHANCEMENTS * Add html_url field to Release struct (#477) (#478) ## [v0.13.2](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.2) - 2020-12-07 * BUGFIXES * Use Predefind Versions & Compare Function (#442) (#446) * ENHANCEMENTS * Add Gitea2Gitea Migration Support (#454) (#455) * Update CreateRepoOption struct (#445) (#447) * Update CommitMeta Struct (#434) (#437) ## [v0.13.1](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.1) - 2020-09-29 * FEATURES * Add GetReleaseByTag (#427) (#430) * BUGFIXES * Return http Response on NotFound too (#428) (#429) * ENHANCEMENTS * Update Struct NotificationSubject (#424) (#425) * Add Debug Mode (#422) (#423) ## [v0.13.0](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.13.0) - 2020-09-15 * BREAKING * Check Gitea Version Requirement (#419) * All Function return http responce (#416) * Remove opts from ListPullReviewComments (#411) * Use enum AccessMode for OrgTeam and Collaborator functions (#408) * CreateOrgOption rename UserName to Name (#386) * EditMilestoneOption also use StateType (#350) * Refactor RepoSearch to be easy usable (#346) * FEATURES * Milestone Functions accept name to identify (#418) * Make http requests with context (#417) * Add GetGlobalAttachmentSettings (#414) * Add GetArchive (#413) * Add GetRepoLanguages + TESTs (#412) * Add CreateBranch (#407) * Add Admin CronTask functions (#406) * Add GetGlobalAPISettings Function (#404) * Add Get Diff and Patch endpoints for pull requests (#398) * Add Validate func for Create/Edit Options (#370) * Add Function to get GetGlobalSettings and GetSettingAllowedReactions (#359) * ENHANCEMENTS * TrackedTime API >= 1.11.x needed (#415) * Update Milestone struct (#410) * Add Fallback for GetPullRequestDiff/Patch (#399) * DeleteToken Accept Names too (#394) * Update ListMilestoneOption struct (#393) * Migration Api Changed (#392) * Refactor Visibletype Orgs (#382) * Extend Notification Functions (#381) * Update GetGlobalSettings Functions (#376) * Allow Creating Closed Milestones (#373) * CreateLabel correct Color if needed for old versions (#365) * Issue/Pull add IsLocked Property (#357) * Update EditPullRequestOption Add Base (#353) * File Create/Update/Delete detect DefaultBranch if Branch not set for old Versions (#352) * Improve Error Handling (#351) ## [v0.12.2](https://gitea.com/gitea/go-sdk/releases/tag/gitea/v0.12.2) - 2020-09-05 * ENHANCEMENTS * Extend Notification Functions (#381) (#385) ## [v0.12.1](https://gitea.com/gitea/go-sdk/pulls?q=&type=all&state=closed&milestone=1268) - 2020-07-09 * ENHANCEMENTS * Improve Error Handling (#351) (#377) * Allow Creating Closed Milestones (#373) (#375) * File Create/Update/Delete detect DefaultBranch if Branch not set for old Versions (#352) (#372) * CreateLabel correct Color if needed for old versions (#365) (#371) * Update EditPullRequestOption Add Base (#353) (#363) ## [v0.12.0](https://gitea.com/gitea/go-sdk/pulls?q=&type=all&state=closed&milestone=1223) - 2020-05-21 * BREAKING * Support 2FA for basic auth & refactor Token functions (#335) * PullMerge: use enum for MergeStyle (#328) * Refactor List/SetRepoTopics (#276) * Remove ListUserIssues() ... (#262) * Extend SearchUsers (#248) * Fix & Refactor UserApp Functions (#247) * Add ListMilestoneOption to ListRepoMilestones (#244) * Add ListIssueCommentOptions for optional param (#243) * Refactor RepoWatch (#241) * Add Pagination Options for List Requests (#205) * FEATURES * Add BranchProtection functions (#341) * Add PullReview functions (#338) * Add Issue Subscription Check & Fix DeleteIssueSubscription (#318) * Add Branch Deletion (#317) * Add Get/Update for oauth2 apps (#311) * Add Create/Get/Delete for oauth2 apps (#305) * Add DeleteFile() (#302) * Add Get/Update/Create File (#281) * Add List/Check/SetPublic/Delete OrgMembership functions (#275) * Add ListRepoCommits (#266) * Add TransferRepo (#264) * Add SearchRepo API Call (#254) * Add ListOptions struct (#249) * Add Notification functions (#226) * Add GetIssueComment (#216) * BUGFIXES * Add missing JSON header to AddCollaborator() (#306) * On Internal Server Error, show request witch caused this (#296) * Fix MergePullRequest & extend Tests (#278) * Fix AddEmail (#260) * ENHANCEMENTS * Check if gitea is able to squash-merge via API (#336) * ListIssues: add milestones filter (#327) * Update CreateRepoOption struct (#300) * Add IssueType as filter for ListIssues (#286) * Extend ListDeployKeys (#268) * Use RepositoryMeta struct on Issues (#267) * Use StateType (#265) * Extend Issue Struct (#258) * IssueSubscribtion: Check http Status responce (#242) ## [v0.11.3](https://gitea.com/gitea/go-sdk/pulls?q=&type=all&state=closed&milestone=1259) - 2020-04-27 * BUGFIXES * Fix MergePullRequest (#278) (#316) * Add missing JSON header to AddCollaborator() (#307) ## [v0.11.2](https://gitea.com/gitea/go-sdk/pulls?q=&type=all&state=closed&milestone=1256) - 2020-03-31 * ENHANCEMENTS * On Internal Server Error, show request witch caused this (#297) ## [v0.11.1](https://gitea.com/gitea/go-sdk/pulls?q=&type=all&state=closed&milestone=1235) - 2020-03-29 * BUGFIXES * Fix SetRepoTopics (#276) (#274) * Fix AddEmail (#260) (#261) * Fix UserApp Functions (#247) (#256) * ENHANCEMENTS * Add IssueType as filter for ListIssues (#288) * Correct version (#259) ## [v0.11.0](https://gitea.com/gitea/go-sdk/pulls?q=&type=all&state=closed&milestone=1222) - 2020-01-27 * FEATURES * Add VersionCheck (#215) * Add Issue Un-/Subscription function (#214) * Add Reaction struct and functions (#213) * Add GetBlob (#212) * BUGFIXES * Fix ListIssue Functions (#225) * Fix ListRepoPullRequests (#219) * ENHANCEMENTS * Add some pull list options (#217) * Extend StopWatch struct & functions (#211) * TESTING * Add Test Framework (#227) * BUILD * Use golangci-lint and revive for linting (#220) go-sdk/CONTRIBUTING.md000066400000000000000000000172221453336351000145020ustar00rootroot00000000000000# Contribution Guidelines ## Introduction This document explains how to contribute changes to the Gitea project. It assumes you have followed the [installation instructions](https://docs.gitea.io/en-us/install-from-binary/). Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io). ## Bug reports Please search the issues on the issue tracker with a variety of keywords to ensure your bug is not already reported. If unique, [open an issue](https://gitea.com/gitea/go-sdk/issues/new) and answer the questions so we can understand and reproduce the problematic behavior. To show us that the issue you are having is in Gitea itself, please write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we can fix the issue. Check out [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html). Please be kind, remember that Gitea comes at no cost to you, and you're getting free help. ## Discuss your design The project welcomes submissions but please let everyone know what you're working on if you want to change or add something to the Gitea repositories. Before starting to write something new for the Gitea project, please [file an issue](https://gitea.com/gitea/go-sdk/issues/new). Significant changes must go through the [change proposal process](https://github.com/go-gitea/proposals) before they can be accepted. This process gives everyone a chance to validate the design, helps prevent duplication of effort, and ensures that the idea fits inside the goals for the project and tools. It also checks that the design is sound before code is written; the code review tool is not the place for high-level discussions. ## Testing redux Before sending code out for review, run all the tests for the whole tree to make sure the changes don't break other usage and keep the compatibility on upgrade. To make sure you are running the test suite exactly like we do, you should install the CLI for [Drone CI](https://github.com/drone/drone), as we are using the server for continous testing, following [these instructions](https://exec-runner.docs.drone.io/installation/). After that you can simply call `drone exec` within your working directory and it will try to run the test suite locally. ## Code review Changes to Gitea must be reviewed before they are accepted, no matter who makes the change even if it is an owner or a maintainer. We use GitHub's pull request workflow to do that and we also use [LGTM](http://lgtm.co) to ensure every PR is reviewed by at least 2 maintainers. Please try to make your pull request easy to review for us. Please read the "[How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md)" guide, it has lots of useful tips for any project you may want to contribute. Some of the key points: * Make small pull requests. The smaller, the faster to review and the more likely it will be merged soon. * Don't make changes unrelated to your PR. Maybe there are typos on some comments, maybe refactoring would be welcome on a function... but if that is not related to your PR, please make *another* PR for that. * Split big pull requests into multiple small ones. An incremental change will be faster to review than a huge PR. ## Sign your work The sign-off is a simple line at the end of the explanation for the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: If you can certify [DCO](DCO), then you just add a line to every git commit message: ``` Signed-off-by: Joe Smith ``` Please use your real name, we really dislike pseudonyms or anonymous contributions. We are in the open-source world without secrets. If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`. ## Maintainers To make sure every PR is checked, we have [team maintainers](https://github.com/orgs/go-gitea/teams/maintainers). Every PR **MUST** be reviewed by at least two maintainers (or owners) before it can get merged. A maintainer should be a contributor of Gitea (or Gogs) and contributed at least 4 accepted PRs. A contributor should apply as a maintainer in the [Gitter develop channel](https://gitter.im/go-gitea/develop). The owners or the team maintainers may invite the contributor. A maintainer should spend some time on code reviews. If a maintainer has no time to do that, they should apply to leave the maintainers team and we will give them the honor of being a member of the [advisors team](https://github.com/orgs/go-gitea/teams/advisors). Of course, if an advisor has time to code review, we will gladly welcome them back to the maintainers team. If a maintainer is inactive for more than 3 months and forgets to leave the maintainers team, the owners may move him or her from the maintainers team to the advisors team. ## Owners Since Gitea is a pure community organization without any company support, to keep the development healthy we will elect three owners every year. All contributors may vote to elect up to three candidates, one of which will be the main owner, and the other two the assistant owners. When the new owners have been elected, the old owners will give up ownership to the newly elected owners. If an owner is unable to do so, the other owners will assist in ceding ownership to the newly elected owners. After the election, the new owners should proactively agree with our [CONTRIBUTING](CONTRIBUTING.md) requirements in the [Discord](https://discord.gg/NsatcWJ) #general channel. Below are the words to speak: ``` I'm honored to having been elected an owner of Gitea, I agree with [CONTRIBUTING](CONTRIBUTING.md). I will spend part of my time on Gitea and lead the development of Gitea. ``` To honor the past owners, here's the history of the owners and the time they served: * 2016-11-04 ~ 2017-12-31 * [Lunny Xiao](https://gitea.com/lunny) * [Thomas Boerger](https://github.com/tboerger) * [Kim Carlbäcker](https://github.com/bkcsoft) * 2018-01-01 ~ 2018-12-31 * [Lunny Xiao](https://gitea.com/lunny) * [Lauris Bukšis-Haberkorns](https://github.com/lafriks) * [Kim Carlbäcker](https://github.com/bkcsoft) * 2019-01-01 ~ 2019-12-31 * [Lunny Xiao](https://gitea.com/lunny) * [Lauris Bukšis-Haberkorns](https://github.com/lafriks) * [Matti Ranta](https://github.com/techknowlogick) ## Versions Gitea has the `master` branch as a tip branch and has version branches such as `v0.9`. `v0.9` is a release branch and we will tag `v0.9.0` for binary download. If `v0.9.0` has bugs, we will accept pull requests on the `v0.9` branch and publish a `v0.9.1` tag, after bringing the bug fix also to the master branch. Since the `master` branch is a tip version, if you wish to use Gitea in production, please download the latest release tag version. All the branches will be protected via GitHub, all the PRs to every branch must be reviewed by two maintainers and must pass the automatic tests. ## Copyright Code that you contribute should use the standard copyright header: ``` // 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. ``` Files in the repository contain copyright from the year they are added to the year they are last changed. If the copyright author is changed, just paste the header below the old one. go-sdk/DCO000066400000000000000000000026161453336351000126020ustar00rootroot00000000000000Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. go-sdk/LICENSE000066400000000000000000000021111453336351000132450ustar00rootroot00000000000000Copyright (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. go-sdk/Makefile000066400000000000000000000103211453336351000137020ustar00rootroot00000000000000GO ?= go WORK_DIR := $(shell pwd) GITEA_SDK_TEST_URL ?= http://localhost:3000 GITEA_SDK_TEST_USERNAME ?= test01 GITEA_SDK_TEST_PASSWORD ?= test01 PACKAGE := code.gitea.io/sdk/gitea GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.4.0 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.0 GITEA_VET_PACKAGE ?= code.gitea.io/gitea-vet@v0.2.1 GITEA_VERSION := 1.18 GITEA_DL := https://dl.gitea.com/gitea/$(GITEA_VERSION)/gitea-$(GITEA_VERSION)- UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) GITEA_DL := $(GITEA_DL)linux- UNAME_P := $(shell uname -p) ifeq ($(UNAME_P),unknown) GITEA_DL := $(GITEA_DL)amd64 endif ifeq ($(UNAME_P),x86_64) GITEA_DL := $(GITEA_DL)amd64 endif ifneq ($(filter %86,$(UNAME_P)),) GITEA_DL := $(GITEA_DL)386 endif ifneq ($(filter arm%,$(UNAME_P)),) GITEA_DL := $(GITEA_DL)arm-5 endif endif ifeq ($(UNAME_S),Darwin) GITEA_DL := $(GITEA_DL)darwin-10.12-amd64 endif .PHONY: all all: clean test build .PHONY: help help: @echo "Make Routines:" @echo " - \"\" run \"make clean test build\"" @echo " - build build sdk" @echo " - clean clean" @echo " - fmt format the code" @echo " - lint run golint" @echo " - vet examines Go source code and reports" @echo " - test run unit tests (need a running gitea)" @echo " - test-instance start a gitea instance for test" .PHONY: clean clean: rm -r -f test cd gitea && $(GO) clean -i ./... .PHONY: fmt fmt: find . -name "*.go" -type f | xargs gofmt -s -w; \ $(GO) run $(GOFUMPT_PACKAGE) -extra -w ./gitea .PHONY: vet vet: # Default vet cd gitea && $(GO) vet $(PACKAGE) # Custom vet cd gitea && $(GO) get $(GITEA_VET_PACKAGE) cd gitea && $(GO) build code.gitea.io/gitea-vet cd gitea && $(GO) vet -vettool=gitea-vet $(PACKAGE) .PHONY: ci-lint ci-lint: @cd gitea/; echo -n "gofumpt ...";\ diff=$$($(GO) run $(GOFUMPT_PACKAGE) -extra -l .); \ if [ -n "$$diff" ]; then \ echo; echo "Not gofumpt-ed"; \ exit 1; \ fi; echo " done"; echo -n "golangci-lint ...";\ $(GO) run $(GOLANGCI_LINT_PACKAGE) run --timeout 5m; \ if [ $$? -eq 1 ]; then \ echo; echo "Doesn't pass golangci-lint"; \ exit 1; \ fi; echo " done"; \ cd -; \ .PHONY: test test: @export GITEA_SDK_TEST_URL=${GITEA_SDK_TEST_URL}; export GITEA_SDK_TEST_USERNAME=${GITEA_SDK_TEST_USERNAME}; export GITEA_SDK_TEST_PASSWORD=${GITEA_SDK_TEST_PASSWORD}; \ if [ -z "$(shell curl --noproxy "*" "${GITEA_SDK_TEST_URL}/api/v1/version" 2> /dev/null)" ]; then \echo "No test-instance detected!"; exit 1; else \ cd gitea && $(GO) test -race -cover -coverprofile coverage.out; \ fi .PHONY: test-instance test-instance: rm -f -r ${WORK_DIR}/test 2> /dev/null; \ mkdir -p ${WORK_DIR}/test/conf/ ${WORK_DIR}/test/data/ wget ${GITEA_DL} -O ${WORK_DIR}/test/gitea-main; \ chmod +x ${WORK_DIR}/test/gitea-main; \ echo "[security]" > ${WORK_DIR}/test/conf/app.ini; \ echo "INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4" >> ${WORK_DIR}/test/conf/app.ini; \ echo "INSTALL_LOCK = true" >> ${WORK_DIR}/test/conf/app.ini; \ echo "SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo" >> ${WORK_DIR}/test/conf/app.ini; \ echo "PASSWORD_COMPLEXITY = off" >> ${WORK_DIR}/test/conf/app.ini; \ echo "[database]" >> ${WORK_DIR}/test/conf/app.ini; \ echo "DB_TYPE = sqlite3" >> ${WORK_DIR}/test/conf/app.ini; \ echo "[repository]" >> ${WORK_DIR}/test/conf/app.ini; \ echo "ROOT = ${WORK_DIR}/test/data/" >> ${WORK_DIR}/test/conf/app.ini; \ echo "[server]" >> ${WORK_DIR}/test/conf/app.ini; \ echo "ROOT_URL = ${GITEA_SDK_TEST_URL}" >> ${WORK_DIR}/test/conf/app.ini; \ ${WORK_DIR}/test/gitea-main migrate -c ${WORK_DIR}/test/conf/app.ini; \ ${WORK_DIR}/test/gitea-main admin user create --username=${GITEA_SDK_TEST_USERNAME} --password=${GITEA_SDK_TEST_PASSWORD} --email=test01@gitea.io --admin=true --must-change-password=false --access-token -c ${WORK_DIR}/test/conf/app.ini; \ ${WORK_DIR}/test/gitea-main web -c ${WORK_DIR}/test/conf/app.ini .PHONY: bench bench: cd gitea && $(GO) test -run=XXXXXX -benchtime=10s -bench=. || exit 1 .PHONY: build build: cd gitea && $(GO) build go-sdk/README.md000066400000000000000000000025721453336351000135320ustar00rootroot00000000000000# Gitea SDK for Go [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Release](https://raster.shields.io/badge/dynamic/json.svg?label=release&url=https://gitea.com/api/v1/repos/gitea/go-sdk/releases&query=$[0].tag_name)](https://gitea.com/gitea/go-sdk/releases) [![Join the chat at https://img.shields.io/discord/322538954119184384.svg](https://img.shields.io/discord/322538954119184384.svg)](https://discord.gg/Gitea) [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/sdk)](https://goreportcard.com/report/code.gitea.io/sdk) [![GoDoc](https://godoc.org/code.gitea.io/sdk/gitea?status.svg)](https://godoc.org/code.gitea.io/sdk/gitea) This project acts as a client SDK implementation written in Go to interact with the Gitea API implementation. For further informations take a look at the current [documentation](https://pkg.go.dev/code.gitea.io/sdk/gitea). Note: function arguments are escaped by the SDK. ## Use it ```go import "code.gitea.io/sdk/gitea" ``` ## Version Requirements * go >= 1.13 * gitea >= 1.11 ## Contributing Fork -> Patch -> Push -> Pull Request ## Authors * [Maintainers](https://github.com/orgs/go-gitea/people) * [Contributors](https://github.com/go-gitea/go-sdk/graphs/contributors) ## License This project is under the MIT License. See the [LICENSE](LICENSE) file for the full license text. go-sdk/docs/000077500000000000000000000000001453336351000131755ustar00rootroot00000000000000go-sdk/docs/migrate-v0.11-to-v0.12.md000066400000000000000000000103411453336351000170750ustar00rootroot00000000000000# Migration Guide: v0.11 to v0.12 v0.12.0 introduces a number of breaking changes, through which it should not be difficult to migrate. Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue. - [List Functions now always need an ListOption as argument (#205) (#243) (244)](#List-Functions-now-always-need-an-ListOption-as-argument) - [Authentification was removed from all Functions (#241) (#335)](Authentification-was-removed-from-all-Functions) - [Some Functions where deleted (#247) (#262)](Some-Functions-where-deleted) - [SearchUsers arguments are move to an Option struct (#248)](SearchUsers-arguments-are-move-to-an-Option-struct) - [RepoTopics functions now expect and return string slice directly (#276)](ListRepoTopics-return-now-string-slice-directly) - [MergePullRequestOption field names changed and Enum is now used (#328)](MergePullRequestOption-field-names-changed-and-Enum-is-now-used) ## List Functions now always need an ListOption as argument since paggination is introduced in gitea v1.12.0 for all list endpoints, all List Functions acept at least **Page** and **PageSize**. If the function had had already an Option struct as argument this one is now extendet, if not a new Options type was created. - migrate old paggination arguments to the new One. - add a empty Option struct if a new one was created. Pulls: - [#205 Add Pagination Options for List Requests](https://gitea.com/gitea/go-sdk/pulls/205) - [#243 Add ListIssueCommentOptions for optional param](https://gitea.com/gitea/go-sdk/pulls/243) - [#244 Add ListMilestoneOption to ListRepoMilestones](https://gitea.com/gitea/go-sdk/pulls/244) ## Authentification was removed from all Functions for Authentification the default credentials/token is used, witch was set on Client initialisation. for RepoWatch functions remove arguments: - GetWatchedRepos: password (second) - WatchRepo: username (first), password (second) - UnWatchRepo: username (first), password (second) for Token functions remove: - the first two argument (user & password), these functions still relay on BasicAuth so if not done, just set username, password and optional otp before executing them. ```go client.SetBasicAuth(username, password) client.SetOTP(otp) ``` Pulls: - [#241 Refactor RepoWatch](https://gitea.com/gitea/go-sdk/pulls/241) - [#335 Support 2FA for basic auth & Refactor Token functions](https://gitea.com/gitea/go-sdk/pulls/335) ## Some Functions where deleted Functions where deleted because they where only workarounds or are helper functions witch could be replaced easely. - BasicAuthEncode if you realy need this just copy the function into your project: ```go func BasicAuthEncode(user, pass string) string { return base64.StdEncoding.EncodeToString([]byte(user + ":" + pass)) } ``` - ListUserIssues If you realy need this just use the Workaround witch was removed with #262 and If you have time a pull upstream to gitea for a real API is always wellcome Pulls: - [#247 Fix & Refactor UserApp Functions](https://gitea.com/gitea/go-sdk/pulls/247) - [#262 Remove ListUserIssues](https://gitea.com/gitea/go-sdk/pulls/262) ## SearchUsers arguments are move to an Option struct Old: `client.SearchUsers(query, limit)` New: `client.SearchUsers(SearchUsersOption{KeyWord: "query", Page: 1, PageSize: limit})` Pull: [#248 extend SearchUsers](https://gitea.com/gitea/go-sdk/pulls/248) ## ListRepoTopics return now string slice directly ListRepoTopics returned a struct with Topics string slice. Now it return the falue of this string slice directly Old: ```go client.SetRepoTopics(user, repo, TopicsList{topic_slice}) ``` New: ```go client.SetRepoTopics(user, repo, topic_slice) ``` Pull: [#276 Refactor List/SetRepoTopics](https://gitea.com/gitea/go-sdk/pulls/276) ## MergePullRequestOption field names changed and Enum is now used Rename **MergeTitleField** to **Title** Rename **MergeMessageField** to **Message** Do is now called Style and expect predefined falues: MergeStyleMerge, MergeStyleRebase, MergeStyleRebaseMerge & MergeStyleSquash Pull: [#328 PullMerge: use enum for MergeStyle](https://gitea.com/gitea/go-sdk/pulls/328) go-sdk/docs/migrate-v0.12-to-v0.13.md000066400000000000000000000062371453336351000171100ustar00rootroot00000000000000# Migration Guide: v0.12 to v0.13 v0.13.0 introduces a number of breaking changes, through which it should not be difficult to migrate. Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue. - [EditMilestoneOption use StateType (#350)](#EditMilestoneOption-use-StateType) - [RepoSearch Options Struct was rewritten (#346)](#RepoSearch-Options-Struct-was-rewritten) - [Variable Renames (#386)](#Variable-Renames) - [Change Type of Permission Field (#408)](#Change-Type-of-Permission-Field) - [All Function return http responce (#416)](#All-Function-return-http-responce) - [NewClient has new Option Interface (#417)](#NewClient-has-new-Option-Interface) ## EditMilestoneOption use StateType Instead of a raw string StateType is now used for State too. just replace old strings with new enum. Pulls: - [#350 EditMilestoneOption also use StateType](https://gitea.com/gitea/go-sdk/pulls/350) ## RepoSearch Options Struct was rewritten Since the API itself is ugly and there was no nameconvention whats o ever. You easely can pass the wrong options and dont get the result you want. Now it is rewritten and translated for the API. The easyest way to migrate is to look at who this function is used and rewritten that code block. If there is a special edgecase you have you can pass a `RawQuery` to the API endpoint. Pulls: - [#346 Refactor RepoSearch to be easy usable](https://gitea.com/gitea/go-sdk/pulls/346) ## Variable Renames Some names of strcut options have been renamed to describe there function/usecase more precisely. if you use `CreateOrgOption` somewhere just rename `UserName` to `Name`. Pulls: - [#386 CreateOrgOption rename UserName to Name](https://gitea.com/gitea/go-sdk/pulls/386) ## Change Type of Permission Field The following functions are affected: ListOrgTeams, ListMyTeams, GetTeam, CreateTeam, EditTeam and AddCollaborator The `Permission` field has changed type from `string` to `AccessMode`, which represent the raw strings you must use before. Just replace the string with the AccessMode equivalent. Pulls: - [#408 Use enum AccessMode for OrgTeam and Collaborator functions](https://gitea.com/gitea/go-sdk/pulls/408) ## All Function return http responce All functions got one new return (`Responce`)! If you just like to migrate, add `_,` before the error return. example: ```diff - user, err := c.GetMyUserInfo() + user, _, err := c.GetMyUserInfo() ``` If you like to check responce if an error ocure, make sure responce is not nil! If an error ocure before an http request (e.g. gitea is to old), it will be nil. Pulls: - [#416 All Function return http responce](https://gitea.com/gitea/go-sdk/pulls/416) ## NewClient has new Option Interface function `NewClient` use functional options now. If you simply like to migrate replace `client := NewClient(giteaUrl, token)` with `client, _ := NewClient(giteaURL, SetToken(token))`. If you like tu utilize them, currently there are: SetContext, SetBasicAuth, SetOTP, SetToken, SetHTTPClient, SetSudo Pulls: - [#417 Make http requests with context](https://gitea.com/gitea/go-sdk/pulls/417) go-sdk/docs/migrate-v0.13-to-v0.14.md000066400000000000000000000027411453336351000171060ustar00rootroot00000000000000# Migration Guide: v0.13 to v0.14 v0.14.0 introduces a number of breaking changes, through which it should not be difficult to migrate. Just follow this guid and if you still encounter problems, ask for help on discord or feel free to create an issue. - [Removed Functions (#467)](#removed-functions) - [Renamed Functions (#467)](#renamed-functions) - [New Optional Fields (#486)](#new-optional-fields) - [Arguemnts are escapted by the SDK iteslve now (#273)](#escape-function-arguments) ## Removed Functions - for **GetUserTrackedTimes** and **GetRepoTrackedTimes** use **ListRepoTrackedTimes** with specific options set Pulls: - [#467 Remove GetUserTrackedTimes](https://gitea.com/gitea/go-sdk/pulls/467) ## Renamed Functions - **ListTrackedTimes** is now **ListIssueTrackedTimes** Pulls: - [#467 Remove & Rename TrackedTimes list functions](https://gitea.com/gitea/go-sdk/pulls/467) ## New Optional Fields The `EditUserOption` struct has gained several new Optional fields. For example Email type changed from `string` to `*string`. The easiest migration path is, to wrap your options with: **OptionalString()**, **OptionalBool()** and **OptionalInt64()** Pulls: - [#486 Update Structs](https://gitea.com/gitea/go-sdk/pulls/486) ## Escape Function Arguments String arguments like `user`, `repo`, `tag`, ... are now url/path/query escapted as they need. If you had issues and did escape arguments by yourselve you have to remove this now. go-sdk/docs/migrate-v0.14-to-v0.15.md000066400000000000000000000015541453336351000171110ustar00rootroot00000000000000# Migration Guide: v0.14 to v0.15 v0.15.0 introduces a number of API changes, which should be simple to migrate. Just follow this guide and if you still encounter problems, ask for help on Discord or feel free to create an issue. - [Changed Struct Fields (#503) (#520)](#changed-struct-fields) ## Changed Struct Fields - The `State` field at **NotificationSubject** changed from **StateType** to **NotifySubjectState**, it also contains `"open"`, `"closed"` and add `"merged"`. - In **Issue**, **CreateIssueOption** and **EditIssueOption** structs, `Assignee` got removed. Use `Assignees`. - `Type` field at **CreateHookOption** now use **HookType** instead of pure string. Pulls: - [#503 Drop deprecations](https://gitea.com/gitea/go-sdk/pulls/503) - [#520 Introduce NotifySubjectState](https://gitea.com/gitea/go-sdk/pulls/520) go-sdk/docs/migrate-v0.15-to-v0.16.md000066400000000000000000000024471453336351000171150ustar00rootroot00000000000000# Migration Guide: v0.15 to v0.16 v0.16.0 introduces a number of API changes, which should be simple to migrate. Just follow this guide and if you still encounter problems, ask for help on Discord or feel free to create an issue. - [Upstream API changes](#upstream-api-changes) - [GetPullRequestDiff: add PullRequestDiffOption parameter (#542)](#getpullrequestdiff) ## Upstream API changes As we aim to track API changes in Gitea 1.16 with this SDK release, you may find this [summary listing of changes](https://gitea.com/gitea/go-sdk/issues/558) helpful. ## GetPullRequestDiff Added new parameter `opts PullRequestDiffOption`. Gitea 1.16 will default to omit binary file changes in diffs; if you still need that information, set `opts.Binary = true`. Related PRs: - [go-sdk#542](https://gitea.com/gitea/go-sdk/pulls/542) - [gitea#17158](https://github.com/go-gitea/gitea/pull/17158) ## ReadNotification, ReadNotifications, ReadRepoNotifications The function now has a new return argument. The read notifications will now be returned by Gitea 1.16. If you don't require this information, use a blank identifier for the return variable. Related PRs: - [go-sdk#590](https://gitea.com/gitea/go-sdk/pulls/590) - [gitea#17064](https://github.com/go-gitea/gitea/pull/17064) go-sdk/gitea/000077500000000000000000000000001453336351000133365ustar00rootroot00000000000000go-sdk/gitea/admin_cron.go000066400000000000000000000025561453336351000160060ustar00rootroot00000000000000// 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 } go-sdk/gitea/admin_org.go000066400000000000000000000022771453336351000156340ustar00rootroot00000000000000// 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 } go-sdk/gitea/admin_repo.go000066400000000000000000000012701453336351000160020ustar00rootroot00000000000000// 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 } go-sdk/gitea/admin_test.go000066400000000000000000000023661453336351000160230ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestAdminOrg(t *testing.T) { log.Println("== TestAdminOrg ==") c := newTestClient() user, _, err := c.GetMyUserInfo() assert.NoError(t, err) orgName := "NewTestOrg" newOrg, _, err := c.AdminCreateOrg(user.UserName, CreateOrgOption{ Name: orgName, FullName: orgName + " FullName", Description: "test adminCreateOrg", Visibility: VisibleTypePublic, }) assert.NoError(t, err) assert.NotEmpty(t, newOrg) assert.EqualValues(t, orgName, newOrg.UserName) orgs, _, err := c.AdminListOrgs(AdminListOrgsOptions{}) assert.NoError(t, err) if assert.True(t, len(orgs) >= 1) { orgs = orgs[len(orgs)-1:] assert.EqualValues(t, newOrg.ID, orgs[0].ID) } _, err = c.DeleteOrg(orgName) assert.NoError(t, err) } func TestAdminCronTasks(t *testing.T) { log.Println("== TestAdminCronTasks ==") c := newTestClient() tasks, _, err := c.ListCronTasks(ListCronTaskOptions{}) assert.NoError(t, err) assert.True(t, len(tasks) > 15) _, err = c.RunCronTasks(tasks[0].Name) assert.NoError(t, err) } go-sdk/gitea/admin_user.go000066400000000000000000000110161453336351000160120ustar00rootroot00000000000000// 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 } go-sdk/gitea/agent.go000066400000000000000000000013311453336351000147610ustar00rootroot00000000000000// Copyright 2022 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. //go:build !windows package gitea import ( "fmt" "net" "os" "golang.org/x/crypto/ssh/agent" ) // hasAgent returns true if the ssh agent is available func hasAgent() bool { if _, err := os.Stat(os.Getenv("SSH_AUTH_SOCK")); err != nil { return false } return true } // GetAgent returns a ssh agent func GetAgent() (agent.Agent, error) { if !hasAgent() { return nil, fmt.Errorf("no ssh agent available") } sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")) if err != nil { return nil, err } return agent.NewClient(sshAgent), nil } go-sdk/gitea/agent_windows.go000066400000000000000000000010561453336351000165370ustar00rootroot00000000000000// Copyright 2022 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. //go:build windows package gitea import ( "fmt" "github.com/davidmz/go-pageant" "golang.org/x/crypto/ssh/agent" ) // hasAgent returns true if pageant is available func hasAgent() bool { return pageant.Available() } // GetAgent returns a ssh agent func GetAgent() (agent.Agent, error) { if !hasAgent() { return nil, fmt.Errorf("no pageant available") } return pageant.New(), nil } go-sdk/gitea/attachment.go000066400000000000000000000074471453336351000160310ustar00rootroot00000000000000// 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, 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, 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, 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 } go-sdk/gitea/client.go000066400000000000000000000303221453336351000151430ustar00rootroot00000000000000// 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" "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strconv" "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.16.0" } // Client represents a thread-safe Gitea API client. type Client struct { url string accessToken string username string password string otp string sudo string userAgent string debug bool httpsigner *HTTPSign client *http.Client ctx context.Context mutex sync.RWMutex serverVersion *version.Version getVersionOnce sync.Once ignoreVersion bool // only set by SetGiteaVersion so don't need a mutex lock } // Response represents the gitea response type Response struct { *http.Response FirstPage int PrevPage int NextPage int LastPage int } // ClientOption are functions used to init a new client type ClientOption func(*Client) error // NewClient initializes and returns a API client. // Usage of all gitea.Client methods is concurrency-safe. func NewClient(url string, options ...ClientOption) (*Client, error) { client := &Client{ url: strings.TrimSuffix(url, "/"), client: &http.Client{}, ctx: context.Background(), } for _, opt := range options { if err := opt(client); err != nil { return nil, err } } if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil { if errors.Is(err, &ErrUnknownVersion{}) { return client, err } return nil, err } return client, nil } // 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) ClientOption { return func(client *Client) error { client.SetHTTPClient(httpClient) return nil } } // SetHTTPClient replaces default http.Client with user given one. func (c *Client) SetHTTPClient(client *http.Client) { c.mutex.Lock() c.client = client c.mutex.Unlock() } // SetToken is an option for NewClient to set token func SetToken(token string) ClientOption { return func(client *Client) error { client.mutex.Lock() client.accessToken = token client.mutex.Unlock() return nil } } // SetBasicAuth is an option for NewClient to set username and password func SetBasicAuth(username, password string) ClientOption { return func(client *Client) error { client.SetBasicAuth(username, password) return nil } } // UseSSHCert is an option for NewClient to enable SSH certificate authentication via HTTPSign // If you want to auth against the ssh-agent you'll need to set a principal, if you want to // use a file on disk you'll need to specify sshKey. // If you have an encrypted sshKey you'll need to also set the passphrase. func UseSSHCert(principal, sshKey, passphrase string) ClientOption { return func(client *Client) error { if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil { return err } client.mutex.Lock() defer client.mutex.Unlock() var err error client.httpsigner, err = NewHTTPSignWithCert(principal, sshKey, passphrase) if err != nil { return err } return nil } } // UseSSHPubkey is an option for NewClient to enable SSH pubkey authentication via HTTPSign // If you want to auth against the ssh-agent you'll need to set a fingerprint, if you want to // use a file on disk you'll need to specify sshKey. // If you have an encrypted sshKey you'll need to also set the passphrase. func UseSSHPubkey(fingerprint, sshKey, passphrase string) ClientOption { return func(client *Client) error { if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil { return err } client.mutex.Lock() defer client.mutex.Unlock() var err error client.httpsigner, err = NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase) if err != nil { return err } return nil } } // SetBasicAuth sets username and password func (c *Client) SetBasicAuth(username, password string) { c.mutex.Lock() c.username, c.password = username, password c.mutex.Unlock() } // SetOTP is an option for NewClient to set OTP for 2FA func SetOTP(otp string) ClientOption { return func(client *Client) error { client.SetOTP(otp) return nil } } // SetOTP sets OTP for 2FA func (c *Client) SetOTP(otp string) { c.mutex.Lock() c.otp = otp c.mutex.Unlock() } // SetContext is an option for NewClient to set the default context func SetContext(ctx context.Context) ClientOption { return func(client *Client) error { client.SetContext(ctx) return nil } } // SetContext set default 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) ClientOption { return func(client *Client) error { client.SetSudo(sudo) return nil } } // SetSudo sets username to impersonate. func (c *Client) SetSudo(sudo string) { c.mutex.Lock() c.sudo = sudo c.mutex.Unlock() } // SetUserAgent is an option for NewClient to set user-agent header func SetUserAgent(userAgent string) ClientOption { return func(client *Client) error { client.SetUserAgent(userAgent) return nil } } // SetUserAgent sets the user-agent to send with every request. func (c *Client) SetUserAgent(userAgent string) { c.mutex.Lock() c.userAgent = userAgent c.mutex.Unlock() } // SetDebugMode is an option for NewClient to enable debug mode func SetDebugMode() ClientOption { return func(client *Client) error { client.mutex.Lock() client.debug = true client.mutex.Unlock() return nil } } func newResponse(r *http.Response) *Response { response := &Response{Response: r} response.parseLinkHeader() return response } func (r *Response) parseLinkHeader() { link := r.Header.Get("Link") if link == "" { return } links := strings.Split(link, ",") for _, l := range links { u, param, ok := strings.Cut(l, ";") if !ok { continue } u = strings.Trim(u, " <>") key, value, ok := strings.Cut(strings.TrimSpace(param), "=") if !ok || key != "rel" { continue } value = strings.Trim(value, "\"") parsed, err := url.Parse(u) if err != nil { continue } page := parsed.Query().Get("page") if page == "" { continue } switch value { case "first": r.FirstPage, _ = strconv.Atoi(page) case "prev": r.PrevPage, _ = strconv.Atoi(page) case "next": r.NextPage, _ = strconv.Atoi(page) case "last": r.LastPage, _ = strconv.Atoi(page) } } } func (c *Client) getWebResponse(method, path string, body io.Reader) ([]byte, *Response, error) { c.mutex.RLock() debug := c.debug if debug { fmt.Printf("%s: %s\nBody: %v\n", method, c.url+path, body) } req, err := http.NewRequestWithContext(c.ctx, method, c.url+path, body) client := c.client // client ref can change from this point on so safe it c.mutex.RUnlock() if err != nil { return nil, nil, err } resp, err := client.Do(req) if err != nil { return nil, nil, err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if debug { fmt.Printf("Response: %v\n\n", resp) } return data, newResponse(resp), err } func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*Response, error) { c.mutex.RLock() debug := c.debug if debug { var bodyStr string if body != nil { bs, _ := io.ReadAll(body) body = bytes.NewReader(bs) bodyStr = string(bs) } fmt.Printf("%s: %s\nHeader: %v\nBody: %s\n", method, c.url+"/api/v1"+path, header, bodyStr) } req, err := http.NewRequestWithContext(c.ctx, method, c.url+"/api/v1"+path, body) if err != nil { c.mutex.RUnlock() return nil, err } if len(c.accessToken) != 0 { 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) } if len(c.userAgent) != 0 { req.Header.Set("User-Agent", c.userAgent) } client := c.client // client ref can change from this point on so safe it c.mutex.RUnlock() for k, v := range header { req.Header[k] = v } if c.httpsigner != nil { err = c.SignRequest(req) if err != nil { return nil, err } } resp, err := client.Do(req) if err != nil { return nil, err } if debug { fmt.Printf("Response: %v\n\n", resp) } return newResponse(resp), nil } // Converts a response for a HTTP status code indicating an error condition // (non-2XX) to a well-known error value and response body. For non-problematic // (2XX) status codes nil will be returned. Note that on a non-2XX response, the // response body stream will have been read and, hence, is closed on return. func statusCodeToErr(resp *Response) (body []byte, err error) { // no error if resp.StatusCode/100 == 2 { return nil, nil } // // error: body will be read for details // defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("body read on HTTP error %d: %v", resp.StatusCode, err) } // Try to unmarshal and get an error message errMap := make(map[string]interface{}) if err = json.Unmarshal(data, &errMap); err != nil { // when the JSON can't be parsed, data was probably empty or a // plain string, so we try to return a helpful error anyway path := resp.Request.URL.Path method := resp.Request.Method header := resp.Request.Header return data, fmt.Errorf("Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body", resp.StatusCode, path, method, header, string(data)) } if msg, ok := errMap["message"]; ok { return data, fmt.Errorf("%v", msg) } // If no error message, at least give status and data return data, fmt.Errorf("%s: %s", resp.Status, string(data)) } func (c *Client) getResponseReader(method, path string, header http.Header, body io.Reader) (io.ReadCloser, *Response, error) { resp, err := c.doRequest(method, path, header, body) if err != nil { return nil, resp, err } // check for errors data, err := statusCodeToErr(resp) if err != nil { return io.NopCloser(bytes.NewReader(data)), resp, err } return resp.Body, resp, nil } func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, *Response, error) { resp, err := c.doRequest(method, path, header, body) if err != nil { return nil, resp, 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 = io.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 } go-sdk/gitea/client_test.go000066400000000000000000000017171453336351000162100ustar00rootroot00000000000000// Copyright 2023 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "net/http" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestParsedPaging(t *testing.T) { resp := newResponse(&http.Response{ Header: http.Header{ "Link": []string{ strings.Join( []string{ `; rel="next"`, `; rel="last"`, `; rel="first"`, `; rel="prev"`, }, ",", ), }, }, }) assert.Equal(t, 1, resp.FirstPage) assert.Equal(t, 1, resp.PrevPage) assert.Equal(t, 3, resp.NextPage) assert.Equal(t, 4, resp.LastPage) } go-sdk/gitea/doc.go000066400000000000000000000006161453336351000144350ustar00rootroot00000000000000// 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 implements a client for the Gitea API. // The version corresponds to the highest supported version // of the gitea API, but backwards-compatibility is mostly // given. package gitea // import "code.gitea.io/sdk/gitea" go-sdk/gitea/fork.go000066400000000000000000000030011453336351000146200ustar00rootroot00000000000000// 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, 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"` // name of the forked repository Name *string `json:"name"` } // CreateFork create a fork of a repository func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, *Response, error) { if err := escapeValidatePathSegments(&user, &repo); err != nil { return nil, nil, err } body, err := json.Marshal(form) if err != nil { return nil, nil, err } 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 } go-sdk/gitea/git_blob.go000066400000000000000000000015021453336351000154440ustar00rootroot00000000000000// 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 } go-sdk/gitea/git_hook.go000066400000000000000000000045011453336351000154700ustar00rootroot00000000000000// 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 } go-sdk/gitea/go.mod000066400000000000000000000004011453336351000144370ustar00rootroot00000000000000module code.gitea.io/sdk/gitea go 1.13 require ( github.com/davidmz/go-pageant v1.0.2 github.com/go-fed/httpsig v1.1.0 github.com/hashicorp/go-version v1.5.0 github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e ) go-sdk/gitea/go.sum000066400000000000000000000070301453336351000144710ustar00rootroot00000000000000github.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/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= go-sdk/gitea/helper.go000066400000000000000000000007541453336351000151520ustar00rootroot00000000000000// 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 } go-sdk/gitea/hook.go000066400000000000000000000153341453336351000146330ustar00rootroot00000000000000// 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"` AuthorizationHeader string `json:"authorization_header"` } // 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"` AuthorizationHeader string `json:"authorization_header"` } // EditOrgHook modify one hook of an organization, with hook id and options func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) (*Response, error) { if err := escapeValidatePathSegments(&org); err != nil { return nil, err } body, err := json.Marshal(&opt) if err != nil { return nil, err } _, 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 } go-sdk/gitea/hook_validate.go000066400000000000000000000031341453336351000164770ustar00rootroot00000000000000// Copyright 2022 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/hex" "io" "net/http" ) // VerifyWebhookSignature verifies that a payload matches the X-Gitea-Signature based on a secret func VerifyWebhookSignature(secret, expected string, payload []byte) (bool, error) { hash := hmac.New(sha256.New, []byte(secret)) if _, err := hash.Write(payload); err != nil { return false, err } expectedSum, err := hex.DecodeString(expected) if err != nil { return false, err } return hmac.Equal(hash.Sum(nil), expectedSum), nil } // VerifyWebhookSignatureMiddleware is a http.Handler for verifying X-Gitea-Signature on incoming webhooks func VerifyWebhookSignatureMiddleware(secret string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var b bytes.Buffer if _, err := io.Copy(&b, r.Body); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } expected := r.Header.Get("X-Gitea-Signature") if expected == "" { http.Error(w, "no signature found", http.StatusBadRequest) return } ok, err := VerifyWebhookSignature(secret, expected, b.Bytes()) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } if !ok { http.Error(w, "invalid payload", http.StatusUnauthorized) return } r.Body = io.NopCloser(&b) next.ServeHTTP(w, r) }) } } go-sdk/gitea/hook_validate_test.go000066400000000000000000000060541453336351000175420ustar00rootroot00000000000000// Copyright 2022 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" ) // Hashers are based on https://github.com/go-gitea/gitea/blob/0dfc2e55ea258d2b1a3cd86e2b6f27a481e495ff/services/webhook/deliver.go#L105-L116 func TestVerifyWebhookSignature(t *testing.T) { secret := "s3cr3t" payload := []byte(`{"foo": "bar", "baz": true}`) hasher := hmac.New(sha256.New, []byte(secret)) hasher.Write(payload) sig := hex.EncodeToString(hasher.Sum(nil)) tt := []struct { Name string Secret string Payload string Succeed bool }{ { Name: "Correct secret and payload", Secret: "s3cr3t", Payload: `{"foo": "bar", "baz": true}`, Succeed: true, }, { Name: "Correct secret bad payload", Secret: "s3cr3t", Payload: "{}", Succeed: false, }, { Name: "Incorrect secret good payload", Secret: "secret", Payload: `{"foo": "bar", "baz": true}`, Succeed: false, }, } for _, tc := range tt { t.Run(tc.Name, func(t *testing.T) { ok, err := VerifyWebhookSignature(tc.Secret, sig, []byte(tc.Payload)) assert.NoError(t, err, "verification should not error") assert.True(t, ok == tc.Succeed, "verification should be %t", tc.Succeed) }) } } func TestVerifyWebhookSignatureHandler(t *testing.T) { secret := "s3cr3t" payload := []byte(`{"foo": "bar", "baz": true}`) hasher := hmac.New(sha256.New, []byte(secret)) hasher.Write(payload) sig := hex.EncodeToString(hasher.Sum(nil)) tt := []struct { Name string Secret string Payload string Signature string Status int }{ { Name: "Correct secret and payload", Secret: "s3cr3t", Payload: `{"foo": "bar", "baz": true}`, Signature: sig, Status: http.StatusOK, }, { Name: "Correct secret bad payload", Secret: "s3cr3t", Payload: "{}", Signature: sig, Status: http.StatusUnauthorized, }, { Name: "Incorrect secret good payload", Secret: "secret", Payload: `{"foo": "bar", "baz": true}`, Signature: sig, Status: http.StatusUnauthorized, }, { Name: "No signature", Status: http.StatusBadRequest, }, } for _, tc := range tt { t.Run(tc.Name, func(t *testing.T) { server := httptest.NewServer(VerifyWebhookSignatureMiddleware(tc.Secret)(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write(nil) }))) defer server.Close() req, err := http.NewRequest(http.MethodPost, server.URL, strings.NewReader(tc.Payload)) assert.NoError(t, err, "should create request") if tc.Signature != "" { req.Header.Set("X-Gitea-Signature", tc.Signature) } resp, err := http.DefaultClient.Do(req) assert.NoError(t, err, "request should be delivered") assert.True(t, resp.StatusCode == tc.Status, "status should be %d, but got %d", tc.Status, resp.StatusCode) }) } } go-sdk/gitea/httpsign.go000066400000000000000000000143451453336351000155340ustar00rootroot00000000000000// Copyright 2022 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "crypto" "encoding/base64" "fmt" "io" "net/http" "os" "strings" "time" "github.com/go-fed/httpsig" "golang.org/x/crypto/ssh" ) // HTTPSign contains the signer used for signing requests type HTTPSign struct { ssh.Signer cert bool } // HTTPSignConfig contains the configuration for creating a HTTPSign type HTTPSignConfig struct { fingerprint string principal string pubkey bool cert bool sshKey string passphrase string } // NewHTTPSignWithPubkey can be used to create a HTTPSign with a public key // if no fingerprint is specified it returns the first public key found func NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase string) (*HTTPSign, error) { return newHTTPSign(&HTTPSignConfig{ fingerprint: fingerprint, pubkey: true, sshKey: sshKey, passphrase: passphrase, }) } // NewHTTPSignWithCert can be used to create a HTTPSign with a certificate // if no principal is specified it returns the first certificate found func NewHTTPSignWithCert(principal, sshKey, passphrase string) (*HTTPSign, error) { return newHTTPSign(&HTTPSignConfig{ principal: principal, cert: true, sshKey: sshKey, passphrase: passphrase, }) } // NewHTTPSign returns a new HTTPSign // It will check the ssh-agent or a local file is config.sshKey is set. // Depending on the configuration it will either use a certificate or a public key func newHTTPSign(config *HTTPSignConfig) (*HTTPSign, error) { var signer ssh.Signer if config.sshKey != "" { priv, err := os.ReadFile(config.sshKey) if err != nil { return nil, err } if config.passphrase == "" { signer, err = ssh.ParsePrivateKey(priv) if err != nil { return nil, err } } else { signer, err = ssh.ParsePrivateKeyWithPassphrase(priv, []byte(config.passphrase)) if err != nil { return nil, err } } if config.cert { certbytes, err := os.ReadFile(config.sshKey + "-cert.pub") if err != nil { return nil, err } pub, _, _, _, err := ssh.ParseAuthorizedKey(certbytes) if err != nil { return nil, err } cert, ok := pub.(*ssh.Certificate) if !ok { return nil, fmt.Errorf("failed to parse certificate") } signer, err = ssh.NewCertSigner(cert, signer) if err != nil { return nil, err } } } else { // if no sshKey is specified, check if we have a ssh-agent and use it agent, err := GetAgent() if err != nil { return nil, err } signers, err := agent.Signers() if err != nil { return nil, err } if len(signers) == 0 { return nil, fmt.Errorf("no signers found") } if config.cert { signer = findCertSigner(signers, config.principal) if signer == nil { return nil, fmt.Errorf("no certificate found for %s", config.principal) } } if config.pubkey { signer = findPubkeySigner(signers, config.fingerprint) if signer == nil { return nil, fmt.Errorf("no public key found for %s", config.fingerprint) } } } return &HTTPSign{ Signer: signer, cert: config.cert, }, nil } // SignRequest signs a HTTP request func (c *Client) SignRequest(r *http.Request) error { var contents []byte headersToSign := []string{httpsig.RequestTarget, "(created)", "(expires)"} if c.httpsigner.cert { // add our certificate to the headers to sign pubkey, _ := ssh.ParsePublicKey(c.httpsigner.Signer.PublicKey().Marshal()) if cert, ok := pubkey.(*ssh.Certificate); ok { certString := base64.RawStdEncoding.EncodeToString(cert.Marshal()) r.Header.Add("x-ssh-certificate", certString) headersToSign = append(headersToSign, "x-ssh-certificate") } else { return fmt.Errorf("no ssh certificate found") } } // if we have a body, the Digest header will be added and we'll include this also in // our signature. if r.Body != nil { body, err := r.GetBody() if err != nil { return fmt.Errorf("getBody() failed: %s", err) } contents, err = io.ReadAll(body) if err != nil { return fmt.Errorf("failed reading body: %s", err) } headersToSign = append(headersToSign, "Digest") } // create a signer for the request and headers, the signature will be valid for 10 seconds signer, _, err := httpsig.NewSSHSigner(c.httpsigner.Signer, httpsig.DigestSha512, headersToSign, httpsig.Signature, 10) if err != nil { return fmt.Errorf("httpsig.NewSSHSigner failed: %s", err) } // sign the request, use the fingerprint if we don't have a certificate keyID := "gitea" if !c.httpsigner.cert { keyID = ssh.FingerprintSHA256(c.httpsigner.Signer.PublicKey()) } err = signer.SignRequest(keyID, r, contents) if err != nil { return fmt.Errorf("httpsig.Signrequest failed: %s", err) } return nil } // findCertSigner returns the Signer containing a valid certificate // if no principal is specified it returns the first certificate found func findCertSigner(sshsigners []ssh.Signer, principal string) ssh.Signer { for _, s := range sshsigners { // Check if the key is a certificate if !strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") { continue } // convert the ssh.Signer to a ssh.Certificate mpubkey, _ := ssh.ParsePublicKey(s.PublicKey().Marshal()) cryptopub := mpubkey.(crypto.PublicKey) cert := cryptopub.(*ssh.Certificate) t := time.Unix(int64(cert.ValidBefore), 0) // make sure the certificate is at least 10 seconds valid if time.Until(t) <= time.Second*10 { continue } if principal == "" { return s } for _, p := range cert.ValidPrincipals { if p == principal { return s } } } return nil } // findPubkeySigner returns the Signer containing a valid public key // if no fingerprint is specified it returns the first public key found func findPubkeySigner(sshsigners []ssh.Signer, fingerprint string) ssh.Signer { for _, s := range sshsigners { // Check if the key is a certificate if strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") { continue } if fingerprint == "" { return s } if strings.TrimSpace(string(ssh.MarshalAuthorizedKey(s.PublicKey()))) == fingerprint { return s } if ssh.FingerprintSHA256(s.PublicKey()) == fingerprint { return s } } return nil } go-sdk/gitea/issue.go000066400000000000000000000220531453336351000150170ustar00rootroot00000000000000// 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 // filter by owner (only works on ListIssues on User) Owner string // filter by team (requires organization owner parameter to be provided and only works on ListIssues on User) Team string } // StateType issue state type 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) } if len(opt.Owner) > 0 { query.Add("owner", opt.Owner) } if len(opt.Team) > 0 { query.Add("team", 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 } // DeleteIssue delete a issue from a repository func (c *Client) DeleteIssue(user, repo string, id int64) (*Response, error) { if err := escapeValidatePathSegments(&user, &repo); err != nil { return nil, err } _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d", user, repo, id), nil, nil) return resp, err } func (c *Client) issueBackwardsCompatibility(issue *Issue) { if c.checkServerVersionGreaterThanOrEqual(version1_12_0) != nil { c.mutex.RLock() issue.HTMLURL = fmt.Sprintf("%s/%s/issues/%d", c.url, issue.Repository.FullName, issue.Index) c.mutex.RUnlock() } } go-sdk/gitea/issue_comment.go000066400000000000000000000120711453336351000165400ustar00rootroot00000000000000// 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 } go-sdk/gitea/issue_comment_test.go000066400000000000000000000054471453336351000176100ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) // TestIssueComment creat a issue and test comment creation/edit/deletion on it func TestIssueComment(t *testing.T) { log.Println("== TestIssueComment ==") c := newTestClient() user, _, err := c.GetMyUserInfo() assert.NoError(t, err) repo, err := createTestRepo(t, "TestIssueCommentRepo", c) assert.NoError(t, err) issue1, _, err := c.CreateIssue(user.UserName, repo.Name, CreateIssueOption{Title: "issue1", Body: "body", Closed: false}) assert.NoError(t, err) assert.EqualValues(t, 1, issue1.Index) issue2, _, err := c.CreateIssue(user.UserName, repo.Name, CreateIssueOption{Title: "issue1", Body: "body", Closed: false}) assert.EqualValues(t, 2, issue2.Index) assert.NoError(t, err) tUser2 := createTestUser(t, "Commenter2", c) tUser3 := createTestUser(t, "Commenter3", c) createOne := func(u *User, issue int64, text string) { c.sudo = u.UserName comment, _, e := c.CreateIssueComment(user.UserName, repo.Name, issue, CreateIssueCommentOption{Body: text}) c.sudo = "" assert.NoError(t, e) assert.NotEmpty(t, comment) assert.EqualValues(t, text, comment.Body) assert.EqualValues(t, u.ID, comment.Poster.ID) } // CreateIssue createOne(user, 1, "what a nice issue") createOne(tUser2, 1, "dont think so") createOne(tUser3, 1, "weow weow") createOne(user, 1, "spam isn't it?") createOne(tUser3, 2, "hehe first commit") createOne(tUser2, 2, "second") createOne(user, 2, "3") _, err = c.AdminDeleteUser(tUser3.UserName) assert.NoError(t, err) // ListRepoIssueComments comments, _, err := c.ListRepoIssueComments(user.UserName, repo.Name, ListIssueCommentOptions{}) assert.NoError(t, err) assert.Len(t, comments, 7) // ListIssueComments comments, _, err = c.ListIssueComments(user.UserName, repo.Name, 2, ListIssueCommentOptions{}) assert.NoError(t, err) assert.Len(t, comments, 3) // GetIssueComment comment, _, err := c.GetIssueComment(user.UserName, repo.Name, comments[1].ID) assert.NoError(t, err) assert.EqualValues(t, comment.Poster.ID, comments[1].Poster.ID) assert.EqualValues(t, comment.Body, comments[1].Body) assert.EqualValues(t, comment.Updated.Unix(), comments[1].Updated.Unix()) // EditIssueComment comment, _, err = c.EditIssueComment(user.UserName, repo.Name, comments[1].ID, EditIssueCommentOption{ Body: "changed my mind", }) assert.NoError(t, err) assert.EqualValues(t, "changed my mind", comment.Body) // DeleteIssueComment _, err = c.DeleteIssueComment(user.UserName, repo.Name, comments[1].ID) assert.NoError(t, err) _, _, err = c.GetIssueComment(user.UserName, repo.Name, comments[1].ID) assert.Error(t, err) } go-sdk/gitea/issue_label.go000066400000000000000000000152541453336351000161630ustar00rootroot00000000000000// 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 } go-sdk/gitea/issue_label_test.go000066400000000000000000000103311453336351000172110ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) // TestLabels test label related func func TestLabels(t *testing.T) { log.Println("== TestLabels ==") c := newTestClient() repo, err := createTestRepo(t, "LabelTestsRepo", c) assert.NoError(t, err) createOpts := CreateLabelOption{ Name: " ", Description: "", Color: "", } err = createOpts.Validate() assert.Error(t, err) assert.EqualValues(t, "invalid color format", err.Error()) createOpts.Color = "12345f" err = createOpts.Validate() assert.Error(t, err) assert.EqualValues(t, "empty name not allowed", err.Error()) createOpts.Name = "label one" labelOne, _, err := c.CreateLabel(repo.Owner.UserName, repo.Name, createOpts) assert.NoError(t, err) assert.EqualValues(t, createOpts.Name, labelOne.Name) assert.EqualValues(t, createOpts.Color, labelOne.Color) labelTwo, _, err := c.CreateLabel(repo.Owner.UserName, repo.Name, CreateLabelOption{ Name: "blue", Color: "#0000FF", Description: "CMYB(100%, 100%, 0%, 0%)", }) assert.NoError(t, err) _, _, err = c.CreateLabel(repo.Owner.UserName, repo.Name, CreateLabelOption{ Name: "gray", Color: "808080", Description: "CMYB(0%, 0%, 0%, 50%)", }) assert.NoError(t, err) _, _, err = c.CreateLabel(repo.Owner.UserName, repo.Name, CreateLabelOption{ Name: "green", Color: "#98F76C", Description: "CMYB(38%, 0%, 56%, 3%)", }) assert.NoError(t, err) labels, resp, err := c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{ListOptions: ListOptions{PageSize: 3}}) assert.NoError(t, err) assert.Len(t, labels, 3) assert.NotNil(t, resp) assert.Contains(t, labels, labelTwo) assert.NotContains(t, labels, labelOne) label, _, err := c.GetRepoLabel(repo.Owner.UserName, repo.Name, labelTwo.ID) assert.NoError(t, err) assert.EqualValues(t, labelTwo, label) label, _, err = c.EditLabel(repo.Owner.UserName, repo.Name, labelTwo.ID, EditLabelOption{ Color: OptionalString("#0E0175"), Description: OptionalString("blueish"), }) assert.NoError(t, err) assert.EqualValues(t, &Label{ ID: labelTwo.ID, Name: labelTwo.Name, Color: "0E0175", Description: "blueish", URL: labelTwo.URL, }, label) labels, _, _ = c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{ListOptions: ListOptions{PageSize: 3}}) createTestIssue(t, c, repo.Name, "test-issue", "", nil, nil, 0, []int64{label.ID}, false, false) issueIndex := int64(1) issueLabels, _, err := c.GetIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, ListLabelsOptions{}) assert.NoError(t, err) assert.Len(t, issueLabels, 1) assert.EqualValues(t, label, issueLabels[0]) _, _, err = c.AddIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, IssueLabelsOption{Labels: []int64{labels[0].ID}}) assert.NoError(t, err) issueLabels, _, err = c.AddIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, IssueLabelsOption{Labels: []int64{labels[1].ID, labels[2].ID}}) assert.NoError(t, err) assert.Len(t, issueLabels, 3) assert.EqualValues(t, labels, issueLabels) labels, _, _ = c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{}) assert.Len(t, labels, 11) issueLabels, _, err = c.ReplaceIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, IssueLabelsOption{Labels: []int64{labels[0].ID, labels[1].ID}}) assert.NoError(t, err) assert.Len(t, issueLabels, 2) _, err = c.DeleteIssueLabel(repo.Owner.UserName, repo.Name, issueIndex, labels[0].ID) assert.NoError(t, err) issueLabels, _, _ = c.GetIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, ListLabelsOptions{}) assert.Len(t, issueLabels, 1) _, err = c.ClearIssueLabels(repo.Owner.UserName, repo.Name, issueIndex) assert.NoError(t, err) issueLabels, _, _ = c.GetIssueLabels(repo.Owner.UserName, repo.Name, issueIndex, ListLabelsOptions{}) assert.Len(t, issueLabels, 0) _, err = c.DeleteLabel(repo.Owner.UserName, repo.Name, labelTwo.ID) assert.NoError(t, err) labels, _, _ = c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{}) assert.Len(t, labels, 10) } go-sdk/gitea/issue_milestone.go000066400000000000000000000171411453336351000171000ustar00rootroot00000000000000// 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, name string) (*Milestone, *Response, error) { if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { // backwards compatibility mode m, resp, err := c.resolveMilestoneByName(owner, repo, name) return m, resp, err } if err := escapeValidatePathSegments(&owner, &repo, &name); err != nil { return nil, nil, err } milestone := new(Milestone) resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%s", owner, repo, name), nil, nil, milestone) return milestone, resp, err } // 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, name string, opt EditMilestoneOption) (*Milestone, *Response, error) { if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != nil { // backwards compatibility mode m, _, err := c.resolveMilestoneByName(owner, repo, name) if err != nil { 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, name string) (*Response, error) { if c.checkServerVersionGreaterThanOrEqual(version1_13_0) != 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.EqualFold(strings.TrimSpace(m.Title), strings.TrimSpace(name)) { return m, resp, nil } } } } go-sdk/gitea/issue_milestone_test.go000066400000000000000000000055471453336351000201460ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "time" "github.com/stretchr/testify/assert" ) func TestMilestones(t *testing.T) { log.Println("== TestMilestones ==") c := newTestClient() repo, _ := createTestRepo(t, "TestMilestones", c) now := time.Now() future := time.Unix(1896134400, 0) // 2030-02-01 closed := "closed" sClosed := StateClosed // CreateMilestone 4x m1, _, err := c.CreateMilestone(repo.Owner.UserName, repo.Name, CreateMilestoneOption{Title: "v1.0", Description: "First Version", Deadline: &now}) assert.NoError(t, err) _, _, err = c.CreateMilestone(repo.Owner.UserName, repo.Name, CreateMilestoneOption{Title: "v2.0", Description: "Second Version", Deadline: &future}) assert.NoError(t, err) _, _, err = c.CreateMilestone(repo.Owner.UserName, repo.Name, CreateMilestoneOption{Title: "v3.0", Description: "Third Version", Deadline: nil}) assert.NoError(t, err) m4, _, err := c.CreateMilestone(repo.Owner.UserName, repo.Name, CreateMilestoneOption{Title: "temp", Description: "part time milestone"}) assert.NoError(t, err) // EditMilestone m1, _, err = c.EditMilestone(repo.Owner.UserName, repo.Name, m1.ID, EditMilestoneOption{Description: &closed, State: &sClosed}) assert.NoError(t, err) // DeleteMilestone _, err = c.DeleteMilestone(repo.Owner.UserName, repo.Name, m4.ID) assert.NoError(t, err) // ListRepoMilestones ml, _, err := c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{}) assert.NoError(t, err) assert.Len(t, ml, 2) ml, _, err = c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{State: StateClosed}) assert.NoError(t, err) assert.Len(t, ml, 1) ml, _, err = c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{State: StateAll}) assert.NoError(t, err) assert.Len(t, ml, 3) ml, _, err = c.ListRepoMilestones(repo.Owner.UserName, repo.Name, ListMilestoneOption{State: StateAll, Name: "V3.0"}) assert.NoError(t, err) assert.Len(t, ml, 1) assert.EqualValues(t, "v3.0", ml[0].Title) // test fallback resolveMilestoneByName m, _, err := c.resolveMilestoneByName(repo.Owner.UserName, repo.Name, "V3.0") assert.NoError(t, err) assert.EqualValues(t, ml[0].ID, m.ID) _, _, err = c.resolveMilestoneByName(repo.Owner.UserName, repo.Name, "NoEvidenceOfExist") assert.Error(t, err) assert.EqualValues(t, "milestone 'NoEvidenceOfExist' do not exist", err.Error()) // GetMilestone _, _, err = c.GetMilestone(repo.Owner.UserName, repo.Name, m4.ID) assert.Error(t, err) m, _, err = c.GetMilestone(repo.Owner.UserName, repo.Name, m1.ID) assert.NoError(t, err) assert.EqualValues(t, m1, m) m2, _, err := c.GetMilestoneByName(repo.Owner.UserName, repo.Name, m.Title) assert.NoError(t, err) assert.EqualValues(t, m, m2) } go-sdk/gitea/issue_reaction.go000066400000000000000000000073671453336351000167160ustar00rootroot00000000000000// 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 } go-sdk/gitea/issue_stopwatch.go000066400000000000000000000040531453336351000171130ustar00rootroot00000000000000// 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 } go-sdk/gitea/issue_subscription.go000066400000000000000000000057631453336351000176340ustar00rootroot00000000000000// 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) } go-sdk/gitea/issue_subscription_test.go000066400000000000000000000031561453336351000206650ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) // TestIssue is main func witch call all Tests for Issue API // (to make sure they are on correct order) func TestIssueSubscription(t *testing.T) { log.Println("== TestIssueSubscription ==") c := newTestClient() repo, _ := createTestRepo(t, "IssueWatch", c) createTestIssue(t, c, repo.Name, "First Issue", "", nil, nil, 0, nil, false, false) wi, _, err := c.CheckIssueSubscription(repo.Owner.UserName, repo.Name, 1) assert.NoError(t, err) assert.True(t, wi.Subscribed) _, err = c.UnWatchRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) wi, _, err = c.CheckIssueSubscription(repo.Owner.UserName, repo.Name, 1) assert.NoError(t, err) assert.True(t, wi.Subscribed) _, err = c.IssueSubscribe(repo.Owner.UserName, repo.Name, 1) if assert.Error(t, err) { assert.EqualValues(t, "already subscribed", err.Error()) } wi, _, err = c.CheckIssueSubscription(repo.Owner.UserName, repo.Name, 1) assert.NoError(t, err) assert.True(t, wi.Subscribed) _, err = c.IssueUnSubscribe(repo.Owner.UserName, repo.Name, 1) assert.NoError(t, err) wi, _, err = c.CheckIssueSubscription(repo.Owner.UserName, repo.Name, 1) assert.NoError(t, err) assert.False(t, wi.Subscribed) _, err = c.WatchRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) wi, _, err = c.CheckIssueSubscription(repo.Owner.UserName, repo.Name, 1) assert.NoError(t, err) assert.False(t, wi.Subscribed) } go-sdk/gitea/issue_template.go000066400000000000000000000074231453336351000167160ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "fmt" ) // IssueTemplate provides metadata and content on an issue template. // There are two types of issue templates: .Markdown- and .Form-based. type IssueTemplate struct { Name string `json:"name"` About string `json:"about"` Filename string `json:"file_name"` IssueTitle string `json:"title"` IssueLabels []string `json:"labels"` IssueRef string `json:"ref"` // If non-nil, this is a form-based template Form []IssueFormElement `json:"body"` // Should only be used when .Form is nil. MarkdownContent string `json:"content"` } // IssueFormElement describes a part of a IssueTemplate form type IssueFormElement struct { ID string `json:"id"` Type IssueFormElementType `json:"type"` Attributes IssueFormElementAttributes `json:"attributes"` Validations IssueFormElementValidations `json:"validations"` } // IssueFormElementAttributes contains the combined set of attributes available on all element types. type IssueFormElementAttributes struct { // required for all element types. // A brief description of the expected user input, which is also displayed in the form. Label string `json:"label"` // required for element types "dropdown", "checkboxes" // for dropdown, contains the available options Options []string `json:"options"` // for element types "markdown", "textarea", "input" // Text that is pre-filled in the input Value string `json:"value"` // for element types "textarea", "input", "dropdown", "checkboxes" // A description of the text area to provide context or guidance, which is displayed in the form. Description string `json:"description"` // for element types "textarea", "input" // A semi-opaque placeholder that renders in the text area when empty. Placeholder string `json:"placeholder"` // for element types "textarea" // A language specifier. If set, the input is rendered as codeblock with syntax highlighting. SyntaxHighlighting string `json:"render"` // for element types "dropdown" Multiple bool `json:"multiple"` } // IssueFormElementValidations contains the combined set of validations available on all element types. type IssueFormElementValidations struct { // for all element types Required bool `json:"required"` // for element types "input" IsNumber bool `json:"is_number"` // for element types "input" Regex string `json:"regex"` } // IssueFormElementType is an enum type IssueFormElementType string const ( // IssueFormElementMarkdown is markdown rendered to the form for context, but omitted in the resulting issue IssueFormElementMarkdown IssueFormElementType = "markdown" // IssueFormElementTextarea is a multi line input IssueFormElementTextarea IssueFormElementType = "textarea" // IssueFormElementInput is a single line input IssueFormElementInput IssueFormElementType = "input" // IssueFormElementDropdown is a select form IssueFormElementDropdown IssueFormElementType = "dropdown" // IssueFormElementCheckboxes are a multi checkbox input IssueFormElementCheckboxes IssueFormElementType = "checkboxes" ) // GetIssueTemplates lists all issue templates of the repository func (c *Client) GetIssueTemplates(owner, repo string) ([]*IssueTemplate, *Response, error) { if err := escapeValidatePathSegments(&owner, &repo); err != nil { return nil, nil, err } templates := new([]*IssueTemplate) resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issue_templates", owner, repo), nil, nil, templates) return *templates, resp, err } // IsForm tells if this template is a form instead of a markdown-based template. func (t IssueTemplate) IsForm() bool { return t.Form != nil } go-sdk/gitea/issue_test.go000066400000000000000000000122721453336351000160600ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "time" "github.com/stretchr/testify/assert" ) // TestIssue is main func witch call all Tests for Issue API // (to make sure they are on correct order) func TestIssue(t *testing.T) { c := newTestClient() createIssue(t, c) // Little sleep in order to give some time for gitea to properly store all information on database. Without this sleep, CI is a bit unstable time.Sleep(100 * time.Millisecond) editIssues(t, c) listIssues(t, c) deleteIssue(t, c) } func createIssue(t *testing.T, c *Client) { log.Println("== TestCreateIssues ==") user, _, err := c.GetMyUserInfo() assert.NoError(t, err) repo, _ := createTestRepo(t, "IssueTestsRepo", c) nowTime := time.Now() mile, _, _ := c.CreateMilestone(user.UserName, repo.Name, CreateMilestoneOption{Title: "mile1"}) label1, _, _ := c.CreateLabel(user.UserName, repo.Name, CreateLabelOption{Name: "Label1", Description: "a", Color: "#ee0701"}) label2, _, _ := c.CreateLabel(user.UserName, repo.Name, CreateLabelOption{Name: "Label2", Description: "b", Color: "#128a0c"}) createTestIssue(t, c, repo.Name, "First Issue", "", nil, nil, 0, nil, false, false) createTestIssue(t, c, repo.Name, "Issue 2", "closed isn't it?", nil, nil, 0, nil, true, false) createTestIssue(t, c, repo.Name, "Issue 3", "", nil, nil, 0, nil, true, false) createTestIssue(t, c, repo.Name, "Feature: spam protect 4", "explain explain explain", []string{user.UserName}, &nowTime, 0, nil, true, false) createTestIssue(t, c, repo.Name, "W 123", "", nil, &nowTime, mile.ID, nil, false, false) createTestIssue(t, c, repo.Name, "First Issue", "", nil, nil, 0, nil, false, false) createTestIssue(t, c, repo.Name, "Do it soon!", "is important!", []string{user.UserName}, &nowTime, mile.ID, []int64{label1.ID, label2.ID}, false, false) createTestIssue(t, c, repo.Name, "Job Done", "you never know", nil, nil, mile.ID, []int64{label2.ID}, true, false) createTestIssue(t, c, repo.Name, "", "you never know", nil, nil, mile.ID, nil, true, true) } func deleteIssue(t *testing.T, c *Client) { log.Println("== TestDeleteIssues ==") user, _, err := c.GetMyUserInfo() assert.NoError(t, err) repo, _ := createTestRepo(t, "IssueTestsRepo", c) issue := createTestIssue(t, c, repo.Name, "Deleteable Issue", "", nil, nil, 0, nil, false, false) _, err = c.DeleteIssue(user.UserName, repo.Name, issue.Index) assert.NoError(t, err) } func editIssues(t *testing.T, c *Client) { log.Println("== TestEditIssues ==") il, _, err := c.ListIssues(ListIssueOption{KeyWord: "soon"}) assert.NoError(t, err) issue, _, err := c.GetIssue(il[0].Poster.UserName, il[0].Repository.Name, il[0].Index) assert.NoError(t, err) state := StateClosed issueNew, _, err := c.EditIssue(issue.Poster.UserName, issue.Repository.Name, issue.Index, EditIssueOption{ Title: "Edited", Body: OptionalString("123 test and go"), State: &state, Ref: OptionalString("main"), }) assert.NoError(t, err) assert.EqualValues(t, issue.ID, issueNew.ID) assert.EqualValues(t, "123 test and go", issueNew.Body) assert.EqualValues(t, "Edited", issueNew.Title) assert.EqualValues(t, "main", issueNew.Ref) } func listIssues(t *testing.T, c *Client) { log.Println("== TestListIssues ==") issues, _, err := c.ListRepoIssues("test01", "IssueTestsRepo", ListIssueOption{ Labels: []string{"Label1", "Label2"}, KeyWord: "", State: "all", }) assert.NoError(t, err) assert.Len(t, issues, 1) issues, _, err = c.ListIssues(ListIssueOption{ Labels: []string{"Label2"}, KeyWord: "Done", State: "all", }) assert.NoError(t, err) assert.Len(t, issues, 1) issues, _, err = c.ListRepoIssues("test01", "IssueTestsRepo", ListIssueOption{ Milestones: []string{"mile1"}, State: "all", }) assert.NoError(t, err) assert.Len(t, issues, 3) for i := range issues { if assert.NotNil(t, issues[i].Milestone) { assert.EqualValues(t, "mile1", issues[i].Milestone.Title) } } issues, _, err = c.ListRepoIssues("test01", "IssueTestsRepo", ListIssueOption{}) assert.NoError(t, err) assert.Len(t, issues, 3) } func createTestIssue(t *testing.T, c *Client, repoName, title, body string, assignees []string, deadline *time.Time, milestone int64, labels []int64, closed, shouldFail bool) *Issue { user, _, err := c.GetMyUserInfo() assert.NoError(t, err) issue, _, e := c.CreateIssue(user.UserName, repoName, CreateIssueOption{ Title: title, Body: body, Assignees: assignees, Deadline: deadline, Milestone: milestone, Labels: labels, Closed: closed, }) if shouldFail { assert.Error(t, e) return nil } assert.NoError(t, e) assert.NotEmpty(t, issue) assert.EqualValues(t, title, issue.Title) assert.EqualValues(t, body, issue.Body) assert.EqualValues(t, len(assignees), len(issue.Assignees)) for i, a := range issue.Assignees { assert.EqualValues(t, assignees[i], a.UserName) } if milestone > 0 { assert.EqualValues(t, milestone, issue.Milestone.ID) } assert.EqualValues(t, len(labels), len(issue.Labels)) if closed { assert.False(t, issue.Closed.IsZero()) } else { assert.Empty(t, issue.Closed) } return issue } go-sdk/gitea/issue_tracked_time.go000066400000000000000000000106751453336351000175410ustar00rootroot00000000000000// 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 } go-sdk/gitea/list_options.go000066400000000000000000000020761453336351000164200ustar00rootroot00000000000000// 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" ) // ListOptions options for using Gitea's API pagination type ListOptions struct { // Setting Page to -1 disables pagination on endpoints that support it. // Page numbering starts at 1. Page int // The default value depends on the server config DEFAULT_PAGING_NUM // The highest valid value depends on the server config MAX_RESPONSE_ITEMS PageSize int } 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 applies default pagination options. // If .Page is set to -1, it will disable pagination. // WARNING: This function is not idempotent, make sure to never call this method twice! func (o *ListOptions) setDefaults() { if o.Page < 0 { o.Page, o.PageSize = 0, 0 return } else if o.Page == 0 { o.Page = 1 } } go-sdk/gitea/main_test.go000066400000000000000000000070541453336351000156560ustar00rootroot00000000000000// 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" "io" "io/ioutil" "log" "net/http" "os" "os/exec" "path/filepath" "runtime" "strconv" "testing" ) func getGiteaURL() string { return os.Getenv("GITEA_SDK_TEST_URL") } func getGiteaToken() string { return os.Getenv("GITEA_SDK_TEST_TOKEN") } func getGiteaUsername() string { return os.Getenv("GITEA_SDK_TEST_USERNAME") } func getGiteaPassword() string { return os.Getenv("GITEA_SDK_TEST_PASSWORD") } func enableRunGitea() bool { r, _ := strconv.ParseBool(os.Getenv("GITEA_SDK_TEST_RUN_GITEA")) return r } func newTestClient() *Client { c, _ := NewClient(getGiteaURL(), newTestClientAuth()) return c } func newTestClientAuth() ClientOption { token := getGiteaToken() if token == "" { return SetBasicAuth(getGiteaUsername(), getGiteaPassword()) } return SetToken(getGiteaToken()) } func giteaMasterPath() string { switch runtime.GOOS { case "darwin": return fmt.Sprintf("https://dl.gitea.io/gitea/master/gitea-master-darwin-10.6-%s", runtime.GOARCH) case "linux": return fmt.Sprintf("https://dl.gitea.io/gitea/master/gitea-master-linux-%s", runtime.GOARCH) case "windows": return fmt.Sprintf("https://dl.gitea.io/gitea/master/gitea-master-windows-4.0-%s.exe", runtime.GOARCH) } return "" } func downGitea() (string, error) { for i := 3; i > 0; i-- { resp, err := http.Get(giteaMasterPath()) if err != nil { continue } defer resp.Body.Close() f, err := ioutil.TempFile(os.TempDir(), "gitea") if err != nil { continue } _, err = io.Copy(f, resp.Body) f.Close() if err != nil { continue } if err = os.Chmod(f.Name(), 0o700); err != nil { return "", err } return f.Name(), nil } return "", fmt.Errorf("Download gitea from %v failed", giteaMasterPath()) } func runGitea() (*os.Process, error) { log.Println("Downloading Gitea from", giteaMasterPath()) p, err := downGitea() if err != nil { log.Fatal(err) } giteaDir := filepath.Dir(p) cfgDir := filepath.Join(giteaDir, "custom", "conf") err = os.MkdirAll(cfgDir, os.ModePerm) if err != nil { log.Fatal(err) } cfg, err := os.Create(filepath.Join(cfgDir, "app.ini")) if err != nil { log.Fatal(err) } _, err = cfg.WriteString(`[security] INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE1NTg4MzY4ODB9.LoKQyK5TN_0kMJFVHWUW0uDAyoGjDP6Mkup4ps2VJN4 INSTALL_LOCK = true SECRET_KEY = 2crAW4UANgvLipDS6U5obRcFosjSJHQANll6MNfX7P0G3se3fKcCwwK3szPyGcbo [database] DB_TYPE = sqlite3 [log] MODE = console LEVEL = Trace REDIRECT_MACARON_LOG = true MACARON = , ROUTER = ,`) cfg.Close() if err != nil { log.Fatal(err) } log.Println("Run gitea migrate", p) err = exec.Command(p, "migrate").Run() if err != nil { log.Fatal(err) } log.Println("Run gitea admin", p) err = exec.Command(p, "admin", "create-user", "--username=test01", "--password=test01", "--email=test01@gitea.io", "--admin=true", "--must-change-password=false", "--access-token").Run() if err != nil { log.Fatal(err) } log.Println("Start Gitea", p) return os.StartProcess(filepath.Base(p), []string{}, &os.ProcAttr{ Dir: giteaDir, }) } func TestMain(m *testing.M) { if enableRunGitea() { p, err := runGitea() if err != nil { log.Fatal(err) return } defer func() { if err := p.Kill(); err != nil { log.Fatal(err) } }() } log.Printf("testing with %v, %v, %v\n", getGiteaURL(), getGiteaUsername(), getGiteaPassword()) exitCode := m.Run() os.Exit(exitCode) } go-sdk/gitea/notifications.go000066400000000000000000000217141453336351000165430ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "fmt" "net/url" "time" ) // NotificationThread expose Notification on API type NotificationThread struct { ID int64 `json:"id"` Repository *Repository `json:"repository"` Subject *NotificationSubject `json:"subject"` Unread bool `json:"unread"` Pinned bool `json:"pinned"` UpdatedAt time.Time `json:"updated_at"` URL string `json:"url"` } // NotificationSubject contains the notification subject (Issue/Pull/Commit) type NotificationSubject struct { Title string `json:"title"` URL string `json:"url"` HTMLURL string `json:"html_url"` LatestCommentURL string `json:"latest_comment_url"` LatestCommentHTMLURL string `json:"latest_comment_html_url"` Type NotifySubjectType `json:"type"` State NotifySubjectState `json:"state"` } // NotifyStatus notification status type 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' // The relevant notification will be returned as the first parameter when the Gitea server is 1.16.0 or higher. func (c *Client) ReadNotification(id int64, status ...NotifyStatus) (*NotificationThread, *Response, error) { if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { return nil, nil, err } link := fmt.Sprintf("/notifications/threads/%d", id) if len(status) != 0 { link += fmt.Sprintf("?to-status=%s", status[0]) } if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil { thread := &NotificationThread{} resp, err := c.getParsedResponse("PATCH", link, nil, nil, thread) return thread, resp, err } _, resp, err := c.getResponse("PATCH", link, nil, nil) return nil, 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 // The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher. func (c *Client) ReadNotifications(opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) { if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { return nil, nil, err } if err := opt.Validate(c); err != nil { return nil, nil, err } link, _ := url.Parse("/notifications") link.RawQuery = opt.QueryEncode() if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil { threads := make([]*NotificationThread, 0, 10) resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads) return threads, resp, err } _, resp, err := c.getResponse("PUT", link.String(), nil, nil) return nil, 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 // The relevant notifications will only be returned as the first parameter when the Gitea server is 1.16.0 or higher. func (c *Client) ReadRepoNotifications(owner, repo string, opt MarkNotificationOptions) ([]*NotificationThread, *Response, error) { if err := escapeValidatePathSegments(&owner, &repo); err != nil { return nil, nil, err } if err := c.checkServerVersionGreaterThanOrEqual(version1_12_0); err != nil { return nil, nil, err } if err := opt.Validate(c); err != nil { return nil, nil, err } link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/notifications", owner, repo)) link.RawQuery = opt.QueryEncode() if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err == nil { threads := make([]*NotificationThread, 0, 10) resp, err := c.getParsedResponse("PUT", link.String(), nil, nil, &threads) return threads, resp, err } _, resp, err := c.getResponse("PUT", link.String(), nil, nil) return nil, resp, err } go-sdk/gitea/notifications_test.go000066400000000000000000000111201453336351000175700ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "fmt" "log" "testing" "time" "github.com/stretchr/testify/assert" ) func TestNotifications(t *testing.T) { log.Println("== TestNotifications ==") // init user2 c := newTestClient() user1, _, err := c.GetMyUserInfo() assert.NoError(t, err) user2 := createTestUser(t, "notify2", c) // create 2 repos repoA, err := createTestRepo(t, "TestNotifications_A", c) assert.NoError(t, err) c.sudo = user2.UserName repoB, err := createTestRepo(t, "TestNotifications_B", c) assert.NoError(t, err) _, err = c.WatchRepo(user1.UserName, repoA.Name) c.sudo = "" assert.NoError(t, err) c.sudo = user2.UserName notifications, _, err := c.ReadNotifications(MarkNotificationOptions{}) assert.NoError(t, err) assert.Len(t, notifications, 0) count, _, err := c.CheckNotifications() assert.EqualValues(t, 0, count) assert.NoError(t, err) c.sudo = "" _, _, err = c.CreateIssue(repoA.Owner.UserName, repoA.Name, CreateIssueOption{Title: "A Issue", Closed: false}) assert.NoError(t, err) issue, _, err := c.CreateIssue(repoB.Owner.UserName, repoB.Name, CreateIssueOption{Title: "B Issue", Closed: false}) assert.NoError(t, err) time.Sleep(time.Second * 1) // CheckNotifications of user2 c.sudo = user2.UserName count, _, err = c.CheckNotifications() assert.NoError(t, err) assert.EqualValues(t, 2, count) // ListNotifications nList, _, err := c.ListNotifications(ListNotificationOptions{}) assert.NoError(t, err) assert.Len(t, nList, 2) for _, n := range nList { assert.EqualValues(t, true, n.Unread) assert.EqualValues(t, "Issue", n.Subject.Type) assert.EqualValues(t, NotifySubjectOpen, nList[0].Subject.State) assert.EqualValues(t, NotifySubjectOpen, nList[1].Subject.State) if n.Subject.Title == "A Issue" { assert.EqualValues(t, repoA.Name, n.Repository.Name) } else if n.Subject.Title == "B Issue" { assert.EqualValues(t, repoB.Name, n.Repository.Name) } else { assert.Error(t, fmt.Errorf("ListNotifications returned a Issue witch should not")) } } // ListRepoNotifications nList, _, err = c.ListRepoNotifications(repoA.Owner.UserName, repoA.Name, ListNotificationOptions{}) assert.NoError(t, err) assert.Len(t, nList, 1) assert.EqualValues(t, "A Issue", nList[0].Subject.Title) // ReadRepoNotifications notifications, _, err = c.ReadRepoNotifications(repoA.Owner.UserName, repoA.Name, MarkNotificationOptions{}) assert.NoError(t, err) assert.Len(t, notifications, 1) // GetThread n, _, err := c.GetNotification(nList[0].ID) assert.NoError(t, err) assert.EqualValues(t, false, n.Unread) assert.EqualValues(t, "A Issue", n.Subject.Title) // ReadNotifications notifications, _, err = c.ReadNotifications(MarkNotificationOptions{}) assert.NoError(t, err) assert.Len(t, notifications, 1) nList, _, err = c.ListNotifications(ListNotificationOptions{}) assert.NoError(t, err) assert.Len(t, nList, 0) // ReadThread iState := StateClosed c.sudo = "" _, _, err = c.EditIssue(repoB.Owner.UserName, repoB.Name, issue.Index, EditIssueOption{State: &iState}) assert.NoError(t, err) time.Sleep(time.Second * 1) c.sudo = user2.UserName nList, _, err = c.ListNotifications(ListNotificationOptions{}) assert.NoError(t, err) count, _, err = c.CheckNotifications() assert.NoError(t, err) assert.EqualValues(t, 1, count) if assert.Len(t, nList, 1) { assert.EqualValues(t, NotifySubjectClosed, nList[0].Subject.State) notification, _, err := c.ReadNotification(nList[0].ID) assert.NoError(t, err) assert.EqualValues(t, notification.ID, nList[0].ID) } c.sudo = "" notifications, _, err = c.ReadNotifications(MarkNotificationOptions{}) assert.NoError(t, err) assert.Len(t, notifications, 2) _, _ = c.DeleteRepo("test01", "Reviews") nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusRead}}) assert.NoError(t, err) assert.Len(t, nList, 2) notification, _, err := c.ReadNotification(nList[0].ID, NotifyStatusPinned) assert.EqualValues(t, notification.ID, nList[0].ID) assert.NoError(t, err) notification, _, err = c.ReadNotification(nList[1].ID, NotifyStatusUnread) assert.EqualValues(t, notification.ID, nList[1].ID) assert.NoError(t, err) nList, _, err = c.ListNotifications(ListNotificationOptions{Status: []NotifyStatus{NotifyStatusPinned, NotifyStatusUnread}}) assert.NoError(t, err) if assert.Len(t, nList, 2) { assert.EqualValues(t, NotifySubjectOpen, nList[0].Subject.State) assert.EqualValues(t, NotifySubjectOpen, nList[1].Subject.State) } } go-sdk/gitea/oauth2.go000066400000000000000000000063131453336351000150720ustar00rootroot00000000000000// 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"` ConfidentialClient bool `json:"confidential_client"` 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"` ConfidentialClient bool `json:"confidential_client"` RedirectURIs []string `json:"redirect_uris"` } // CreateOauth2 create an Oauth2 Application and returns a completed Oauth2 object. func (c *Client) CreateOauth2(opt CreateOauth2Option) (*Oauth2, *Response, error) { if err := c.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 } go-sdk/gitea/oauth2_test.go000066400000000000000000000050341453336351000161300ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestOauth2(t *testing.T) { log.Println("== TestOauth2Application ==") c := newTestClient() user := createTestUser(t, "oauth2_user", c) c.SetSudo(user.UserName) type test struct { name string confidentialClient *bool } boolTrue := true boolFalse := false testCases := []test{ {"ConfidentialClient unset should fallback to false", nil}, {"ConfidentialClient true", &boolTrue}, {"ConfidentialClient false", &boolFalse}, } for _, testCase := range testCases { createOptions := CreateOauth2Option{ Name: "test", RedirectURIs: []string{"http://test/test"}, } if testCase.confidentialClient != nil { createOptions.ConfidentialClient = *testCase.confidentialClient } newApp, _, err := c.CreateOauth2(createOptions) assert.NoError(t, err, testCase.name) assert.NotNil(t, newApp, testCase.name) assert.EqualValues(t, "test", newApp.Name, testCase.name) if testCase.confidentialClient != nil { assert.EqualValues(t, *testCase.confidentialClient, newApp.ConfidentialClient, testCase.name) } else { assert.EqualValues(t, false, newApp.ConfidentialClient, testCase.name) } a, _, err := c.ListOauth2(ListOauth2Option{}) assert.NoError(t, err, testCase.name) assert.Len(t, a, 1, testCase.name) assert.EqualValues(t, newApp.Name, a[0].Name, testCase.name) assert.EqualValues(t, newApp.ConfidentialClient, a[0].ConfidentialClient, testCase.name) b, _, err := c.GetOauth2(newApp.ID) assert.NoError(t, err, testCase.name) assert.EqualValues(t, newApp.Name, b.Name, testCase.name) assert.EqualValues(t, newApp.ConfidentialClient, b.ConfidentialClient, testCase.name) b, _, err = c.UpdateOauth2(newApp.ID, CreateOauth2Option{ Name: newApp.Name, ConfidentialClient: !newApp.ConfidentialClient, RedirectURIs: []string{"https://test/login"}, }) assert.NoError(t, err, testCase.name) assert.EqualValues(t, newApp.Name, b.Name, testCase.name) assert.EqualValues(t, "https://test/login", b.RedirectURIs[0], testCase.name) assert.EqualValues(t, newApp.ID, b.ID, testCase.name) assert.NotEqual(t, newApp.ClientSecret, b.ClientSecret, testCase.name) assert.NotEqual(t, newApp.ConfidentialClient, b.ConfidentialClient, testCase.name) _, err = c.DeleteOauth2(newApp.ID) assert.NoError(t, err, testCase.name) } } go-sdk/gitea/org.go000066400000000000000000000115361453336351000144620ustar00rootroot00000000000000// 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 } go-sdk/gitea/org_action.go000066400000000000000000000015271453336351000160160ustar00rootroot00000000000000// Copyright 2023 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "fmt" "net/url" ) // ListOrgMembershipOption list OrgMembership options type ListOrgActionSecretOption struct { ListOptions } // ListOrgMembership list an organization's members func (c *Client) ListOrgActionSecret(org string, opt ListOrgActionSecretOption) ([]*Secret, *Response, error) { if err := escapeValidatePathSegments(&org); err != nil { return nil, nil, err } opt.setDefaults() secrets := make([]*Secret, 0, opt.PageSize) link, _ := url.Parse(fmt.Sprintf("/orgs/%s/actions/secrets", org)) link.RawQuery = opt.getURLQuery().Encode() resp, err := c.getParsedResponse("GET", link.String(), jsonHeader, nil, &secrets) return secrets, resp, err } go-sdk/gitea/org_member.go000066400000000000000000000106741453336351000160130ustar00rootroot00000000000000// 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) } } // OrgPermissions represents the permissions for an user in an organization type OrgPermissions struct { CanCreateRepository bool `json:"can_create_repository"` CanRead bool `json:"can_read"` CanWrite bool `json:"can_write"` IsAdmin bool `json:"is_admin"` IsOwner bool `json:"is_owner"` } // GetOrgPermissions returns user permissions for specific organization. func (c *Client) GetOrgPermissions(org, user string) (*OrgPermissions, *Response, error) { if err := escapeValidatePathSegments(&org, &user); err != nil { return nil, nil, err } perm := &OrgPermissions{} resp, err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs/%s/permissions", user, org), jsonHeader, nil, &perm) if err != nil { return nil, resp, err } return perm, resp, nil } go-sdk/gitea/org_member_test.go000066400000000000000000000036371453336351000170530ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) /* // DeleteOrgMembership remove a member from an organization func (c *Client) DeleteOrgMembership(org, user string) error {} */ func TestOrgMembership(t *testing.T) { log.Println("== TestOrgMembership ==") c := newTestClient() user := createTestUser(t, "org_mem_user", c) c.SetSudo(user.UserName) newOrg, _, err := c.CreateOrg(CreateOrgOption{Name: "MemberOrg"}) assert.NoError(t, err) assert.NotNil(t, newOrg) // Check func check, _, err := c.CheckPublicOrgMembership(newOrg.UserName, user.UserName) assert.NoError(t, err) assert.False(t, check) check, _, err = c.CheckOrgMembership(newOrg.UserName, user.UserName) assert.NoError(t, err) assert.True(t, check) perm, _, err := c.GetOrgPermissions(newOrg.UserName, user.UserName) assert.NoError(t, err) assert.NotNil(t, perm) assert.True(t, perm.IsOwner) _, err = c.SetPublicOrgMembership(newOrg.UserName, user.UserName, true) assert.NoError(t, err) check, _, err = c.CheckPublicOrgMembership(newOrg.UserName, user.UserName) assert.NoError(t, err) assert.True(t, check) u, _, err := c.ListOrgMembership(newOrg.UserName, ListOrgMembershipOption{}) assert.NoError(t, err) assert.Len(t, u, 1) assert.EqualValues(t, user.UserName, u[0].UserName) u, _, err = c.ListPublicOrgMembership(newOrg.UserName, ListOrgMembershipOption{}) assert.NoError(t, err) assert.Len(t, u, 1) assert.EqualValues(t, user.UserName, u[0].UserName) _, err = c.DeleteOrgMembership(newOrg.UserName, user.UserName) assert.Error(t, err) c.sudo = "" _, err = c.AdminDeleteUser(user.UserName) assert.Error(t, err) _, err = c.DeleteOrg(newOrg.UserName) assert.NoError(t, err) _, err = c.AdminDeleteUser(user.UserName) assert.NoError(t, err) } go-sdk/gitea/org_team.go000066400000000000000000000232731453336351000154710ustar00rootroot00000000000000// 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" ) // 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" // RepoUnitPackages represents packages of a repository RepoUnitPackages RepoUnitType = "repo.packages" ) // 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 } // SearchTeamsOptions options for searching teams. type SearchTeamsOptions struct { ListOptions Query string IncludeDescription bool } func (o SearchTeamsOptions) getURLQuery() url.Values { query := make(url.Values) query.Add("page", fmt.Sprintf("%d", o.Page)) query.Add("limit", fmt.Sprintf("%d", o.PageSize)) query.Add("q", o.Query) query.Add("include_desc", fmt.Sprintf("%t", o.IncludeDescription)) return query } // TeamSearchResults is the JSON struct that is returned from Team search API. type TeamSearchResults struct { OK bool `json:"ok"` Error string `json:"error"` Data []*Team `json:"data"` } // SearchOrgTeams search for teams in a org. func (c *Client) SearchOrgTeams(org string, opt *SearchTeamsOptions) ([]*Team, *Response, error) { responseBody := TeamSearchResults{} opt.setDefaults() resp, err := c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/teams/search?%s", org, opt.getURLQuery().Encode()), nil, nil, &responseBody) if err != nil { return nil, resp, err } if !responseBody.OK { return nil, resp, fmt.Errorf("gitea error: %v", responseBody.Error) } return responseBody.Data, resp, err } // CreateTeamOption options for creating a team type CreateTeamOption struct { Name string `json:"name"` Description string `json:"description"` Permission AccessMode `json:"permission"` CanCreateOrgRepo bool `json:"can_create_org_repo"` IncludesAllRepositories bool `json:"includes_all_repositories"` 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 } go-sdk/gitea/org_team_test.go000066400000000000000000000027071453336351000165270ustar00rootroot00000000000000// Copyright 2021 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func createTestOrgTeams(t *testing.T, c *Client, org, name string, accessMode AccessMode, units []RepoUnitType) (*Team, error) { team, _, e := c.CreateTeam(org, CreateTeamOption{ Name: name, Description: name + "'s team desc", Permission: accessMode, CanCreateOrgRepo: false, IncludesAllRepositories: false, Units: units, }) assert.NoError(t, e) assert.NotNil(t, team) return team, e } func TestTeamSearch(t *testing.T) { log.Println("== TestTeamSearch ==") c := newTestClient() orgName := "TestTeamsOrg" // prepare for test _, _, err := c.CreateOrg(CreateOrgOption{ Name: orgName, Visibility: VisibleTypePublic, RepoAdminChangeTeamAccess: true, }) defer func() { _, _ = c.DeleteOrg(orgName) }() assert.NoError(t, err) if _, err = createTestOrgTeams(t, c, orgName, "Admins", AccessModeAdmin, []RepoUnitType{RepoUnitCode, RepoUnitIssues, RepoUnitPulls, RepoUnitReleases}); err != nil { return } teams, _, err := c.SearchOrgTeams(orgName, &SearchTeamsOptions{ Query: "Admins", }) assert.NoError(t, err) if assert.Len(t, teams, 1) { assert.Equal(t, "Admins", teams[0].Name) } } go-sdk/gitea/org_test.go000066400000000000000000000021441453336351000155140ustar00rootroot00000000000000// Copyright 2021 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "testing" "github.com/stretchr/testify/assert" ) func createTestOrgRepo(t *testing.T, c *Client, name string) (func(), *Repository, error) { _, _, err := c.GetOrg(name) if err == nil { _, _ = c.DeleteOrg(name) } _, _, err = c.CreateOrg(CreateOrgOption{ Name: name, Visibility: VisibleTypePublic, RepoAdminChangeTeamAccess: true, }) if !assert.NoError(t, err) { return nil, nil, err } _, _, err = c.GetRepo(name, name) if err == nil { _, _ = c.DeleteRepo(name, name) } repo, _, err := c.CreateOrgRepo(name, CreateRepoOption{ Name: name, Description: "A test Repo: " + name, AutoInit: true, Gitignores: "C,C++", License: "MIT", Readme: "Default", IssueLabels: "Default", Private: false, }) assert.NoError(t, err) assert.NotNil(t, repo) return func() { _, _ = c.DeleteRepo(name, name) _, _ = c.DeleteOrg(name) }, repo, err } go-sdk/gitea/package.go000066400000000000000000000062021453336351000152600ustar00rootroot00000000000000// Copyright 2023 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "fmt" "time" ) // Package represents a package type Package struct { // the package's id ID int64 `json:"id"` // the package's owner Owner User `json:"owner"` // the repo this package belongs to (if any) Repository *string `json:"repository"` // the package's creator Creator User `json:"creator"` // the type of package: Type string `json:"type"` // the name of the package Name string `json:"name"` // the version of the package Version string `json:"version"` // the date the package was uploaded CreatedAt time.Time `json:"created_at"` } // PackageFile represents a file from a package type PackageFile struct { // the file's ID ID int64 `json:"id"` // the size of the file in bytes Size int64 `json:"size"` // the name of the file Name string `json:"name"` // the md5 hash of the file MD5 string `json:"md5"` // the sha1 hash of the file SHA1 string `json:"sha1"` // the sha256 hash of the file SHA256 string `json:"sha256"` // the sha512 hash of the file SHA512 string `json:"sha512"` } // ListPackagesOptions options for listing packages type ListPackagesOptions struct { ListOptions } // ListPackages lists all the packages owned by a given owner (user, organisation) func (c *Client) ListPackages(owner string, opt ListPackagesOptions) ([]*Package, *Response, error) { if err := escapeValidatePathSegments(&owner); err != nil { return nil, nil, err } opt.setDefaults() packages := make([]*Package, 0, opt.PageSize) resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s?%s", owner, opt.getURLQuery().Encode()), nil, nil, &packages) return packages, resp, err } // GetPackage gets the details of a specific package version func (c *Client) GetPackage(owner, packageType, name, version string) (*Package, *Response, error) { if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil { return nil, nil, err } foundPackage := new(Package) resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil, foundPackage) return foundPackage, resp, err } // DeletePackage deletes a specific package version func (c *Client) DeletePackage(owner, packageType, name, version string) (*Response, error) { if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil { return nil, err } _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/packages/%s/%s/%s/%s", owner, packageType, name, version), nil, nil) return resp, err } // ListPackageFiles lists the files within a package func (c *Client) ListPackageFiles(owner, packageType, name, version string) ([]*PackageFile, *Response, error) { if err := escapeValidatePathSegments(&owner, &packageType, &name, &version); err != nil { return nil, nil, err } packageFiles := make([]*PackageFile, 0) resp, err := c.getParsedResponse("GET", fmt.Sprintf("/packages/%s/%s/%s/%s/files", owner, packageType, name, version), nil, nil, &packageFiles) return packageFiles, resp, err } go-sdk/gitea/package_test.go000066400000000000000000000052221453336351000163200ustar00rootroot00000000000000// Copyright 2023 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "bytes" "fmt" "log" "net/http" "os" "testing" "time" "github.com/stretchr/testify/assert" ) // create an org with a single package for testing purposes func createTestPackage(t *testing.T, c *Client) error { _, _ = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1") _, _ = c.DeleteOrg("PackageOrg") _, _, _ = c.CreateOrg(CreateOrgOption{Name: "PackageOrg"}) client := &http.Client{ Timeout: time.Second * 10, } reader := bytes.NewReader([]byte("Hello world!")) url := fmt.Sprintf("%s/api/packages/PackageOrg/generic/MyPackage/v1/file1.txt", os.Getenv("GITEA_SDK_TEST_URL")) req, err := http.NewRequest(http.MethodPut, url, reader) if err != nil { log.Println(err) return err } req.SetBasicAuth(os.Getenv("GITEA_SDK_TEST_USERNAME"), os.Getenv("GITEA_SDK_TEST_PASSWORD")) response, err := client.Do(req) if err != nil { return err } defer response.Body.Close() return nil } func TestListPackages(t *testing.T) { log.Println("== TestListPackages ==") c := newTestClient() err := createTestPackage(t, c) assert.NoError(t, err) packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{ ListOptions{ Page: 1, PageSize: 1000, }, }) assert.NoError(t, err) assert.Len(t, packagesList, 1) } func TestGetPackage(t *testing.T) { log.Println("== TestGetPackage ==") c := newTestClient() err := createTestPackage(t, c) assert.NoError(t, err) pkg, _, err := c.GetPackage("PackageOrg", "generic", "MyPackage", "v1") assert.NoError(t, err) assert.NotNil(t, pkg) assert.True(t, pkg.Name == "MyPackage") assert.True(t, pkg.Version == "v1") assert.NotEmpty(t, pkg.CreatedAt) } func TestDeletePackage(t *testing.T) { log.Println("== TestDeletePackage ==") c := newTestClient() err := createTestPackage(t, c) assert.NoError(t, err) _, err = c.DeletePackage("PackageOrg", "generic", "MyPackage", "v1") assert.NoError(t, err) // no packages should be listed following deletion packagesList, _, err := c.ListPackages("PackageOrg", ListPackagesOptions{ ListOptions{ Page: 1, PageSize: 1000, }, }) assert.NoError(t, err) assert.Len(t, packagesList, 0) } func TestListPackageFiles(t *testing.T) { log.Println("== TestListPackageFiles ==") c := newTestClient() err := createTestPackage(t, c) assert.NoError(t, err) packageFiles, _, err := c.ListPackageFiles("PackageOrg", "generic", "MyPackage", "v1") assert.NoError(t, err) assert.Len(t, packageFiles, 1) assert.True(t, packageFiles[0].Name == "file1.txt") } go-sdk/gitea/pull.go000066400000000000000000000323211453336351000146420ustar00rootroot00000000000000// 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"` AllowMaintainerEdit bool `json:"allow_maintainer_edit"` 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"` } // ChangedFile is a changed file in a diff type ChangedFile struct { Filename string `json:"filename"` PreviousFilename string `json:"previous_filename"` Status string `json:"status"` Additions int `json:"additions"` Deletions int `json:"deletions"` Changes int `json:"changes"` HTMLURL string `json:"html_url"` ContentsURL string `json:"contents_url"` RawURL string `json:"raw_url"` } // ListPullRequestsOptions options for listing pull requests type ListPullRequestsOptions struct { ListOptions 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"` RemoveDeadline *bool `json:"unset_due_date"` AllowMaintainerEdit *bool `json:"allow_maintainer_edit"` } // 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"` MergeCommitID string `json:"MergeCommitID"` Title string `json:"MergeTitleField"` Message string `json:"MergeMessageField"` DeleteBranchAfterMerge bool `json:"delete_branch_after_merge"` ForceMerge bool `json:"force_merge"` HeadCommitId string `json:"head_commit_id"` MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed"` } // Validate the MergePullRequestOption struct func (opt MergePullRequestOption) Validate(c *Client) error { if opt.Style == MergeStyleSquash { if err := c.checkServerVersionGreaterThanOrEqual(version1_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 } // PullRequestDiffOptions options for GET /repos///pulls/.[diff|patch] type PullRequestDiffOptions struct { // Include binary file changes when requesting a .diff Binary bool } // QueryEncode converts the options to a query string func (o PullRequestDiffOptions) QueryEncode() string { query := make(url.Values) query.Add("binary", fmt.Sprintf("%v", o.Binary)) return query.Encode() } type pullRequestDiffType string const ( pullRequestDiffTypeDiff pullRequestDiffType = "diff" pullRequestDiffTypePatch pullRequestDiffType = "patch" ) // getPullRequestDiffOrPatch gets the patch or diff file as bytes for a PR func (c *Client) getPullRequestDiffOrPatch(owner, repo string, kind pullRequestDiffType, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) { if err := escapeValidatePathSegments(&owner, &repo); err != nil { return nil, nil, err } if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { r, _, err2 := c.GetRepo(owner, repo) if err2 != nil { return nil, nil, err } if r.Private { return nil, nil, err } url := fmt.Sprintf("/%s/%s/pulls/%d.%s?%s", owner, repo, index, kind, opts.QueryEncode()) return c.getWebResponse("GET", url, nil) } return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d.%s", owner, repo, index, kind), nil, nil) } // GetPullRequestPatch gets the git patchset of a PR func (c *Client) GetPullRequestPatch(owner, repo string, index int64) ([]byte, *Response, error) { return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypePatch, index, PullRequestDiffOptions{}) } // GetPullRequestDiff gets the diff of a PR. For Gitea >= 1.16, you must set includeBinary to get an applicable diff func (c *Client) GetPullRequestDiff(owner, repo string, index int64, opts PullRequestDiffOptions) ([]byte, *Response, error) { return c.getPullRequestDiffOrPatch(owner, repo, pullRequestDiffTypeDiff, index, opts) } // ListPullRequestCommitsOptions options for listing pull requests type ListPullRequestCommitsOptions struct { ListOptions } // ListPullRequestCommits list commits for a pull request func (c *Client) ListPullRequestCommits(owner, repo string, index int64, opt ListPullRequestCommitsOptions) ([]*Commit, *Response, error) { if err := escapeValidatePathSegments(&owner, &repo); err != nil { return nil, nil, err } link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/commits", owner, repo, index)) opt.setDefaults() commits := make([]*Commit, 0, opt.PageSize) link.RawQuery = opt.getURLQuery().Encode() resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits) return commits, resp, err } // fixPullHeadSha is a workaround for https://github.com/go-gitea/gitea/issues/12675 // When no head sha is available, this is because the branch got deleted in the base repo. // pr.Head.Ref points in this case not to the head repo branch name, but the base repo ref, // which stays available to resolve the commit sha. This is fixed for gitea >= 1.14.0 func fixPullHeadSha(client *Client, pr *PullRequest) error { if pr.Base != nil && pr.Base.Repository != nil && pr.Base.Repository.Owner != nil && pr.Head != nil && pr.Head.Ref != "" && pr.Head.Sha == "" { owner := pr.Base.Repository.Owner.UserName repo := pr.Base.Repository.Name refs, _, err := client.GetRepoRefs(owner, repo, pr.Head.Ref) if err != nil { return err } else if len(refs) == 0 { return fmt.Errorf("unable to resolve PR ref '%s'", pr.Head.Ref) } pr.Head.Sha = refs[0].Object.SHA } return nil } // ListPullRequestFilesOptions options for listing pull request files type ListPullRequestFilesOptions struct { ListOptions } // ListPullRequestFiles list changed files for a pull request func (c *Client) ListPullRequestFiles(owner, repo string, index int64, opt ListPullRequestFilesOptions) ([]*ChangedFile, *Response, error) { if err := escapeValidatePathSegments(&owner, &repo); err != nil { return nil, nil, err } link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/pulls/%d/files", owner, repo, index)) opt.setDefaults() files := make([]*ChangedFile, 0, opt.PageSize) link.RawQuery = opt.getURLQuery().Encode() resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &files) return files, resp, err } go-sdk/gitea/pull_review.go000066400000000000000000000252621453336351000162310ustar00rootroot00000000000000// 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(opt.Comments) == 0 && 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 } go-sdk/gitea/pull_review_test.go000066400000000000000000000157571453336351000173000ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestPullReview(t *testing.T) { log.Println("== TestPullReview ==") c := newTestClient() repoName := "Reviews" repo, pull, submitter, reviewer, success := preparePullReviewTest(t, c, repoName) if !success { return } // CreatePullReview r1, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{ State: ReviewStateComment, Body: "I'll have a look at it later", }) assert.NoError(t, err) if assert.NotNil(t, r1) { assert.EqualValues(t, ReviewStateComment, r1.State) assert.EqualValues(t, 1, r1.Reviewer.ID) } c.SetSudo(submitter.UserName) _, _, err = c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{ State: ReviewStateApproved, Body: "lgtm it myself", }) assert.Error(t, err) r2, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{ State: ReviewStateComment, Body: "no seriously please have a look at it", }) assert.NoError(t, err) assert.NotNil(t, r2) c.SetSudo(reviewer.UserName) r3, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{ State: ReviewStateApproved, Body: "lgtm", Comments: []CreatePullReviewComment{ { Path: "WOW-file", Body: "no better name - really?", NewLineNum: 1, }, }, }) assert.NoError(t, err) assert.NotNil(t, r3) // ListPullReviews c.SetSudo("") rl, _, err := c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{}) assert.NoError(t, err) assert.Len(t, rl, 3) for i := range rl { assert.EqualValues(t, pull.HTMLURL, rl[i].HTMLPullURL) if rl[i].CodeCommentsCount == 1 { assert.EqualValues(t, reviewer.ID, rl[i].Reviewer.ID) } } // GetPullReview rNew, _, err := c.GetPullReview(repo.Owner.UserName, repo.Name, pull.Index, r3.ID) assert.NoError(t, err) assert.EqualValues(t, r3, rNew) // DeletePullReview c.SetSudo(submitter.UserName) _, err = c.DeletePullReview(repo.Owner.UserName, repo.Name, pull.Index, r2.ID) assert.NoError(t, err) _, err = c.DeletePullReview(repo.Owner.UserName, repo.Name, pull.Index, r3.ID) assert.Error(t, err) // SubmitPullReview c.SetSudo("") r4, resp, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{ Body: "...", Comments: []CreatePullReviewComment{ { Path: "WOW-file", Body: "its ok", NewLineNum: 1, }, }, }) assert.NoError(t, err) assert.NotNil(t, resp) r5, _, err := c.CreatePullReview(repo.Owner.UserName, repo.Name, pull.Index, CreatePullReviewOptions{ Body: "...", Comments: []CreatePullReviewComment{ { Path: "WOW-file", Body: "hehe and here it is", NewLineNum: 3, }, }, }) assert.NoError(t, err) assert.EqualValues(t, r4.ID, r5.ID) r, _, err := c.SubmitPullReview(repo.Owner.UserName, repo.Name, pull.Index, r4.ID, SubmitPullReviewOptions{ State: ReviewStateRequestChanges, Body: "one nit", }) assert.NoError(t, err) assert.EqualValues(t, r4.ID, r.ID) assert.EqualValues(t, ReviewStateRequestChanges, r.State) // ListPullReviewComments rcl, _, err := c.ListPullReviewComments(repo.Owner.UserName, repo.Name, pull.Index, r.ID) assert.NoError(t, err) assert.EqualValues(t, r.CodeCommentsCount, len(rcl)) for _, rc := range rcl { assert.EqualValues(t, pull.HTMLURL, rc.HTMLPullURL) if rc.LineNum == 3 { assert.EqualValues(t, "hehe and here it is", rc.Body) } else { assert.EqualValues(t, 1, rc.LineNum) assert.EqualValues(t, "its ok", rc.Body) } } r, _, err = c.GetPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID) assert.NoError(t, err) assert.False(t, r.Dismissed) // DismissPullReview resp, err = c.DismissPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID, DismissPullReviewOptions{Message: "stale"}) assert.NoError(t, err) if assert.NotNil(t, resp) { assert.EqualValues(t, 200, resp.StatusCode) } r, _, _ = c.GetPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID) assert.True(t, r.Dismissed) // UnDismissPullReview resp, err = c.UnDismissPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID) assert.NoError(t, err) if assert.NotNil(t, resp) { assert.EqualValues(t, 200, resp.StatusCode) } r, _, _ = c.GetPullReview(repo.Owner.UserName, repo.Name, pull.Index, r.ID) assert.False(t, r.Dismissed) rl, _, err = c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{}) assert.NoError(t, err) assert.Len(t, rl, 3) c.SetSudo(submitter.UserName) resp, err = c.CreateReviewRequests(repo.Owner.UserName, repo.Name, pull.Index, PullReviewRequestOptions{Reviewers: []string{reviewer.UserName}}) assert.NoError(t, err) assert.NotNil(t, resp) rl, _, _ = c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{}) if assert.Len(t, rl, 4) { assert.EqualValues(t, ReviewStateRequestReview, rl[3].State) } c.SetSudo(reviewer.UserName) resp, err = c.DeleteReviewRequests(repo.Owner.UserName, repo.Name, pull.Index, PullReviewRequestOptions{Reviewers: []string{reviewer.UserName}}) assert.NoError(t, err) assert.NotNil(t, resp) rl, _, _ = c.ListPullReviews(repo.Owner.UserName, repo.Name, pull.Index, ListPullReviewsOptions{}) assert.Len(t, rl, 3) c.SetSudo("") _, err = c.AdminDeleteUser(reviewer.UserName) assert.NoError(t, err) _, err = c.AdminDeleteUser(submitter.UserName) assert.NoError(t, err) } func preparePullReviewTest(t *testing.T, c *Client, repoName string) (*Repository, *PullRequest, *User, *User, bool) { repo, err := createTestRepo(t, repoName, c) if !assert.NoError(t, err) { return nil, nil, nil, nil, false } pullSubmitter := createTestUser(t, "pull_submitter", c) write := AccessModeWrite _, err = c.AddCollaborator(repo.Owner.UserName, repo.Name, pullSubmitter.UserName, AddCollaboratorOption{ Permission: &write, }) assert.NoError(t, err) c.SetSudo("pull_submitter") newFile, _, err := c.CreateFile(repo.Owner.UserName, repo.Name, "WOW-file", CreateFileOptions{ Content: "QSBuZXcgRmlsZQoKYW5kIHNvbWUgbGluZXMK", FileOptions: FileOptions{ Message: "creat a new file", BranchName: "main", NewBranchName: "new_file", }, }) if !assert.NoError(t, err) || !assert.NotNil(t, newFile) { return nil, nil, nil, nil, false } pull, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{ Base: "main", Head: "new_file", Title: "Creat a NewFile", }) assert.NoError(t, err) assert.NotNil(t, pull) c.SetSudo("") reviewer := createTestUser(t, "pull_reviewer", c) admin := AccessModeAdmin _, err = c.AddCollaborator(repo.Owner.UserName, repo.Name, pullSubmitter.UserName, AddCollaboratorOption{ Permission: &admin, }) assert.NoError(t, err) return repo, pull, pullSubmitter, reviewer, pull.Poster.ID == pullSubmitter.ID } go-sdk/gitea/pull_test.go000066400000000000000000000155311453336351000157050ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestPull(t *testing.T) { log.Println("== TestPull ==") c := newTestClient() user, _, err := c.GetMyUserInfo() assert.NoError(t, err) repoName := "repo_pull_test" forkOrg := "ForkOrg" if !preparePullTest(t, c, repoName, forkOrg) { return } // ListRepoPullRequests list PRs of one repository pulls, _, err := c.ListRepoPullRequests(user.UserName, repoName, ListPullRequestsOptions{}) assert.NoError(t, err) assert.Len(t, pulls, 0) pullUpdateFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{ Base: "main", Head: forkOrg + ":overwrite_licence", Title: "overwrite a file", }) assert.NoError(t, err) assert.NotNil(t, pullUpdateFile) pullNewFile, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{ Base: "main", Head: forkOrg + ":new_file", Title: "create a file", }) assert.NoError(t, err) assert.NotNil(t, pullNewFile) pullConflict, _, err := c.CreatePullRequest(c.username, repoName, CreatePullRequestOption{ Base: "main", Head: forkOrg + ":will_conflict", Title: "this pull will conflict", }) assert.NoError(t, err) assert.NotNil(t, pullConflict) pulls, _, err = c.ListRepoPullRequests(user.UserName, repoName, ListPullRequestsOptions{}) assert.NoError(t, err) assert.Len(t, pulls, 3) diff, _, err := c.GetPullRequestDiff(c.username, repoName, pullUpdateFile.Index, PullRequestDiffOptions{ Binary: true, }) assert.NoError(t, err) assert.True(t, len(diff) > 1100 && len(diff) < 1300) patch, _, err := c.GetPullRequestPatch(c.username, repoName, pullUpdateFile.Index) assert.NoError(t, err) assert.True(t, len(patch) > len(diff)) commits, _, err := c.ListPullRequestCommits(c.username, repoName, pullUpdateFile.Index, ListPullRequestCommitsOptions{}) assert.NoError(t, err) if assert.Len(t, commits, 1) && assert.Len(t, commits[0].Files, 1) { assert.EqualValues(t, "LICENSE", commits[0].Files[0].Filename) } files, _, err := c.ListPullRequestFiles(c.username, repoName, pullUpdateFile.Index, ListPullRequestFilesOptions{}) assert.NoError(t, err) assert.Len(t, files, 1) file := files[0] assert.EqualValues(t, "LICENSE", file.Filename) assert.EqualValues(t, "changed", file.Status) assert.EqualValues(t, 3, file.Additions) assert.EqualValues(t, 9, file.Deletions) assert.EqualValues(t, 12, file.Changes) // test Update pull pr, _, err := c.GetPullRequest(user.UserName, repoName, pullUpdateFile.Index) assert.NoError(t, err) assert.NotNil(t, pr) assert.False(t, pullUpdateFile.HasMerged) assert.True(t, pullUpdateFile.Mergeable) merged, _, err := c.MergePullRequest(user.UserName, repoName, pullUpdateFile.Index, MergePullRequestOption{ Style: MergeStyleSquash, Title: pullUpdateFile.Title, Message: "squash: " + pullUpdateFile.Title, }) assert.NoError(t, err) assert.True(t, merged) merged, _, err = c.IsPullRequestMerged(user.UserName, repoName, pullUpdateFile.Index) assert.NoError(t, err) assert.True(t, merged) pr, _, err = c.GetPullRequest(user.UserName, repoName, pullUpdateFile.Index) assert.NoError(t, err) assert.EqualValues(t, pullUpdateFile.Head.Name, pr.Head.Name) assert.EqualValues(t, pullUpdateFile.Base.Name, pr.Base.Name) assert.NotEqual(t, pullUpdateFile.Base.Sha, pr.Base.Sha) assert.Len(t, *pr.MergedCommitID, 40) assert.True(t, pr.HasMerged) // test conflict pull pr, _, err = c.GetPullRequest(user.UserName, repoName, pullConflict.Index) assert.NoError(t, err) assert.False(t, pr.HasMerged) assert.False(t, pr.Mergeable) merged, _, err = c.MergePullRequest(user.UserName, repoName, pullConflict.Index, MergePullRequestOption{ Style: MergeStyleMerge, Title: "pullConflict", Message: "pullConflict Msg", }) assert.NoError(t, err) assert.False(t, merged) merged, _, err = c.IsPullRequestMerged(user.UserName, repoName, pullConflict.Index) assert.NoError(t, err) assert.False(t, merged) pr, _, err = c.GetPullRequest(user.UserName, repoName, pullConflict.Index) assert.NoError(t, err) assert.Nil(t, pr.MergedCommitID) assert.False(t, pr.HasMerged) state := StateClosed pr, _, err = c.EditPullRequest(user.UserName, repoName, pullConflict.Index, EditPullRequestOption{ Title: "confl", State: &state, }) assert.NoError(t, err) assert.EqualValues(t, state, pr.State) pulls, _, err = c.ListRepoPullRequests(user.UserName, repoName, ListPullRequestsOptions{ State: StateClosed, Sort: "leastupdate", }) assert.NoError(t, err) assert.Len(t, pulls, 2) } func preparePullTest(t *testing.T, c *Client, repoName, forkOrg string) bool { _, _ = c.DeleteRepo(forkOrg, repoName) _, _ = c.DeleteRepo(c.username, repoName) _, _ = c.DeleteOrg(forkOrg) origRepo, err := createTestRepo(t, repoName, c) if !assert.NoError(t, err) { return false } org, _, err := c.CreateOrg(CreateOrgOption{Name: forkOrg}) assert.NoError(t, err) forkRepo, _, err := c.CreateFork(origRepo.Owner.UserName, origRepo.Name, CreateForkOption{Organization: &org.UserName}) assert.NoError(t, err) assert.NotNil(t, forkRepo) mainLicense, _, err := c.GetContents(forkRepo.Owner.UserName, forkRepo.Name, "main", "LICENSE") if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) { return false } updatedFile, _, err := c.UpdateFile(forkRepo.Owner.UserName, forkRepo.Name, "LICENSE", UpdateFileOptions{ FileOptions: FileOptions{ Message: "Overwrite", BranchName: "main", NewBranchName: "overwrite_licence", }, SHA: mainLicense.SHA, Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=", }) if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) { return false } newFile, _, err := c.CreateFile(forkRepo.Owner.UserName, forkRepo.Name, "WOW-file", CreateFileOptions{ Content: "QSBuZXcgRmlsZQo=", FileOptions: FileOptions{ Message: "creat a new file", BranchName: "main", NewBranchName: "new_file", }, }) if !assert.NoError(t, err) || !assert.NotNil(t, newFile) { return false } conflictFile1, _, err := c.CreateFile(origRepo.Owner.UserName, origRepo.Name, "bad-file", CreateFileOptions{ Content: "U3RhcnQgQ29uZmxpY3QK", FileOptions: FileOptions{ Message: "Start Conflict", BranchName: "main", }, }) if !assert.NoError(t, err) || !assert.NotNil(t, conflictFile1) { return false } conflictFile2, _, err := c.CreateFile(forkRepo.Owner.UserName, forkRepo.Name, "bad-file", CreateFileOptions{ Content: "V2lsbEhhdmUgQ29uZmxpY3QK", FileOptions: FileOptions{ Message: "creat a new file witch will conflict", BranchName: "main", NewBranchName: "will_conflict", }, }) if !assert.NoError(t, err) || !assert.NotNil(t, conflictFile2) { return false } return true } go-sdk/gitea/release.go000066400000000000000000000141421453336351000153070ustar00rootroot00000000000000// 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("pre-release", 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, 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, tag string) (*Response, error) { if err := escapeValidatePathSegments(&user, &repo, &tag); err != nil { return nil, err } if err := c.checkServerVersionGreaterThanOrEqual(version1_14_0); err != nil { return nil, err } _, resp, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/releases/tags/%s", user, repo, tag), nil, nil) return resp, err } // fallbackGetReleaseByTag is fallback for old gitea installations ( < 1.13.0 ) func (c *Client) fallbackGetReleaseByTag(owner, repo, tag string) (*Release, *Response, error) { for i := 1; ; i++ { rl, resp, err := c.ListReleases(owner, repo, ListReleasesOptions{ListOptions: ListOptions{Page: i}}) if err != nil { return nil, resp, err } if len(rl) == 0 { return nil, newResponse(&http.Response{StatusCode: 404}), fmt.Errorf("release with tag '%s' not found", tag) } for _, r := range rl { if r.TagName == tag { return r, resp, nil } } } } go-sdk/gitea/release_test.go000066400000000000000000000073351453336351000163540ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "fmt" "log" "testing" "github.com/stretchr/testify/assert" ) func TestRelease(t *testing.T) { log.Println("== TestRelease ==") c := newTestClient() repo, _ := createTestRepo(t, "ReleaseTests", c) // ListReleases rl, _, err := c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{}) assert.NoError(t, err) assert.Len(t, rl, 0) // CreateRelease r, _, err := c.CreateRelease(repo.Owner.UserName, repo.Name, CreateReleaseOption{ TagName: "awesome", Target: "main", Title: "Release 1", Note: "yes it's awesome", IsDraft: true, IsPrerelease: true, }) assert.NoError(t, err) assert.EqualValues(t, "awesome", r.TagName) assert.EqualValues(t, true, r.IsPrerelease) assert.EqualValues(t, true, r.IsDraft) assert.EqualValues(t, "Release 1", r.Title) assert.EqualValues(t, fmt.Sprintf("%s/api/v1/repos/%s/releases/%d", c.url, repo.FullName, r.ID), r.URL) assert.EqualValues(t, "main", r.Target) assert.EqualValues(t, "yes it's awesome", r.Note) assert.EqualValues(t, c.username, r.Publisher.UserName) rl, _, _ = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{}) assert.Len(t, rl, 1) // GetRelease r2, _, err := c.GetRelease(repo.Owner.UserName, repo.Name, r.ID) assert.NoError(t, err) assert.EqualValues(t, r, r2) r2, _, err = c.GetReleaseByTag(repo.Owner.UserName, repo.Name, r.TagName) assert.NoError(t, err) assert.EqualValues(t, r, r2) // ListRelease without pre-releases tr := true rl, _, err = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{ IsPreRelease: &tr, }) assert.NoError(t, err) assert.Len(t, rl, 1) // created release is a pre-release // test fallback r2, _, err = c.fallbackGetReleaseByTag(repo.Owner.UserName, repo.Name, r.TagName) assert.NoError(t, err) assert.EqualValues(t, r, r2) // EditRelease r2, _, err = c.EditRelease(repo.Owner.UserName, repo.Name, r.ID, EditReleaseOption{ Title: "Release Awesome", Note: "", IsDraft: OptionalBool(false), IsPrerelease: OptionalBool(false), }) assert.NoError(t, err) assert.EqualValues(t, r.Target, r2.Target) assert.EqualValues(t, false, r2.IsDraft) assert.EqualValues(t, false, r2.IsPrerelease) assert.EqualValues(t, r.Note, r2.Note) // DeleteRelease _, err = c.DeleteRelease(repo.Owner.UserName, repo.Name, r.ID) assert.NoError(t, err) rl, _, _ = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{}) assert.Len(t, rl, 0) // CreateRelease _, _, err = c.CreateRelease(repo.Owner.UserName, repo.Name, CreateReleaseOption{ TagName: "aNewReleaseTag", Target: "main", Title: "Title of aNewReleaseTag", }) assert.NoError(t, err) // DeleteReleaseByTag _, err = c.DeleteReleaseByTag(repo.Owner.UserName, repo.Name, "aNewReleaseTag") assert.NoError(t, err) rl, _, _ = c.ListReleases(repo.Owner.UserName, repo.Name, ListReleasesOptions{}) assert.Len(t, rl, 0) _, err = c.DeleteReleaseByTag(repo.Owner.UserName, repo.Name, "aNewReleaseTag") assert.Error(t, err) // Test Response if try to get not existing release _, resp, err := c.GetRelease(repo.Owner.UserName, repo.Name, 1234) assert.Error(t, err) if assert.NotNil(t, resp) { assert.EqualValues(t, 404, resp.StatusCode) } _, resp, err = c.GetReleaseByTag(repo.Owner.UserName, repo.Name, "not_here") assert.Error(t, err) if assert.NotNil(t, resp) { assert.EqualValues(t, 404, resp.StatusCode) } _, resp, err = c.fallbackGetReleaseByTag(repo.Owner.UserName, repo.Name, "not_here") assert.Error(t, err) if assert.NotNil(t, resp) { assert.EqualValues(t, 404, resp.StatusCode) } } go-sdk/gitea/repo.go000066400000000000000000000503231453336351000146350ustar00rootroot00000000000000// 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"` MirrorUpdated time.Time `json:"mirror_updated,omitempty"` 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 } newQuery := link.Query() newQuery.Add("private", "false") link.RawQuery = newQuery.Encode() } } 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("description has more than 255 chars") } if len(opt.DefaultBranch) > 100 { return fmt.Errorf("default branch name has more than 100 chars") } if len(opt.TrustModel) != 0 { if err := c.checkServerVersionGreaterThanOrEqual(version1_13_0); err != nil { return err } } return nil } // CreateRepo creates a repository for authenticated user. func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, *Response, error) { if err := opt.Validate(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 } go-sdk/gitea/repo_branch.go000066400000000000000000000117521453336351000161550ustar00rootroot00000000000000// 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 } go-sdk/gitea/repo_branch_protection.go000066400000000000000000000210521453336351000204150ustar00rootroot00000000000000// 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"` RuleName string `json:"rule_name"` EnablePush bool `json:"enable_push"` EnablePushWhitelist bool `json:"enable_push_whitelist"` PushWhitelistUsernames []string `json:"push_whitelist_usernames"` PushWhitelistTeams []string `json:"push_whitelist_teams"` PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` EnableMergeWhitelist bool `json:"enable_merge_whitelist"` MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` MergeWhitelistTeams []string `json:"merge_whitelist_teams"` EnableStatusCheck bool `json:"enable_status_check"` StatusCheckContexts []string `json:"status_check_contexts"` RequiredApprovals int64 `json:"required_approvals"` EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` DismissStaleApprovals bool `json:"dismiss_stale_approvals"` RequireSignedCommits bool `json:"require_signed_commits"` ProtectedFilePatterns string `json:"protected_file_patterns"` UnprotectedFilePatterns string `json:"unprotected_file_patterns"` Created time.Time `json:"created_at"` Updated time.Time `json:"updated_at"` } // CreateBranchProtectionOption options for creating a branch protection type CreateBranchProtectionOption struct { BranchName string `json:"branch_name"` RuleName string `json:"rule_name"` EnablePush bool `json:"enable_push"` EnablePushWhitelist bool `json:"enable_push_whitelist"` PushWhitelistUsernames []string `json:"push_whitelist_usernames"` PushWhitelistTeams []string `json:"push_whitelist_teams"` PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"` EnableMergeWhitelist bool `json:"enable_merge_whitelist"` MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` MergeWhitelistTeams []string `json:"merge_whitelist_teams"` EnableStatusCheck bool `json:"enable_status_check"` StatusCheckContexts []string `json:"status_check_contexts"` RequiredApprovals int64 `json:"required_approvals"` EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` DismissStaleApprovals bool `json:"dismiss_stale_approvals"` RequireSignedCommits bool `json:"require_signed_commits"` ProtectedFilePatterns string `json:"protected_file_patterns"` UnprotectedFilePatterns string `json:"unprotected_file_patterns"` } // EditBranchProtectionOption options for editing a branch protection type EditBranchProtectionOption struct { EnablePush *bool `json:"enable_push"` EnablePushWhitelist *bool `json:"enable_push_whitelist"` PushWhitelistUsernames []string `json:"push_whitelist_usernames"` PushWhitelistTeams []string `json:"push_whitelist_teams"` PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"` EnableMergeWhitelist *bool `json:"enable_merge_whitelist"` MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` MergeWhitelistTeams []string `json:"merge_whitelist_teams"` EnableStatusCheck *bool `json:"enable_status_check"` StatusCheckContexts []string `json:"status_check_contexts"` RequiredApprovals *int64 `json:"required_approvals"` EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"` ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"` BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"` BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"` DismissStaleApprovals *bool `json:"dismiss_stale_approvals"` RequireSignedCommits *bool `json:"require_signed_commits"` ProtectedFilePatterns *string `json:"protected_file_patterns"` UnprotectedFilePatterns *string `json:"unprotected_file_patterns"` } // ListBranchProtectionsOptions list branch protection options 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 } go-sdk/gitea/repo_branch_test.go000066400000000000000000000130051453336351000172050ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "sort" "testing" "github.com/stretchr/testify/assert" ) func TestRepoBranches(t *testing.T) { log.Println("== TestRepoBranches ==") c := newTestClient() repoName := "branches" repo := prepareBranchTest(t, c, repoName) if repo == nil { return } bl, _, err := c.ListRepoBranches(repo.Owner.UserName, repo.Name, ListRepoBranchesOptions{}) assert.NoError(t, err) assert.Len(t, bl, 3) sort.Slice(bl, func(i, j int) bool { return bl[i].Name < bl[j].Name }) assert.EqualValues(t, "feature", bl[0].Name) assert.EqualValues(t, "main", bl[1].Name) assert.EqualValues(t, "update", bl[2].Name) b, _, err := c.GetRepoBranch(repo.Owner.UserName, repo.Name, "update") assert.NoError(t, err) assert.EqualValues(t, bl[2].Commit.ID, b.Commit.ID) assert.EqualValues(t, bl[2].Commit.Added, b.Commit.Added) s, _, err := c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "main") assert.NoError(t, err) assert.False(t, s) s, _, err = c.DeleteRepoBranch(repo.Owner.UserName, repo.Name, "feature") assert.NoError(t, err) assert.True(t, s) bl, _, err = c.ListRepoBranches(repo.Owner.UserName, repo.Name, ListRepoBranchesOptions{}) assert.NoError(t, err) assert.Len(t, bl, 2) b, _, err = c.GetRepoBranch(repo.Owner.UserName, repo.Name, "feature") assert.Error(t, err) assert.Nil(t, b) bNew, _, err := c.CreateBranch(repo.Owner.UserName, repo.Name, CreateBranchOption{BranchName: "NewBranch"}) assert.NoError(t, err) b, _, err = c.GetRepoBranch(repo.Owner.UserName, repo.Name, bNew.Name) assert.NoError(t, err) assert.EqualValues(t, bNew, b) } func TestRepoBranchProtection(t *testing.T) { log.Println("== TestRepoBranchProtection ==") c := newTestClient() repoName := "BranchProtection" repo := prepareBranchTest(t, c, repoName) if repo == nil { return } assert.NotNil(t, repo) // ListBranchProtections bpl, _, err := c.ListBranchProtections(repo.Owner.UserName, repo.Name, ListBranchProtectionsOptions{}) assert.NoError(t, err) assert.Len(t, bpl, 0) // CreateBranchProtection bp, _, err := c.CreateBranchProtection(repo.Owner.UserName, repo.Name, CreateBranchProtectionOption{ BranchName: "main", EnablePush: true, EnablePushWhitelist: true, PushWhitelistUsernames: []string{"test01"}, EnableMergeWhitelist: true, MergeWhitelistUsernames: []string{"test01"}, BlockOnOutdatedBranch: true, }) assert.NoError(t, err) assert.EqualValues(t, "main", bp.BranchName) assert.EqualValues(t, false, bp.EnableStatusCheck) assert.EqualValues(t, true, bp.EnablePush) assert.EqualValues(t, true, bp.EnablePushWhitelist) assert.EqualValues(t, []string{"test01"}, bp.PushWhitelistUsernames) bp, _, err = c.CreateBranchProtection(repo.Owner.UserName, repo.Name, CreateBranchProtectionOption{ BranchName: "update", EnablePush: false, EnableMergeWhitelist: true, MergeWhitelistUsernames: []string{"test01"}, }) assert.NoError(t, err) assert.NotNil(t, bp) bpl, _, err = c.ListBranchProtections(repo.Owner.UserName, repo.Name, ListBranchProtectionsOptions{}) assert.NoError(t, err) assert.Len(t, bpl, 2) // GetBranchProtection bp, _, err = c.GetBranchProtection(repo.Owner.UserName, repo.Name, bpl[0].BranchName) assert.NoError(t, err) assert.EqualValues(t, bpl[0], bp) // EditBranchProtection bp, _, err = c.EditBranchProtection(repo.Owner.UserName, repo.Name, bpl[0].BranchName, EditBranchProtectionOption{ EnablePush: OptionalBool(false), EnablePushWhitelist: OptionalBool(false), PushWhitelistUsernames: nil, RequiredApprovals: OptionalInt64(1), EnableApprovalsWhitelist: OptionalBool(true), ApprovalsWhitelistUsernames: []string{"test01"}, }) assert.NoError(t, err) assert.NotEqual(t, bpl[0], bp) assert.EqualValues(t, bpl[0].BranchName, bp.BranchName) assert.EqualValues(t, bpl[0].EnableMergeWhitelist, bp.EnableMergeWhitelist) assert.EqualValues(t, bpl[0].Created, bp.Created) // DeleteBranchProtection _, err = c.DeleteBranchProtection(repo.Owner.UserName, repo.Name, bpl[1].BranchName) assert.NoError(t, err) bpl, _, err = c.ListBranchProtections(repo.Owner.UserName, repo.Name, ListBranchProtectionsOptions{}) assert.NoError(t, err) assert.Len(t, bpl, 1) } func prepareBranchTest(t *testing.T, c *Client, repoName string) *Repository { origRepo, err := createTestRepo(t, repoName, c) if !assert.NoError(t, err) { return nil } mainLicense, _, err := c.GetContents(origRepo.Owner.UserName, origRepo.Name, "main", "README.md") if !assert.NoError(t, err) || !assert.NotNil(t, mainLicense) { return nil } updatedFile, _, err := c.UpdateFile(origRepo.Owner.UserName, origRepo.Name, "README.md", UpdateFileOptions{ FileOptions: FileOptions{ Message: "update it", BranchName: "main", NewBranchName: "update", }, SHA: mainLicense.SHA, Content: "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=", }) if !assert.NoError(t, err) || !assert.NotNil(t, updatedFile) { return nil } newFile, _, err := c.CreateFile(origRepo.Owner.UserName, origRepo.Name, "WOW-file", CreateFileOptions{ Content: "QSBuZXcgRmlsZQo=", FileOptions: FileOptions{ Message: "creat a new file", BranchName: "main", NewBranchName: "feature", }, }) if !assert.NoError(t, err) || !assert.NotNil(t, newFile) { return nil } return origRepo } go-sdk/gitea/repo_collaborator.go000066400000000000000000000125151453336351000174010ustar00rootroot00000000000000// 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 } // CollaboratorPermissionResult result type for CollaboratorPermission type CollaboratorPermissionResult struct { Permission AccessMode `json:"permission"` Role string `json:"role_name"` User *User `json:"user"` } // ListCollaborators list a repository's collaborators func (c *Client) ListCollaborators(user, repo string, opt ListCollaboratorsOptions) ([]*User, *Response, error) { if err := escapeValidatePathSegments(&user, &repo); err != nil { return nil, nil, err } opt.setDefaults() collaborators := make([]*User, 0, opt.PageSize) resp, err := c.getParsedResponse("GET", 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 } // CollaboratorPermission gets collaborator permission of a repository func (c *Client) CollaboratorPermission(user, repo, collaborator string) (*CollaboratorPermissionResult, *Response, error) { if err := escapeValidatePathSegments(&user, &repo, &collaborator); err != nil { return nil, nil, err } rv := new(CollaboratorPermissionResult) resp, err := c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", user, repo, collaborator), nil, nil, rv) if err != nil { return nil, resp, err } if resp.StatusCode != 200 { rv = nil } return rv, resp, nil } // AddCollaboratorOption options when adding a user as a collaborator of a repository type AddCollaboratorOption struct { Permission *AccessMode `json:"permission"` } // 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 } go-sdk/gitea/repo_collaborator_test.go000066400000000000000000000055421453336351000204420ustar00rootroot00000000000000// Copyright 2021 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestRepoCollaborator(t *testing.T) { log.Println("== TestRepoCollaborator ==") c := newTestClient() repo, _ := createTestRepo(t, "RepoCollaborators", c) createTestUser(t, "ping", c) createTestUser(t, "pong", c) defer func() { _, err := c.AdminDeleteUser("ping") assert.NoError(t, err) _, err = c.AdminDeleteUser("pong") assert.NoError(t, err) }() collaborators, _, err := c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{}) assert.NoError(t, err) assert.Len(t, collaborators, 0) mode := AccessModeAdmin resp, err := c.AddCollaborator(repo.Owner.UserName, repo.Name, "ping", AddCollaboratorOption{Permission: &mode}) assert.NoError(t, err) assert.EqualValues(t, 204, resp.StatusCode) permissonPing, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "ping") assert.NoError(t, err) assert.EqualValues(t, 200, resp.StatusCode) assert.EqualValues(t, AccessModeAdmin, permissonPing.Permission) assert.EqualValues(t, "ping", permissonPing.User.UserName) mode = AccessModeRead _, err = c.AddCollaborator(repo.Owner.UserName, repo.Name, "pong", AddCollaboratorOption{Permission: &mode}) assert.NoError(t, err) permissonPong, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "pong") assert.NoError(t, err) assert.EqualValues(t, 200, resp.StatusCode) assert.EqualValues(t, AccessModeRead, permissonPong.Permission) assert.EqualValues(t, "pong", permissonPong.User.UserName) collaborators, _, err = c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{}) assert.NoError(t, err) assert.Len(t, collaborators, 2) assert.EqualValues(t, []string{"ping", "pong"}, userToStringSlice(collaborators)) reviewers, _, err := c.GetReviewers(repo.Owner.UserName, repo.Name) assert.NoError(t, err) assert.Len(t, reviewers, 3) assert.EqualValues(t, []string{"ping", "pong", "test01"}, userToStringSlice(reviewers)) assignees, _, err := c.GetAssignees(repo.Owner.UserName, repo.Name) assert.NoError(t, err) assert.Len(t, assignees, 2) assert.EqualValues(t, []string{"ping", "test01"}, userToStringSlice(assignees)) resp, err = c.DeleteCollaborator(repo.Owner.UserName, repo.Name, "ping") assert.NoError(t, err) assert.EqualValues(t, 204, resp.StatusCode) collaborators, _, err = c.ListCollaborators(repo.Owner.UserName, repo.Name, ListCollaboratorsOptions{}) assert.NoError(t, err) assert.Len(t, collaborators, 1) permissonNotExists, resp, err := c.CollaboratorPermission(repo.Owner.UserName, repo.Name, "user_that_not_exists") assert.Error(t, err) assert.EqualValues(t, 404, resp.StatusCode) assert.Nil(t, permissonNotExists) } go-sdk/gitea/repo_commit.go000066400000000000000000000110001453336351000161720ustar00rootroot00000000000000// 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"` Verification *PayloadCommitVerification `json:"verification"` } // CommitStats contains stats from a Git commit type CommitStats struct { Total int `json:"total"` Additions int `json:"additions"` Deletions int `json:"deletions"` } // Commit contains information generated from a Git commit. type Commit struct { *CommitMeta HTMLURL string `json:"html_url"` RepoCommit *RepoCommit `json:"commit"` Author *User `json:"author"` Committer *User `json:"committer"` Parents []*CommitMeta `json:"parents"` Files []*CommitAffectedFiles `json:"files"` Stats *CommitStats `json:"stats"` } // 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 // Path indicates that only commits that include the path's file/dir should be returned. Path string } // QueryEncode turns options into querystring argument func (opt *ListCommitOptions) QueryEncode() string { query := opt.getURLQuery() if opt.SHA != "" { query.Add("sha", opt.SHA) } if opt.Path != "" { query.Add("path", opt.Path) } return query.Encode() } // ListRepoCommits return list of commits from a repo func (c *Client) ListRepoCommits(user, repo string, opt ListCommitOptions) ([]*Commit, *Response, error) { if err := escapeValidatePathSegments(&user, &repo); err != nil { return nil, nil, err } link, _ := url.Parse(fmt.Sprintf("/repos/%s/%s/commits", user, repo)) opt.setDefaults() commits := make([]*Commit, 0, opt.PageSize) link.RawQuery = opt.QueryEncode() resp, err := c.getParsedResponse("GET", link.String(), nil, nil, &commits) return commits, resp, err } // GetCommitDiff returns the commit's raw diff. func (c *Client) GetCommitDiff(user, repo, commitID string) ([]byte, *Response, error) { if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil { return nil, nil, err } if err := escapeValidatePathSegments(&user, &repo); err != nil { return nil, nil, err } return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypeDiff), nil, nil) } // GetCommitPatch returns the commit's raw patch. func (c *Client) GetCommitPatch(user, repo, commitID string) ([]byte, *Response, error) { if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil { return nil, nil, err } if err := escapeValidatePathSegments(&user, &repo); err != nil { return nil, nil, err } return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/git/commits/%s.%s", user, repo, commitID, pullRequestDiffTypePatch), nil, nil) } go-sdk/gitea/repo_commit_test.go000066400000000000000000000045271453336351000172510ustar00rootroot00000000000000// 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 ( "encoding/base64" "log" "testing" "github.com/stretchr/testify/assert" ) func TestListRepoCommits(t *testing.T) { log.Println("== TestListRepoCommits ==") c := newTestClient() repo, err := createTestRepo(t, "ListRepoCommits", c) assert.NoError(t, err) l, _, err := c.ListRepoCommits(repo.Owner.UserName, repo.Name, ListCommitOptions{}) assert.NoError(t, err) assert.Len(t, l, 1) assert.EqualValues(t, "Initial commit\n", l[0].RepoCommit.Message) assert.EqualValues(t, "gpg.error.not_signed_commit", l[0].RepoCommit.Verification.Reason) assert.EqualValues(t, 100, l[0].Stats.Additions) } func TestGetCommitDiffOrPatch(t *testing.T) { log.Println("== TestGetCommitDiffOrPatch ==") c := newTestClient() repo, err := createTestRepo(t, "TestGetCommitDiffOrPatch", c) assert.NoError(t, err) // Add a new simple small commit to the repository. fileResponse, _, err := c.CreateFile(repo.Owner.UserName, repo.Name, "NOT_A_LICENSE", CreateFileOptions{ Content: base64.StdEncoding.EncodeToString([]byte("But is it?\n")), FileOptions: FileOptions{ Message: "Ensure people know it's not a license!", Committer: Identity{ Name: "Sup3rCookie", Email: "Sup3rCookie@example.com", }, }, }) assert.NoError(t, err) // Test the diff output. diffOutput, _, err := c.GetCommitDiff(repo.Owner.UserName, repo.Name, fileResponse.Commit.SHA) assert.NoError(t, err) assert.EqualValues(t, "diff --git a/NOT_A_LICENSE b/NOT_A_LICENSE\nnew file mode 100644\nindex 0000000..f27a20a\n--- /dev/null\n+++ b/NOT_A_LICENSE\n@@ -0,0 +1 @@\n+But is it?\n", string(diffOutput)) // Test the patch output. patchOutput, _, err := c.GetCommitPatch(repo.Owner.UserName, repo.Name, fileResponse.Commit.SHA) assert.NoError(t, err) // Use contains, because we cannot include the first part, because of dates + non-static CommitID.. assert.Contains(t, string(patchOutput), "Subject: [PATCH] Ensure people know it's not a license!\n\n---\n NOT_A_LICENSE | 1 +\n 1 file changed, 1 insertion(+)\n create mode 100644 NOT_A_LICENSE\n\ndiff --git a/NOT_A_LICENSE b/NOT_A_LICENSE\nnew file mode 100644\nindex 0000000..f27a20a\n--- /dev/null\n+++ b/NOT_A_LICENSE\n@@ -0,0 +1 @@\n+But is it?\n") } go-sdk/gitea/repo_file.go000066400000000000000000000240311453336351000156310ustar00rootroot00000000000000// 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" "io" "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. // it optional can resolve lfs pointers and server the file instead // e.g.: ref -> master, filepath -> README.md (no leading slash) func (c *Client) GetFile(owner, repo, ref, filepath string, resolveLFS ...bool) ([]byte, *Response, error) { reader, resp, err := c.GetFileReader(owner, repo, ref, filepath, resolveLFS...) if reader == nil { return nil, resp, err } defer reader.Close() data, err2 := io.ReadAll(reader) if err2 != nil { return nil, resp, err2 } return data, resp, err } // GetFileReader return reader for download a file of repository, ref can be branch/tag/commit. // it optional can resolve lfs pointers and server the file instead // e.g.: ref -> master, filepath -> README.md (no leading slash) func (c *Client) GetFileReader(owner, repo, ref, filepath string, resolveLFS ...bool) (io.ReadCloser, *Response, error) { if err := escapeValidatePathSegments(&owner, &repo); err != nil { return nil, nil, err } // resolve lfs if len(resolveLFS) != 0 && resolveLFS[0] { if err := c.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil { return nil, nil, err } return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/media/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil) } // normal get filepath = pathEscapeSegments(filepath) if c.checkServerVersionGreaterThanOrEqual(version1_14_0) != nil { ref = pathEscapeSegments(ref) return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", owner, repo, ref, filepath), nil, nil) } return c.getResponseReader("GET", fmt.Sprintf("/repos/%s/%s/raw/%s?ref=%s", owner, repo, filepath, url.QueryEscape(ref)), nil, nil) } // GetContents get the metadata and contents of a file in a repository // ref is optional func (c *Client) GetContents(owner, repo, ref, filepath string) (*ContentsResponse, *Response, error) { data, resp, err := c.getDirOrFileContents(owner, repo, ref, filepath) if err != nil { return nil, resp, err } cr := new(ContentsResponse) 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 } go-sdk/gitea/repo_file_test.go000066400000000000000000000101571453336351000166740ustar00rootroot00000000000000// 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/base64" "log" "testing" "github.com/stretchr/testify/assert" ) func TestFileCreateUpdateGet(t *testing.T) { log.Println("== TestFileCRUD ==") c := newTestClient() repo, err := createTestRepo(t, "ChangeFiles", c) assert.NoError(t, err) assert.NotNil(t, repo) raw, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "main", "README.md") assert.NoError(t, err) assert.EqualValues(t, "IyBDaGFuZ2VGaWxlcwoKQSB0ZXN0IFJlcG86IENoYW5nZUZpbGVz", base64.StdEncoding.EncodeToString(raw)) testFileName := "A+#&ä" newFile, _, err := c.CreateFile(repo.Owner.UserName, repo.Name, testFileName, CreateFileOptions{ FileOptions: FileOptions{ Message: "create file " + testFileName, }, Content: "ZmlsZUEK", }) assert.NoError(t, err) raw, _, _ = c.GetFile(repo.Owner.UserName, repo.Name, "main", testFileName) assert.EqualValues(t, "ZmlsZUEK", base64.StdEncoding.EncodeToString(raw)) updatedFile, _, err := c.UpdateFile(repo.Owner.UserName, repo.Name, testFileName, UpdateFileOptions{ FileOptions: FileOptions{ Message: "add a new line", }, SHA: newFile.Content.SHA, Content: "ZmlsZUEKCmFuZCBhIG5ldyBsaW5lCg==", }) assert.NoError(t, err) assert.NotNil(t, updatedFile) file, _, err := c.GetContents(repo.Owner.UserName, repo.Name, "main", testFileName) assert.NoError(t, err) assert.EqualValues(t, updatedFile.Content.SHA, file.SHA) assert.EqualValues(t, &updatedFile.Content.Content, &file.Content) _, err = c.DeleteFile(repo.Owner.UserName, repo.Name, testFileName, DeleteFileOptions{ FileOptions: FileOptions{ Message: "Delete File " + testFileName, }, SHA: updatedFile.Content.SHA, }) assert.NoError(t, err) _, resp, err := c.GetFile(repo.Owner.UserName, repo.Name, "main", testFileName) assert.Error(t, err) assert.EqualValues(t, "The target couldn't be found.", err.Error()) assert.EqualValues(t, 404, resp.StatusCode) licence, _, err := c.GetContents(repo.Owner.UserName, repo.Name, "", "LICENSE") assert.NoError(t, err) licenceRaw, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "", "LICENSE") assert.NoError(t, err) testContent := "Tk9USElORyBJUyBIRVJFIEFOWU1PUkUKSUYgWU9VIExJS0UgVE8gRklORCBTT01FVEhJTkcKV0FJVCBGT1IgVEhFIEZVVFVSRQo=" updatedFile, _, err = c.UpdateFile(repo.Owner.UserName, repo.Name, "LICENSE", UpdateFileOptions{ FileOptions: FileOptions{ Message: "Overwrite", BranchName: "main", NewBranchName: "overwrite-a+/&licence", }, SHA: licence.SHA, Content: testContent, }) assert.NoError(t, err) assert.NotNil(t, updatedFile) licenceRawNew, _, err := c.GetFile(repo.Owner.UserName, repo.Name, "overwrite-a+/&licence", "LICENSE") assert.NoError(t, err) assert.NotNil(t, licence) assert.False(t, bytes.Equal(licenceRaw, licenceRawNew)) assert.EqualValues(t, testContent, base64.StdEncoding.EncodeToString(licenceRawNew)) // ListContents in root dir of default branch dir, resp, err := c.ListContents(repo.Owner.UserName, repo.Name, "", "") assert.NoError(t, err) assert.Len(t, dir, 3) assert.NotNil(t, resp) // ListContents in not existing dir of default branch _, resp, err = c.ListContents(repo.Owner.UserName, repo.Name, "", "/hehe/") assert.Error(t, err) assert.EqualValues(t, 404, resp.StatusCode) // ListContents in root dir of not existing branch _, resp, err = c.ListContents(repo.Owner.UserName, repo.Name, "no-ref-at-all", "") assert.Error(t, err) assert.EqualValues(t, 404, resp.StatusCode) // ListContents try to get file as dir dir, resp, err = c.ListContents(repo.Owner.UserName, repo.Name, "", "LICENSE") if assert.Error(t, err) { assert.EqualValues(t, "expect directory, got file", err.Error()) } assert.Nil(t, dir) assert.EqualValues(t, 200, resp.StatusCode) // GetContents try to get dir as file file, resp, err = c.GetContents(repo.Owner.UserName, repo.Name, "", "") if assert.Error(t, err) { assert.EqualValues(t, "expect file, got directory", err.Error()) } assert.Nil(t, file) assert.EqualValues(t, 200, resp.StatusCode) } go-sdk/gitea/repo_key.go000066400000000000000000000056021453336351000155050ustar00rootroot00000000000000// 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 } go-sdk/gitea/repo_migrate.go000066400000000000000000000104141453336351000163420ustar00rootroot00000000000000// 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 } go-sdk/gitea/repo_refs.go000066400000000000000000000042361453336351000156560ustar00rootroot00000000000000// 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) } go-sdk/gitea/repo_stars.go000066400000000000000000000062511453336351000160520ustar00rootroot00000000000000// 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 } go-sdk/gitea/repo_stars_test.go000066400000000000000000000034541453336351000171130ustar00rootroot00000000000000// Copyright 2021 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestRepoStaring(t *testing.T) { log.Println("== TestRepoStaring ==") // init user2 c := newTestClient() user1, _, err := c.GetMyUserInfo() assert.NoError(t, err) userA := createTestUser(t, "stargazer_a", c) userB := createTestUser(t, "stargazer_b", c) repo, _ := createTestRepo(t, "toStar", c) if repo == nil { t.Skip() } is, _, err := c.IsRepoStarring(repo.Owner.UserName, repo.Name) assert.NoError(t, err) assert.False(t, is) repos, _, err := c.GetMyStarredRepos() assert.NoError(t, err) assert.Len(t, repos, 0) _, err = c.StarRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) c.SetSudo(userA.UserName) _, err = c.StarRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) c.SetSudo(userB.UserName) _, err = c.StarRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) users, _, err := c.ListRepoStargazers(repo.Owner.UserName, repo.Name, ListStargazersOptions{}) assert.NoError(t, err) assert.Len(t, users, 3) assert.EqualValues(t, user1.UserName, users[0].UserName) _, err = c.UnStarRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) _, err = c.UnStarRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) c.SetSudo("") users, _, err = c.ListRepoStargazers(repo.Owner.UserName, repo.Name, ListStargazersOptions{}) assert.NoError(t, err) assert.Len(t, users, 2) repos, _, err = c.GetMyStarredRepos() assert.NoError(t, err) assert.Len(t, repos, 1) reposNew, _, err := c.GetStarredRepos(user1.UserName) assert.NoError(t, err) assert.Len(t, repos, 1) assert.EqualValues(t, repos, reposNew) } go-sdk/gitea/repo_tag.go000066400000000000000000000102251453336351000154650ustar00rootroot00000000000000// 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 } go-sdk/gitea/repo_tag_test.go000066400000000000000000000034321453336351000165260ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "fmt" "log" "testing" "github.com/stretchr/testify/assert" ) func TestTags(t *testing.T) { log.Println("== TestTags ==") c := newTestClient() repo, _ := createTestRepo(t, "TestTags", c) // Create Tags cTagMSG := "A tag message.\n\n:)" cTag, resp, err := c.CreateTag(repo.Owner.UserName, repo.Name, CreateTagOption{ TagName: "tag1", Message: cTagMSG, Target: "main", }) assert.NoError(t, err) assert.EqualValues(t, 201, resp.StatusCode) assert.EqualValues(t, cTagMSG, cTag.Message) assert.EqualValues(t, fmt.Sprintf("%s/%s/TestTags/archive/tag1.zip", c.url, c.username), cTag.ZipballURL) tags, _, err := c.ListRepoTags(repo.Owner.UserName, repo.Name, ListRepoTagsOptions{}) assert.NoError(t, err) assert.Len(t, tags, 1) assert.EqualValues(t, cTag, tags[0]) // get tag gTag, _, err := c.GetTag(repo.Owner.UserName, repo.Name, cTag.Name) assert.NoError(t, err) assert.EqualValues(t, cTag, gTag) aTag, _, err := c.GetAnnotatedTag(repo.Owner.UserName, repo.Name, cTag.ID) assert.NoError(t, err) assert.EqualValues(t, cTag.Name, aTag.Tag) assert.EqualValues(t, cTag.ID, aTag.SHA) assert.EqualValues(t, fmt.Sprintf("%s/api/v1/repos/%s/TestTags/git/tags/%s", c.url, c.username, cTag.ID), aTag.URL) assert.EqualValues(t, cTag.Message+"\n", aTag.Message) assert.EqualValues(t, "commit", aTag.Object.Type) // DeleteReleaseTag resp, err = c.DeleteTag(repo.Owner.UserName, repo.Name, "tag1") assert.NoError(t, err) assert.EqualValues(t, 204, resp.StatusCode) tags, _, err = c.ListRepoTags(repo.Owner.UserName, repo.Name, ListRepoTagsOptions{}) assert.NoError(t, err) assert.Len(t, tags, 0) } go-sdk/gitea/repo_team.go000066400000000000000000000043611453336351000156440ustar00rootroot00000000000000// 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 } go-sdk/gitea/repo_team_test.go000066400000000000000000000045011453336351000166770ustar00rootroot00000000000000// Copyright 2021 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestRepoTeamManagement(t *testing.T) { log.Println("== TestRepoTeamManagement ==") c := newTestClient() // prepare for test clean, repo, err := createTestOrgRepo(t, c, "RepoTeamManagement") if err != nil { return } defer clean() if _, err = createTestOrgTeams(t, c, repo.Owner.UserName, "Admins", AccessModeAdmin, []RepoUnitType{RepoUnitCode, RepoUnitIssues, RepoUnitPulls, RepoUnitReleases}); err != nil { return } if _, err = createTestOrgTeams(t, c, repo.Owner.UserName, "CodeManager", AccessModeWrite, []RepoUnitType{RepoUnitCode}); err != nil { return } if _, err = createTestOrgTeams(t, c, repo.Owner.UserName, "IssueManager", AccessModeWrite, []RepoUnitType{RepoUnitIssues, RepoUnitPulls}); err != nil { return } // test teams, _, err := c.GetRepoTeams(repo.Owner.UserName, repo.Name) assert.NoError(t, err) if !assert.Len(t, teams, 1) { return } assert.EqualValues(t, AccessModeOwner, teams[0].Permission) team, _, err := c.CheckRepoTeam(repo.Owner.UserName, repo.Name, "Admins") assert.NoError(t, err) assert.Nil(t, team) resp, err := c.AddRepoTeam(repo.Owner.UserName, repo.Name, "Admins") assert.NoError(t, err) assert.EqualValues(t, 204, resp.StatusCode) resp, err = c.AddRepoTeam(repo.Owner.UserName, repo.Name, "CodeManager") assert.NoError(t, err) assert.EqualValues(t, 204, resp.StatusCode) resp, err = c.AddRepoTeam(repo.Owner.UserName, repo.Name, "IssueManager") assert.NoError(t, err) assert.EqualValues(t, 204, resp.StatusCode) team, _, err = c.CheckRepoTeam(repo.Owner.UserName, repo.Name, "Admins") assert.NoError(t, err) if assert.NotNil(t, team) { assert.EqualValues(t, "Admins", team.Name) assert.EqualValues(t, AccessModeAdmin, team.Permission) } teams, _, err = c.GetRepoTeams(repo.Owner.UserName, repo.Name) assert.NoError(t, err) assert.Len(t, teams, 4) resp, err = c.RemoveRepoTeam(repo.Owner.UserName, repo.Name, "IssueManager") assert.NoError(t, err) assert.EqualValues(t, 204, resp.StatusCode) team, _, err = c.CheckRepoTeam(repo.Owner.UserName, repo.Name, "IssueManager") assert.NoError(t, err) assert.Nil(t, team) } go-sdk/gitea/repo_template.go000066400000000000000000000040741453336351000165320ustar00rootroot00000000000000// 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 } go-sdk/gitea/repo_template_test.go000066400000000000000000000025771453336351000175770ustar00rootroot00000000000000// Copyright 2021 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestRepoFromTemplate(t *testing.T) { log.Println("== TestRepoFromTemplate ==") c := newTestClient() repo, err := createTestRepo(t, "TemplateRepo", c) assert.NoError(t, err) repo, _, err = c.EditRepo(repo.Owner.UserName, repo.Name, EditRepoOption{Template: OptionalBool(true)}) assert.NoError(t, err) _, err = c.SetRepoTopics(repo.Owner.UserName, repo.Name, []string{"abc", "def", "ghi"}) assert.NoError(t, err) newRepo, resp, err := c.CreateRepoFromTemplate(repo.Owner.UserName, repo.Name, CreateRepoFromTemplateOption{ Owner: repo.Owner.UserName, Name: "repoFromTemplate", Description: "", Topics: true, Labels: true, }) assert.NoError(t, err) assert.EqualValues(t, 201, resp.StatusCode) assert.False(t, newRepo.Template) labels, _, err := c.ListRepoLabels(repo.Owner.UserName, repo.Name, ListLabelsOptions{}) assert.NoError(t, err) assert.Len(t, labels, 7) topics, _, _ := c.ListRepoTopics(repo.Owner.UserName, repo.Name, ListRepoTopicsOptions{}) assert.EqualValues(t, []string{"abc", "def", "ghi"}, topics) _, err = c.DeleteRepo(repo.Owner.UserName, "repoFromTemplate") assert.NoError(t, err) } go-sdk/gitea/repo_test.go000066400000000000000000000127441453336351000157010ustar00rootroot00000000000000// 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" "io" "log" "testing" "time" "github.com/stretchr/testify/assert" ) func TestCreateRepo(t *testing.T) { log.Println("== TestCreateRepo ==") c := newTestClient() user, _, err := c.GetMyUserInfo() assert.NoError(t, err) repoName := "test1" _, _, err = c.GetRepo(user.UserName, repoName) if err != nil { repo, _, err := c.CreateRepo(CreateRepoOption{ Name: repoName, }) assert.NoError(t, err) assert.NotNil(t, repo) } _, err = c.DeleteRepo(user.UserName, repoName) assert.NoError(t, err) } func TestRepoMigrateAndLanguages(t *testing.T) { log.Println("== TestMigrateRepo ==") c := newTestClient() user, _, uErr := c.GetMyUserInfo() assert.NoError(t, uErr) _, _, err := c.GetRepo(user.UserName, "sdk-mirror") if err == nil { _, _ = c.DeleteRepo(user.UserName, "sdk-mirror") } repoM, _, err := c.MigrateRepo(MigrateRepoOption{ CloneAddr: "https://gitea.com/gitea/go-sdk.git", RepoName: "sdk-mirror", RepoOwner: user.UserName, Mirror: true, Private: false, Description: "mirror sdk", }) assert.NoError(t, err) repoG, _, err := c.GetRepo(repoM.Owner.UserName, repoM.Name) assert.NoError(t, err) assert.EqualValues(t, repoM.ID, repoG.ID) assert.EqualValues(t, "main", repoG.DefaultBranch) assert.True(t, repoG.Mirror) assert.False(t, repoG.Empty) assert.EqualValues(t, 1, repoG.Watchers) var zeroTime time.Time assert.NotEqual(t, zeroTime, repoG.MirrorUpdated) log.Println("== TestRepoLanguages ==") time.Sleep(time.Second) lang, _, err := c.GetRepoLanguages(repoM.Owner.UserName, repoM.Name) assert.NoError(t, err) assert.Len(t, lang, 2) assert.True(t, 217441 < lang["Go"]) assert.True(t, 3614 < lang["Makefile"] && 6000 > lang["Makefile"]) } func TestSearchRepo(t *testing.T) { log.Println("== TestSearchRepo ==") c := newTestClient() repo, err := createTestRepo(t, "RepoSearch1", c) assert.NoError(t, err) _, err = c.AddRepoTopic(repo.Owner.UserName, repo.Name, "TestTopic1") assert.NoError(t, err) _, err = c.AddRepoTopic(repo.Owner.UserName, repo.Name, "TestTopic2") assert.NoError(t, err) repo, err = createTestRepo(t, "RepoSearch2", c) assert.NoError(t, err) _, err = c.AddRepoTopic(repo.Owner.UserName, repo.Name, "TestTopic1") assert.NoError(t, err) repos, _, err := c.SearchRepos(SearchRepoOptions{ Keyword: "Search1", KeywordInDescription: true, }) assert.NoError(t, err) assert.NotNil(t, repos) assert.Len(t, repos, 1) repos, _, err = c.SearchRepos(SearchRepoOptions{ Keyword: "Search", KeywordInDescription: true, }) assert.NoError(t, err) assert.NotNil(t, repos) assert.Len(t, repos, 2) repos, _, err = c.SearchRepos(SearchRepoOptions{ Keyword: "TestTopic1", KeywordInDescription: true, }) assert.NoError(t, err) assert.NotNil(t, repos) assert.Len(t, repos, 2) repos, _, err = c.SearchRepos(SearchRepoOptions{ Keyword: "TestTopic2", KeywordInDescription: true, }) assert.NoError(t, err) assert.NotNil(t, repos) assert.Len(t, repos, 1) _, err = c.DeleteRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) } func TestDeleteRepo(t *testing.T) { log.Println("== TestDeleteRepo ==") c := newTestClient() repo, _ := createTestRepo(t, "TestDeleteRepo", c) _, err := c.DeleteRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) } func TestGetArchive(t *testing.T) { log.Println("== TestGetArchive ==") c := newTestClient() repo, _ := createTestRepo(t, "ToDownload", c) time.Sleep(time.Second / 2) archive, _, err := c.GetArchive(repo.Owner.UserName, repo.Name, "main", ZipArchive) assert.NoError(t, err) assert.True(t, len(archive) > 1500 && len(archive) < 1700) } func TestGetArchiveReader(t *testing.T) { log.Println("== TestGetArchiveReader ==") c := newTestClient() repo, _ := createTestRepo(t, "ToDownload", c) time.Sleep(time.Second / 2) r, _, err := c.GetArchiveReader(repo.Owner.UserName, repo.Name, "main", ZipArchive) assert.NoError(t, err) defer r.Close() archive := bytes.NewBuffer(nil) nBytes, err := io.Copy(archive, r) assert.NoError(t, err) assert.True(t, nBytes > 1500) assert.EqualValues(t, nBytes, len(archive.Bytes())) } func TestGetRepoByID(t *testing.T) { log.Println("== TestGetRepoByID ==") c := newTestClient() testrepo, _ := createTestRepo(t, "TestGetRepoByID", c) repo, _, err := c.GetRepoByID(testrepo.ID) assert.NoError(t, err) assert.NotNil(t, repo) assert.EqualValues(t, testrepo.ID, repo.ID) _, err = c.DeleteRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) } // standard func to create a init repo for test routines func createTestRepo(t *testing.T, name string, c *Client) (*Repository, error) { user, _, uErr := c.GetMyUserInfo() assert.NoError(t, uErr) repo, _, err := c.GetRepo(user.UserName, name) // We need to check that the received repo is not a // redirected one, it could be the case that gitea redirect us // to a new repo(because it e.g. was transferred or renamed). if err == nil && repo.Owner.UserName == user.UserName { _, _ = c.DeleteRepo(user.UserName, name) } repo, _, err = c.CreateRepo(CreateRepoOption{ Name: name, Description: "A test Repo: " + name, AutoInit: true, Gitignores: "C,C++", License: "MIT", Readme: "Default", IssueLabels: "Default", Private: false, }) assert.NoError(t, err) assert.NotNil(t, repo) return repo, err } go-sdk/gitea/repo_topics.go000066400000000000000000000041251453336351000162150ustar00rootroot00000000000000// 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 } go-sdk/gitea/repo_topics_test.go000066400000000000000000000031211453336351000172470ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "sort" "testing" "github.com/stretchr/testify/assert" ) func TestRepoTopics(t *testing.T) { log.Println("== TestRepoTopics ==") c := newTestClient() repo, err := createTestRepo(t, "RandomTopic", c) assert.NoError(t, err) // Add _, err = c.AddRepoTopic(repo.Owner.UserName, repo.Name, "best") assert.NoError(t, err) _, err = c.AddRepoTopic(repo.Owner.UserName, repo.Name, "git") assert.NoError(t, err) _, err = c.AddRepoTopic(repo.Owner.UserName, repo.Name, "gitea") assert.NoError(t, err) _, err = c.AddRepoTopic(repo.Owner.UserName, repo.Name, "drone") assert.NoError(t, err) // Get List tl, _, err := c.ListRepoTopics(repo.Owner.UserName, repo.Name, ListRepoTopicsOptions{}) assert.NoError(t, err) assert.Len(t, tl, 4) // Del _, err = c.DeleteRepoTopic(repo.Owner.UserName, repo.Name, "drone") assert.NoError(t, err) _, err = c.DeleteRepoTopic(repo.Owner.UserName, repo.Name, "best") assert.NoError(t, err) tl, _, err = c.ListRepoTopics(repo.Owner.UserName, repo.Name, ListRepoTopicsOptions{}) assert.NoError(t, err) assert.Len(t, tl, 2) // Set List newTopics := []string{"analog", "digital", "cat"} _, err = c.SetRepoTopics(repo.Owner.UserName, repo.Name, newTopics) assert.NoError(t, err) tl, _, _ = c.ListRepoTopics(repo.Owner.UserName, repo.Name, ListRepoTopicsOptions{}) assert.Len(t, tl, 3) sort.Strings(tl) sort.Strings(newTopics) assert.EqualValues(t, newTopics, tl) } go-sdk/gitea/repo_transfer.go000066400000000000000000000042441453336351000165420ustar00rootroot00000000000000// 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 } // AcceptRepoTransfer accepts a repo transfer. func (c *Client) AcceptRepoTransfer(owner, reponame string) (*Repository, *Response, error) { if err := escapeValidatePathSegments(&owner, &reponame); err != nil { return nil, nil, err } if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil { return nil, nil, err } repo := new(Repository) resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/accept", owner, reponame), jsonHeader, nil, repo) return repo, resp, err } // RejectRepoTransfer rejects a repo transfer. func (c *Client) RejectRepoTransfer(owner, reponame string) (*Repository, *Response, error) { if err := escapeValidatePathSegments(&owner, &reponame); err != nil { return nil, nil, err } if err := c.checkServerVersionGreaterThanOrEqual(version1_16_0); err != nil { return nil, nil, err } repo := new(Repository) resp, err := c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/transfer/reject", owner, reponame), jsonHeader, nil, repo) return repo, resp, err } go-sdk/gitea/repo_transfer_test.go000066400000000000000000000023601453336351000175760ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestRepoTransfer(t *testing.T) { log.Printf("== TestRepoTransfer ==") c := newTestClient() org, _, err := c.AdminCreateOrg(c.username, CreateOrgOption{Name: "TransferOrg"}) assert.NoError(t, err) repo, err := createTestRepo(t, "ToMove", c) assert.NoError(t, err) newRepo, _, err := c.TransferRepo(c.username, repo.Name, TransferRepoOption{NewOwner: org.UserName}) assert.NoError(t, err) // admin transfer repository will execute immediately but not set as pendding. assert.NotNil(t, newRepo) assert.EqualValues(t, "ToMove", newRepo.Name) repo, err = createTestRepo(t, "ToMove", c) assert.NoError(t, err) _, resp, err := c.TransferRepo(c.username, repo.Name, TransferRepoOption{NewOwner: org.UserName}) assert.EqualValues(t, 422, resp.StatusCode) assert.Error(t, err) _, err = c.DeleteRepo(repo.Owner.UserName, repo.Name) assert.NoError(t, err) _, err = c.DeleteRepo(newRepo.Owner.UserName, newRepo.Name) assert.NoError(t, err) _, err = c.DeleteOrg(org.UserName) assert.NoError(t, err) } go-sdk/gitea/repo_tree.go000066400000000000000000000024131453336351000156510ustar00rootroot00000000000000// 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) 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 } go-sdk/gitea/repo_watch.go000066400000000000000000000053371453336351000160300ustar00rootroot00000000000000// 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", "/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) } go-sdk/gitea/repo_watch_test.go000066400000000000000000000025311453336351000170600ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestRepoWatch(t *testing.T) { log.Printf("== TestRepoWatch ==") c := newTestClient() rawVersion, _, err := c.ServerVersion() assert.NoError(t, err) assert.True(t, true, rawVersion != "") repo1, _ := createTestRepo(t, "TestRepoWatch_1", c) repo2, _ := createTestRepo(t, "TestRepoWatch_2", c) assert.NotEqual(t, repo1, repo2) // GetWatchedRepos wl, _, err := c.GetWatchedRepos("test01") assert.NoError(t, err) assert.NotNil(t, wl) maxcount := len(wl) // GetMyWatchedRepos wl, _, err = c.GetMyWatchedRepos() assert.NoError(t, err) assert.Len(t, wl, maxcount) // CheckRepoWatch isWatching, _, err := c.CheckRepoWatch(repo1.Owner.UserName, repo1.Name) assert.NoError(t, err) assert.True(t, isWatching) // UnWatchRepo _, err = c.UnWatchRepo(repo1.Owner.UserName, repo1.Name) assert.NoError(t, err) isWatching, _, _ = c.CheckRepoWatch(repo1.Owner.UserName, repo1.Name) assert.False(t, isWatching) // WatchRepo _, err = c.WatchRepo(repo1.Owner.UserName, repo1.Name) assert.NoError(t, err) isWatching, _, _ = c.CheckRepoWatch(repo1.Owner.UserName, repo1.Name) assert.True(t, isWatching) } go-sdk/gitea/secret.go000066400000000000000000000005251453336351000151540ustar00rootroot00000000000000// Copyright 2023 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import "time" type Secret struct { // the secret's name Name string `json:"name"` // Date and Time of secret creation Created time.Time `json:"created_at"` } go-sdk/gitea/settings.go000066400000000000000000000062041453336351000155270ustar00rootroot00000000000000// 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 } go-sdk/gitea/settings_test.go000066400000000000000000000025551453336351000165730ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestGetGlobalSettings(t *testing.T) { log.Println("== TestGetGlobalSettings ==") c := newTestClient() uiSettings, _, err := c.GetGlobalUISettings() assert.NoError(t, err) expectedAllowedReactions := []string{"+1", "-1", "laugh", "hooray", "confused", "heart", "rocket", "eyes"} assert.ElementsMatch(t, expectedAllowedReactions, uiSettings.AllowedReactions) repoSettings, _, err := c.GetGlobalRepoSettings() assert.NoError(t, err) assert.EqualValues(t, &GlobalRepoSettings{ HTTPGitDisabled: false, MirrorsDisabled: false, LFSDisabled: true, }, repoSettings) apiSettings, _, err := c.GetGlobalAPISettings() assert.NoError(t, err) assert.EqualValues(t, &GlobalAPISettings{ MaxResponseItems: 50, DefaultPagingNum: 30, DefaultGitTreesPerPage: 1000, DefaultMaxBlobSize: 10485760, }, apiSettings) attachSettings, _, err := c.GetGlobalAttachmentSettings() assert.NoError(t, err) if assert.NotEmpty(t, attachSettings.AllowedTypes) { attachSettings.AllowedTypes = "" } assert.EqualValues(t, &GlobalAttachmentSettings{ Enabled: true, MaxSize: 4, MaxFiles: 5, }, attachSettings) } go-sdk/gitea/status.go000066400000000000000000000073251453336351000152170ustar00rootroot00000000000000// 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 } go-sdk/gitea/status_test.go000066400000000000000000000051071453336351000162520ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestCommitStatus(t *testing.T) { log.Println("== TestCommitStatus ==") c := newTestClient() user, _, err := c.GetMyUserInfo() assert.NoError(t, err) repoName := "CommitStatuses" origRepo, err := createTestRepo(t, repoName, c) if !assert.NoError(t, err) { return } commits, _, _ := c.ListRepoCommits(user.UserName, repoName, ListCommitOptions{ ListOptions: ListOptions{}, SHA: origRepo.DefaultBranch, }) if !assert.Len(t, commits, 1) { return } sha := commits[0].SHA combiStats, resp, err := c.GetCombinedStatus(user.UserName, repoName, sha) assert.NoError(t, err) assert.NotNil(t, resp) assert.NotNil(t, combiStats) assert.EqualValues(t, 0, combiStats.TotalCount) statuses, resp, err := c.ListStatuses(user.UserName, repoName, sha, ListStatusesOption{}) assert.NoError(t, err) assert.NotNil(t, resp) assert.NotNil(t, statuses) assert.Len(t, statuses, 0) createStatus(t, c, user.UserName, repoName, sha, "http://dummy.test", "start testing", "ultraCI", StatusPending) createStatus(t, c, user.UserName, repoName, sha, "https://more.secure", "just a warning", "warn/bot", StatusWarning) createStatus(t, c, user.UserName, repoName, sha, "http://dummy.test", "test failed", "ultraCI", StatusFailure) createStatus(t, c, user.UserName, repoName, sha, "http://dummy.test", "start testing", "ultraCI", StatusPending) createStatus(t, c, user.UserName, repoName, sha, "http://dummy.test", "test passed", "ultraCI", StatusSuccess) statuses, resp, err = c.ListStatuses(user.UserName, repoName, sha, ListStatusesOption{}) assert.NoError(t, err) assert.NotNil(t, resp) assert.NotNil(t, statuses) assert.Len(t, statuses, 5) combiStats, resp, err = c.GetCombinedStatus(user.UserName, repoName, sha) assert.NoError(t, err) assert.NotNil(t, resp) assert.NotNil(t, combiStats) assert.EqualValues(t, 2, combiStats.TotalCount) assert.EqualValues(t, StatusState("warning"), combiStats.State) assert.Len(t, combiStats.Statuses, 2) } func createStatus(t *testing.T, c *Client, userName, repoName, sha, url, desc, context string, state StatusState) { stats, resp, err := c.CreateStatus(userName, repoName, sha, CreateStatusOption{ State: state, TargetURL: url, Description: desc, Context: context, }) assert.NoError(t, err) assert.NotNil(t, resp) assert.NotNil(t, stats) assert.EqualValues(t, state, stats.State) } go-sdk/gitea/user.go000066400000000000000000000045461453336351000146540ustar00rootroot00000000000000// 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) } go-sdk/gitea/user_app.go000066400000000000000000000117051453336351000155070ustar00rootroot00000000000000// 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" ) // AccessTokenScope represents the scope for an access token. type AccessTokenScope string const ( AccessTokenScopeAll AccessTokenScope = "all" AccessTokenScopeRepo AccessTokenScope = "repo" AccessTokenScopeRepoStatus AccessTokenScope = "repo:status" AccessTokenScopePublicRepo AccessTokenScope = "public_repo" AccessTokenScopeAdminOrg AccessTokenScope = "admin:org" AccessTokenScopeWriteOrg AccessTokenScope = "write:org" AccessTokenScopeReadOrg AccessTokenScope = "read:org" AccessTokenScopeAdminPublicKey AccessTokenScope = "admin:public_key" AccessTokenScopeWritePublicKey AccessTokenScope = "write:public_key" AccessTokenScopeReadPublicKey AccessTokenScope = "read:public_key" AccessTokenScopeAdminRepoHook AccessTokenScope = "admin:repo_hook" AccessTokenScopeWriteRepoHook AccessTokenScope = "write:repo_hook" AccessTokenScopeReadRepoHook AccessTokenScope = "read:repo_hook" AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook" AccessTokenScopeAdminUserHook AccessTokenScope = "admin:user_hook" AccessTokenScopeNotification AccessTokenScope = "notification" AccessTokenScopeUser AccessTokenScope = "user" AccessTokenScopeReadUser AccessTokenScope = "read:user" AccessTokenScopeUserEmail AccessTokenScope = "user:email" AccessTokenScopeUserFollow AccessTokenScope = "user:follow" AccessTokenScopeDeleteRepo AccessTokenScope = "delete_repo" AccessTokenScopePackage AccessTokenScope = "package" AccessTokenScopeWritePackage AccessTokenScope = "write:package" AccessTokenScopeReadPackage AccessTokenScope = "read:package" AccessTokenScopeDeletePackage AccessTokenScope = "delete:package" AccessTokenScopeAdminGPGKey AccessTokenScope = "admin:gpg_key" AccessTokenScopeWriteGPGKey AccessTokenScope = "write:gpg_key" AccessTokenScopeReadGPGKey AccessTokenScope = "read:gpg_key" AccessTokenScopeAdminApplication AccessTokenScope = "admin:application" AccessTokenScopeWriteApplication AccessTokenScope = "write:application" AccessTokenScopeReadApplication AccessTokenScope = "read:application" AccessTokenScopeSudo AccessTokenScope = "sudo" ) // AccessToken represents an API access token. type AccessToken struct { ID int64 `json:"id"` Name string `json:"name"` Token string `json:"sha1"` TokenLastEight string `json:"token_last_eight"` Scopes []AccessTokenScope `json:"scopes"` } // 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"` Scopes []AccessTokenScope `json:"scopes"` } // 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") } token := "" switch reflect.ValueOf(value).Kind() { case reflect.Int64: token = fmt.Sprintf("%d", value.(int64)) case reflect.String: if err := c.checkServerVersionGreaterThanOrEqual(version1_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 } go-sdk/gitea/user_email.go000066400000000000000000000034511453336351000160150ustar00rootroot00000000000000// 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 } go-sdk/gitea/user_follow.go000066400000000000000000000064441453336351000162350ustar00rootroot00000000000000// 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 } go-sdk/gitea/user_gpgkey.go000066400000000000000000000057271453336351000162240ustar00rootroot00000000000000// 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 } go-sdk/gitea/user_key.go000066400000000000000000000054161453336351000155210ustar00rootroot00000000000000// 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 } go-sdk/gitea/user_search.go000066400000000000000000000022711453336351000161720ustar00rootroot00000000000000// 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()) } go-sdk/gitea/user_settings.go000066400000000000000000000040741453336351000165700ustar00rootroot00000000000000// 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 } go-sdk/gitea/user_settings_test.go000066400000000000000000000022251453336351000176230ustar00rootroot00000000000000// Copyright 2021 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestUserSettings(t *testing.T) { log.Println("== TestUserSettings ==") c := newTestClient() userConf, _, err := c.GetUserSettings() assert.NoError(t, err) assert.NotNil(t, userConf) assert.EqualValues(t, UserSettings{ Theme: "auto", HideEmail: false, HideActivity: false, }, *userConf) userConf, _, err = c.UpdateUserSettings(UserSettingsOptions{ FullName: OptionalString("Admin User on Test"), Language: OptionalString("de_de"), HideEmail: OptionalBool(true), }) assert.NoError(t, err) assert.NotNil(t, userConf) assert.EqualValues(t, UserSettings{ FullName: "Admin User on Test", Theme: "auto", Language: "de_de", HideEmail: true, HideActivity: false, }, *userConf) _, _, err = c.UpdateUserSettings(UserSettingsOptions{ FullName: OptionalString(""), Language: OptionalString(""), HideEmail: OptionalBool(false), }) assert.NoError(t, err) } go-sdk/gitea/user_test.go000066400000000000000000000133321453336351000157040ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestMyUser(t *testing.T) { log.Println("== TestMyUser ==") c := newTestClient() user, _, err := c.GetMyUserInfo() assert.NoError(t, err) assert.EqualValues(t, 1, user.ID) assert.EqualValues(t, "test01", user.UserName) assert.EqualValues(t, "test01@gitea.io", user.Email) assert.EqualValues(t, "", user.FullName) assert.EqualValues(t, "https://secure.gravatar.com/avatar/d794373e882a68fb173cef817fb6180a?d=identicon", user.AvatarURL) assert.True(t, user.IsAdmin) } func TestUserApp(t *testing.T) { log.Println("== TestUserApp ==") c := newTestClient() result, _, err := c.ListAccessTokens(ListAccessTokensOptions{}) assert.NoError(t, err) assert.Len(t, result, 1) assert.EqualValues(t, "gitea-admin", result[0].Name) t1, _, err := c.CreateAccessToken(CreateAccessTokenOption{Name: "TestCreateAccessToken"}) assert.NoError(t, err) assert.EqualValues(t, "TestCreateAccessToken", t1.Name) result, _, _ = c.ListAccessTokens(ListAccessTokensOptions{}) assert.Len(t, result, 2) _, err = c.DeleteAccessToken(t1.ID) assert.NoError(t, err) result, _, _ = c.ListAccessTokens(ListAccessTokensOptions{}) assert.Len(t, result, 1) } func TestUserSearch(t *testing.T) { log.Println("== TestUserSearch ==") c := newTestClient() createTestUser(t, "tu1", c) createTestUser(t, "eatIt_2", c) createTestUser(t, "thirdIs3", c) createTestUser(t, "advancedUser", c) createTestUser(t, "1n2n3n", c) createTestUser(t, "otherIt", c) ul, _, err := c.SearchUsers(SearchUsersOption{KeyWord: "other"}) assert.NoError(t, err) assert.Len(t, ul, 1) ul, _, err = c.SearchUsers(SearchUsersOption{KeyWord: "notInTESTcase"}) assert.NoError(t, err) assert.Len(t, ul, 0) ul, _, err = c.SearchUsers(SearchUsersOption{KeyWord: "It"}) assert.NoError(t, err) assert.Len(t, ul, 2) } func TestUserFollow(t *testing.T) { log.Println("== TestUserFollow ==") c := newTestClient() me, _, _ := c.GetMyUserInfo() uA := "uFollow_A" uB := "uFollow_B" uC := "uFollow_C" createTestUser(t, uA, c) createTestUser(t, uB, c) createTestUser(t, uC, c) // A follow ME // B follow C & ME // C follow A & B & ME c.sudo = uA _, err := c.Follow(me.UserName) assert.NoError(t, err) c.sudo = uB _, err = c.Follow(me.UserName) assert.NoError(t, err) _, err = c.Follow(uC) assert.NoError(t, err) c.sudo = uC _, err = c.Follow(me.UserName) assert.NoError(t, err) _, err = c.Follow(uA) assert.NoError(t, err) _, err = c.Follow(uB) assert.NoError(t, err) // C unfollow me _, err = c.Unfollow(me.UserName) assert.NoError(t, err) // ListMyFollowers of me c.sudo = "" f, _, err := c.ListMyFollowers(ListFollowersOptions{}) assert.NoError(t, err) assert.Len(t, f, 2) // ListFollowers of A f, _, err = c.ListFollowers(uA, ListFollowersOptions{}) assert.NoError(t, err) assert.Len(t, f, 1) // ListMyFollowing of me f, _, err = c.ListMyFollowing(ListFollowingOptions{}) assert.NoError(t, err) assert.Len(t, f, 0) // ListFollowing of A f, _, err = c.ListFollowing(uA, ListFollowingOptions{}) assert.NoError(t, err) assert.Len(t, f, 1) assert.EqualValues(t, me.ID, f[0].ID) isFollow, _ := c.IsFollowing(uA) assert.False(t, isFollow) isFollow, _ = c.IsUserFollowing(uB, uC) assert.True(t, isFollow) } func TestUserEmail(t *testing.T) { log.Println("== TestUserEmail ==") c := newTestClient() userN := "TestUserEmail" createTestUser(t, userN, c) c.sudo = userN // ListEmails el, _, err := c.ListEmails(ListEmailsOptions{}) assert.NoError(t, err) assert.Len(t, el, 1) assert.EqualValues(t, "testuseremail@gitea.io", el[0].Email) assert.True(t, el[0].Primary) // AddEmail mails := []string{"wow@mail.send", "speed@mail.me"} el, _, err = c.AddEmail(CreateEmailOption{Emails: mails}) assert.NoError(t, err) assert.Len(t, el, 2) _, _, err = c.AddEmail(CreateEmailOption{Emails: []string{mails[1]}}) assert.Error(t, err) el, _, err = c.ListEmails(ListEmailsOptions{}) assert.NoError(t, err) assert.Len(t, el, 3) // DeleteEmail _, err = c.DeleteEmail(DeleteEmailOption{Emails: []string{mails[1]}}) assert.NoError(t, err) _, err = c.DeleteEmail(DeleteEmailOption{Emails: []string{"imaginary@e.de"}}) assert.Error(t, err) el, _, err = c.ListEmails(ListEmailsOptions{}) assert.NoError(t, err) assert.Len(t, el, 2) _, err = c.DeleteEmail(DeleteEmailOption{Emails: []string{mails[0]}}) assert.NoError(t, err) } func TestGetUserByID(t *testing.T) { log.Println("== TestGetUserByID ==") c := newTestClient() user1 := createTestUser(t, "user1", c) user2 := createTestUser(t, "user2", c) r1, _, err := c.GetUserByID(user1.ID) assert.NoError(t, err) assert.NotNil(t, r1) assert.Equal(t, user1.UserName, r1.UserName) r2, _, err := c.GetUserByID(user2.ID) assert.NoError(t, err) assert.NotNil(t, r2) assert.Equal(t, user2.UserName, r2.UserName) r3, _, err := c.GetUserByID(42) assert.Error(t, err) assert.Nil(t, r3) r4, _, err := c.GetUserByID(-1) assert.Error(t, err) assert.Nil(t, r4) } func createTestUser(t *testing.T, username string, client *Client) *User { user, _, _ := client.GetUserInfo(username) if user.ID != 0 { return user } user, _, err := client.AdminCreateUser(CreateUserOption{Username: username, Password: username + "!1234", Email: username + "@gitea.io", MustChangePassword: OptionalBool(false), SendNotify: false}) assert.NoError(t, err) return user } // userToStringSlice return string slice based on UserName of users func userToStringSlice(users []*User) []string { result := make([]string, 0, len(users)) for i := range users { result = append(result, users[i].UserName) } return result } go-sdk/gitea/version.go000066400000000000000000000067061453336351000153630ustar00rootroot00000000000000// 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" "strings" "github.com/hashicorp/go-version" ) // ServerVersion returns the version of the server func (c *Client) ServerVersion() (string, *Response, error) { 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 } // SetGiteaVersion configures the Client to assume the given version of the // Gitea server, instead of querying the server for it when initializing. // Use "" to skip all canonical ways in the SDK to check for versions func SetGiteaVersion(v string) ClientOption { if v == "" { return func(c *Client) error { c.ignoreVersion = true return nil } } return func(c *Client) (err error) { c.getVersionOnce.Do(func() { c.serverVersion, err = version.NewVersion(v) }) return } } // predefined versions only have to be parsed by library once var ( version1_11_0 = version.Must(version.NewVersion("1.11.0")) version1_11_5 = version.Must(version.NewVersion("1.11.5")) version1_12_0 = version.Must(version.NewVersion("1.12.0")) version1_12_3 = version.Must(version.NewVersion("1.12.3")) version1_13_0 = version.Must(version.NewVersion("1.13.0")) version1_14_0 = version.Must(version.NewVersion("1.14.0")) version1_15_0 = version.Must(version.NewVersion("1.15.0")) version1_16_0 = version.Must(version.NewVersion("1.16.0")) version1_17_0 = version.Must(version.NewVersion("1.17.0")) ) // ErrUnknownVersion is an unknown version from the API type ErrUnknownVersion struct { raw string } // Error fulfills error func (e ErrUnknownVersion) Error() string { return fmt.Sprintf("unknown version: %s", e.raw) } func (_ ErrUnknownVersion) Is(target error) bool { _, ok1 := target.(*ErrUnknownVersion) _, ok2 := target.(ErrUnknownVersion) return ok1 || ok2 } // checkServerVersionGreaterThanOrEqual is the canonical way in the SDK to check for versions for API compatibility reasons func (c *Client) checkServerVersionGreaterThanOrEqual(v *version.Version) error { if c.ignoreVersion { return nil } if err := c.loadServerVersion(); err != nil { return err } 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 { if strings.TrimSpace(raw) != "" { // Version was something, just not recognized c.serverVersion = version1_11_0 err = &ErrUnknownVersion{raw: raw} } return } }) return } go-sdk/gitea/version_test.go000066400000000000000000000016741453336351000164210ustar00rootroot00000000000000// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package gitea import ( "log" "testing" "github.com/stretchr/testify/assert" ) func TestVersion(t *testing.T) { log.Printf("== TestVersion ==") c := newTestClient() rawVersion, _, err := c.ServerVersion() assert.NoError(t, err) assert.True(t, true, rawVersion != "") assert.NoError(t, c.checkServerVersionGreaterThanOrEqual(version1_11_0)) assert.Error(t, c.CheckServerVersionConstraint("< 1.11.0")) c.serverVersion = version1_11_0 assert.Error(t, c.checkServerVersionGreaterThanOrEqual(version1_15_0)) c.ignoreVersion = true assert.NoError(t, c.checkServerVersionGreaterThanOrEqual(version1_15_0)) c, err = NewClient(getGiteaURL(), newTestClientAuth(), SetGiteaVersion("1.12.123")) assert.NoError(t, err) assert.NoError(t, c.CheckServerVersionConstraint("=1.12.123")) }