diff --git a/.air.toml b/.air.toml index 71d7623dfb..6825118b95 100644 --- a/.air.toml +++ b/.air.toml @@ -10,3 +10,4 @@ include_file = ["main.go"] include_dir = ["cmd", "models", "modules", "options", "routers", "services"] exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata", "models/fixtures", "models/migrations/fixtures", "modules/migration/file_format_testdata", "modules/avatar/identicon/testdata"] exclude_regex = ["_test.go$", "_gen.go$"] +stop_on_error = true diff --git a/.drone.yml b/.drone.yml index 5096ce781f..459536621b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -267,35 +267,6 @@ steps: - name: deps path: /go ---- -kind: pipeline -type: docker -name: compliance-docs - -platform: - os: linux - arch: amd64 - -trigger: - event: - - pull_request - paths: - include: - - "docs/**" - - "*.md" - -steps: - - name: deps-frontend - image: node:18 - pull: always - commands: - - make deps-frontend - - - name: lint-md - image: node:18 - commands: - - make lint-md - --- kind: pipeline type: docker @@ -1033,49 +1004,6 @@ steps: from_secret: github_token depends_on: [gpg-sign] ---- -kind: pipeline -type: docker -name: docs - -platform: - os: linux - arch: arm64 - -depends_on: - - compliance - -trigger: - event: - - push - - tag - - pull_request - paths: - include: - - "docs/**" - -steps: - - name: build-docs - image: gitea/test_env:linux-1.20-arm64 - commands: - - cd docs - - make trans-copy clean build - - - name: publish-docs - image: techknowlogick/drone-netlify:latest - pull: always - settings: - path: docs/public/ - site_id: d2260bae-7861-4c02-8646-8f6440b12672 - environment: - NETLIFY_TOKEN: - from_secret: netlify_token - when: - branch: - - main - event: - - push - --- kind: pipeline type: docker @@ -1339,7 +1267,7 @@ steps: pull: always settings: auto_tag: false - tags: ${DRONE_BRANCH##release/v}-dev-linux-amd64 + tags: ${DRONE_BRANCH##release/v}-nightly-linux-amd64 repo: gitea/gitea build_args: - GOPROXY=https://goproxy.io @@ -1361,7 +1289,7 @@ steps: settings: dockerfile: Dockerfile.rootless auto_tag: false - tags: ${DRONE_BRANCH##release/v}-dev-linux-amd64-rootless + tags: ${DRONE_BRANCH##release/v}-nightly-linux-amd64-rootless repo: gitea/gitea build_args: - GOPROXY=https://goproxy.io @@ -1378,43 +1306,6 @@ steps: exclude: - pull_request ---- -kind: pipeline -type: docker -name: docker-linux-arm64-dry-run - -platform: - os: linux - arch: arm64 - -depends_on: - - compliance - -trigger: - event: - - pull_request - paths: - exclude: - - "docs/**" - -steps: - - name: dryrun - image: plugins/docker:latest - pull: always - settings: - dry_run: true - repo: gitea/gitea - tags: linux-arm64 - build_args: - - GOPROXY=https://goproxy.io - environment: - PLUGIN_MIRROR: - from_secret: plugin_mirror - DOCKER_BUILDKIT: 1 - when: - event: - - pull_request - --- kind: pipeline type: docker @@ -1681,7 +1572,7 @@ steps: pull: always settings: auto_tag: false - tags: ${DRONE_BRANCH##release/v}-dev-linux-arm64 + tags: ${DRONE_BRANCH##release/v}-nightly-linux-arm64 repo: gitea/gitea build_args: - GOPROXY=https://goproxy.io @@ -1703,7 +1594,7 @@ steps: settings: dockerfile: Dockerfile.rootless auto_tag: false - tags: ${DRONE_BRANCH##release/v}-dev-linux-arm64-rootless + tags: ${DRONE_BRANCH##release/v}-nightly-linux-arm64-rootless repo: gitea/gitea build_args: - GOPROXY=https://goproxy.io @@ -1818,57 +1709,3 @@ depends_on: - docker-linux-arm64-release - docker-linux-amd64-release-branch - docker-linux-arm64-release-branch - ---- -kind: pipeline -type: docker -name: notifications - -platform: - os: linux - arch: arm64 - -clone: - disable: true - -trigger: - branch: - - main - - "release/*" - event: - - push - - tag - status: - - success - - failure - -depends_on: - - testing-mysql - - testing-mysql8 - - testing-mssql - - testing-pgsql - - testing-sqlite - - release-version - - release-latest - - docker-linux-amd64-release - - docker-linux-arm64-release - - docker-linux-amd64-release-version - - docker-linux-arm64-release-version - - docker-linux-amd64-release-candidate-version - - docker-linux-arm64-release-candidate-version - - docker-linux-amd64-release-branch - - docker-linux-arm64-release-branch - - docker-manifest - - docker-manifest-version - - docs - -steps: - - name: discord - image: appleboy/drone-discord:1.2.4 - pull: always - settings: - message: "{{#success build.status}} ✅ Build #{{build.number}} of `{{repo.name}}` succeeded.\n\n📝 Commit by {{commit.author}} on `{{commit.branch}}`:\n``` {{commit.message}} ```\n\n🌐 {{ build.link }} {{else}} ❌ Build #{{build.number}} of `{{repo.name}}` failed.\n\n📝 Commit by {{commit.author}} on `{{commit.branch}}`:\n``` {{commit.message}} ```\n\n🌐 {{ build.link }} {{/success}}\n" - webhook_id: - from_secret: discord_webhook_id - webhook_token: - from_secret: discord_webhook_token diff --git a/.eslintrc.yaml b/.eslintrc.yaml index f87a4149ac..878b74cba4 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -15,6 +15,7 @@ plugins: - eslint-plugin-no-jquery - eslint-plugin-sonarjs - eslint-plugin-custom-elements + - eslint-plugin-regexp env: es2022: true @@ -472,6 +473,80 @@ rules: quote-props: [0] quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}] radix: [2, as-needed] + regexp/confusing-quantifier: [2] + regexp/control-character-escape: [2] + regexp/hexadecimal-escape: [0] + regexp/letter-case: [0] + regexp/match-any: [2] + regexp/negation: [2] + regexp/no-contradiction-with-assertion: [0] + regexp/no-control-character: [0] + regexp/no-dupe-characters-character-class: [2] + regexp/no-dupe-disjunctions: [2] + regexp/no-empty-alternative: [2] + regexp/no-empty-capturing-group: [2] + regexp/no-empty-character-class: [0] + regexp/no-empty-group: [2] + regexp/no-empty-lookarounds-assertion: [2] + regexp/no-escape-backspace: [2] + regexp/no-extra-lookaround-assertions: [0] + regexp/no-invalid-regexp: [2] + regexp/no-invisible-character: [2] + regexp/no-lazy-ends: [2] + regexp/no-legacy-features: [2] + regexp/no-misleading-capturing-group: [0] + regexp/no-misleading-unicode-character: [0] + regexp/no-missing-g-flag: [2] + regexp/no-non-standard-flag: [2] + regexp/no-obscure-range: [2] + regexp/no-octal: [2] + regexp/no-optional-assertion: [2] + regexp/no-potentially-useless-backreference: [2] + regexp/no-standalone-backslash: [2] + regexp/no-super-linear-backtracking: [0] + regexp/no-super-linear-move: [0] + regexp/no-trivially-nested-assertion: [2] + regexp/no-trivially-nested-quantifier: [2] + regexp/no-unused-capturing-group: [0] + regexp/no-useless-assertions: [2] + regexp/no-useless-backreference: [2] + regexp/no-useless-character-class: [2] + regexp/no-useless-dollar-replacements: [2] + regexp/no-useless-escape: [2] + regexp/no-useless-flag: [2] + regexp/no-useless-lazy: [2] + regexp/no-useless-non-capturing-group: [2] + regexp/no-useless-quantifier: [2] + regexp/no-useless-range: [2] + regexp/no-useless-two-nums-quantifier: [2] + regexp/no-zero-quantifier: [2] + regexp/optimal-lookaround-quantifier: [2] + regexp/optimal-quantifier-concatenation: [0] + regexp/prefer-character-class: [0] + regexp/prefer-d: [0] + regexp/prefer-escape-replacement-dollar-char: [0] + regexp/prefer-lookaround: [0] + regexp/prefer-named-backreference: [0] + regexp/prefer-named-capture-group: [0] + regexp/prefer-named-replacement: [0] + regexp/prefer-plus-quantifier: [2] + regexp/prefer-predefined-assertion: [2] + regexp/prefer-quantifier: [0] + regexp/prefer-question-quantifier: [2] + regexp/prefer-range: [2] + regexp/prefer-regexp-exec: [2] + regexp/prefer-regexp-test: [2] + regexp/prefer-result-array-groups: [0] + regexp/prefer-star-quantifier: [2] + regexp/prefer-unicode-codepoint-escapes: [2] + regexp/prefer-w: [0] + regexp/require-unicode-regexp: [0] + regexp/sort-alternatives: [0] + regexp/sort-character-class-elements: [0] + regexp/sort-flags: [0] + regexp/strict: [2] + regexp/unicode-escape: [0] + regexp/use-ignore-case: [0] require-atomic-updates: [0] require-await: [0] require-unicode-regexp: [0] diff --git a/.github/workflows/pull-compliance_docs.yml b/.github/workflows/pull-compliance_docs.yml new file mode 100644 index 0000000000..e3c3a42541 --- /dev/null +++ b/.github/workflows/pull-compliance_docs.yml @@ -0,0 +1,22 @@ +name: Compliance testing for documentation + +on: + pull_request: + paths: + - "docs/**" + - "*.md" + +jobs: + compliance-docs: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup node + uses: actions/setup-node@v2 + with: + node-version: 18 + - name: install dependencies + run: make deps-frontend + - name: lint markdown + run: make lint-md diff --git a/.github/workflows/pull-docker_dryrun.yml b/.github/workflows/pull-docker_dryrun.yml new file mode 100644 index 0000000000..719668db81 --- /dev/null +++ b/.github/workflows/pull-docker_dryrun.yml @@ -0,0 +1,19 @@ +name: Docker build dry run + +on: [pull_request] + +jobs: + docker_dryrun: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Build and push + uses: docker/build-push-action@v4 + with: + push: false + tags: gitea/gitea:linux-amd64 + build-args: | + GOPROXY=https://goproxy.io diff --git a/.github/workflows/push-publish_docs.yml b/.github/workflows/push-publish_docs.yml new file mode 100644 index 0000000000..579157ccd8 --- /dev/null +++ b/.github/workflows/push-publish_docs.yml @@ -0,0 +1,31 @@ +name: Publish documentation + +on: + push: + paths: + - "docs/**" + branches: + - main + +jobs: + compliance-docs: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v3 + - name: setup go + uses: actions/setup-go@v4 + with: + go-version: '>=1.20.1' + - name: build docs + run: | + cd docs + make trans-copy clean build + - name: publish to netlify + uses: nwtgck/actions-netlify@v2.0 + with: + production-branch: main + publish-dir: docs/public/ + env: + NETLIFY_SITE_ID: d2260bae-7861-4c02-8646-8f6440b12672 + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} diff --git a/.lgtm b/.lgtm deleted file mode 100644 index d39781b0bb..0000000000 --- a/.lgtm +++ /dev/null @@ -1,3 +0,0 @@ -pattern = "(?)LGTM" -self_approval_off = true -ignore_maintainers_file = true diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml index 64c4122ec3..220af352a1 100644 --- a/.stylelintrc.yaml +++ b/.stylelintrc.yaml @@ -96,7 +96,7 @@ rules: property-no-vendor-prefix: null rule-empty-line-before: null rule-selector-property-disallowed-list: null - scale-unlimited/declaration-strict-value: [color, {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor)$/}] + scale-unlimited/declaration-strict-value: [[color, background-color, border-color], {ignoreValues: /^(inherit|transparent|unset|initial|currentcolor|none)$/, ignoreFunctions: false}] selector-attribute-name-disallowed-list: null selector-attribute-operator-allowed-list: null selector-attribute-operator-disallowed-list: null diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bdd4c04f0..c9d1bab896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,53 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.19.2](https://github.com/go-gitea/gitea/releases/tag/1.19.2) - 2023-04-26 + +* SECURITY + * Require repo scope for PATs for private repos and basic authentication (#24362) (#24364) + * Only delete secrets belonging to its owner (#24284) (#24286) +* API + * Fix typo in API route (#24310) (#24332) + * Fix access token issue on some public endpoints (#24194) (#24259) +* ENHANCEMENTS + * Fix broken clone script on an empty archived repo (#24339) (#24348) + * Fix Monaco IOS keyboard button (#24341) (#24347) + * Don't set meta `theme-color` by default (#24340) (#24346) + * Wrap too long push mirror addresses (#21120) (#24334) + * Add --font-weight-bold and set previous bold to 601 (#24307) (#24331) + * Unify nightly naming across binaries and docker images (#24116) (#24308) + * Fix footer display (#24251) (#24269) + * Fix label color, fix divider in dropdown (#24215) (#24244) + * Vertical widths of containers removed (#24184) (#24211) + * Use correct locale key for forks page (#24172) (#24175) + * Sort repo topic labels by name (#24123) (#24153) + * Highlight selected file in the PR file tree (#23947) (#24126) +* BUGFIXES + * Fix auth check bug (#24382) (#24387) + * Add tags list for repos whose release setting is disabled (#23465) (#24369) + * Fix wrong error info in RepoRefForAPI (#24344) (#24351) + * Fix no edit/close/delete button in org repo project view page (#24349) + * Respect the REGISTER_MANUAL_CONFIRM setting when registering via OIDC (#24035) (#24333) + * Remove org users who belong to no teams (#24247) (#24313) + * Fix bug when deleting wiki with no code write permission (#24274) (#24295) + * Handle canceled workflow as a warning instead of a fail (#24282) (#24292) + * Load reviewer for comments when dismissing a review (#24281) (#24288) + * Show commit history for closed/merged PRs (#24238) (#24261) + * Fix owner team access mode value in team_unit table (#24224) + * Fix issue attachment handling (#24202) (#24221) + * Fix incorrect CORS default values (#24206) (#24217) + * Fix template error in pull request with deleted head repo (#24192) (#24216) + * Don't list root repository on compare page if pulls not allowed (#24183) (#24210) + * Fix calReleaseNumCommitsBehind (#24148) (#24197) + * Fix Org edit page bugs: renaming detection, maxlength (#24161) (#24171) + * Update redis library to support redis v7 (#24114) (#24156) + * Use 1.18's aria role for dropdown menus (#24144) (#24155) + * Fix 2-dot direct compare to use the right base commit (#24133) (#24150) + * Fix incorrect server error content in RunnersList (#24118) (#24121) + * Fix mismatch between hook events and github event types (#24048) (#24091) +* BUILD + * Support converting varchar to nvarchar for mssql database (#24105) (#24168) + ## [1.19.1](https://github.com/go-gitea/gitea/releases/tag/v1.19.1) - 2023-04-12 * BREAKING diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2cea0a4c57..c410efb065 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,8 +121,7 @@ See the [hacking instructions](https://docs.gitea.io/en-us/hacking-on-gitea/). Changes to Gitea must be reviewed before they are accepted—no matter who makes the change, even if they are 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. +pull request workflow to do that. Every PR is reviewed by at least 2 maintainers. Please try to make your pull request easy to review for us. And, please read the *[How to get faster PR reviews](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews)* guide; diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 1b6ead5df6..57285b6a92 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -114,6 +114,11 @@ "path": "github.com/bits-and-blooms/bitset/LICENSE", "licenseText": "Copyright (c) 2014 Will Fitzgerald. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, + { + "name": "github.com/blakesmith/ar", + "path": "github.com/blakesmith/ar/COPYING", + "licenseText": "Copyright (c) 2013 Blake Smith \u003cblakesmith0@gmail.com\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n" + }, { "name": "github.com/blevesearch/bleve/v2", "path": "github.com/blevesearch/bleve/v2/LICENSE", @@ -1104,4 +1109,4 @@ "path": "xorm.io/xorm/LICENSE", "licenseText": "Copyright (c) 2013 - 2015 The Xorm Authors\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* Neither the name of the {organization} nor the names of its\n contributors may be used to endorse or promote products derived from\n this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" } -] \ No newline at end of file +] diff --git a/build/generate-go-licenses.go b/build/generate-go-licenses.go index bdac51e71c..addab0762a 100644 --- a/build/generate-go-licenses.go +++ b/build/generate-go-licenses.go @@ -82,6 +82,11 @@ func main() { panic(err) } + // Ensure file has a final newline + if jsonBytes[len(jsonBytes)-1] != '\n' { + jsonBytes = append(jsonBytes, '\n') + } + err = os.WriteFile(out, jsonBytes, 0o644) if err != nil { panic(err) diff --git a/cmd/migrate_storage_test.go b/cmd/migrate_storage_test.go index e6658b6e65..7f3de894ba 100644 --- a/cmd/migrate_storage_test.go +++ b/cmd/migrate_storage_test.go @@ -25,7 +25,7 @@ func TestMigratePackages(t *testing.T) { creator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) content := "package main\n\nfunc main() {\nfmt.Println(\"hi\")\n}\n" - buf, err := packages_module.CreateHashedBufferFromReader(strings.NewReader(content), 1024) + buf, err := packages_module.CreateHashedBufferFromReaderWithSize(strings.NewReader(content), 1024) assert.NoError(t, err) defer buf.Close() diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 144691c00c..be043f3339 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -992,7 +992,7 @@ ROUTER = console ;; ;; List of file extensions for which lines should be wrapped in the Monaco editor ;; Separate extensions with a comma. To line wrap files without an extension, just put a comma -;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd, +;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,.livemd, ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1334,7 +1334,7 @@ ROUTER = console ;; ;; List of file extensions that should be rendered/edited as Markdown ;; Separate the extensions with a comma. To render files without any extension as markdown, just put a comma -;FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd +;FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd,.livemd ;; ;; Enables math inline and block detection ;ENABLE_MATH = true @@ -1899,11 +1899,6 @@ ROUTER = console ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; Specifies the format for fully outputted dates. Defaults to RFC1123 -;; Special supported values are ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, RFC3339, RFC3339Nano, Kitchen, Stamp, StampMilli, StampMicro and StampNano -;; For more information about the format see http://golang.org/pkg/time/#pkg-constants -;FORMAT = -;; ;; Location the UI time display i.e. Asia/Shanghai ;; Empty means server's location setting ;DEFAULT_UI_LOCATION = @@ -2501,6 +2496,8 @@ ROUTER = console ;LIMIT_SIZE_CONDA = -1 ;; Maximum size of a Container upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_CONTAINER = -1 +;; Maximum size of a Debian upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) +;LIMIT_SIZE_DEBIAN = -1 ;; Maximum size of a Generic upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_GENERIC = -1 ;; Maximum size of a Helm upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) diff --git a/docs/assets/js/search.js b/docs/assets/js/search.js index 315a5ebd91..f731e0b224 100644 --- a/docs/assets/js/search.js +++ b/docs/assets/js/search.js @@ -138,8 +138,8 @@ function populateResults(result) { function render(templateString, data) { let conditionalMatches, copy; - const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g; - // since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop + const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*\}/g; + // since loop below depends on re.lastIndex, we use a copy to capture any manipulations whilst inside the loop copy = templateString; while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) { if (data[conditionalMatches[1]]) { diff --git a/docs/config.yaml b/docs/config.yaml index d79a91b0a4..90a4c808f7 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -10,7 +10,7 @@ enableEmoji: true permalinks: post: /:year/:month/:title/ - doc: /:slug/ + doc: /:sections[1:]/:slug/ page: /:slug/ default: /:slug/ diff --git a/docs/content/doc/administration/_index.en-us.md b/docs/content/doc/administration/_index.en-us.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/administration/_index.zh-cn.md b/docs/content/doc/administration/_index.zh-cn.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/administration/_index.zh-tw.md b/docs/content/doc/administration/_index.zh-tw.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/administration/adding-legal-pages.en-us.md b/docs/content/doc/administration/adding-legal-pages.en-us.md index 48aa21154c..6de145ce09 100644 --- a/docs/content/doc/administration/adding-legal-pages.en-us.md +++ b/docs/content/doc/administration/adding-legal-pages.en-us.md @@ -5,6 +5,8 @@ slug: adding-legal-pages weight: 110 toc: false draft: false +aliases: + - /en-us/adding-legal-pages menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/backup-and-restore.en-us.md b/docs/content/doc/administration/backup-and-restore.en-us.md index b1e5a6aee0..7dee038b57 100644 --- a/docs/content/doc/administration/backup-and-restore.en-us.md +++ b/docs/content/doc/administration/backup-and-restore.en-us.md @@ -5,6 +5,8 @@ slug: "backup-and-restore" weight: 11 toc: false draft: false +aliases: + - /en-us/backup-and-restore menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/backup-and-restore.zh-cn.md b/docs/content/doc/administration/backup-and-restore.zh-cn.md index 602657f419..1a8f620a8d 100644 --- a/docs/content/doc/administration/backup-and-restore.zh-cn.md +++ b/docs/content/doc/administration/backup-and-restore.zh-cn.md @@ -5,6 +5,8 @@ slug: "backup-and-restore" weight: 11 toc: false draft: false +aliases: + - /zh-cn/backup-and-restore menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/backup-and-restore.zh-tw.md b/docs/content/doc/administration/backup-and-restore.zh-tw.md index cab95217a3..07b9b0726b 100644 --- a/docs/content/doc/administration/backup-and-restore.zh-tw.md +++ b/docs/content/doc/administration/backup-and-restore.zh-tw.md @@ -5,6 +5,8 @@ slug: "backup-and-restore" weight: 11 toc: false draft: false +aliases: + - /zh-tw/backup-and-restore menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/cmd-embedded.en-us.md b/docs/content/doc/administration/cmd-embedded.en-us.md index 229a1c01d6..36cf00082b 100644 --- a/docs/content/doc/administration/cmd-embedded.en-us.md +++ b/docs/content/doc/administration/cmd-embedded.en-us.md @@ -5,6 +5,8 @@ slug: "cmd-embedded" weight: 20 toc: false draft: false +aliases: + - /en-us/cmd-embedded menu: sidebar: parent: "administration" @@ -38,7 +40,7 @@ gitea embedded list [--include-vendored] [patterns...] The `--include-vendored` flag makes the command include vendored files, which are normally excluded; that is, files from external libraries that are required for Gitea -(e.g. [font-awesome](https://fontawesome.com/), [octicons](https://octicons.github.com/), etc). +(e.g. [octicons](https://octicons.github.com/), etc). A list of file search patterns can be provided. Gitea uses [gobwas/glob](https://github.com/gobwas/glob) for its glob syntax. Here are some examples: diff --git a/docs/content/doc/administration/command-line.en-us.md b/docs/content/doc/administration/command-line.en-us.md index 4d01d6e640..bf4578afec 100644 --- a/docs/content/doc/administration/command-line.en-us.md +++ b/docs/content/doc/administration/command-line.en-us.md @@ -5,6 +5,8 @@ slug: "command-line" weight: 1 toc: false draft: false +aliases: + - /en-us/command-line menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/config-cheat-sheet.en-us.md b/docs/content/doc/administration/config-cheat-sheet.en-us.md index 74110ea443..b1e393b6c5 100644 --- a/docs/content/doc/administration/config-cheat-sheet.en-us.md +++ b/docs/content/doc/administration/config-cheat-sheet.en-us.md @@ -5,6 +5,8 @@ slug: "config-cheat-sheet" weight: 30 toc: false draft: false +aliases: + - /en-us/config-cheat-sheet menu: sidebar: parent: "administration" @@ -117,7 +119,7 @@ In addition there is _`StaticRootPath`_ which can be set as a built-in at build ### Repository - Editor (`repository.editor`) -- `LINE_WRAP_EXTENSIONS`: **.txt,.md,.markdown,.mdown,.mkd,**: List of file extensions for which lines should be wrapped in the Monaco editor. Separate extensions with a comma. To line wrap files without an extension, just put a comma +- `LINE_WRAP_EXTENSIONS`: **.txt,.md,.markdown,.mdown,.mkd,.livemd,**: List of file extensions for which lines should be wrapped in the Monaco editor. Separate extensions with a comma. To line wrap files without an extension, just put a comma - `PREVIEWABLE_FILE_MODES`: **markdown**: Valid file modes that have a preview API associated with them, such as `api/v1/markdown`. Separate the values by commas. The preview tab in edit mode won't be displayed if the file extension doesn't match. ### Repository - Pull Request (`repository.pull-request`) @@ -277,6 +279,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `CUSTOM_URL_SCHEMES`: Use a comma separated list (ftp,git,svn) to indicate additional URL hyperlinks to be rendered in Markdown. URLs beginning in http and https are always displayed +- `FILE_EXTENSIONS`: **.md,.markdown,.mdown,.mkd,.livemd**: List of file extensions that should be rendered/edited as Markdown. Separate the extensions with a comma. To render files without any extension as markdown, just put a comma. - `ENABLE_MATH`: **true**: Enables detection of `\(...\)`, `\[...\]`, `$...$` and `$$...$$` blocks as math blocks. ## Server (`server`) @@ -1204,7 +1207,6 @@ in this mapping or the filetype using heuristics. ## Time (`time`) -- `FORMAT`: Time format to display on UI. i.e. RFC1123 or 2006-01-02 15:04:05 - `DEFAULT_UI_LOCATION`: Default location of time on the UI, so that we can display correct user's time on UI. i.e. Asia/Shanghai ## Task (`task`) @@ -1249,6 +1251,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf - `LIMIT_SIZE_CONAN`: **-1**: Maximum size of a Conan upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_CONDA`: **-1**: Maximum size of a Conda upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_CONTAINER`: **-1**: Maximum size of a Container upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) +- `LIMIT_SIZE_DEBIAN`: **-1**: Maximum size of a Debian upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_GENERIC`: **-1**: Maximum size of a Generic upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_HELM`: **-1**: Maximum size of a Helm upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_MAVEN`: **-1**: Maximum size of a Maven upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) diff --git a/docs/content/doc/administration/config-cheat-sheet.zh-cn.md b/docs/content/doc/administration/config-cheat-sheet.zh-cn.md index 485d106234..eb015908bb 100644 --- a/docs/content/doc/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/administration/config-cheat-sheet.zh-cn.md @@ -5,6 +5,8 @@ slug: "config-cheat-sheet" weight: 30 toc: false draft: false +aliases: + - /zh-cn/config-cheat-sheet menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/customizing-gitea.en-us.md b/docs/content/doc/administration/customizing-gitea.en-us.md index db31dc949b..54ce2a715f 100644 --- a/docs/content/doc/administration/customizing-gitea.en-us.md +++ b/docs/content/doc/administration/customizing-gitea.en-us.md @@ -5,6 +5,8 @@ slug: "customizing-gitea" weight: 100 toc: false draft: false +aliases: + - /en-us/customizing-gitea menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/customizing-gitea.zh-cn.md b/docs/content/doc/administration/customizing-gitea.zh-cn.md index 38729153f5..47230ddeb2 100644 --- a/docs/content/doc/administration/customizing-gitea.zh-cn.md +++ b/docs/content/doc/administration/customizing-gitea.zh-cn.md @@ -5,6 +5,8 @@ slug: "customizing-gitea" weight: 100 toc: false draft: false +aliases: + - /zh-cn/customizing-gitea menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/email-setup.en-us.md b/docs/content/doc/administration/email-setup.en-us.md index b8a3324f16..92ec95cce4 100644 --- a/docs/content/doc/administration/email-setup.en-us.md +++ b/docs/content/doc/administration/email-setup.en-us.md @@ -5,6 +5,8 @@ slug: "email-setup" weight: 12 toc: false draft: false +aliases: + - /en-us/email-setup menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/environment-variables.en-us.md b/docs/content/doc/administration/environment-variables.en-us.md index 83ff6f359a..92dbeb80c6 100644 --- a/docs/content/doc/administration/environment-variables.en-us.md +++ b/docs/content/doc/administration/environment-variables.en-us.md @@ -5,6 +5,8 @@ slug: "environment-variables" weight: 10 toc: false draft: false +aliases: + - /en-us/environment-variables menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/environment-variables.zh-cn.md b/docs/content/doc/administration/environment-variables.zh-cn.md index e148838581..cbc9787c35 100644 --- a/docs/content/doc/administration/environment-variables.zh-cn.md +++ b/docs/content/doc/administration/environment-variables.zh-cn.md @@ -5,6 +5,8 @@ slug: "environment-variables" weight: 10 toc: false draft: false +aliases: + - /zh-cn/environment-variables menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/external-renderers.en-us.md b/docs/content/doc/administration/external-renderers.en-us.md index a62c20e326..5e64bb847f 100644 --- a/docs/content/doc/administration/external-renderers.en-us.md +++ b/docs/content/doc/administration/external-renderers.en-us.md @@ -5,6 +5,8 @@ slug: "external-renderers" weight: 60 toc: false draft: false +aliases: + - /en-us/external-renderers menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/fail2ban-setup.en-us.md b/docs/content/doc/administration/fail2ban-setup.en-us.md index 420c6ae549..1638e0dd1f 100644 --- a/docs/content/doc/administration/fail2ban-setup.en-us.md +++ b/docs/content/doc/administration/fail2ban-setup.en-us.md @@ -5,6 +5,8 @@ slug: "fail2ban-setup" weight: 16 toc: false draft: false +aliases: + - /en-us/fail2ban-setup menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/fail2ban-setup.zh-cn.md b/docs/content/doc/administration/fail2ban-setup.zh-cn.md index 920d3f4af2..f4f3cff4ca 100644 --- a/docs/content/doc/administration/fail2ban-setup.zh-cn.md +++ b/docs/content/doc/administration/fail2ban-setup.zh-cn.md @@ -5,6 +5,8 @@ slug: "fail2ban-setup" weight: 16 toc: false draft: false +aliases: + - /zh-cn/fail2ban-setup menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/git-lfs-support.en-us.md b/docs/content/doc/administration/git-lfs-support.en-us.md index 95f4b95887..884b19896a 100644 --- a/docs/content/doc/administration/git-lfs-support.en-us.md +++ b/docs/content/doc/administration/git-lfs-support.en-us.md @@ -5,6 +5,8 @@ slug: "git-lfs-setup" weight: 12 toc: false draft: false +aliases: + - /en-us/git-lfs-setup menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/https-support.en-us.md b/docs/content/doc/administration/https-support.en-us.md index f5cd286823..d59ae2e8ee 100644 --- a/docs/content/doc/administration/https-support.en-us.md +++ b/docs/content/doc/administration/https-support.en-us.md @@ -5,6 +5,8 @@ slug: "https-setup" weight: 12 toc: false draft: false +aliases: + - /en-us/https-setup menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/logging-documentation.en-us.md b/docs/content/doc/administration/logging-documentation.en-us.md index 029eb5de09..3b2bf80769 100644 --- a/docs/content/doc/administration/logging-documentation.en-us.md +++ b/docs/content/doc/administration/logging-documentation.en-us.md @@ -5,6 +5,8 @@ slug: "logging-configuration" weight: 40 toc: false draft: false +aliases: + - /en-us/logging-configuration menu: sidebar: parent: "administration" @@ -29,7 +31,7 @@ As mentioned below, there is a fully functional log output by default, so it is ## Collecting Logs for Help -To collect logs for help and issue report, see [Support Options]({{< relref "doc/help/seek-help.en-us.md" >}}). +To collect logs for help and issue report, see [Support Options]({{< relref "doc/help/support.en-us.md" >}}). ## The `[log]` section diff --git a/docs/content/doc/administration/mail-templates.en-us.md b/docs/content/doc/administration/mail-templates.en-us.md index bc4b2ead30..0740ccaa5f 100644 --- a/docs/content/doc/administration/mail-templates.en-us.md +++ b/docs/content/doc/administration/mail-templates.en-us.md @@ -5,6 +5,8 @@ slug: "mail-templates" weight: 45 toc: false draft: false +aliases: + - /en-us/mail-templates menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/repo-indexer.en-us.md b/docs/content/doc/administration/repo-indexer.en-us.md index 11ce547a0d..81d2243476 100644 --- a/docs/content/doc/administration/repo-indexer.en-us.md +++ b/docs/content/doc/administration/repo-indexer.en-us.md @@ -5,6 +5,8 @@ slug: "repo-indexer" weight: 45 toc: false draft: false +aliases: + - /en-us/repo-indexer menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/reverse-proxies.en-us.md b/docs/content/doc/administration/reverse-proxies.en-us.md index 5132ee83f1..d11552bb9f 100644 --- a/docs/content/doc/administration/reverse-proxies.en-us.md +++ b/docs/content/doc/administration/reverse-proxies.en-us.md @@ -5,6 +5,8 @@ slug: "reverse-proxies" weight: 16 toc: false draft: false +aliases: + - /en-us/reverse-proxies menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/reverse-proxies.zh-cn.md b/docs/content/doc/administration/reverse-proxies.zh-cn.md index e622773df1..6504f7209b 100644 --- a/docs/content/doc/administration/reverse-proxies.zh-cn.md +++ b/docs/content/doc/administration/reverse-proxies.zh-cn.md @@ -5,6 +5,8 @@ slug: "reverse-proxies" weight: 16 toc: false draft: false +aliases: + - /zh-cn/reverse-proxies menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/search-engines-indexation.en-us.md b/docs/content/doc/administration/search-engines-indexation.en-us.md index 8c7f79d4b8..27427531ce 100644 --- a/docs/content/doc/administration/search-engines-indexation.en-us.md +++ b/docs/content/doc/administration/search-engines-indexation.en-us.md @@ -5,6 +5,8 @@ slug: "search-engines-indexation" weight: 60 toc: false draft: false +aliases: + - /en-us/search-engines-indexation menu: sidebar: parent: "administration" diff --git a/docs/content/doc/administration/signing.en-us.md b/docs/content/doc/administration/signing.en-us.md index 34580ab428..de539e34b2 100644 --- a/docs/content/doc/administration/signing.en-us.md +++ b/docs/content/doc/administration/signing.en-us.md @@ -5,6 +5,8 @@ slug: "signing" weight: 50 toc: false draft: false +aliases: + - /en-us/signing menu: sidebar: parent: "administration" diff --git a/docs/content/doc/contributing/_index.de-de.md b/docs/content/doc/contributing/_index.de-de.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/contributing/_index.en-us.md b/docs/content/doc/contributing/_index.en-us.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/contributing/_index.zh-cn.md b/docs/content/doc/contributing/_index.zh-cn.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/contributing/_index.zh-tw.md b/docs/content/doc/contributing/_index.zh-tw.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/contributing/guidelines-backend.en-us.md b/docs/content/doc/contributing/guidelines-backend.en-us.md index 6ae0ba517d..fd0f8b0830 100644 --- a/docs/content/doc/contributing/guidelines-backend.en-us.md +++ b/docs/content/doc/contributing/guidelines-backend.en-us.md @@ -5,6 +5,8 @@ slug: "guidelines-backend" weight: 20 toc: false draft: false +aliases: + - /en-us/guidelines-backend menu: sidebar: parent: "contributing" diff --git a/docs/content/doc/contributing/guidelines-frontend.en-us.md b/docs/content/doc/contributing/guidelines-frontend.en-us.md index bde7d31510..39a70eee97 100644 --- a/docs/content/doc/contributing/guidelines-frontend.en-us.md +++ b/docs/content/doc/contributing/guidelines-frontend.en-us.md @@ -5,6 +5,8 @@ slug: "guidelines-frontend" weight: 20 toc: false draft: false +aliases: + - /en-us/guidelines-frontend menu: sidebar: parent: "contributing" diff --git a/docs/content/doc/contributing/guidelines-refactoring.en-us.md b/docs/content/doc/contributing/guidelines-refactoring.en-us.md index 913ce8d9bb..c8c8ee0ee1 100644 --- a/docs/content/doc/contributing/guidelines-refactoring.en-us.md +++ b/docs/content/doc/contributing/guidelines-refactoring.en-us.md @@ -5,6 +5,8 @@ slug: "guidelines-refactoring" weight: 20 toc: false draft: false +aliases: + - /en-us/guidelines-refactoring menu: sidebar: parent: "contributing" diff --git a/docs/content/doc/contributing/localization.en-us.md b/docs/content/doc/contributing/localization.en-us.md index 3006ca599b..7f78fb3fa8 100644 --- a/docs/content/doc/contributing/localization.en-us.md +++ b/docs/content/doc/contributing/localization.en-us.md @@ -5,6 +5,8 @@ slug: "localization" weight: 10 toc: false draft: false +aliases: + - /en-us/localization menu: sidebar: parent: "contributing" diff --git a/docs/content/doc/contributing/localization.zh-cn.md b/docs/content/doc/contributing/localization.zh-cn.md index c20f4d4138..99a9f3743f 100644 --- a/docs/content/doc/contributing/localization.zh-cn.md +++ b/docs/content/doc/contributing/localization.zh-cn.md @@ -5,6 +5,8 @@ slug: "localization" weight: 20 toc: false draft: false +aliases: + - /zh-cn/localization menu: sidebar: parent: "contributing" diff --git a/docs/content/doc/contributing/localization.zh-tw.md b/docs/content/doc/contributing/localization.zh-tw.md index 815fbc3119..cfdc1dcb9b 100644 --- a/docs/content/doc/contributing/localization.zh-tw.md +++ b/docs/content/doc/contributing/localization.zh-tw.md @@ -5,6 +5,8 @@ slug: "localization" weight: 20 toc: false draft: false +aliases: + - /zh-tw/localization menu: sidebar: parent: "contributing" diff --git a/docs/content/doc/development/_index.en-us.md b/docs/content/doc/development/_index.en-us.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/development/_index.zh-cn.md b/docs/content/doc/development/_index.zh-cn.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/development/_index.zh-tw.md b/docs/content/doc/development/_index.zh-tw.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/development/api-usage.en-us.md b/docs/content/doc/development/api-usage.en-us.md index 641012607b..fe334827c3 100644 --- a/docs/content/doc/development/api-usage.en-us.md +++ b/docs/content/doc/development/api-usage.en-us.md @@ -5,6 +5,8 @@ slug: "api-usage" weight: 40 toc: false draft: false +aliases: + - /en-us/api-usage menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/api-usage.zh-cn.md b/docs/content/doc/development/api-usage.zh-cn.md index b0821039ff..ceb69b3f0e 100644 --- a/docs/content/doc/development/api-usage.zh-cn.md +++ b/docs/content/doc/development/api-usage.zh-cn.md @@ -5,6 +5,8 @@ slug: "api-usage" weight: 40 toc: false draft: false +aliases: + - /zh-cn/api-usage menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/hacking-on-gitea.en-us.md b/docs/content/doc/development/hacking-on-gitea.en-us.md index da38d238ac..8ac7fcc34c 100644 --- a/docs/content/doc/development/hacking-on-gitea.en-us.md +++ b/docs/content/doc/development/hacking-on-gitea.en-us.md @@ -5,6 +5,8 @@ slug: "hacking-on-gitea" weight: 10 toc: false draft: false +aliases: + - /en-us/hacking-on-gitea menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/hacking-on-gitea.zh-cn.md b/docs/content/doc/development/hacking-on-gitea.zh-cn.md index c63d0c4685..3a4078dfdb 100644 --- a/docs/content/doc/development/hacking-on-gitea.zh-cn.md +++ b/docs/content/doc/development/hacking-on-gitea.zh-cn.md @@ -5,6 +5,8 @@ slug: "hacking-on-gitea" weight: 10 toc: false draft: false +aliases: + - /zh-cn/hacking-on-gitea menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/integrations.en-us.md b/docs/content/doc/development/integrations.en-us.md index dae557c9b8..bbb50ae71d 100644 --- a/docs/content/doc/development/integrations.en-us.md +++ b/docs/content/doc/development/integrations.en-us.md @@ -5,6 +5,8 @@ slug: "integrations" weight: 65 toc: false draft: false +aliases: + - /en-us/integrations menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/integrations.zh-tw.md b/docs/content/doc/development/integrations.zh-tw.md index fdc2103990..278a8f41d5 100644 --- a/docs/content/doc/development/integrations.zh-tw.md +++ b/docs/content/doc/development/integrations.zh-tw.md @@ -5,6 +5,8 @@ slug: "integrations" weight: 65 toc: false draft: false +aliases: + - /zh-tw/integrations menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/migrations.en-us.md b/docs/content/doc/development/migrations.en-us.md index 1d9103254b..f411634156 100644 --- a/docs/content/doc/development/migrations.en-us.md +++ b/docs/content/doc/development/migrations.en-us.md @@ -5,6 +5,8 @@ slug: "migrations-interfaces" weight: 55 toc: false draft: false +aliases: + - /en-us/migrations-interfaces menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/migrations.zh-tw.md b/docs/content/doc/development/migrations.zh-tw.md index bf995598f5..de4ddd788d 100644 --- a/docs/content/doc/development/migrations.zh-tw.md +++ b/docs/content/doc/development/migrations.zh-tw.md @@ -5,6 +5,8 @@ slug: "migrations-interfaces" weight: 55 toc: false draft: false +aliases: + - /zh-tw/migrations-interfaces menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/oauth2-provider.en-us.md b/docs/content/doc/development/oauth2-provider.en-us.md index 7be7a42934..b279e97a6e 100644 --- a/docs/content/doc/development/oauth2-provider.en-us.md +++ b/docs/content/doc/development/oauth2-provider.en-us.md @@ -5,6 +5,8 @@ slug: "oauth2-provider" weight: 41 toc: false draft: false +aliases: + - /en-us/oauth2-provider menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/oauth2-provider.zh-cn.md b/docs/content/doc/development/oauth2-provider.zh-cn.md index 926500d64f..3fbf174efc 100644 --- a/docs/content/doc/development/oauth2-provider.zh-cn.md +++ b/docs/content/doc/development/oauth2-provider.zh-cn.md @@ -5,6 +5,8 @@ slug: "oauth2-provider" weight: 41 toc: false draft: false +aliases: + - /zh-cn/oauth2-provider menu: sidebar: parent: "development" diff --git a/docs/content/doc/development/oauth2-provider.zh-tw.md b/docs/content/doc/development/oauth2-provider.zh-tw.md index b28e48d61e..8d62264abc 100644 --- a/docs/content/doc/development/oauth2-provider.zh-tw.md +++ b/docs/content/doc/development/oauth2-provider.zh-tw.md @@ -5,6 +5,8 @@ slug: "oauth2-provider" weight: 41 toc: false draft: false +aliases: + - /zh-tw/oauth2-provider menu: sidebar: parent: "development" diff --git a/docs/content/doc/help/_index.en-us.md b/docs/content/doc/help/_index.en-us.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/help/_index.zh-cn.md b/docs/content/doc/help/_index.zh-cn.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/help/_index.zh-tw.md b/docs/content/doc/help/_index.zh-tw.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/help/faq.en-us.md b/docs/content/doc/help/faq.en-us.md index 4847e8e03b..53215e18a5 100644 --- a/docs/content/doc/help/faq.en-us.md +++ b/docs/content/doc/help/faq.en-us.md @@ -5,6 +5,8 @@ slug: "faq" weight: 5 toc: false draft: false +aliases: + - /en-us/faq menu: sidebar: parent: "help" @@ -17,7 +19,7 @@ menu: This page contains some common questions and answers. -For more help resources, check all [Support Options]({{< relref "doc/help/seek-help.en-us.md" >}}). +For more help resources, check all [Support Options]({{< relref "doc/help/support.en-us.md" >}}). **Table of Contents** @@ -408,7 +410,7 @@ Stdout on systemd goes to the journal by default. Try using `journalctl`, `journ Similarly, stdout on docker can be viewed using `docker logs `. -To collect logs for help and issue report, see [Support Options]({{< relref "doc/help/seek-help.en-us.md" >}}). +To collect logs for help and issue report, see [Support Options]({{< relref "doc/help/support.en-us.md" >}}). ## Initial logging diff --git a/docs/content/doc/help/seek-help.en-us.md b/docs/content/doc/help/support.en-us.md similarity index 97% rename from docs/content/doc/help/seek-help.en-us.md rename to docs/content/doc/help/support.en-us.md index 0e56b9a575..c6db3d6eb3 100644 --- a/docs/content/doc/help/seek-help.en-us.md +++ b/docs/content/doc/help/support.en-us.md @@ -1,16 +1,18 @@ --- date: "2018-05-21T15:00:00+00:00" title: "Support Options" -slug: "seek-help" +slug: "support" weight: 20 toc: false draft: false +aliases: + - /en-us/seek-help menu: sidebar: parent: "help" name: "Support Options" weight: 20 - identifier: "seek-help" + identifier: "support" --- # Support Options diff --git a/docs/content/doc/help/seek-help.zh-cn.md b/docs/content/doc/help/support.zh-cn.md similarity index 89% rename from docs/content/doc/help/seek-help.zh-cn.md rename to docs/content/doc/help/support.zh-cn.md index 80febad248..775dfe83bd 100644 --- a/docs/content/doc/help/seek-help.zh-cn.md +++ b/docs/content/doc/help/support.zh-cn.md @@ -1,16 +1,18 @@ --- date: "2017-01-20T15:00:00+08:00" title: "需要帮助" -slug: "seek-help" +slug: "support" weight: 20 toc: false draft: false +aliases: + - /zh-cn/seek-help menu: sidebar: parent: "help" name: "需要帮助" weight: 20 - identifier: "seek-help" + identifier: "support" --- ## 需要帮助? diff --git a/docs/content/doc/help/seek-help.zh-tw.md b/docs/content/doc/help/support.zh-tw.md similarity index 93% rename from docs/content/doc/help/seek-help.zh-tw.md rename to docs/content/doc/help/support.zh-tw.md index f87dad547a..a9c35eaafb 100644 --- a/docs/content/doc/help/seek-help.zh-tw.md +++ b/docs/content/doc/help/support.zh-tw.md @@ -1,16 +1,18 @@ --- date: "2018-05-21T15:00:00+00:00" title: "取得協助" -slug: "seek-help" +slug: "support" weight: 20 toc: false draft: false +aliases: + - /zh-tw/seek-help menu: sidebar: parent: "help" name: "取得協助" weight: 20 - identifier: "seek-help" + identifier: "support" --- # 取得協助 diff --git a/docs/content/doc/installation/_index.en-us.md b/docs/content/doc/installation/_index.en-us.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/installation/_index.fr-fr.md b/docs/content/doc/installation/_index.fr-fr.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/installation/_index.zh-cn.md b/docs/content/doc/installation/_index.zh-cn.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/installation/_index.zh-tw.md b/docs/content/doc/installation/_index.zh-tw.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/installation/comparison.en-us.md b/docs/content/doc/installation/comparison.en-us.md index 082b2a85ed..578b00c211 100644 --- a/docs/content/doc/installation/comparison.en-us.md +++ b/docs/content/doc/installation/comparison.en-us.md @@ -5,6 +5,8 @@ slug: "comparison" weight: 5 toc: false draft: false +aliases: + - /en-us/comparison menu: sidebar: name: "Comparison" diff --git a/docs/content/doc/installation/comparison.zh-cn.md b/docs/content/doc/installation/comparison.zh-cn.md index 7dfca526eb..06c770caa2 100644 --- a/docs/content/doc/installation/comparison.zh-cn.md +++ b/docs/content/doc/installation/comparison.zh-cn.md @@ -5,6 +5,8 @@ slug: "comparison" weight: 5 toc: false draft: false +aliases: + - /zh-cn/comparison menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/comparison.zh-tw.md b/docs/content/doc/installation/comparison.zh-tw.md index 8919052343..042acba8d3 100644 --- a/docs/content/doc/installation/comparison.zh-tw.md +++ b/docs/content/doc/installation/comparison.zh-tw.md @@ -5,6 +5,8 @@ slug: "comparison" weight: 5 toc: false draft: false +aliases: + - /zh-tw/comparison menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/database-preparation.en-us.md b/docs/content/doc/installation/database-preparation.en-us.md index edad89b07c..4b0d1b5ba8 100644 --- a/docs/content/doc/installation/database-preparation.en-us.md +++ b/docs/content/doc/installation/database-preparation.en-us.md @@ -5,6 +5,8 @@ slug: "database-prep" weight: 10 toc: false draft: false +aliases: + - /en-us/database-prep menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/from-binary.en-us.md b/docs/content/doc/installation/from-binary.en-us.md index ce1d7e5a2c..c7824c3480 100644 --- a/docs/content/doc/installation/from-binary.en-us.md +++ b/docs/content/doc/installation/from-binary.en-us.md @@ -5,6 +5,8 @@ slug: "install-from-binary" weight: 15 toc: false draft: false +aliases: + - /en-us/install-from-binary menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/from-binary.fr-fr.md b/docs/content/doc/installation/from-binary.fr-fr.md index 7dfbe7dd5f..eea3819384 100644 --- a/docs/content/doc/installation/from-binary.fr-fr.md +++ b/docs/content/doc/installation/from-binary.fr-fr.md @@ -5,6 +5,8 @@ slug: "install-from-binary" weight: 15 toc: false draft: false +aliases: + - /fr-fr/install-from-binary menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/from-binary.zh-cn.md b/docs/content/doc/installation/from-binary.zh-cn.md index 55cf6c1da2..abe799dc94 100644 --- a/docs/content/doc/installation/from-binary.zh-cn.md +++ b/docs/content/doc/installation/from-binary.zh-cn.md @@ -5,6 +5,8 @@ slug: "install-from-binary" weight: 15 toc: false draft: false +aliases: + - /zh-cn/install-from-binary menu: sidebar: parent: "installation" @@ -161,4 +163,4 @@ GITEA_WORK_DIR=/var/lib/gitea/ /usr/local/bin/gitea web -c /etc/gitea/app.ini > 更多经验总结,请参考英文版 [Troubleshooting](/en-us/install-from-binary/#troubleshooting) -如果从本页中没有找到你需要的内容,请访问 [帮助页面]({{< relref "seek-help.zh-cn.md" >}}) +如果从本页中没有找到你需要的内容,请访问 [帮助页面]({{< relref "support.zh-cn.md" >}}) diff --git a/docs/content/doc/installation/from-binary.zh-tw.md b/docs/content/doc/installation/from-binary.zh-tw.md index 3991b101ae..858cee2193 100644 --- a/docs/content/doc/installation/from-binary.zh-tw.md +++ b/docs/content/doc/installation/from-binary.zh-tw.md @@ -5,6 +5,8 @@ slug: "install-from-binary" weight: 15 toc: false draft: false +aliases: + - /zh-tw/install-from-binary menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/from-package.en-us.md b/docs/content/doc/installation/from-package.en-us.md index 2615b7d27d..15283d5837 100644 --- a/docs/content/doc/installation/from-package.en-us.md +++ b/docs/content/doc/installation/from-package.en-us.md @@ -5,6 +5,8 @@ slug: "install-from-package" weight: 20 toc: false draft: false +aliases: + - /en-us/install-from-package menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/from-package.fr-fr.md b/docs/content/doc/installation/from-package.fr-fr.md index eff74c31e5..b7e842400d 100644 --- a/docs/content/doc/installation/from-package.fr-fr.md +++ b/docs/content/doc/installation/from-package.fr-fr.md @@ -5,6 +5,8 @@ slug: "install-from-package" weight: 20 toc: false draft: false +aliases: + - /fr-fr/install-from-package menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/from-package.zh-cn.md b/docs/content/doc/installation/from-package.zh-cn.md index e3ed3aa371..a8e92694f9 100644 --- a/docs/content/doc/installation/from-package.zh-cn.md +++ b/docs/content/doc/installation/from-package.zh-cn.md @@ -5,6 +5,8 @@ slug: "install-from-package" weight: 20 toc: false draft: false +aliases: + - /zh-cn/install-from-package menu: sidebar: parent: "installation" @@ -107,4 +109,4 @@ make install clean ## 需要帮助? -如果从本页中没有找到你需要的内容,请访问 [帮助页面]({{< relref "seek-help.zh-cn.md" >}}) +如果从本页中没有找到你需要的内容,请访问 [帮助页面]({{< relref "support.zh-cn.md" >}}) diff --git a/docs/content/doc/installation/from-package.zh-tw.md b/docs/content/doc/installation/from-package.zh-tw.md index 1616ba6655..c5347991ec 100644 --- a/docs/content/doc/installation/from-package.zh-tw.md +++ b/docs/content/doc/installation/from-package.zh-tw.md @@ -5,6 +5,8 @@ slug: "install-from-package" weight: 20 toc: false draft: false +aliases: + - /zh-tw/install-from-package menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/from-source.en-us.md b/docs/content/doc/installation/from-source.en-us.md index 45064a43a8..72a3e12472 100644 --- a/docs/content/doc/installation/from-source.en-us.md +++ b/docs/content/doc/installation/from-source.en-us.md @@ -5,6 +5,8 @@ slug: "install-from-source" weight: 30 toc: false draft: false +aliases: + - /en-us/install-from-source menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/from-source.fr-fr.md b/docs/content/doc/installation/from-source.fr-fr.md index ebb0941754..544059b573 100644 --- a/docs/content/doc/installation/from-source.fr-fr.md +++ b/docs/content/doc/installation/from-source.fr-fr.md @@ -5,6 +5,8 @@ slug: "install-from-source" weight: 30 toc: false draft: false +aliases: + - /fr-fr/install-from-source menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/from-source.zh-cn.md b/docs/content/doc/installation/from-source.zh-cn.md index 9d5c50cbda..8f3ccbe7e2 100644 --- a/docs/content/doc/installation/from-source.zh-cn.md +++ b/docs/content/doc/installation/from-source.zh-cn.md @@ -5,6 +5,8 @@ slug: "install-from-source" weight: 30 toc: false draft: false +aliases: + - /zh-cn/install-from-source menu: sidebar: parent: "installation" @@ -104,4 +106,4 @@ CC=aarch64-unknown-linux-gnu-gcc GOOS=linux GOARCH=arm64 TAGS="bindata sqlite sq ## 需要帮助? -如果从本页中没有找到你需要的内容,请访问 [帮助页面]({{< relref "seek-help.zh-cn.md" >}}) +如果从本页中没有找到你需要的内容,请访问 [帮助页面]({{< relref "support.zh-cn.md" >}}) diff --git a/docs/content/doc/installation/from-source.zh-tw.md b/docs/content/doc/installation/from-source.zh-tw.md index fc9c2dca4c..f9e6e06eff 100644 --- a/docs/content/doc/installation/from-source.zh-tw.md +++ b/docs/content/doc/installation/from-source.zh-tw.md @@ -5,6 +5,8 @@ slug: "install-from-source" weight: 30 toc: false draft: false +aliases: + - /zh-tw/install-from-source menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/on-cloud-provider.en-us.md b/docs/content/doc/installation/on-cloud-provider.en-us.md index 550ef3f677..20ca812eff 100644 --- a/docs/content/doc/installation/on-cloud-provider.en-us.md +++ b/docs/content/doc/installation/on-cloud-provider.en-us.md @@ -5,6 +5,8 @@ slug: "install-on-cloud-provider" weight: 90 toc: false draft: false +aliases: + - /en-us/install-on-cloud-provider menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/on-kubernetes.en-us.md b/docs/content/doc/installation/on-kubernetes.en-us.md index e8f3612ea4..b46a61df02 100644 --- a/docs/content/doc/installation/on-kubernetes.en-us.md +++ b/docs/content/doc/installation/on-kubernetes.en-us.md @@ -5,6 +5,8 @@ slug: "install-on-kubernetes" weight: 80 toc: false draft: false +aliases: + - /en-us/install-on-kubernetes menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/on-kubernetes.zh-cn.md b/docs/content/doc/installation/on-kubernetes.zh-cn.md index 91dec2e73a..367aef1cf3 100644 --- a/docs/content/doc/installation/on-kubernetes.zh-cn.md +++ b/docs/content/doc/installation/on-kubernetes.zh-cn.md @@ -5,6 +5,8 @@ slug: "install-on-kubernetes" weight: 80 toc: false draft: false +aliases: + - /zh-cn/install-on-kubernetes menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/on-kubernetes.zh-tw.md b/docs/content/doc/installation/on-kubernetes.zh-tw.md index 6d31a14b05..51446911d5 100644 --- a/docs/content/doc/installation/on-kubernetes.zh-tw.md +++ b/docs/content/doc/installation/on-kubernetes.zh-tw.md @@ -5,6 +5,8 @@ slug: "install-on-kubernetes" weight: 80 toc: false draft: false +aliases: + - /zh-tw/install-on-kubernetes menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/run-as-service-in-ubuntu.en-us.md b/docs/content/doc/installation/run-as-service-in-ubuntu.en-us.md index 19b3be1a22..3587dfc01a 100644 --- a/docs/content/doc/installation/run-as-service-in-ubuntu.en-us.md +++ b/docs/content/doc/installation/run-as-service-in-ubuntu.en-us.md @@ -5,6 +5,8 @@ slug: "linux-service" weight: 40 toc: false draft: false +aliases: + - /en-us/linux-service menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/run-as-service-in-ubuntu.zh-cn.md b/docs/content/doc/installation/run-as-service-in-ubuntu.zh-cn.md index aa00a42aa6..62312c060c 100644 --- a/docs/content/doc/installation/run-as-service-in-ubuntu.zh-cn.md +++ b/docs/content/doc/installation/run-as-service-in-ubuntu.zh-cn.md @@ -5,6 +5,8 @@ slug: "linux-service" weight: 40 toc: false draft: false +aliases: + - /zh-cn/linux-service menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/run-as-service-in-ubuntu.zh-tw.md b/docs/content/doc/installation/run-as-service-in-ubuntu.zh-tw.md index de811d73e7..6b8a4cfce8 100644 --- a/docs/content/doc/installation/run-as-service-in-ubuntu.zh-tw.md +++ b/docs/content/doc/installation/run-as-service-in-ubuntu.zh-tw.md @@ -5,6 +5,8 @@ slug: "linux-service" weight: 40 toc: false draft: false +aliases: + - /zh-tw/linux-service menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/upgrade-from-gitea.en-us.md b/docs/content/doc/installation/upgrade-from-gitea.en-us.md index 8748573a2f..a6415f7861 100644 --- a/docs/content/doc/installation/upgrade-from-gitea.en-us.md +++ b/docs/content/doc/installation/upgrade-from-gitea.en-us.md @@ -5,6 +5,8 @@ slug: "upgrade-from-gitea" weight: 100 toc: false draft: false +aliases: + - /en-us/upgrade-from-gitea menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/upgrade-from-gogs.en-us.md b/docs/content/doc/installation/upgrade-from-gogs.en-us.md index 5b08ed2f6a..2e149c6a2b 100644 --- a/docs/content/doc/installation/upgrade-from-gogs.en-us.md +++ b/docs/content/doc/installation/upgrade-from-gogs.en-us.md @@ -5,6 +5,8 @@ slug: "upgrade-from-gogs" weight: 101 toc: false draft: false +aliases: + - /en-us/upgrade-from-gogs menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/upgrade-from-gogs.fr-fr.md b/docs/content/doc/installation/upgrade-from-gogs.fr-fr.md index 2ce1b4bf3d..9a46562f06 100644 --- a/docs/content/doc/installation/upgrade-from-gogs.fr-fr.md +++ b/docs/content/doc/installation/upgrade-from-gogs.fr-fr.md @@ -5,6 +5,8 @@ slug: "upgrade-from-gogs" weight: 101 toc: false draft: false +aliases: + - /fr-fr/upgrade-from-gogs menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/upgrade-from-gogs.zh-cn.md b/docs/content/doc/installation/upgrade-from-gogs.zh-cn.md index 048cfee793..f537896ca1 100644 --- a/docs/content/doc/installation/upgrade-from-gogs.zh-cn.md +++ b/docs/content/doc/installation/upgrade-from-gogs.zh-cn.md @@ -5,6 +5,8 @@ slug: "upgrade-from-gogs" weight: 101 toc: false draft: false +aliases: + - /zh-cn/upgrade-from-gogs menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/upgrade-from-gogs.zh-tw.md b/docs/content/doc/installation/upgrade-from-gogs.zh-tw.md index 0082bf73cb..9812efaf94 100644 --- a/docs/content/doc/installation/upgrade-from-gogs.zh-tw.md +++ b/docs/content/doc/installation/upgrade-from-gogs.zh-tw.md @@ -5,6 +5,8 @@ slug: "upgrade-from-gogs" weight: 101 toc: false draft: false +aliases: + - /zh-tw/upgrade-from-gogs menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/windows-service.en-us.md b/docs/content/doc/installation/windows-service.en-us.md index 6efc1fd582..d3f5a9abae 100644 --- a/docs/content/doc/installation/windows-service.en-us.md +++ b/docs/content/doc/installation/windows-service.en-us.md @@ -5,6 +5,8 @@ slug: "windows-service" weight: 50 toc: false draft: false +aliases: + - /en-us/windows-service menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/windows-service.fr-fr.md b/docs/content/doc/installation/windows-service.fr-fr.md index a0f8f057c0..c4e00b04e1 100644 --- a/docs/content/doc/installation/windows-service.fr-fr.md +++ b/docs/content/doc/installation/windows-service.fr-fr.md @@ -5,6 +5,8 @@ slug: "windows-service" weight: 50 toc: false draft: false +aliases: + - /fr-fr/windows-service menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/windows-service.zh-cn.md b/docs/content/doc/installation/windows-service.zh-cn.md index 8388a057b3..0f2a0f5869 100644 --- a/docs/content/doc/installation/windows-service.zh-cn.md +++ b/docs/content/doc/installation/windows-service.zh-cn.md @@ -5,6 +5,8 @@ slug: "windows-service" weight: 50 toc: false draft: false +aliases: + - /zh-cn/windows-service menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/windows-service.zh-tw.md b/docs/content/doc/installation/windows-service.zh-tw.md index a2934a52d1..5764d647fc 100644 --- a/docs/content/doc/installation/windows-service.zh-tw.md +++ b/docs/content/doc/installation/windows-service.zh-tw.md @@ -5,6 +5,8 @@ slug: "windows-service" weight: 50 toc: false draft: false +aliases: + - /zh-tw/windows-service menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/with-docker-rootless.en-us.md b/docs/content/doc/installation/with-docker-rootless.en-us.md index f33e99c580..e1073a1d67 100644 --- a/docs/content/doc/installation/with-docker-rootless.en-us.md +++ b/docs/content/doc/installation/with-docker-rootless.en-us.md @@ -5,6 +5,8 @@ slug: "install-with-docker-rootless" weight: 60 toc: false draft: false +aliases: + - /en-us/install-with-docker-rootless menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/with-docker.en-us.md b/docs/content/doc/installation/with-docker.en-us.md index 05764e0bc9..043a10c08f 100644 --- a/docs/content/doc/installation/with-docker.en-us.md +++ b/docs/content/doc/installation/with-docker.en-us.md @@ -5,6 +5,8 @@ slug: "install-with-docker" weight: 70 toc: false draft: false +aliases: + - /en-us/install-with-docker menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/with-docker.fr-fr.md b/docs/content/doc/installation/with-docker.fr-fr.md index 59f2b0752a..362aa5fc67 100644 --- a/docs/content/doc/installation/with-docker.fr-fr.md +++ b/docs/content/doc/installation/with-docker.fr-fr.md @@ -5,6 +5,8 @@ slug: "install-with-docker" weight: 70 toc: false draft: false +aliases: + - /fr-fr/install-with-docker menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/with-docker.zh-cn.md b/docs/content/doc/installation/with-docker.zh-cn.md index e5bb07b495..50acc3ffae 100644 --- a/docs/content/doc/installation/with-docker.zh-cn.md +++ b/docs/content/doc/installation/with-docker.zh-cn.md @@ -5,6 +5,8 @@ slug: "install-with-docker" weight: 70 toc: false draft: false +aliases: + - /zh-cn/install-with-docker menu: sidebar: parent: "installation" diff --git a/docs/content/doc/installation/with-docker.zh-tw.md b/docs/content/doc/installation/with-docker.zh-tw.md index e2e47e8702..fdf5a0101d 100644 --- a/docs/content/doc/installation/with-docker.zh-tw.md +++ b/docs/content/doc/installation/with-docker.zh-tw.md @@ -5,6 +5,8 @@ slug: "install-with-docker" weight: 70 toc: false draft: false +aliases: + - /zh-tw/install-with-docker menu: sidebar: parent: "installation" diff --git a/docs/content/doc/help/search.de-de.md b/docs/content/doc/search.de-de.md similarity index 93% rename from docs/content/doc/help/search.de-de.md rename to docs/content/doc/search.de-de.md index d22e9c1b2e..29c153171e 100644 --- a/docs/content/doc/help/search.de-de.md +++ b/docs/content/doc/search.de-de.md @@ -5,6 +5,8 @@ slug: "search" weight: 1 toc: false draft: false +aliases: + - /de-de/help/search sitemap: priority : 1 layout: "search" diff --git a/docs/content/doc/help/search.en-us.md b/docs/content/doc/search.en-us.md similarity index 93% rename from docs/content/doc/help/search.en-us.md rename to docs/content/doc/search.en-us.md index d22e9c1b2e..60a4898c42 100644 --- a/docs/content/doc/help/search.en-us.md +++ b/docs/content/doc/search.en-us.md @@ -5,6 +5,8 @@ slug: "search" weight: 1 toc: false draft: false +aliases: + - /en-us/help/search sitemap: priority : 1 layout: "search" diff --git a/docs/content/doc/help/search.fr-fr.md b/docs/content/doc/search.fr-fr.md similarity index 93% rename from docs/content/doc/help/search.fr-fr.md rename to docs/content/doc/search.fr-fr.md index 421f7830b2..d3f85c966f 100644 --- a/docs/content/doc/help/search.fr-fr.md +++ b/docs/content/doc/search.fr-fr.md @@ -5,6 +5,8 @@ slug: "search" weight: 1 toc: false draft: false +aliases: + - /fr-fr/help/search sitemap: priority : 1 layout: "search" diff --git a/docs/content/doc/help/search.nl-nl.md b/docs/content/doc/search.nl-nl.md similarity index 93% rename from docs/content/doc/help/search.nl-nl.md rename to docs/content/doc/search.nl-nl.md index d22e9c1b2e..9ca7a34d48 100644 --- a/docs/content/doc/help/search.nl-nl.md +++ b/docs/content/doc/search.nl-nl.md @@ -5,6 +5,8 @@ slug: "search" weight: 1 toc: false draft: false +aliases: + - /nl-nl/help/search sitemap: priority : 1 layout: "search" diff --git a/docs/content/doc/help/search.pt-br.md b/docs/content/doc/search.pt-br.md similarity index 93% rename from docs/content/doc/help/search.pt-br.md rename to docs/content/doc/search.pt-br.md index d22e9c1b2e..0a70026c23 100644 --- a/docs/content/doc/help/search.pt-br.md +++ b/docs/content/doc/search.pt-br.md @@ -5,6 +5,8 @@ slug: "search" weight: 1 toc: false draft: false +aliases: + - /pt-br/help/search sitemap: priority : 1 layout: "search" diff --git a/docs/content/doc/help/search.zh-cn.md b/docs/content/doc/search.zh-cn.md similarity index 93% rename from docs/content/doc/help/search.zh-cn.md rename to docs/content/doc/search.zh-cn.md index 778df0d47d..50415c259e 100644 --- a/docs/content/doc/help/search.zh-cn.md +++ b/docs/content/doc/search.zh-cn.md @@ -5,6 +5,8 @@ slug: "search" weight: 1 toc: false draft: false +aliases: + - /zh-cn/help/search sitemap: priority : 1 layout: "search" diff --git a/docs/content/doc/help/search.zh-tw.md b/docs/content/doc/search.zh-tw.md similarity index 93% rename from docs/content/doc/help/search.zh-tw.md rename to docs/content/doc/search.zh-tw.md index 8f2ce17ec6..746cb14c11 100644 --- a/docs/content/doc/help/search.zh-tw.md +++ b/docs/content/doc/search.zh-tw.md @@ -5,6 +5,8 @@ slug: "search" weight: 1 toc: false draft: false +aliases: + - /zh-tw/help/search sitemap: priority : 1 layout: "search" diff --git a/docs/content/doc/usage/_index.en-us.md b/docs/content/doc/usage/_index.en-us.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/usage/_index.zh-cn.md b/docs/content/doc/usage/_index.zh-cn.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/usage/_index.zh-tw.md b/docs/content/doc/usage/_index.zh-tw.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/usage/agit-support.en-us.md b/docs/content/doc/usage/agit-support.en-us.md index b005e1bd73..30e2879e89 100644 --- a/docs/content/doc/usage/agit-support.en-us.md +++ b/docs/content/doc/usage/agit-support.en-us.md @@ -5,6 +5,8 @@ slug: "agit-setup" weight: 12 toc: false draft: false +aliases: + - /en-us/agit-setup menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/authentication.en-us.md b/docs/content/doc/usage/authentication.en-us.md index 5478ee94f6..2b8cdd2964 100644 --- a/docs/content/doc/usage/authentication.en-us.md +++ b/docs/content/doc/usage/authentication.en-us.md @@ -5,6 +5,8 @@ slug: "authentication" weight: 10 toc: false draft: false +aliases: + - /en-us/authentication menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/authentication.zh-cn.md b/docs/content/doc/usage/authentication.zh-cn.md index 403c3f43ce..37ecc8bab1 100644 --- a/docs/content/doc/usage/authentication.zh-cn.md +++ b/docs/content/doc/usage/authentication.zh-cn.md @@ -5,6 +5,8 @@ slug: "authentication" weight: 10 toc: false draft: false +aliases: + - /zh-cn/authentication menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/authentication.zh-tw.md b/docs/content/doc/usage/authentication.zh-tw.md index 958c498fc6..75959802b1 100644 --- a/docs/content/doc/usage/authentication.zh-tw.md +++ b/docs/content/doc/usage/authentication.zh-tw.md @@ -5,6 +5,8 @@ slug: "authentication" weight: 10 toc: false draft: false +aliases: + - /zh-tw/authentication menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/clone-filter.en-us.md b/docs/content/doc/usage/clone-filter.en-us.md index 7d4ba2d9a8..8331c138bf 100644 --- a/docs/content/doc/usage/clone-filter.en-us.md +++ b/docs/content/doc/usage/clone-filter.en-us.md @@ -5,6 +5,8 @@ slug: "clone-filters" weight: 25 draft: false toc: false +aliases: + - /en-us/clone-filters menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/incoming-email.en-us.md b/docs/content/doc/usage/incoming-email.en-us.md index 867e9d8a69..205b3dd8ed 100644 --- a/docs/content/doc/usage/incoming-email.en-us.md +++ b/docs/content/doc/usage/incoming-email.en-us.md @@ -5,6 +5,8 @@ slug: "incoming-email" weight: 13 draft: false toc: false +aliases: + - /en-us/incoming-email menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/issue-pull-request-templates.en-us.md b/docs/content/doc/usage/issue-pull-request-templates.en-us.md index 107484689b..b48763cf8e 100644 --- a/docs/content/doc/usage/issue-pull-request-templates.en-us.md +++ b/docs/content/doc/usage/issue-pull-request-templates.en-us.md @@ -5,6 +5,8 @@ slug: "issue-pull-request-templates" weight: 15 toc: false draft: false +aliases: + - /en-us/issue-pull-request-templates menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/issue-pull-request-templates.zh-cn.md b/docs/content/doc/usage/issue-pull-request-templates.zh-cn.md index 866378e2ac..135dcf8de8 100644 --- a/docs/content/doc/usage/issue-pull-request-templates.zh-cn.md +++ b/docs/content/doc/usage/issue-pull-request-templates.zh-cn.md @@ -5,6 +5,8 @@ slug: "issue-pull-request-templates" weight: 15 toc: true draft: false +aliases: + - /zh-cn/issue-pull-request-templates menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/labels.en-us.md b/docs/content/doc/usage/labels.en-us.md index 7c2494971f..bf60951d8c 100644 --- a/docs/content/doc/usage/labels.en-us.md +++ b/docs/content/doc/usage/labels.en-us.md @@ -5,6 +5,8 @@ slug: "labels" weight: 13 toc: false draft: false +aliases: + - /en-us/labels menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/linked-references.en-us.md b/docs/content/doc/usage/linked-references.en-us.md index 335d2654e3..4e95193015 100644 --- a/docs/content/doc/usage/linked-references.en-us.md +++ b/docs/content/doc/usage/linked-references.en-us.md @@ -5,6 +5,8 @@ slug: "automatically-linked-references" weight: 15 toc: false draft: false +aliases: + - /en-us/automatically-linked-references menu: sidebar: parent: "usage" @@ -176,28 +178,28 @@ In the examples, `User1/Repo1` refers to the repository where the reference is u | Reference in User1/Repo1 | Repo1 issues are external | RepoZ issues are external | Should render | | --------------------------- | :-----------------------: | :-----------------------: | ------------------------------------------------------- | -| `#1234` | no | N/A | A link to issue/pull 1234 in `User1/Repo1` | -| `!1234` | no | N/A | A link to issue/pull 1234 in `User1/Repo1` | -| `#1234` | yes | N/A | A link to _external issue_ 1234 for `User1/Repo1` | -| `!1234` | yes | N/A | A link to _PR_ 1234 for `User1/Repo1` | -| `User1/Repo1#1234` | no | N/A | A link to issue/pull 1234 in `User1/Repo1` | -| `User1/Repo1!1234` | no | N/A | A link to issue/pull 1234 in `User1/Repo1` | -| `User1/Repo1#1234` | yes | N/A | A link to _external issue_ 1234 for `User1/Repo1` | -| `User1/Repo1!1234` | yes | N/A | A link to _PR_ 1234 for `User1/Repo1` | -| `UserZ/RepoZ#1234` | N/A | no | A link to issue/pull 1234 in `UserZ/RepoZ` | -| `UserZ/RepoZ!1234` | N/A | no | A link to issue/pull 1234 in `UserZ/RepoZ` | -| `UserZ/RepoZ#1234` | N/A | yes | A link to _external issue_ 1234 for `UserZ/RepoZ` | -| `UserZ/RepoZ!1234` | N/A | yes | A link to _PR_ 1234 for `UserZ/RepoZ` | +| `#1234` | no | - | A link to issue/pull 1234 in `User1/Repo1` | +| `!1234` | no | - | A link to issue/pull 1234 in `User1/Repo1` | +| `#1234` | yes | - | A link to _external issue_ 1234 for `User1/Repo1` | +| `!1234` | yes | - | A link to _PR_ 1234 for `User1/Repo1` | +| `User1/Repo1#1234` | no | - | A link to issue/pull 1234 in `User1/Repo1` | +| `User1/Repo1!1234` | no | - | A link to issue/pull 1234 in `User1/Repo1` | +| `User1/Repo1#1234` | yes | - | A link to _external issue_ 1234 for `User1/Repo1` | +| `User1/Repo1!1234` | yes | - | A link to _PR_ 1234 for `User1/Repo1` | +| `UserZ/RepoZ#1234` | - | no | A link to issue/pull 1234 in `UserZ/RepoZ` | +| `UserZ/RepoZ!1234` | - | no | A link to issue/pull 1234 in `UserZ/RepoZ` | +| `UserZ/RepoZ#1234` | - | yes | A link to _external issue_ 1234 for `UserZ/RepoZ` | +| `UserZ/RepoZ!1234` | - | yes | A link to _PR_ 1234 for `UserZ/RepoZ` | | **Alphanumeric issue IDs:** | - | - | - | -| `AAA-1234` | yes | N/A | A link to _external issue_ `AAA-1234` for `User1/Repo1` | -| `!1234` | yes | N/A | A link to _PR_ 1234 for `User1/Repo1` | -| `User1/Repo1!1234` | yes | N/A | A link to _PR_ 1234 for `User1/Repo1` | -| _Not supported_ | N/A | yes | A link to _external issue_ `AAA-1234` for `UserZ/RepoZ` | -| `UserZ/RepoZ!1234` | N/A | yes | A link to _PR_ 1234 in `UserZ/RepoZ` | +| `AAA-1234` | yes | - | A link to _external issue_ `AAA-1234` for `User1/Repo1` | +| `!1234` | yes | - | A link to _PR_ 1234 for `User1/Repo1` | +| `User1/Repo1!1234` | yes | - | A link to _PR_ 1234 for `User1/Repo1` | +| _Not supported_ | - | yes | A link to _external issue_ `AAA-1234` for `UserZ/RepoZ` | +| `UserZ/RepoZ!1234` | - | yes | A link to _PR_ 1234 in `UserZ/RepoZ` | _The last section is for repositories with external issue trackers that use alphanumeric format._ -_**N/A**: not applicable._ +_**-**: not applicable._ Note: automatic references between repositories with different types of issues (external vs. internal) are not fully supported and may render invalid links. diff --git a/docs/content/doc/usage/merge-message-templates.en-us.md b/docs/content/doc/usage/merge-message-templates.en-us.md index 70710b989e..751f07e13f 100644 --- a/docs/content/doc/usage/merge-message-templates.en-us.md +++ b/docs/content/doc/usage/merge-message-templates.en-us.md @@ -5,6 +5,8 @@ slug: "merge-message-templates" weight: 15 toc: false draft: false +aliases: + - /en-us/merge-message-templates menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/packages/_index.en-us.md b/docs/content/doc/usage/packages/_index.en-us.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/content/doc/packages/cargo.en-us.md b/docs/content/doc/usage/packages/cargo.en-us.md similarity index 99% rename from docs/content/doc/packages/cargo.en-us.md rename to docs/content/doc/usage/packages/cargo.en-us.md index 1beeba407b..a74384ccef 100644 --- a/docs/content/doc/packages/cargo.en-us.md +++ b/docs/content/doc/usage/packages/cargo.en-us.md @@ -1,7 +1,7 @@ --- date: "2022-11-20T00:00:00+00:00" title: "Cargo Packages Repository" -slug: "usage/packages/cargo" +slug: "cargo" weight: 5 draft: false toc: false diff --git a/docs/content/doc/packages/chef.en-us.md b/docs/content/doc/usage/packages/chef.en-us.md similarity index 98% rename from docs/content/doc/packages/chef.en-us.md rename to docs/content/doc/usage/packages/chef.en-us.md index ab7150a2a2..dc5ca21e98 100644 --- a/docs/content/doc/packages/chef.en-us.md +++ b/docs/content/doc/usage/packages/chef.en-us.md @@ -1,7 +1,7 @@ --- date: "2023-01-20T00:00:00+00:00" title: "Chef Packages Repository" -slug: "usage/packages/chef" +slug: "chef" weight: 5 draft: false toc: false diff --git a/docs/content/doc/packages/composer.en-us.md b/docs/content/doc/usage/packages/composer.en-us.md similarity index 99% rename from docs/content/doc/packages/composer.en-us.md rename to docs/content/doc/usage/packages/composer.en-us.md index 6055ff3194..16c367eb2c 100644 --- a/docs/content/doc/packages/composer.en-us.md +++ b/docs/content/doc/usage/packages/composer.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "Composer Packages Repository" -slug: "usage/packages/composer" +slug: "composer" weight: 10 draft: false toc: false diff --git a/docs/content/doc/packages/conan.en-us.md b/docs/content/doc/usage/packages/conan.en-us.md similarity index 98% rename from docs/content/doc/packages/conan.en-us.md rename to docs/content/doc/usage/packages/conan.en-us.md index ff89fc153e..8c8fe4e262 100644 --- a/docs/content/doc/packages/conan.en-us.md +++ b/docs/content/doc/usage/packages/conan.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "Conan Packages Repository" -slug: "usage/packages/conan" +slug: "conan" weight: 20 draft: false toc: false diff --git a/docs/content/doc/packages/conda.en-us.md b/docs/content/doc/usage/packages/conda.en-us.md similarity index 98% rename from docs/content/doc/packages/conda.en-us.md rename to docs/content/doc/usage/packages/conda.en-us.md index 1a7cd86334..c9a852239f 100644 --- a/docs/content/doc/packages/conda.en-us.md +++ b/docs/content/doc/usage/packages/conda.en-us.md @@ -1,7 +1,7 @@ --- date: "2022-12-28T00:00:00+00:00" title: "Conda Packages Repository" -slug: "usage/packages/conda" +slug: "conda" weight: 25 draft: false toc: false diff --git a/docs/content/doc/packages/container.en-us.md b/docs/content/doc/usage/packages/container.en-us.md similarity index 98% rename from docs/content/doc/packages/container.en-us.md rename to docs/content/doc/usage/packages/container.en-us.md index 5fd990b4b2..457e6fb1a4 100644 --- a/docs/content/doc/packages/container.en-us.md +++ b/docs/content/doc/usage/packages/container.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "Container Registry" -slug: "usage/packages/container" +slug: "container" weight: 30 draft: false toc: false diff --git a/docs/content/doc/usage/packages/debian.en-us.md b/docs/content/doc/usage/packages/debian.en-us.md new file mode 100644 index 0000000000..7506b5ce2f --- /dev/null +++ b/docs/content/doc/usage/packages/debian.en-us.md @@ -0,0 +1,134 @@ +--- +date: "2023-01-07T00:00:00+00:00" +title: "Debian Packages Repository" +slug: "packages/debian" +draft: false +toc: false +menu: + sidebar: + parent: "packages" + name: "Debian" + weight: 35 + identifier: "debian" +--- + +# Debian Packages Repository + +Publish [Debian](https://www.debian.org/distrib/packages) packages for your user or organization. + +**Table of Contents** + +{{< toc >}} + +## Requirements + +To work with the Debian registry, you need to use a HTTP client like `curl` to upload and a package manager like `apt` to consume packages. + +The following examples use `apt`. + +## Configuring the package registry + +To register the Debian registry add the url to the list of known apt sources: + +```shell +echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list +``` + +| Placeholder | Description | +| -------------- | ----------- | +| `owner` | The owner of the package. | +| `distribution` | The distribution to use. | +| `component` | The component to use. | + +If the registry is private, provide credentials in the url. You can use a password or a [personal access token]({{< relref "doc/development/api-usage.en-us.md#authentication" >}}): + +```shell +echo "deb https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list +``` + +The Debian registry files are signed with a PGP key which must be known to apt: + +```shell +sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{owner}.asc +``` + +Afterwards update the local package index: + +```shell +apt update +``` + +## Publish a package + +To publish a Debian package (`*.deb`), perform a HTTP `PUT` operation with the package content in the request body. + +``` +PUT https://gitea.example.com/api/packages/{owner}/debian/pool/{distribution}/{component}/upload +``` + +| Parameter | Description | +| -------------- | ----------- | +| `owner` | The owner of the package. | +| `distribution` | The distribution may match the release name of the OS, ex: `bionic`. | +| `component` | The component can be used to group packages or just `main` or similar. | + +Example request using HTTP Basic authentication: + +```shell +curl --user your_username:your_password_or_token \ + --upload-file path/to/file.deb \ + https://gitea.example.com/api/packages/testuser/debian/pool/bionic/main/upload +``` + +If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/development/api-usage.en-us.md#authentication" >}}) instead of the password. +You cannot publish a file with the same name twice to a package. You must delete the existing package version first. + +The server reponds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `201 Created` | The package has been published. | +| `400 Bad Request` | The package name, version, distribution, component or architecture are invalid. | +| `409 Conflict` | A package file with the same combination of parameters exists already. | + +## Delete a package + +To delete a Debian package perform a HTTP `DELETE` operation. This will delete the package version too if there is no file left. + +``` +DELETE https://gitea.example.com/api/packages/{owner}/debian/pool/{distribution}/{component}/{package_name}/{package_version}/{architecture} +``` + +| Parameter | Description | +| ----------------- | ----------- | +| `owner` | The owner of the package. | +| `package_name` | The package name. | +| `package_version` | The package version. | +| `distribution` | The package distribution. | +| `component` | The package component. | +| `architecture` | The package architecture. | + +Example request using HTTP Basic authentication: + +```shell +curl --user your_username:your_token_or_password -X DELETE \ + https://gitea.example.com/api/packages/testuser/debian/pools/bionic/main/test-package/1.0.0/amd64 +``` + +The server reponds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `204 No Content` | Success | +| `404 Not Found` | The package or file was not found. | + +## Install a package + +To install a package from the Debian registry, execute the following commands: + +```shell +# use latest version +apt install {package_name} +# use specific version +apt install {package_name}={package_version} +``` diff --git a/docs/content/doc/packages/generic.en-us.md b/docs/content/doc/usage/packages/generic.en-us.md similarity index 99% rename from docs/content/doc/packages/generic.en-us.md rename to docs/content/doc/usage/packages/generic.en-us.md index 55b1868bfb..fbfe42d50a 100644 --- a/docs/content/doc/packages/generic.en-us.md +++ b/docs/content/doc/usage/packages/generic.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "Generic Packages Repository" -slug: "usage/packages/generic" +slug: "generic" weight: 40 draft: false toc: false diff --git a/docs/content/doc/packages/helm.en-us.md b/docs/content/doc/usage/packages/helm.en-us.md similarity index 98% rename from docs/content/doc/packages/helm.en-us.md rename to docs/content/doc/usage/packages/helm.en-us.md index 263218b31e..1db1e8758b 100644 --- a/docs/content/doc/packages/helm.en-us.md +++ b/docs/content/doc/usage/packages/helm.en-us.md @@ -1,7 +1,7 @@ --- date: "2022-04-14T00:00:00+00:00" title: "Helm Chart Registry" -slug: "usage/packages/helm" +slug: "helm" weight: 50 draft: false toc: false diff --git a/docs/content/doc/packages/maven.en-us.md b/docs/content/doc/usage/packages/maven.en-us.md similarity index 99% rename from docs/content/doc/packages/maven.en-us.md rename to docs/content/doc/usage/packages/maven.en-us.md index 408a02a710..81c3e22197 100644 --- a/docs/content/doc/packages/maven.en-us.md +++ b/docs/content/doc/usage/packages/maven.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "Maven Packages Repository" -slug: "usage/packages/maven" +slug: "maven" weight: 60 draft: false toc: false diff --git a/docs/content/doc/packages/npm.en-us.md b/docs/content/doc/usage/packages/npm.en-us.md similarity index 99% rename from docs/content/doc/packages/npm.en-us.md rename to docs/content/doc/usage/packages/npm.en-us.md index fb13510840..a15cdb9a2b 100644 --- a/docs/content/doc/packages/npm.en-us.md +++ b/docs/content/doc/usage/packages/npm.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "npm Packages Repository" -slug: "usage/packages/npm" +slug: "npm" weight: 70 draft: false toc: false diff --git a/docs/content/doc/packages/nuget.en-us.md b/docs/content/doc/usage/packages/nuget.en-us.md similarity index 99% rename from docs/content/doc/packages/nuget.en-us.md rename to docs/content/doc/usage/packages/nuget.en-us.md index 1309b4cab6..aa88fa625e 100644 --- a/docs/content/doc/packages/nuget.en-us.md +++ b/docs/content/doc/usage/packages/nuget.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "NuGet Packages Repository" -slug: "usage/packages/nuget" +slug: "nuget" weight: 80 draft: false toc: false diff --git a/docs/content/doc/packages/overview.en-us.md b/docs/content/doc/usage/packages/overview.en-us.md similarity index 66% rename from docs/content/doc/packages/overview.en-us.md rename to docs/content/doc/usage/packages/overview.en-us.md index e6d984af77..8e4fd87e7b 100644 --- a/docs/content/doc/packages/overview.en-us.md +++ b/docs/content/doc/usage/packages/overview.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "Package Registry" -slug: "usage/packages/overview" +slug: "overview" weight: 1 draft: false toc: false @@ -27,22 +27,23 @@ The following package managers are currently supported: | Name | Language | Package client | | ---- | -------- | -------------- | -| [Cargo]({{< relref "doc/packages/cargo.en-us.md" >}}) | Rust | `cargo` | -| [Chef]({{< relref "doc/packages/chef.en-us.md" >}}) | - | `knife` | -| [Composer]({{< relref "doc/packages/composer.en-us.md" >}}) | PHP | `composer` | -| [Conan]({{< relref "doc/packages/conan.en-us.md" >}}) | C++ | `conan` | -| [Conda]({{< relref "doc/packages/conda.en-us.md" >}}) | - | `conda` | -| [Container]({{< relref "doc/packages/container.en-us.md" >}}) | - | any OCI compliant client | -| [Generic]({{< relref "doc/packages/generic.en-us.md" >}}) | - | any HTTP client | -| [Helm]({{< relref "doc/packages/helm.en-us.md" >}}) | - | any HTTP client, `cm-push` | -| [Maven]({{< relref "doc/packages/maven.en-us.md" >}}) | Java | `mvn`, `gradle` | -| [npm]({{< relref "doc/packages/npm.en-us.md" >}}) | JavaScript | `npm`, `yarn`, `pnpm` | -| [NuGet]({{< relref "doc/packages/nuget.en-us.md" >}}) | .NET | `nuget` | -| [Pub]({{< relref "doc/packages/pub.en-us.md" >}}) | Dart | `dart`, `flutter` | -| [PyPI]({{< relref "doc/packages/pypi.en-us.md" >}}) | Python | `pip`, `twine` | -| [RubyGems]({{< relref "doc/packages/rubygems.en-us.md" >}}) | Ruby | `gem`, `Bundler` | -| [Swift]({{< relref "doc/packages/rubygems.en-us.md" >}}) | Swift | `swift` | -| [Vagrant]({{< relref "doc/packages/vagrant.en-us.md" >}}) | - | `vagrant` | +| [Cargo]({{< relref "doc/usage/packages/cargo.en-us.md" >}}) | Rust | `cargo` | +| [Chef]({{< relref "doc/usage/packages/chef.en-us.md" >}}) | - | `knife` | +| [Composer]({{< relref "doc/usage/packages/composer.en-us.md" >}}) | PHP | `composer` | +| [Conan]({{< relref "doc/usage/packages/conan.en-us.md" >}}) | C++ | `conan` | +| [Conda]({{< relref "doc/usage/packages/conda.en-us.md" >}}) | - | `conda` | +| [Container]({{< relref "doc/usage/packages/container.en-us.md" >}}) | - | any OCI compliant client | +| [Debian]({{< relref "doc/usage/packages/debian.en-us.md" >}}) | - | `apt` | +| [Generic]({{< relref "doc/usage/packages/generic.en-us.md" >}}) | - | any HTTP client | +| [Helm]({{< relref "doc/usage/packages/helm.en-us.md" >}}) | - | any HTTP client, `cm-push` | +| [Maven]({{< relref "doc/usage/packages/maven.en-us.md" >}}) | Java | `mvn`, `gradle` | +| [npm]({{< relref "doc/usage/packages/npm.en-us.md" >}}) | JavaScript | `npm`, `yarn`, `pnpm` | +| [NuGet]({{< relref "doc/usage/packages/nuget.en-us.md" >}}) | .NET | `nuget` | +| [Pub]({{< relref "doc/usage/packages/pub.en-us.md" >}}) | Dart | `dart`, `flutter` | +| [PyPI]({{< relref "doc/usage/packages/pypi.en-us.md" >}}) | Python | `pip`, `twine` | +| [RubyGems]({{< relref "doc/usage/packages/rubygems.en-us.md" >}}) | Ruby | `gem`, `Bundler` | +| [Swift]({{< relref "doc/usage/packages/rubygems.en-us.md" >}}) | Swift | `swift` | +| [Vagrant]({{< relref "doc/usage/packages/vagrant.en-us.md" >}}) | - | `vagrant` | **The following paragraphs only apply if Packages are not globally disabled!** diff --git a/docs/content/doc/packages/pub.en-us.md b/docs/content/doc/usage/packages/pub.en-us.md similarity index 98% rename from docs/content/doc/packages/pub.en-us.md rename to docs/content/doc/usage/packages/pub.en-us.md index ebe42f1822..f2df2016ac 100644 --- a/docs/content/doc/packages/pub.en-us.md +++ b/docs/content/doc/usage/packages/pub.en-us.md @@ -1,7 +1,7 @@ --- date: "2022-07-31T00:00:00+00:00" title: "Pub Packages Repository" -slug: "usage/packages/pub" +slug: "pub" weight: 90 draft: false toc: false diff --git a/docs/content/doc/packages/pypi.en-us.md b/docs/content/doc/usage/packages/pypi.en-us.md similarity index 98% rename from docs/content/doc/packages/pypi.en-us.md rename to docs/content/doc/usage/packages/pypi.en-us.md index 163eaa9646..84d9453daa 100644 --- a/docs/content/doc/packages/pypi.en-us.md +++ b/docs/content/doc/usage/packages/pypi.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "PyPI Packages Repository" -slug: "usage/packages/pypi" +slug: "pypi" weight: 100 draft: false toc: false diff --git a/docs/content/doc/packages/rubygems.en-us.md b/docs/content/doc/usage/packages/rubygems.en-us.md similarity index 98% rename from docs/content/doc/packages/rubygems.en-us.md rename to docs/content/doc/usage/packages/rubygems.en-us.md index cb53b137d5..be60ed37af 100644 --- a/docs/content/doc/packages/rubygems.en-us.md +++ b/docs/content/doc/usage/packages/rubygems.en-us.md @@ -1,7 +1,7 @@ --- date: "2021-07-20T00:00:00+00:00" title: "RubyGems Packages Repository" -slug: "usage/packages/rubygems" +slug: "rubygems" weight: 110 draft: false toc: false diff --git a/docs/content/doc/packages/storage.en-us.md b/docs/content/doc/usage/packages/storage.en-us.md similarity index 99% rename from docs/content/doc/packages/storage.en-us.md rename to docs/content/doc/usage/packages/storage.en-us.md index cd5952eb24..15481ba7a3 100644 --- a/docs/content/doc/packages/storage.en-us.md +++ b/docs/content/doc/usage/packages/storage.en-us.md @@ -1,7 +1,7 @@ --- date: "2022-11-01T00:00:00+00:00" title: "Storage" -slug: "usage/packages/storage" +slug: "storage" weight: 5 draft: false toc: false diff --git a/docs/content/doc/packages/swift.en-us.md b/docs/content/doc/usage/packages/swift.en-us.md similarity index 99% rename from docs/content/doc/packages/swift.en-us.md rename to docs/content/doc/usage/packages/swift.en-us.md index 3f0cb13239..a1c818021c 100644 --- a/docs/content/doc/packages/swift.en-us.md +++ b/docs/content/doc/usage/packages/swift.en-us.md @@ -1,7 +1,7 @@ --- date: "2023-01-10T00:00:00+00:00" title: "Swift Packages Repository" -slug: "usage/packages/swift" +slug: "swift" weight: 95 draft: false toc: false diff --git a/docs/content/doc/packages/vagrant.en-us.md b/docs/content/doc/usage/packages/vagrant.en-us.md similarity index 98% rename from docs/content/doc/packages/vagrant.en-us.md rename to docs/content/doc/usage/packages/vagrant.en-us.md index e224a99554..5c633b4f71 100644 --- a/docs/content/doc/packages/vagrant.en-us.md +++ b/docs/content/doc/usage/packages/vagrant.en-us.md @@ -1,7 +1,7 @@ --- date: "2022-08-23T00:00:00+00:00" title: "Vagrant Packages Repository" -slug: "usage/packages/vagrant" +slug: "vagrant" weight: 120 draft: false toc: false diff --git a/docs/content/doc/usage/permissions.en-us.md b/docs/content/doc/usage/permissions.en-us.md index 013dbfabd4..0778f6943f 100644 --- a/docs/content/doc/usage/permissions.en-us.md +++ b/docs/content/doc/usage/permissions.en-us.md @@ -5,6 +5,8 @@ slug: "permissions" weight: 14 toc: false draft: false +aliases: + - /en-us/permissions menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/protected-tags.en-us.md b/docs/content/doc/usage/protected-tags.en-us.md index f7404d32cb..c5e763659d 100644 --- a/docs/content/doc/usage/protected-tags.en-us.md +++ b/docs/content/doc/usage/protected-tags.en-us.md @@ -5,6 +5,8 @@ slug: "protected-tags" weight: 45 toc: false draft: false +aliases: + - /en-us/protected-tags menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/pull-request.en-us.md b/docs/content/doc/usage/pull-request.en-us.md index c7fea88a5a..feb8f848c9 100644 --- a/docs/content/doc/usage/pull-request.en-us.md +++ b/docs/content/doc/usage/pull-request.en-us.md @@ -5,6 +5,8 @@ slug: "pull-request" weight: 13 toc: false draft: false +aliases: + - /en-us/pull-request menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/pull-request.zh-cn.md b/docs/content/doc/usage/pull-request.zh-cn.md index 04125672df..d64690c05e 100644 --- a/docs/content/doc/usage/pull-request.zh-cn.md +++ b/docs/content/doc/usage/pull-request.zh-cn.md @@ -5,6 +5,8 @@ slug: "pull-request" weight: 13 toc: false draft: false +aliases: + - /zh-cn/pull-request menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/pull-request.zh-tw.md b/docs/content/doc/usage/pull-request.zh-tw.md index b666fece13..54cd178321 100644 --- a/docs/content/doc/usage/pull-request.zh-tw.md +++ b/docs/content/doc/usage/pull-request.zh-tw.md @@ -5,6 +5,8 @@ slug: "pull-request" weight: 13 toc: false draft: false +aliases: + - /zh-tw/pull-request menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/push.en-us.md b/docs/content/doc/usage/push.en-us.md index cf858e4e51..097b07c9b4 100644 --- a/docs/content/doc/usage/push.en-us.md +++ b/docs/content/doc/usage/push.en-us.md @@ -5,6 +5,9 @@ slug: "push" weight: 15 toc: false draft: false +aliases: + - /en-us/push-to-create + - /en-us/push-options menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/push.zh-tw.md b/docs/content/doc/usage/push.zh-tw.md index f97d4285cc..5fe5052c11 100644 --- a/docs/content/doc/usage/push.zh-tw.md +++ b/docs/content/doc/usage/push.zh-tw.md @@ -5,6 +5,8 @@ slug: "push" weight: 15 toc: false draft: false +aliases: + - /zh-tw/push-options menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/repo-mirror.en-us.md b/docs/content/doc/usage/repo-mirror.en-us.md index 9a52282bb3..5e2b5c3238 100644 --- a/docs/content/doc/usage/repo-mirror.en-us.md +++ b/docs/content/doc/usage/repo-mirror.en-us.md @@ -5,6 +5,8 @@ slug: "repo-mirror" weight: 45 toc: false draft: false +aliases: + - /en-us/repo-mirror menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/secrets.en-us.md b/docs/content/doc/usage/secrets.en-us.md index b45044cc33..c193a54a6c 100644 --- a/docs/content/doc/usage/secrets.en-us.md +++ b/docs/content/doc/usage/secrets.en-us.md @@ -1,10 +1,12 @@ --- date: "2022-12-19T21:26:00+08:00" title: "Secrets" -slug: "usage/secrets" +slug: "secrets" weight: 50 draft: false toc: false +aliases: + - /en-us/secrets menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/template-repositories.en-us.md b/docs/content/doc/usage/template-repositories.en-us.md index 9a2a23ed2b..0c278648b3 100644 --- a/docs/content/doc/usage/template-repositories.en-us.md +++ b/docs/content/doc/usage/template-repositories.en-us.md @@ -5,6 +5,8 @@ slug: "template-repositories" weight: 14 toc: false draft: false +aliases: + - /en-us/template-repositories menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/webhooks.en-us.md b/docs/content/doc/usage/webhooks.en-us.md index 457113c5cf..24cd48c919 100644 --- a/docs/content/doc/usage/webhooks.en-us.md +++ b/docs/content/doc/usage/webhooks.en-us.md @@ -5,6 +5,8 @@ slug: "webhooks" weight: 30 toc: false draft: false +aliases: + - /en-us/webhooks menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/webhooks.zh-cn.md b/docs/content/doc/usage/webhooks.zh-cn.md index 9e9f2bf502..44d97cc83f 100644 --- a/docs/content/doc/usage/webhooks.zh-cn.md +++ b/docs/content/doc/usage/webhooks.zh-cn.md @@ -5,6 +5,8 @@ slug: "webhooks" weight: 30 toc: false draft: false +aliases: + - /zh-cn/webhooks menu: sidebar: parent: "usage" diff --git a/docs/content/doc/usage/webhooks.zh-tw.md b/docs/content/doc/usage/webhooks.zh-tw.md index 9afad42369..ca5991354a 100644 --- a/docs/content/doc/usage/webhooks.zh-tw.md +++ b/docs/content/doc/usage/webhooks.zh-tw.md @@ -5,6 +5,8 @@ slug: "webhooks" weight: 30 toc: false draft: false +aliases: + - /zh-tw/webhooks menu: sidebar: parent: "usage" diff --git a/docs/content/page/index.zh-cn.md b/docs/content/page/index.zh-cn.md index 5af67f5531..da25d8ee13 100644 --- a/docs/content/page/index.zh-cn.md +++ b/docs/content/page/index.zh-cn.md @@ -70,4 +70,4 @@ Gitea的首要目标是创建一个极易安装,运行非常快速,安装和 ## 需要帮助? -如果从本页中没有找到你需要的内容,请访问 [帮助页面]({{< relref "seek-help.zh-cn.md" >}}) +如果从本页中没有找到你需要的内容,请访问 [帮助页面]({{< relref "support.zh-cn.md" >}}) diff --git a/docs/layouts/alias.html b/docs/layouts/alias.html new file mode 100644 index 0000000000..b96cb34219 --- /dev/null +++ b/docs/layouts/alias.html @@ -0,0 +1,13 @@ + + + + + {{ .Permalink }} + + + + + + diff --git a/go.mod b/go.mod index a71b136cb1..58e90cc0fd 100644 --- a/go.mod +++ b/go.mod @@ -14,9 +14,11 @@ require ( gitea.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 gitea.com/lunny/levelqueue v0.4.2-0.20220729054728-f020868cc2f7 github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/NYTimes/gziphandler v1.1.1 github.com/PuerkitoBio/goquery v1.8.0 github.com/alecthomas/chroma/v2 v2.5.0 + github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blevesearch/bleve/v2 v2.3.6 github.com/bufbuild/connect-go v1.3.1 github.com/buildkite/terminal-to-html/v3 v3.7.0 @@ -96,6 +98,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/syndtr/goleveldb v1.0.0 github.com/tstranex/u2f v1.0.0 + github.com/ulikunitz/xz v0.5.11 github.com/urfave/cli v1.22.12 github.com/xanzy/go-gitlab v0.80.2 github.com/xeipuuv/gojsonschema v1.2.0 @@ -125,7 +128,6 @@ require ( cloud.google.com/go/compute v1.18.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect - github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect @@ -260,7 +262,6 @@ require ( github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/toqueteos/webbrowser v1.2.0 // indirect - github.com/ulikunitz/xz v0.5.11 // indirect github.com/unknwon/com v1.0.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.44.0 // indirect diff --git a/go.sum b/go.sum index 2a3cc5967e..ba969dddee 100644 --- a/go.sum +++ b/go.sum @@ -162,6 +162,8 @@ github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBM github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8= github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM= github.com/blevesearch/bleve/v2 v2.3.6 h1:NlntUHcV5CSWIhpugx4d/BRMGCiaoI8ZZXrXlahzNq4= github.com/blevesearch/bleve/v2 v2.3.6/go.mod h1:JM2legf1cKVkdV8Ehu7msKIOKC0McSw0Q16Fmv9vsW4= diff --git a/models/db/search.go b/models/db/search.go index 26e082756a..105cb64c41 100644 --- a/models/db/search.go +++ b/models/db/search.go @@ -31,5 +31,5 @@ const ( const ( // Which means a condition to filter the records which don't match any id. // It's different from zero which means the condition could be ignored. - NoneID = -1 + NoConditionID = -1 ) diff --git a/models/issues/issue.go b/models/issues/issue.go index 4f8e9e161d..8c173433f2 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -1273,7 +1273,9 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { applySubscribedCondition(sess, opts.SubscriberID) } - if len(opts.MilestoneIDs) > 0 { + if len(opts.MilestoneIDs) == 1 && opts.MilestoneIDs[0] == db.NoConditionID { + sess.And("issue.milestone_id = 0") + } else if len(opts.MilestoneIDs) > 0 { sess.In("issue.milestone_id", opts.MilestoneIDs) } @@ -1287,7 +1289,7 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) { if opts.ProjectID > 0 { sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id"). And("project_issue.project_id=?", opts.ProjectID) - } else if opts.ProjectID == db.NoneID { // show those that are in no project + } else if opts.ProjectID == db.NoConditionID { // show those that are in no project sess.And(builder.NotIn("issue.id", builder.Select("issue_id").From("project_issue"))) } @@ -1721,6 +1723,8 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats, if opts.MilestoneID > 0 { sess.And("issue.milestone_id = ?", opts.MilestoneID) + } else if opts.MilestoneID == db.NoConditionID { + sess.And("issue.milestone_id = 0") } if opts.ProjectID > 0 { diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 5320a96bd8..b1d3eb1b03 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -487,6 +487,10 @@ var migrations = []Migration{ NewMigration("Fix ExternalTracker and ExternalWiki accessMode in owner and admin team", v1_20.FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam), // v254 -> v255 NewMigration("Add ActionTaskOutput table", v1_20.AddActionTaskOutputTable), + // v255 -> v256 + NewMigration("Add ArchivedUnix Column", v1_20.AddArchivedUnixToRepository), + // v256 -> v257 + NewMigration("Add is_internal column to package", v1_20.AddIsInternalColumnToPackage), // to modify later NewMigration("Add size limit on repository", v1_20.AddSizeLimitOnRepo), } diff --git a/models/migrations/v1_20/v255.go b/models/migrations/v1_20/v255.go new file mode 100644 index 0000000000..14b70f8f96 --- /dev/null +++ b/models/migrations/v1_20/v255.go @@ -0,0 +1,23 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_20 //nolint + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func AddArchivedUnixToRepository(x *xorm.Engine) error { + type Repository struct { + ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"` + } + + if err := x.Sync(new(Repository)); err != nil { + return err + } + + _, err := x.Exec("UPDATE repository SET archived_unix = updated_unix WHERE is_archived = ? AND archived_unix = 0", true) + return err +} diff --git a/models/migrations/v1_20/v256.go b/models/migrations/v1_20/v256.go new file mode 100644 index 0000000000..822153b93e --- /dev/null +++ b/models/migrations/v1_20/v256.go @@ -0,0 +1,23 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_20 //nolint + +import ( + "xorm.io/xorm" +) + +func AddIsInternalColumnToPackage(x *xorm.Engine) error { + type Package struct { + ID int64 `xorm:"pk autoincr"` + OwnerID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` + RepoID int64 `xorm:"INDEX"` + Type string `xorm:"UNIQUE(s) INDEX NOT NULL"` + Name string `xorm:"NOT NULL"` + LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` + SemverCompatible bool `xorm:"NOT NULL DEFAULT false"` + IsInternal bool `xorm:"NOT NULL DEFAULT false"` + } + + return x.Sync(new(Package)) +} diff --git a/models/packages/container/search.go b/models/packages/container/search.go index b65c8634d6..0d3664d384 100644 --- a/models/packages/container/search.go +++ b/models/packages/container/search.go @@ -101,16 +101,7 @@ func getContainerBlobsLimit(ctx context.Context, opts *BlobSearchOptions, limit return nil, err } - pfds := make([]*packages.PackageFileDescriptor, 0, len(pfs)) - for _, pf := range pfs { - pfd, err := packages.GetPackageFileDescriptor(ctx, pf) - if err != nil { - return nil, err - } - pfds = append(pfds, pfd) - } - - return pfds, nil + return packages.GetPackageFileDescriptors(ctx, pfs) } // GetManifestVersions gets all package versions representing the matching manifest diff --git a/models/packages/debian/search.go b/models/packages/debian/search.go new file mode 100644 index 0000000000..332a4f7040 --- /dev/null +++ b/models/packages/debian/search.go @@ -0,0 +1,131 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package debian + +import ( + "context" + "strconv" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + debian_module "code.gitea.io/gitea/modules/packages/debian" + + "xorm.io/builder" +) + +type PackageSearchOptions struct { + OwnerID int64 + Distribution string + Component string + Architecture string +} + +// SearchLatestPackages gets the latest packages matching the search options +func SearchLatestPackages(ctx context.Context, opts *PackageSearchOptions) ([]*packages.PackageFileDescriptor, error) { + var cond builder.Cond = builder.Eq{ + "package_file.is_lead": true, + "package.type": packages.TypeDebian, + "package.owner_id": opts.OwnerID, + "package.is_internal": false, + "package_version.is_internal": false, + } + + props := make(map[string]string) + if opts.Distribution != "" { + props[debian_module.PropertyDistribution] = opts.Distribution + } + if opts.Component != "" { + props[debian_module.PropertyComponent] = opts.Component + } + if opts.Architecture != "" { + props[debian_module.PropertyArchitecture] = opts.Architecture + } + + if len(props) > 0 { + var propsCond builder.Cond = builder.Eq{ + "package_property.ref_type": packages.PropertyTypeFile, + } + propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_file.id")) + + propsCondBlock := builder.NewCond() + for name, value := range props { + propsCondBlock = propsCondBlock.Or(builder.Eq{ + "package_property.name": name, + "package_property.value": value, + }) + } + propsCond = propsCond.And(propsCondBlock) + + cond = cond.And(builder.Eq{ + strconv.Itoa(len(props)): builder.Select("COUNT(*)").Where(propsCond).From("package_property"), + }) + } + + cond = cond. + And(builder.Expr("pv2.id IS NULL")) + + joinCond := builder. + Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))"). + And(builder.Eq{"pv2.is_internal": false}) + + pfs := make([]*packages.PackageFile, 0, 10) + err := db.GetEngine(ctx). + Table("package_file"). + Select("package_file.*"). + Join("INNER", "package_version", "package_version.id = package_file.version_id"). + Join("LEFT", "package_version pv2", joinCond). + Join("INNER", "package", "package.id = package_version.package_id"). + Where(cond). + Desc("package_version.created_unix"). + Find(&pfs) + if err != nil { + return nil, err + } + + return packages.GetPackageFileDescriptors(ctx, pfs) +} + +// GetDistributions gets all available distributions +func GetDistributions(ctx context.Context, ownerID int64) ([]string, error) { + return getDistinctPropertyValues(ctx, ownerID, "", debian_module.PropertyDistribution) +} + +// GetComponents gets all available components for the given distribution +func GetComponents(ctx context.Context, ownerID int64, distribution string) ([]string, error) { + return getDistinctPropertyValues(ctx, ownerID, distribution, debian_module.PropertyComponent) +} + +// GetArchitectures gets all available architectures for the given distribution +func GetArchitectures(ctx context.Context, ownerID int64, distribution string) ([]string, error) { + return getDistinctPropertyValues(ctx, ownerID, distribution, debian_module.PropertyArchitecture) +} + +func getDistinctPropertyValues(ctx context.Context, ownerID int64, distribution, propName string) ([]string, error) { + var cond builder.Cond = builder.Eq{ + "package_property.ref_type": packages.PropertyTypeFile, + "package_property.name": propName, + "package.type": packages.TypeDebian, + "package.owner_id": ownerID, + } + if distribution != "" { + innerCond := builder. + Expr("pp.ref_id = package_property.ref_id"). + And(builder.Eq{ + "pp.ref_type": packages.PropertyTypeFile, + "pp.name": debian_module.PropertyDistribution, + "pp.value": distribution, + }) + cond = cond.And(builder.Exists(builder.Select("pp.ref_id").From("package_property pp").Where(innerCond))) + } + + values := make([]string, 0, 5) + return values, db.GetEngine(ctx). + Table("package_property"). + Distinct("package_property.value"). + Join("INNER", "package_file", "package_file.id = package_property.ref_id"). + Join("INNER", "package_version", "package_version.id = package_file.version_id"). + Join("INNER", "package", "package.id = package_version.package_id"). + Where(cond). + Find(&values) +} diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index 974c5b2c36..9256dd5630 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/packages/conan" "code.gitea.io/gitea/modules/packages/conda" "code.gitea.io/gitea/modules/packages/container" + "code.gitea.io/gitea/modules/packages/debian" "code.gitea.io/gitea/modules/packages/helm" "code.gitea.io/gitea/modules/packages/maven" "code.gitea.io/gitea/modules/packages/npm" @@ -127,13 +128,9 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc return nil, err } - pfds := make([]*PackageFileDescriptor, 0, len(pfs)) - for _, pf := range pfs { - pfd, err := GetPackageFileDescriptor(ctx, pf) - if err != nil { - return nil, err - } - pfds = append(pfds, pfd) + pfds, err := GetPackageFileDescriptors(ctx, pfs) + if err != nil { + return nil, err } var metadata interface{} @@ -150,6 +147,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc metadata = &conda.VersionMetadata{} case TypeContainer: metadata = &container.Metadata{} + case TypeDebian: + metadata = &debian.Metadata{} case TypeGeneric: // generic packages have no metadata case TypeHelm: @@ -210,6 +209,19 @@ func GetPackageFileDescriptor(ctx context.Context, pf *PackageFile) (*PackageFil }, nil } +// GetPackageFileDescriptors gets the package file descriptors for the package files +func GetPackageFileDescriptors(ctx context.Context, pfs []*PackageFile) ([]*PackageFileDescriptor, error) { + pfds := make([]*PackageFileDescriptor, 0, len(pfs)) + for _, pf := range pfs { + pfd, err := GetPackageFileDescriptor(ctx, pf) + if err != nil { + return nil, err + } + pfds = append(pfds, pfd) + } + return pfds, nil +} + // GetPackageDescriptors gets the package descriptions for the versions func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*PackageDescriptor, error) { pds := make([]*PackageDescriptor, 0, len(pvs)) diff --git a/models/packages/package.go b/models/packages/package.go index ccc9257c31..cc860b9a90 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -36,6 +36,7 @@ const ( TypeConan Type = "conan" TypeConda Type = "conda" TypeContainer Type = "container" + TypeDebian Type = "debian" TypeGeneric Type = "generic" TypeHelm Type = "helm" TypeMaven Type = "maven" @@ -55,6 +56,7 @@ var TypeList = []Type{ TypeConan, TypeConda, TypeContainer, + TypeDebian, TypeGeneric, TypeHelm, TypeMaven, @@ -82,6 +84,8 @@ func (pt Type) Name() string { return "Conda" case TypeContainer: return "Container" + case TypeDebian: + return "Debian" case TypeGeneric: return "Generic" case TypeHelm: @@ -121,6 +125,8 @@ func (pt Type) SVGName() string { return "gitea-conda" case TypeContainer: return "octicon-container" + case TypeDebian: + return "gitea-debian" case TypeGeneric: return "octicon-package" case TypeHelm: @@ -154,6 +160,7 @@ type Package struct { Name string `xorm:"NOT NULL"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` SemverCompatible bool `xorm:"NOT NULL DEFAULT false"` + IsInternal bool `xorm:"NOT NULL DEFAULT false"` } // TryInsertPackage inserts a package. If a package exists already, ErrDuplicatePackage is returned @@ -214,9 +221,10 @@ func GetPackageByID(ctx context.Context, packageID int64) (*Package, error) { // GetPackageByName gets a package by name func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) { var cond builder.Cond = builder.Eq{ - "package.owner_id": ownerID, - "package.type": packageType, - "package.lower_name": strings.ToLower(name), + "package.owner_id": ownerID, + "package.type": packageType, + "package.lower_name": strings.ToLower(name), + "package.is_internal": false, } p := &Package{} @@ -236,8 +244,9 @@ func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name // GetPackagesByType gets all packages of a specific type func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([]*Package, error) { var cond builder.Cond = builder.Eq{ - "package.owner_id": ownerID, - "package.type": packageType, + "package.owner_id": ownerID, + "package.type": packageType, + "package.is_internal": false, } ps := make([]*Package, 0, 10) diff --git a/models/packages/package_file.go b/models/packages/package_file.go index 97e7a0d407..28e2a0111a 100644 --- a/models/packages/package_file.go +++ b/models/packages/package_file.go @@ -117,13 +117,15 @@ func DeleteFileByID(ctx context.Context, fileID int64) error { // PackageFileSearchOptions are options for SearchXXX methods type PackageFileSearchOptions struct { - OwnerID int64 - PackageType string - VersionID int64 - Query string - CompositeKey string - Properties map[string]string - OlderThan time.Duration + OwnerID int64 + PackageType string + VersionID int64 + Query string + CompositeKey string + Properties map[string]string + OlderThan time.Duration + HashAlgorithm string + Hash string db.Paginator } @@ -182,6 +184,26 @@ func (opts *PackageFileSearchOptions) toConds() builder.Cond { cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-opts.OlderThan).Unix()}) } + if opts.Hash != "" { + var field string + switch strings.ToLower(opts.HashAlgorithm) { + case "md5": + field = "package_blob.hash_md5" + case "sha1": + field = "package_blob.hash_sha1" + case "sha256": + field = "package_blob.hash_sha256" + case "sha512": + fallthrough + default: // default to SHA512 if not specified or unknown + field = "package_blob.hash_sha512" + } + innerCond := builder. + Expr("package_blob.id = package_file.blob_id"). + And(builder.Eq{field: opts.Hash}) + cond = cond.And(builder.Exists(builder.Select("package_blob.id").From("package_blob").Where(innerCond))) + } + return cond } diff --git a/models/packages/package_version.go b/models/packages/package_version.go index 759c20abed..ab1bcddae5 100644 --- a/models/packages/package_version.go +++ b/models/packages/package_version.go @@ -173,7 +173,7 @@ const ( ) // PackageSearchOptions are options for SearchXXX methods -// Besides IsInternal are all fields optional and are not used if they have their default value (nil, "", 0) +// All fields optional and are not used if they have their default value (nil, "", 0) type PackageSearchOptions struct { OwnerID int64 RepoID int64 @@ -192,7 +192,9 @@ type PackageSearchOptions struct { func (opts *PackageSearchOptions) toConds() builder.Cond { cond := builder.NewCond() if !opts.IsInternal.IsNone() { - cond = builder.Eq{"package_version.is_internal": opts.IsInternal.IsTrue()} + cond = builder.Eq{ + "package_version.is_internal": opts.IsInternal.IsTrue(), + } } if opts.OwnerID != 0 { diff --git a/models/repo/archiver.go b/models/repo/archiver.go index 11ecaff34c..70f53cfe15 100644 --- a/models/repo/archiver.go +++ b/models/repo/archiver.go @@ -146,6 +146,13 @@ func FindRepoArchives(opts FindRepoArchiversOption) ([]*RepoArchiver, error) { // SetArchiveRepoState sets if a repo is archived func SetArchiveRepoState(repo *Repository, isArchived bool) (err error) { repo.IsArchived = isArchived - _, err = db.GetEngine(db.DefaultContext).Where("id = ?", repo.ID).Cols("is_archived").NoAutoTime().Update(repo) + + if isArchived { + repo.ArchivedUnix = timeutil.TimeStampNow() + } else { + repo.ArchivedUnix = timeutil.TimeStamp(0) + } + + _, err = db.GetEngine(db.DefaultContext).ID(repo.ID).Cols("is_archived", "archived_unix").NoAutoTime().Update(repo) return err } diff --git a/models/repo/repo.go b/models/repo/repo.go index d63c0449b6..361086ea42 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -175,8 +175,9 @@ type Repository struct { // Avatar: ID(10-20)-md5(32) - must fit into 64 symbols Avatar string `xorm:"VARCHAR(64)"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT 0"` } func init() { @@ -765,12 +766,9 @@ func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) { return repos, db.GetEngine(db.DefaultContext).In("id", ids).Find(&repos) } -// IsRepositoryExist returns true if the repository with given name under user has already existed. -func IsRepositoryExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) { - has, err := db.GetEngine(ctx).Get(&Repository{ - OwnerID: u.ID, - LowerName: strings.ToLower(repoName), - }) +// IsRepositoryModelOrDirExist returns true if the repository with given name under user has already existed. +func IsRepositoryModelOrDirExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) { + has, err := IsRepositoryModelExist(ctx, u, repoName) if err != nil { return false, err } @@ -778,6 +776,13 @@ func IsRepositoryExist(ctx context.Context, u *user_model.User, repoName string) return has || isDir, err } +func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) { + return db.GetEngine(ctx).Get(&Repository{ + OwnerID: u.ID, + LowerName: strings.ToLower(repoName), + }) +} + // GetTemplateRepo populates repo.TemplateRepo for a generated repository and // returns an error on failure (NOTE: no error is returned for // non-generated repositories, and TemplateRepo will be left untouched) diff --git a/models/repo/update.go b/models/repo/update.go index f4cb67bb8f..4894e0a1b9 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -116,7 +116,7 @@ func CheckCreateRepository(doer, u *user_model.User, name string, overwriteOrAdo return err } - has, err := IsRepositoryExist(db.DefaultContext, u, name) + has, err := IsRepositoryModelOrDirExist(db.DefaultContext, u, name) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { @@ -147,7 +147,7 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s return err } - has, err := IsRepositoryExist(db.DefaultContext, repo.Owner, newRepoName) + has, err := IsRepositoryModelOrDirExist(db.DefaultContext, repo.Owner, newRepoName) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { diff --git a/models/repo_transfer.go b/models/repo_transfer.go index 27a77f9b8c..1c873cec57 100644 --- a/models/repo_transfer.go +++ b/models/repo_transfer.go @@ -172,7 +172,7 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m } // Check if new owner has repository with same name. - if has, err := repo_model.IsRepositoryExist(ctx, newOwner, repo.Name); err != nil { + if has, err := repo_model.IsRepositoryModelExist(ctx, newOwner, repo.Name); err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { return repo_model.ErrRepoAlreadyExist{ @@ -249,7 +249,7 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo newOwnerName = newOwner.Name // ensure capitalisation matches // Check if new owner has repository with same name. - if has, err := repo_model.IsRepositoryExist(ctx, newOwner, repo.Name); err != nil { + if has, err := repo_model.IsRepositoryModelOrDirExist(ctx, newOwner, repo.Name); err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { return repo_model.ErrRepoAlreadyExist{ diff --git a/models/user/setting.go b/models/user/setting.go index aec79b756b..a41e494db9 100644 --- a/models/user/setting.go +++ b/models/user/setting.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/cache" setting_module "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) @@ -42,6 +43,10 @@ func (err ErrUserSettingIsNotExist) Error() string { return fmt.Sprintf("Setting[%s] is not exist", err.Key) } +func (err ErrUserSettingIsNotExist) Unwrap() error { + return util.ErrNotExist +} + // IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExist func IsErrUserSettingIsNotExist(err error) bool { _, ok := err.(ErrUserSettingIsNotExist) diff --git a/models/user/user.go b/models/user/user.go index 053d6680cd..46c4440e5f 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -346,7 +346,7 @@ func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListO Select("`user`.*"). Join("LEFT", "follow", "`user`.id=follow.follow_id"). Where("follow.user_id=?", u.ID). - And("`user`.type=?", UserTypeIndividual). + And("`user`.type IN (?, ?)", UserTypeIndividual, UserTypeOrganization). And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { @@ -1210,23 +1210,25 @@ func isUserVisibleToViewerCond(viewer *User) builder.Cond { return builder.Neq{ "`user`.visibility": structs.VisibleTypePrivate, }.Or( + // viewer's following builder.In("`user`.id", builder. Select("`follow`.user_id"). From("follow"). Where(builder.Eq{"`follow`.follow_id": viewer.ID})), - builder.In("`user`.id", - builder. - Select("`team_user`.uid"). - From("team_user"). - Join("INNER", "`team_user` AS t2", "`team_user`.id = `t2`.id"). - Where(builder.Eq{"`t2`.uid": viewer.ID})), + // viewer's org user builder.In("`user`.id", builder. Select("`team_user`.uid"). From("team_user"). Join("INNER", "`team_user` AS t2", "`team_user`.org_id = `t2`.org_id"). - Where(builder.Eq{"`t2`.uid": viewer.ID}))) + Where(builder.Eq{"`t2`.uid": viewer.ID})), + // viewer's org + builder.In("`user`.id", + builder. + Select("`team_user`.org_id"). + From("team_user"). + Where(builder.Eq{"`team_user`.uid": viewer.ID}))) } // IsUserVisibleToViewer check if viewer is able to see user profile diff --git a/modules/context/api.go b/modules/context/api.go index f7a3384691..ae245ec1cb 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -337,12 +337,13 @@ func RepoRefForAPI(next http.Handler) http.Handler { if git.IsErrNotExist(err) { ctx.NotFound() } else { - ctx.Error(http.StatusInternalServerError, "GetBlobByPath", err) + ctx.Error(http.StatusInternalServerError, "GetCommit", err) } return } ctx.Repo.Commit = commit ctx.Repo.TreePath = ctx.Params("*") + next.ServeHTTP(w, req) return } diff --git a/modules/context/context.go b/modules/context/context.go index f262c7cce7..cd7fcebe55 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -446,6 +446,17 @@ func (ctx *Context) JSON(status int, content interface{}) { } } +func removeSessionCookieHeader(w http.ResponseWriter) { + cookies := w.Header()["Set-Cookie"] + w.Header().Del("Set-Cookie") + for _, cookie := range cookies { + if strings.HasPrefix(cookie, setting.SessionConfig.CookieName+"=") { + continue + } + w.Header().Add("Set-Cookie", cookie) + } +} + // Redirect redirects the request func (ctx *Context) Redirect(location string, status ...int) { code := http.StatusSeeOther @@ -453,6 +464,15 @@ func (ctx *Context) Redirect(location string, status ...int) { code = status[0] } + if strings.Contains(location, "://") || strings.HasPrefix(location, "//") { + // Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path + // 1. the first request to "/my-path" contains cookie + // 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking) + // 3. Gitea's Sessioner doesn't see the session cookie, so it generates a new session id, and returns it to browser + // 4. then the browser accepts the empty session, then the user is logged out + // So in this case, we should remove the session cookie from the response header + removeSessionCookieHeader(ctx.Resp) + } http.Redirect(ctx.Resp, ctx.Req, location, code) } @@ -657,7 +677,7 @@ func getCsrfOpts() CsrfOptions { // Contexter initializes a classic context for a request. func Contexter(ctx context.Context) func(next http.Handler) http.Handler { - _, rnd := templates.HTMLRenderer(ctx) + rnd := templates.HTMLRenderer() csrfOpts := getCsrfOpts() if !setting.IsProd { CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose diff --git a/modules/context/context_test.go b/modules/context/context_test.go new file mode 100644 index 0000000000..e1460c1fd7 --- /dev/null +++ b/modules/context/context_test.go @@ -0,0 +1,40 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package context + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +type mockResponseWriter struct { + header http.Header +} + +func (m *mockResponseWriter) Header() http.Header { + return m.header +} + +func (m *mockResponseWriter) Write(bytes []byte) (int, error) { + panic("implement me") +} + +func (m *mockResponseWriter) WriteHeader(statusCode int) { + panic("implement me") +} + +func TestRemoveSessionCookieHeader(t *testing.T) { + w := &mockResponseWriter{} + w.header = http.Header{} + w.header.Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "foo"}).String()) + w.header.Add("Set-Cookie", (&http.Cookie{Name: "other", Value: "bar"}).String()) + assert.Len(t, w.Header().Values("Set-Cookie"), 2) + removeSessionCookieHeader(w) + assert.Len(t, w.Header().Values("Set-Cookie"), 1) + assert.Contains(t, "other=bar", w.Header().Get("Set-Cookie")) +} diff --git a/modules/context/package.go b/modules/context/package.go index 2a0159eb5c..6c418b3164 100644 --- a/modules/context/package.go +++ b/modules/context/package.go @@ -131,7 +131,7 @@ func determineAccessMode(ctx *Context) (perm.AccessMode, error) { // PackageContexter initializes a package context for a request. func PackageContexter(ctx gocontext.Context) func(next http.Handler) http.Handler { - _, rnd := templates.HTMLRenderer(ctx) + rnd := templates.HTMLRenderer() return func(next http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { ctx := Context{ diff --git a/modules/context/permission.go b/modules/context/permission.go index 8cb5d09eb9..cc53fb99ed 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -4,6 +4,10 @@ package context import ( + "net/http" + + auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/log" ) @@ -106,3 +110,32 @@ func RequireRepoReaderOr(unitTypes ...unit.Type) func(ctx *Context) { ctx.NotFound(ctx.Req.URL.RequestURI(), nil) } } + +// RequireRepoScopedToken check whether personal access token has repo scope +func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository) { + if !ctx.IsBasicAuth || ctx.Data["IsApiToken"] != true { + return + } + + var err error + scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope) + if ok { // it's a personal access token but not oauth2 token + var scopeMatched bool + scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeRepo) + if err != nil { + ctx.ServerError("HasScope", err) + return + } + if !scopeMatched && !repo.IsPrivate { + scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopePublicRepo) + if err != nil { + ctx.ServerError("HasScope", err) + return + } + } + if !scopeMatched { + ctx.Error(http.StatusForbidden) + return + } + } +} diff --git a/modules/git/commit.go b/modules/git/commit.go index 610d27c68a..f28c315cb5 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -187,8 +187,8 @@ func (c *Commit) CommitsCount() (int64, error) { } // CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize -func (c *Commit) CommitsByRange(page, pageSize int) ([]*Commit, error) { - return c.repo.commitsByRange(c.ID, page, pageSize) +func (c *Commit) CommitsByRange(page, pageSize int, not string) ([]*Commit, error) { + return c.repo.commitsByRange(c.ID, page, pageSize, not) } // CommitsBefore returns all the commits before current revision diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 153a116b06..30a82eb297 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -90,14 +90,22 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) { return commits[0], nil } -func (repo *Repository) commitsByRange(id SHA1, page, pageSize int) ([]*Commit, error) { - stdout, _, err := NewCommand(repo.Ctx, "log"). - AddOptionFormat("--skip=%d", (page-1)*pageSize).AddOptionFormat("--max-count=%d", pageSize).AddArguments(prettyLogFormat). - AddDynamicArguments(id.String()). - RunStdBytes(&RunOpts{Dir: repo.Path}) +func (repo *Repository) commitsByRange(id SHA1, page, pageSize int, not string) ([]*Commit, error) { + cmd := NewCommand(repo.Ctx, "log"). + AddOptionFormat("--skip=%d", (page-1)*pageSize). + AddOptionFormat("--max-count=%d", pageSize). + AddArguments(prettyLogFormat). + AddDynamicArguments(id.String()) + + if not != "" { + cmd.AddOptionValues("--not", not) + } + + stdout, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } + return repo.parsePrettyFormatLogToList(stdout) } diff --git a/modules/packages/debian/metadata.go b/modules/packages/debian/metadata.go new file mode 100644 index 0000000000..dee524c8ff --- /dev/null +++ b/modules/packages/debian/metadata.go @@ -0,0 +1,218 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package debian + +import ( + "archive/tar" + "bufio" + "compress/gzip" + "io" + "net/mail" + "regexp" + "strings" + + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/validation" + + "github.com/blakesmith/ar" + "github.com/klauspost/compress/zstd" + "github.com/ulikunitz/xz" +) + +const ( + PropertyDistribution = "debian.distribution" + PropertyComponent = "debian.component" + PropertyArchitecture = "debian.architecture" + PropertyControl = "debian.control" + PropertyRepositoryIncludeInRelease = "debian.repository.include_in_release" + + SettingKeyPrivate = "debian.key.private" + SettingKeyPublic = "debian.key.public" + + RepositoryPackage = "_debian" + RepositoryVersion = "_repository" + + controlTar = "control.tar" +) + +var ( + ErrMissingControlFile = util.NewInvalidArgumentErrorf("control file is missing") + ErrUnsupportedCompression = util.NewInvalidArgumentErrorf("unsupported compression algorithm") + ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid") + ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") + ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid") + + // https://www.debian.org/doc/debian-policy/ch-controlfields.html#source + namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9+-.]+\z`) + // https://www.debian.org/doc/debian-policy/ch-controlfields.html#version + versionPattern = regexp.MustCompile(`\A(?:[0-9]:)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`) +) + +type Package struct { + Name string + Version string + Architecture string + Control string + Metadata *Metadata +} + +type Metadata struct { + Maintainer string `json:"maintainer,omitempty"` + ProjectURL string `json:"project_url,omitempty"` + Description string `json:"description,omitempty"` + Dependencies []string `json:"dependencies,omitempty"` +} + +// ParsePackage parses the Debian package file +// https://manpages.debian.org/bullseye/dpkg-dev/deb.5.en.html +func ParsePackage(r io.Reader) (*Package, error) { + arr := ar.NewReader(r) + + for { + hd, err := arr.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + if strings.HasPrefix(hd.Name, controlTar) { + var inner io.Reader + switch hd.Name[len(controlTar):] { + case "": + inner = arr + case ".gz": + gzr, err := gzip.NewReader(arr) + if err != nil { + return nil, err + } + defer gzr.Close() + + inner = gzr + case ".xz": + xzr, err := xz.NewReader(arr) + if err != nil { + return nil, err + } + + inner = xzr + case ".zst": + zr, err := zstd.NewReader(arr) + if err != nil { + return nil, err + } + defer zr.Close() + + inner = zr + default: + return nil, ErrUnsupportedCompression + } + + tr := tar.NewReader(inner) + for { + hd, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + if hd.Typeflag != tar.TypeReg { + continue + } + + if hd.FileInfo().Name() == "control" { + return ParseControlFile(tr) + } + } + } + } + + return nil, ErrMissingControlFile +} + +// ParseControlFile parses a Debian control file to retrieve the metadata +func ParseControlFile(r io.Reader) (*Package, error) { + p := &Package{ + Metadata: &Metadata{}, + } + + key := "" + var depends strings.Builder + var control strings.Builder + + s := bufio.NewScanner(io.TeeReader(r, &control)) + for s.Scan() { + line := s.Text() + + trimmed := strings.TrimSpace(line) + if trimmed == "" { + continue + } + + if line[0] == ' ' || line[0] == '\t' { + switch key { + case "Description": + p.Metadata.Description += line + case "Depends": + depends.WriteString(trimmed) + } + } else { + parts := strings.SplitN(trimmed, ":", 2) + if len(parts) < 2 { + continue + } + + key = parts[0] + value := strings.TrimSpace(parts[1]) + switch key { + case "Package": + if !namePattern.MatchString(value) { + return nil, ErrInvalidName + } + p.Name = value + case "Version": + if !versionPattern.MatchString(value) { + return nil, ErrInvalidVersion + } + p.Version = value + case "Architecture": + if value == "" { + return nil, ErrInvalidArchitecture + } + p.Architecture = value + case "Maintainer": + a, err := mail.ParseAddress(value) + if err != nil || a.Name == "" { + p.Metadata.Maintainer = value + } else { + p.Metadata.Maintainer = a.Name + } + case "Description": + p.Metadata.Description = value + case "Depends": + depends.WriteString(value) + case "Homepage": + if validation.IsValidURL(value) { + p.Metadata.ProjectURL = value + } + } + } + } + if err := s.Err(); err != nil { + return nil, err + } + + dependencies := strings.Split(depends.String(), ",") + for i := range dependencies { + dependencies[i] = strings.TrimSpace(dependencies[i]) + } + p.Metadata.Dependencies = dependencies + + p.Control = control.String() + + return p, nil +} diff --git a/modules/packages/debian/metadata_test.go b/modules/packages/debian/metadata_test.go new file mode 100644 index 0000000000..69fd51ea79 --- /dev/null +++ b/modules/packages/debian/metadata_test.go @@ -0,0 +1,171 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package debian + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "io" + "testing" + + "github.com/blakesmith/ar" + "github.com/klauspost/compress/zstd" + "github.com/stretchr/testify/assert" + "github.com/ulikunitz/xz" +) + +const ( + packageName = "gitea" + packageVersion = "0:1.0.1-te~st" + packageArchitecture = "amd64" + packageAuthor = "KN4CK3R" + description = "Description with multiple lines." + projectURL = "https://gitea.io" +) + +func TestParsePackage(t *testing.T) { + createArchive := func(files map[string][]byte) io.Reader { + var buf bytes.Buffer + aw := ar.NewWriter(&buf) + aw.WriteGlobalHeader() + for filename, content := range files { + hdr := &ar.Header{ + Name: filename, + Mode: 0o600, + Size: int64(len(content)), + } + aw.WriteHeader(hdr) + aw.Write(content) + } + return &buf + } + + t.Run("MissingControlFile", func(t *testing.T) { + data := createArchive(map[string][]byte{"dummy.txt": {}}) + + p, err := ParsePackage(data) + assert.Nil(t, p) + assert.ErrorIs(t, err, ErrMissingControlFile) + }) + + t.Run("Compression", func(t *testing.T) { + t.Run("Unsupported", func(t *testing.T) { + data := createArchive(map[string][]byte{"control.tar.foo": {}}) + + p, err := ParsePackage(data) + assert.Nil(t, p) + assert.ErrorIs(t, err, ErrUnsupportedCompression) + }) + + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + tw.WriteHeader(&tar.Header{ + Name: "control", + Mode: 0o600, + Size: 50, + }) + tw.Write([]byte("Package: gitea\nVersion: 1.0.0\nArchitecture: amd64\n")) + tw.Close() + + t.Run("None", func(t *testing.T) { + data := createArchive(map[string][]byte{"control.tar": buf.Bytes()}) + + p, err := ParsePackage(data) + assert.NotNil(t, p) + assert.NoError(t, err) + assert.Equal(t, "gitea", p.Name) + }) + + t.Run("gz", func(t *testing.T) { + var zbuf bytes.Buffer + zw := gzip.NewWriter(&zbuf) + zw.Write(buf.Bytes()) + zw.Close() + + data := createArchive(map[string][]byte{"control.tar.gz": zbuf.Bytes()}) + + p, err := ParsePackage(data) + assert.NotNil(t, p) + assert.NoError(t, err) + assert.Equal(t, "gitea", p.Name) + }) + + t.Run("xz", func(t *testing.T) { + var xbuf bytes.Buffer + xw, _ := xz.NewWriter(&xbuf) + xw.Write(buf.Bytes()) + xw.Close() + + data := createArchive(map[string][]byte{"control.tar.xz": xbuf.Bytes()}) + + p, err := ParsePackage(data) + assert.NotNil(t, p) + assert.NoError(t, err) + assert.Equal(t, "gitea", p.Name) + }) + + t.Run("zst", func(t *testing.T) { + var zbuf bytes.Buffer + zw, _ := zstd.NewWriter(&zbuf) + zw.Write(buf.Bytes()) + zw.Close() + + data := createArchive(map[string][]byte{"control.tar.zst": zbuf.Bytes()}) + + p, err := ParsePackage(data) + assert.NotNil(t, p) + assert.NoError(t, err) + assert.Equal(t, "gitea", p.Name) + }) + }) +} + +func TestParseControlFile(t *testing.T) { + buildContent := func(name, version, architecture string) *bytes.Buffer { + var buf bytes.Buffer + buf.WriteString("Package: " + name + "\nVersion: " + version + "\nArchitecture: " + architecture + "\nMaintainer: " + packageAuthor + " \nHomepage: " + projectURL + "\nDepends: a,\n b\nDescription: Description\n with multiple\n lines.") + return &buf + } + + t.Run("InvalidName", func(t *testing.T) { + for _, name := range []string{"", "-cd"} { + p, err := ParseControlFile(buildContent(name, packageVersion, packageArchitecture)) + assert.Nil(t, p) + assert.ErrorIs(t, err, ErrInvalidName) + } + }) + + t.Run("InvalidVersion", func(t *testing.T) { + for _, version := range []string{"", "1-", ":1.0", "1_0"} { + p, err := ParseControlFile(buildContent(packageName, version, packageArchitecture)) + assert.Nil(t, p) + assert.ErrorIs(t, err, ErrInvalidVersion) + } + }) + + t.Run("InvalidArchitecture", func(t *testing.T) { + p, err := ParseControlFile(buildContent(packageName, packageVersion, "")) + assert.Nil(t, p) + assert.ErrorIs(t, err, ErrInvalidArchitecture) + }) + + t.Run("Valid", func(t *testing.T) { + content := buildContent(packageName, packageVersion, packageArchitecture) + full := content.String() + + p, err := ParseControlFile(content) + assert.NoError(t, err) + assert.NotNil(t, p) + + assert.Equal(t, packageName, p.Name) + assert.Equal(t, packageVersion, p.Version) + assert.Equal(t, packageArchitecture, p.Architecture) + assert.Equal(t, description, p.Metadata.Description) + assert.Equal(t, projectURL, p.Metadata.ProjectURL) + assert.Equal(t, packageAuthor, p.Metadata.Maintainer) + assert.Equal(t, []string{"a", "b"}, p.Metadata.Dependencies) + assert.Equal(t, full, p.Control) + }) +} diff --git a/modules/packages/hashed_buffer.go b/modules/packages/hashed_buffer.go index ef00a45057..017ddf1c8f 100644 --- a/modules/packages/hashed_buffer.go +++ b/modules/packages/hashed_buffer.go @@ -25,8 +25,15 @@ type HashedBuffer struct { combinedWriter io.Writer } -// NewHashedBuffer creates a hashed buffer with a specific maximum memory size -func NewHashedBuffer(maxMemorySize int) (*HashedBuffer, error) { +const DefaultMemorySize = 32 * 1024 * 1024 + +// NewHashedBuffer creates a hashed buffer with the default memory size +func NewHashedBuffer() (*HashedBuffer, error) { + return NewHashedBufferWithSize(DefaultMemorySize) +} + +// NewHashedBuffer creates a hashed buffer with a specific memory size +func NewHashedBufferWithSize(maxMemorySize int) (*HashedBuffer, error) { b, err := filebuffer.New(maxMemorySize) if err != nil { return nil, err @@ -43,9 +50,14 @@ func NewHashedBuffer(maxMemorySize int) (*HashedBuffer, error) { }, nil } -// CreateHashedBufferFromReader creates a hashed buffer and copies the provided reader data into it. -func CreateHashedBufferFromReader(r io.Reader, maxMemorySize int) (*HashedBuffer, error) { - b, err := NewHashedBuffer(maxMemorySize) +// CreateHashedBufferFromReader creates a hashed buffer with the default memory size and copies the provided reader data into it. +func CreateHashedBufferFromReader(r io.Reader) (*HashedBuffer, error) { + return CreateHashedBufferFromReaderWithSize(r, DefaultMemorySize) +} + +// CreateHashedBufferFromReaderWithSize creates a hashed buffer and copies the provided reader data into it. +func CreateHashedBufferFromReaderWithSize(r io.Reader, maxMemorySize int) (*HashedBuffer, error) { + b, err := NewHashedBufferWithSize(maxMemorySize) if err != nil { return nil, err } diff --git a/modules/packages/hashed_buffer_test.go b/modules/packages/hashed_buffer_test.go index e907aa0605..564e782f18 100644 --- a/modules/packages/hashed_buffer_test.go +++ b/modules/packages/hashed_buffer_test.go @@ -26,7 +26,7 @@ func TestHashedBuffer(t *testing.T) { } for _, c := range cases { - buf, err := CreateHashedBufferFromReader(strings.NewReader(c.Data), c.MaxMemorySize) + buf, err := CreateHashedBufferFromReaderWithSize(strings.NewReader(c.Data), c.MaxMemorySize) assert.NoError(t, err) assert.EqualValues(t, len(c.Data), buf.Size()) diff --git a/modules/packages/nuget/symbol_extractor.go b/modules/packages/nuget/symbol_extractor.go index b709eac4c1..81bf0371a0 100644 --- a/modules/packages/nuget/symbol_extractor.go +++ b/modules/packages/nuget/symbol_extractor.go @@ -63,7 +63,7 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) { return err } - buf, err := packages.CreateHashedBufferFromReader(f, 32*1024*1024) + buf, err := packages.CreateHashedBufferFromReader(f) f.Close() diff --git a/modules/queue/queue_channel_test.go b/modules/queue/queue_channel_test.go index 9b92398bac..f9dae742e2 100644 --- a/modules/queue/queue_channel_test.go +++ b/modules/queue/queue_channel_test.go @@ -4,6 +4,7 @@ package queue import ( + "os" "sync" "testing" "time" @@ -101,6 +102,9 @@ func TestChannelQueue_Batch(t *testing.T) { } func TestChannelQueue_Pause(t *testing.T) { + if os.Getenv("CI") != "" { + t.Skip("Skipping because test is flaky on CI") + } lock := sync.Mutex{} var queue Queue var err error diff --git a/modules/queue/unique_queue_disk_channel_test.go b/modules/queue/unique_queue_disk_channel_test.go index f75c69f785..e2fe4aceee 100644 --- a/modules/queue/unique_queue_disk_channel_test.go +++ b/modules/queue/unique_queue_disk_channel_test.go @@ -4,6 +4,7 @@ package queue import ( + "os" "strconv" "sync" "testing" @@ -15,6 +16,10 @@ import ( ) func TestPersistableChannelUniqueQueue(t *testing.T) { + if os.Getenv("CI") != "" { + t.Skip("Skipping because test is flaky on CI") + } + tmpDir := t.TempDir() _ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`) diff --git a/modules/repository/create.go b/modules/repository/create.go index e926009c5c..63be5cac25 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -35,7 +35,7 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re return err } - has, err := repo_model.IsRepositoryExist(ctx, u, repo.Name) + has, err := repo_model.IsRepositoryModelExist(ctx, u, repo.Name) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { diff --git a/modules/setting/markup.go b/modules/setting/markup.go index 969e30e888..6c2246342b 100644 --- a/modules/setting/markup.go +++ b/modules/setting/markup.go @@ -33,7 +33,7 @@ var Markdown = struct { }{ EnableHardLineBreakInComments: true, EnableHardLineBreakInDocuments: false, - FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd", ","), + FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd,.livemd", ","), EnableMath: true, } diff --git a/modules/setting/packages.go b/modules/setting/packages.go index 89601c3b99..b52bbf40c7 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -30,6 +30,7 @@ var ( LimitSizeConan int64 LimitSizeConda int64 LimitSizeContainer int64 + LimitSizeDebian int64 LimitSizeGeneric int64 LimitSizeHelm int64 LimitSizeMaven int64 @@ -73,6 +74,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) { Packages.LimitSizeConan = mustBytes(sec, "LIMIT_SIZE_CONAN") Packages.LimitSizeConda = mustBytes(sec, "LIMIT_SIZE_CONDA") Packages.LimitSizeContainer = mustBytes(sec, "LIMIT_SIZE_CONTAINER") + Packages.LimitSizeDebian = mustBytes(sec, "LIMIT_SIZE_DEBIAN") Packages.LimitSizeGeneric = mustBytes(sec, "LIMIT_SIZE_GENERIC") Packages.LimitSizeHelm = mustBytes(sec, "LIMIT_SIZE_HELM") Packages.LimitSizeMaven = mustBytes(sec, "LIMIT_SIZE_MAVEN") diff --git a/modules/setting/repository.go b/modules/setting/repository.go index bae3c658a4..20ed6d0dcd 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -168,7 +168,7 @@ var ( Editor: struct { LineWrapExtensions []string }{ - LineWrapExtensions: strings.Split(".txt,.md,.markdown,.mdown,.mkd,", ","), + LineWrapExtensions: strings.Split(".txt,.md,.markdown,.mdown,.mkd,.livemd,", ","), }, // Repository upload settings diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 7a1b7d17a6..9ab55e91c5 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -250,6 +250,9 @@ func loadCommonSettingsFrom(cfg ConfigProvider) { loadLogFrom(cfg) loadServerFrom(cfg) loadSSHFrom(cfg) + + mustCurrentRunUserMatch(cfg) // it depends on the SSH config, only non-builtin SSH server requires this check + loadOAuth2From(cfg) loadSecurityFrom(cfg) loadAttachmentFrom(cfg) @@ -282,14 +285,6 @@ func loadRunModeFrom(rootCfg ConfigProvider) { RunMode = rootSec.Key("RUN_MODE").MustString("prod") } IsProd = strings.EqualFold(RunMode, "prod") - // Does not check run user when the install lock is off. - installLock := rootCfg.Section("security").Key("INSTALL_LOCK").MustBool(false) - if installLock { - currentUser, match := IsRunUserMatchCurrentUser(RunUser) - if !match { - log.Fatal("Expect user '%s' but current user is: %s", RunUser, currentUser) - } - } // check if we run as root if os.Getuid() == 0 { @@ -301,6 +296,17 @@ func loadRunModeFrom(rootCfg ConfigProvider) { } } +func mustCurrentRunUserMatch(rootCfg ConfigProvider) { + // Does not check run user when the "InstallLock" is off. + installLock := rootCfg.Section("security").Key("INSTALL_LOCK").MustBool(false) + if installLock { + currentUser, match := IsRunUserMatchCurrentUser(RunUser) + if !match { + log.Fatal("Expect user '%s' but current user is: %s", RunUser, currentUser) + } + } +} + // LoadSettings initializes the settings for normal start up func LoadSettings() { loadDBSetting(CfgProvider) diff --git a/modules/setting/time.go b/modules/setting/time.go index 5fd0fdb92f..6d2aa80f5b 100644 --- a/modules/setting/time.go +++ b/modules/setting/time.go @@ -9,45 +9,10 @@ import ( "code.gitea.io/gitea/modules/log" ) -var ( - // Time settings - TimeFormat string - // UILocation is the location on the UI, so that we can display the time on UI. - DefaultUILocation = time.Local -) +// DefaultUILocation is the location on the UI, so that we can display the time on UI. +var DefaultUILocation = time.Local func loadTimeFrom(rootCfg ConfigProvider) { - timeFormatKey := rootCfg.Section("time").Key("FORMAT").MustString("") - if timeFormatKey != "" { - TimeFormat = map[string]string{ - "ANSIC": time.ANSIC, - "UnixDate": time.UnixDate, - "RubyDate": time.RubyDate, - "RFC822": time.RFC822, - "RFC822Z": time.RFC822Z, - "RFC850": time.RFC850, - "RFC1123": time.RFC1123, - "RFC1123Z": time.RFC1123Z, - "RFC3339": time.RFC3339, - "RFC3339Nano": time.RFC3339Nano, - "Kitchen": time.Kitchen, - "Stamp": time.Stamp, - "StampMilli": time.StampMilli, - "StampMicro": time.StampMicro, - "StampNano": time.StampNano, - }[timeFormatKey] - // When the TimeFormatKey does not exist in the previous map e.g.'2006-01-02 15:04:05' - if len(TimeFormat) == 0 { - TimeFormat = timeFormatKey - TestTimeFormat, _ := time.Parse(TimeFormat, TimeFormat) - if TestTimeFormat.Format(time.RFC3339) != "2006-01-02T15:04:05Z" { - log.Warn("Provided TimeFormat: %s does not create a fully specified date and time.", TimeFormat) - log.Warn("In order to display dates and times correctly please check your time format has 2006, 01, 02, 15, 04 and 05") - } - log.Trace("Custom TimeFormat: %s", TimeFormat) - } - } - zone := rootCfg.Section("time").Key("DEFAULT_UI_LOCATION").String() if zone != "" { var err error diff --git a/modules/structs/miscellaneous.go b/modules/structs/miscellaneous.go index 8acea84d6c..bff10f95b7 100644 --- a/modules/structs/miscellaneous.go +++ b/modules/structs/miscellaneous.go @@ -72,6 +72,28 @@ type ServerVersion struct { Version string `json:"version"` } +// GitignoreTemplateInfo name and text of a gitignore template +type GitignoreTemplateInfo struct { + Name string `json:"name"` + Source string `json:"source"` +} + +// LicensesListEntry is used for the API +type LicensesTemplateListEntry struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` +} + +// LicensesInfo contains information about a License +type LicenseTemplateInfo struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` + Implementation string `json:"implementation"` + Body string `json:"body"` +} + // APIError is an api error with a message type APIError struct { Message string `json:"message"` diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 300dae3726..9fcb038262 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -80,6 +80,7 @@ type Repository struct { Created time.Time `json:"created_at"` // swagger:strfmt date-time Updated time.Time `json:"updated_at"` + ArchivedAt time.Time `json:"archived_at"` Permissions *Permission `json:"permissions,omitempty"` HasIssues bool `json:"has_issues"` InternalTracker *InternalTracker `json:"internal_tracker,omitempty"` diff --git a/modules/templates/helper.go b/modules/templates/helper.go index b7bef20560..4abd94d46e 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -5,54 +5,31 @@ package templates import ( - "bytes" "context" - "encoding/hex" "fmt" "html" "html/template" - "math" - "mime" "net/url" - "path/filepath" - "regexp" "strings" "time" - "unicode" - activities_model "code.gitea.io/gitea/models/activities" - "code.gitea.io/gitea/models/avatars" - issues_model "code.gitea.io/gitea/models/issues" - "code.gitea.io/gitea/models/organization" - repo_model "code.gitea.io/gitea/models/repo" system_model "code.gitea.io/gitea/models/system" - user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/emoji" - "code.gitea.io/gitea/modules/git" - giturl "code.gitea.io/gitea/modules/git/url" - gitea_html "code.gitea.io/gitea/modules/html" - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" - "code.gitea.io/gitea/modules/markup/markdown" - "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/svg" "code.gitea.io/gitea/modules/templates/eval" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/gitdiff" - - "github.com/editorconfig/editorconfig-core-go/v2" ) -// Used from static.go && dynamic.go -var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}[\s]*$`) - // NewFuncMap returns functions for injecting to templates -func NewFuncMap() []template.FuncMap { - return []template.FuncMap{map[string]interface{}{ +func NewFuncMap() template.FuncMap { + return map[string]interface{}{ + "DumpVar": dumpVar, + // ----------------------------------------------------------------- // html/template related functions "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names. @@ -63,6 +40,7 @@ func NewFuncMap() []template.FuncMap { "JSEscape": template.JSEscapeString, "Str2html": Str2html, // TODO: rename it to SanitizeHTML "URLJoin": util.URLJoin, + "DotEscape": DotEscape, "PathEscape": url.PathEscape, "PathEscapeSegments": util.PathEscapeSegments, @@ -70,30 +48,7 @@ func NewFuncMap() []template.FuncMap { // utils "StringUtils": NewStringUtils, "SliceUtils": NewSliceUtils, - - // ----------------------------------------------------------------- - // string / json - // TODO: move string helper functions to StringUtils - "Join": strings.Join, - "DotEscape": DotEscape, - "EllipsisString": base.EllipsisString, - "DumpVar": dumpVar, - - "Json": func(in interface{}) string { - out, err := json.Marshal(in) - if err != nil { - return "" - } - return string(out) - }, - "JsonPrettyPrint": func(in string) string { - var out bytes.Buffer - err := json.Indent(&out, []byte(in), "", " ") - if err != nil { - return "" - } - return out.String() - }, + "JsonUtils": NewJsonUtils, // ----------------------------------------------------------------- // svg / avatar / icon @@ -107,31 +62,7 @@ func NewFuncMap() []template.FuncMap { "MigrationIcon": MigrationIcon, "ActionIcon": ActionIcon, - "SortArrow": func(normSort, revSort, urlSort string, isDefault bool) template.HTML { - // if needed - if len(normSort) == 0 || len(urlSort) == 0 { - return "" - } - - if len(urlSort) == 0 && isDefault { - // if sort is sorted as default add arrow tho this table header - if isDefault { - return svg.RenderHTML("octicon-triangle-down", 16) - } - } else { - // if sort arg is in url test if it correlates with column header sort arguments - // the direction of the arrow should indicate the "current sort order", up means ASC(normal), down means DESC(rev) - if urlSort == normSort { - // the table is sorted with this header normal - return svg.RenderHTML("octicon-triangle-up", 16) - } else if urlSort == revSort { - // the table is sorted with this header reverse - return svg.RenderHTML("octicon-triangle-down", 16) - } - } - // the table is NOT sorted with this header - return "" - }, + "SortArrow": SortArrow, // ----------------------------------------------------------------- // time / number / format @@ -141,9 +72,6 @@ func NewFuncMap() []template.FuncMap { "TimeSinceUnix": timeutil.TimeSinceUnix, "DateTime": timeutil.DateTime, "Sec2Time": util.SecToTime, - "DateFmtLong": func(t time.Time) string { - return t.Format(time.RFC3339) - }, "LoadTimes": func(startTime time.Time) string { return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" }, @@ -245,32 +173,9 @@ func NewFuncMap() []template.FuncMap { "ReactionToEmoji": ReactionToEmoji, "RenderNote": RenderNote, - "RenderMarkdownToHtml": func(ctx context.Context, input string) template.HTML { - output, err := markdown.RenderString(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: setting.AppSubURL, - }, input) - if err != nil { - log.Error("RenderString: %v", err) - } - return template.HTML(output) - }, - "RenderLabel": func(ctx context.Context, label *issues_model.Label) template.HTML { - return template.HTML(RenderLabel(ctx, label)) - }, - "RenderLabels": func(ctx context.Context, labels []*issues_model.Label, repoLink string) template.HTML { - htmlCode := `` - for _, label := range labels { - // Protect against nil value in labels - shouldn't happen but would cause a panic if so - if label == nil { - continue - } - htmlCode += fmt.Sprintf("%s ", - repoLink, label.ID, RenderLabel(ctx, label)) - } - htmlCode += "" - return template.HTML(htmlCode) - }, + "RenderMarkdownToHtml": RenderMarkdownToHtml, + "RenderLabel": RenderLabel, + "RenderLabels": RenderLabels, // ----------------------------------------------------------------- // misc @@ -281,122 +186,9 @@ func NewFuncMap() []template.FuncMap { "CommentMustAsDiff": gitdiff.CommentMustAsDiff, "MirrorRemoteAddress": mirrorRemoteAddress, - "ParseDeadline": func(deadline string) []string { - return strings.Split(deadline, "|") - }, - "FilenameIsImage": func(filename string) bool { - mimeType := mime.TypeByExtension(filepath.Ext(filename)) - return strings.HasPrefix(mimeType, "image/") - }, - "TabSizeClass": func(ec interface{}, filename string) string { - var ( - value *editorconfig.Editorconfig - ok bool - ) - if ec != nil { - if value, ok = ec.(*editorconfig.Editorconfig); !ok || value == nil { - return "tab-size-8" - } - def, err := value.GetDefinitionForFilename(filename) - if err != nil { - log.Error("tab size class: getting definition for filename: %v", err) - return "tab-size-8" - } - if def.TabWidth > 0 { - return fmt.Sprintf("tab-size-%d", def.TabWidth) - } - } - return "tab-size-8" - }, - "SubJumpablePath": func(str string) []string { - var path []string - index := strings.LastIndex(str, "/") - if index != -1 && index != len(str) { - path = append(path, str[0:index+1], str[index+1:]) - } else { - path = append(path, str) - } - return path - }, - "CompareLink": func(baseRepo, repo *repo_model.Repository, branchName string) string { - var curBranch string - if repo.ID != baseRepo.ID { - curBranch += fmt.Sprintf("%s/%s:", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name)) - } - curBranch += util.PathEscapeSegments(branchName) - - return fmt.Sprintf("%s/compare/%s...%s", - baseRepo.Link(), - util.PathEscapeSegments(baseRepo.DefaultBranch), - curBranch, - ) - }, - }} -} - -// AvatarHTML creates the HTML for an avatar -func AvatarHTML(src string, size int, class, name string) template.HTML { - sizeStr := fmt.Sprintf(`%d`, size) - - if name == "" { - name = "avatar" + "FilenameIsImage": FilenameIsImage, + "TabSizeClass": TabSizeClass, } - - return template.HTML(``) -} - -// Avatar renders user avatars. args: user, size (int), class (string) -func Avatar(ctx context.Context, item interface{}, others ...interface{}) template.HTML { - size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...) - - switch t := item.(type) { - case *user_model.User: - src := t.AvatarLinkWithSize(ctx, size*setting.Avatar.RenderedSizeFactor) - if src != "" { - return AvatarHTML(src, size, class, t.DisplayName()) - } - case *repo_model.Collaborator: - src := t.AvatarLinkWithSize(ctx, size*setting.Avatar.RenderedSizeFactor) - if src != "" { - return AvatarHTML(src, size, class, t.DisplayName()) - } - case *organization.Organization: - src := t.AsUser().AvatarLinkWithSize(ctx, size*setting.Avatar.RenderedSizeFactor) - if src != "" { - return AvatarHTML(src, size, class, t.AsUser().DisplayName()) - } - } - - return template.HTML("") -} - -// AvatarByAction renders user avatars from action. args: action, size (int), class (string) -func AvatarByAction(ctx context.Context, action *activities_model.Action, others ...interface{}) template.HTML { - action.LoadActUser(ctx) - return Avatar(ctx, action.ActUser, others...) -} - -// RepoAvatar renders repo avatars. args: repo, size(int), class (string) -func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTML { - size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...) - - src := repo.RelAvatarLink() - if src != "" { - return AvatarHTML(src, size, class, repo.FullName()) - } - return template.HTML("") -} - -// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string) -func AvatarByEmail(ctx context.Context, email, name string, others ...interface{}) template.HTML { - size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...) - src := avatars.GenerateEmailAvatarFastLink(ctx, email, size*setting.Avatar.RenderedSizeFactor) - - if src != "" { - return AvatarHTML(src, size, class, name) - } - - return template.HTML("") } // Safe render raw as HTML @@ -414,342 +206,6 @@ func DotEscape(raw string) string { return strings.ReplaceAll(raw, ".", "\u200d.\u200d") } -// RenderCommitMessage renders commit message with XSS-safe and special links. -func RenderCommitMessage(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { - return RenderCommitMessageLink(ctx, msg, urlPrefix, "", metas) -} - -// RenderCommitMessageLink renders commit message as a XXS-safe link to the provided -// default url, handling for special links. -func RenderCommitMessageLink(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { - cleanMsg := template.HTMLEscapeString(msg) - // we can safely assume that it will not return any error, since there - // shouldn't be any special HTML. - fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: urlPrefix, - DefaultLink: urlDefault, - Metas: metas, - }, cleanMsg) - if err != nil { - log.Error("RenderCommitMessage: %v", err) - return "" - } - msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n") - if len(msgLines) == 0 { - return template.HTML("") - } - return template.HTML(msgLines[0]) -} - -// RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to -// the provided default url, handling for special links without email to links. -func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { - msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace) - lineEnd := strings.IndexByte(msgLine, '\n') - if lineEnd > 0 { - msgLine = msgLine[:lineEnd] - } - msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace) - if len(msgLine) == 0 { - return template.HTML("") - } - - // we can safely assume that it will not return any error, since there - // shouldn't be any special HTML. - renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: urlPrefix, - DefaultLink: urlDefault, - Metas: metas, - }, template.HTMLEscapeString(msgLine)) - if err != nil { - log.Error("RenderCommitMessageSubject: %v", err) - return template.HTML("") - } - return template.HTML(renderedMessage) -} - -// RenderCommitBody extracts the body of a commit message without its title. -func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { - msgLine := strings.TrimRightFunc(msg, unicode.IsSpace) - lineEnd := strings.IndexByte(msgLine, '\n') - if lineEnd > 0 { - msgLine = msgLine[lineEnd+1:] - } else { - return template.HTML("") - } - msgLine = strings.TrimLeftFunc(msgLine, unicode.IsSpace) - if len(msgLine) == 0 { - return template.HTML("") - } - - renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: urlPrefix, - Metas: metas, - }, template.HTMLEscapeString(msgLine)) - if err != nil { - log.Error("RenderCommitMessage: %v", err) - return "" - } - return template.HTML(renderedMessage) -} - -// Match text that is between back ticks. -var codeMatcher = regexp.MustCompile("`([^`]+)`") - -// RenderCodeBlock renders "`…`" as highlighted "" block. -// Intended for issue and PR titles, these containers should have styles for "" elements -func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML { - htmlWithCodeTags := codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), "$1") // replace with HTML tags - return template.HTML(htmlWithCodeTags) -} - -// RenderIssueTitle renders issue/pull title with defined post processors -func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[string]string) template.HTML { - renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: urlPrefix, - Metas: metas, - }, template.HTMLEscapeString(text)) - if err != nil { - log.Error("RenderIssueTitle: %v", err) - return template.HTML("") - } - return template.HTML(renderedText) -} - -// RenderLabel renders a label -func RenderLabel(ctx context.Context, label *issues_model.Label) string { - labelScope := label.ExclusiveScope() - - textColor := "#111" - if label.UseLightTextColor() { - textColor = "#eee" - } - - description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) - - if labelScope == "" { - // Regular label - return fmt.Sprintf("
%s
", - textColor, label.Color, description, RenderEmoji(ctx, label.Name)) - } - - // Scoped label - scopeText := RenderEmoji(ctx, labelScope) - itemText := RenderEmoji(ctx, label.Name[len(labelScope)+1:]) - - itemColor := label.Color - scopeColor := label.Color - if r, g, b, err := label.ColorRGB(); err == nil { - // Make scope and item background colors slightly darker and lighter respectively. - // More contrast needed with higher luminance, empirically tweaked. - luminance := (0.299*r + 0.587*g + 0.114*b) / 255 - contrast := 0.01 + luminance*0.03 - // Ensure we add the same amount of contrast also near 0 and 1. - darken := contrast + math.Max(luminance+contrast-1.0, 0.0) - lighten := contrast + math.Max(contrast-luminance, 0.0) - // Compute factor to keep RGB values proportional. - darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0) - lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0) - - scopeBytes := []byte{ - uint8(math.Min(math.Round(r*darkenFactor), 255)), - uint8(math.Min(math.Round(g*darkenFactor), 255)), - uint8(math.Min(math.Round(b*darkenFactor), 255)), - } - itemBytes := []byte{ - uint8(math.Min(math.Round(r*lightenFactor), 255)), - uint8(math.Min(math.Round(g*lightenFactor), 255)), - uint8(math.Min(math.Round(b*lightenFactor), 255)), - } - - itemColor = "#" + hex.EncodeToString(itemBytes) - scopeColor = "#" + hex.EncodeToString(scopeBytes) - } - - return fmt.Sprintf(""+ - "
%s
"+ - "
%s
"+ - "
", - description, - textColor, scopeColor, scopeText, - textColor, itemColor, itemText) -} - -// RenderEmoji renders html text with emoji post processors -func RenderEmoji(ctx context.Context, text string) template.HTML { - renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx}, - template.HTMLEscapeString(text)) - if err != nil { - log.Error("RenderEmoji: %v", err) - return template.HTML("") - } - return template.HTML(renderedText) -} - -// ReactionToEmoji renders emoji for use in reactions -func ReactionToEmoji(reaction string) template.HTML { - val := emoji.FromCode(reaction) - if val != nil { - return template.HTML(val.Emoji) - } - val = emoji.FromAlias(reaction) - if val != nil { - return template.HTML(val.Emoji) - } - return template.HTML(fmt.Sprintf(`:%s:`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction))) -} - -// RenderNote renders the contents of a git-notes file as a commit message. -func RenderNote(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { - cleanMsg := template.HTMLEscapeString(msg) - fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: urlPrefix, - Metas: metas, - }, cleanMsg) - if err != nil { - log.Error("RenderNote: %v", err) - return "" - } - return template.HTML(fullMessage) -} - -// IsMultilineCommitMessage checks to see if a commit message contains multiple lines. -func IsMultilineCommitMessage(msg string) bool { - return strings.Count(strings.TrimSpace(msg), "\n") >= 1 -} - -// Actioner describes an action -type Actioner interface { - GetOpType() activities_model.ActionType - GetActUserName() string - GetRepoUserName() string - GetRepoName() string - GetRepoPath() string - GetRepoLink() string - GetBranch() string - GetContent() string - GetCreate() time.Time - GetIssueInfos() []string -} - -// ActionIcon accepts an action operation type and returns an icon class name. -func ActionIcon(opType activities_model.ActionType) string { - switch opType { - case activities_model.ActionCreateRepo, activities_model.ActionTransferRepo, activities_model.ActionRenameRepo: - return "repo" - case activities_model.ActionCommitRepo, activities_model.ActionPushTag, activities_model.ActionDeleteTag, activities_model.ActionDeleteBranch: - return "git-commit" - case activities_model.ActionCreateIssue: - return "issue-opened" - case activities_model.ActionCreatePullRequest: - return "git-pull-request" - case activities_model.ActionCommentIssue, activities_model.ActionCommentPull: - return "comment-discussion" - case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest: - return "git-merge" - case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest: - return "issue-closed" - case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: - return "issue-reopened" - case activities_model.ActionMirrorSyncPush, activities_model.ActionMirrorSyncCreate, activities_model.ActionMirrorSyncDelete: - return "mirror" - case activities_model.ActionApprovePullRequest: - return "check" - case activities_model.ActionRejectPullRequest: - return "diff" - case activities_model.ActionPublishRelease: - return "tag" - case activities_model.ActionPullReviewDismissed: - return "x" - default: - return "question" - } -} - -// ActionContent2Commits converts action content to push commits -func ActionContent2Commits(act Actioner) *repository.PushCommits { - push := repository.NewPushCommits() - - if act == nil || act.GetContent() == "" { - return push - } - - if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil { - log.Error("json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err) - } - - if push.Len == 0 { - push.Len = len(push.Commits) - } - - return push -} - -// DiffLineTypeToStr returns diff line type name -func DiffLineTypeToStr(diffType int) string { - switch diffType { - case 2: - return "add" - case 3: - return "del" - case 4: - return "tag" - } - return "same" -} - -// MigrationIcon returns a SVG name matching the service an issue/comment was migrated from -func MigrationIcon(hostname string) string { - switch hostname { - case "github.com": - return "octicon-mark-github" - default: - return "gitea-git" - } -} - -type remoteAddress struct { - Address string - Username string - Password string -} - -func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string, ignoreOriginalURL bool) remoteAddress { - a := remoteAddress{} - - remoteURL := m.OriginalURL - if ignoreOriginalURL || remoteURL == "" { - var err error - remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName) - if err != nil { - log.Error("GetRemoteURL %v", err) - return a - } - } - - u, err := giturl.Parse(remoteURL) - if err != nil { - log.Error("giturl.Parse %v", err) - return a - } - - if u.Scheme != "ssh" && u.Scheme != "file" { - if u.User != nil { - a.Username = u.User.Username() - a.Password, _ = u.User.Password() - } - u.User = nil - } - a.Address = u.String() - - return a -} - // Eval the expression and return the result, see the comment of eval.Expr for details. // To use this helper function in templates, pass each token as a separate parameter. // diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go index 2cecef5f84..d60be88727 100644 --- a/modules/templates/htmlrenderer.go +++ b/modules/templates/htmlrenderer.go @@ -6,7 +6,6 @@ package templates import ( "bufio" "bytes" - "context" "errors" "fmt" "io" @@ -15,24 +14,29 @@ import ( "regexp" "strconv" "strings" + "sync" "sync/atomic" texttemplate "text/template" "code.gitea.io/gitea/modules/assetfs" + "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates/scopedtmpl" "code.gitea.io/gitea/modules/util" ) -var rendererKey interface{} = "templatesHtmlRenderer" - type TemplateExecutor scopedtmpl.TemplateExecutor type HTMLRender struct { templates atomic.Pointer[scopedtmpl.ScopedTemplate] } +var ( + htmlRender *HTMLRender + htmlRenderOnce sync.Once +) + var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors") func (h *HTMLRender) HTML(w io.Writer, status int, name string, data interface{}) error { @@ -55,14 +59,14 @@ func (h *HTMLRender) TemplateLookup(name string) (TemplateExecutor, error) { return nil, ErrTemplateNotInitialized } - return tmpls.Executor(name, NewFuncMap()[0]) + return tmpls.Executor(name, NewFuncMap()) } func (h *HTMLRender) CompileTemplates() error { assets := AssetFS() extSuffix := ".tmpl" tmpls := scopedtmpl.NewScopedTemplate() - tmpls.Funcs(NewFuncMap()[0]) + tmpls.Funcs(NewFuncMap()) files, err := ListWebTemplateAssetNames(assets) if err != nil { return nil @@ -86,20 +90,21 @@ func (h *HTMLRender) CompileTemplates() error { return nil } -// HTMLRenderer returns the current html renderer for the context or creates and stores one within the context for future use -func HTMLRenderer(ctx context.Context) (context.Context, *HTMLRender) { - if renderer, ok := ctx.Value(rendererKey).(*HTMLRender); ok { - return ctx, renderer - } +// HTMLRenderer init once and returns the globally shared html renderer +func HTMLRenderer() *HTMLRender { + htmlRenderOnce.Do(initHTMLRenderer) + return htmlRender +} +func initHTMLRenderer() { rendererType := "static" if !setting.IsProd { rendererType = "auto-reloading" } - log.Log(1, log.DEBUG, "Creating "+rendererType+" HTML Renderer") + log.Debug("Creating %s HTML Renderer", rendererType) - renderer := &HTMLRender{} - if err := renderer.CompileTemplates(); err != nil { + htmlRender = &HTMLRender{} + if err := htmlRender.CompileTemplates(); err != nil { p := &templateErrorPrettier{assets: AssetFS()} wrapFatal(p.handleFuncNotDefinedError(err)) wrapFatal(p.handleUnexpectedOperandError(err)) @@ -107,14 +112,14 @@ func HTMLRenderer(ctx context.Context) (context.Context, *HTMLRender) { wrapFatal(p.handleGenericTemplateError(err)) log.Fatal("HTMLRenderer CompileTemplates error: %v", err) } + if !setting.IsProd { - go AssetFS().WatchLocalChanges(ctx, func() { - if err := renderer.CompileTemplates(); err != nil { + go AssetFS().WatchLocalChanges(graceful.GetManager().ShutdownContext(), func() { + if err := htmlRender.CompileTemplates(); err != nil { log.Error("Template error: %v\n%s", err, log.Stack(2)) } }) } - return context.WithValue(ctx, rendererKey, renderer), renderer } func wrapFatal(msg string) { diff --git a/modules/templates/mailer.go b/modules/templates/mailer.go index 280ac0e587..ac1715fcd0 100644 --- a/modules/templates/mailer.go +++ b/modules/templates/mailer.go @@ -6,6 +6,7 @@ package templates import ( "context" "html/template" + "regexp" "strings" texttmpl "text/template" @@ -14,6 +15,8 @@ import ( "code.gitea.io/gitea/modules/setting" ) +var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}\s*$`) + // mailSubjectTextFuncMap returns functions for injecting to text templates, it's only used for mail subject func mailSubjectTextFuncMap() texttmpl.FuncMap { return texttmpl.FuncMap{ @@ -55,9 +58,7 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) { bodyTemplates := template.New("") subjectTemplates.Funcs(mailSubjectTextFuncMap()) - for _, funcs := range NewFuncMap() { - bodyTemplates.Funcs(funcs) - } + bodyTemplates.Funcs(NewFuncMap()) assetFS := AssetFS() refreshTemplates := func() { diff --git a/modules/templates/util_avatar.go b/modules/templates/util_avatar.go new file mode 100644 index 0000000000..3badc97cb9 --- /dev/null +++ b/modules/templates/util_avatar.go @@ -0,0 +1,84 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "context" + "fmt" + "html" + "html/template" + + activities_model "code.gitea.io/gitea/models/activities" + "code.gitea.io/gitea/models/avatars" + "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + gitea_html "code.gitea.io/gitea/modules/html" + "code.gitea.io/gitea/modules/setting" +) + +// AvatarHTML creates the HTML for an avatar +func AvatarHTML(src string, size int, class, name string) template.HTML { + sizeStr := fmt.Sprintf(`%d`, size) + + if name == "" { + name = "avatar" + } + + return template.HTML(``) +} + +// Avatar renders user avatars. args: user, size (int), class (string) +func Avatar(ctx context.Context, item interface{}, others ...interface{}) template.HTML { + size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...) + + switch t := item.(type) { + case *user_model.User: + src := t.AvatarLinkWithSize(ctx, size*setting.Avatar.RenderedSizeFactor) + if src != "" { + return AvatarHTML(src, size, class, t.DisplayName()) + } + case *repo_model.Collaborator: + src := t.AvatarLinkWithSize(ctx, size*setting.Avatar.RenderedSizeFactor) + if src != "" { + return AvatarHTML(src, size, class, t.DisplayName()) + } + case *organization.Organization: + src := t.AsUser().AvatarLinkWithSize(ctx, size*setting.Avatar.RenderedSizeFactor) + if src != "" { + return AvatarHTML(src, size, class, t.AsUser().DisplayName()) + } + } + + return template.HTML("") +} + +// AvatarByAction renders user avatars from action. args: action, size (int), class (string) +func AvatarByAction(ctx context.Context, action *activities_model.Action, others ...interface{}) template.HTML { + action.LoadActUser(ctx) + return Avatar(ctx, action.ActUser, others...) +} + +// RepoAvatar renders repo avatars. args: repo, size(int), class (string) +func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTML { + size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...) + + src := repo.RelAvatarLink() + if src != "" { + return AvatarHTML(src, size, class, repo.FullName()) + } + return template.HTML("") +} + +// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string) +func AvatarByEmail(ctx context.Context, email, name string, others ...interface{}) template.HTML { + size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...) + src := avatars.GenerateEmailAvatarFastLink(ctx, email, size*setting.Avatar.RenderedSizeFactor) + + if src != "" { + return AvatarHTML(src, size, class, name) + } + + return template.HTML("") +} diff --git a/modules/templates/util_json.go b/modules/templates/util_json.go new file mode 100644 index 0000000000..71a4e23d36 --- /dev/null +++ b/modules/templates/util_json.go @@ -0,0 +1,35 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "bytes" + + "code.gitea.io/gitea/modules/json" +) + +type JsonUtils struct{} //nolint:revive + +var jsonUtils = JsonUtils{} + +func NewJsonUtils() *JsonUtils { //nolint:revive + return &jsonUtils +} + +func (su *JsonUtils) EncodeToString(v any) string { + out, err := json.Marshal(v) + if err != nil { + return "" + } + return string(out) +} + +func (su *JsonUtils) PrettyIndent(s string) string { + var out bytes.Buffer + err := json.Indent(&out, []byte(s), "", " ") + if err != nil { + return "" + } + return out.String() +} diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go new file mode 100644 index 0000000000..599a0942ce --- /dev/null +++ b/modules/templates/util_misc.go @@ -0,0 +1,209 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "context" + "fmt" + "html/template" + "mime" + "path/filepath" + "strings" + "time" + + activities_model "code.gitea.io/gitea/models/activities" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/git" + giturl "code.gitea.io/gitea/modules/git/url" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/svg" + + "github.com/editorconfig/editorconfig-core-go/v2" +) + +func SortArrow(normSort, revSort, urlSort string, isDefault bool) template.HTML { + // if needed + if len(normSort) == 0 || len(urlSort) == 0 { + return "" + } + + if len(urlSort) == 0 && isDefault { + // if sort is sorted as default add arrow tho this table header + if isDefault { + return svg.RenderHTML("octicon-triangle-down", 16) + } + } else { + // if sort arg is in url test if it correlates with column header sort arguments + // the direction of the arrow should indicate the "current sort order", up means ASC(normal), down means DESC(rev) + if urlSort == normSort { + // the table is sorted with this header normal + return svg.RenderHTML("octicon-triangle-up", 16) + } else if urlSort == revSort { + // the table is sorted with this header reverse + return svg.RenderHTML("octicon-triangle-down", 16) + } + } + // the table is NOT sorted with this header + return "" +} + +// IsMultilineCommitMessage checks to see if a commit message contains multiple lines. +func IsMultilineCommitMessage(msg string) bool { + return strings.Count(strings.TrimSpace(msg), "\n") >= 1 +} + +// Actioner describes an action +type Actioner interface { + GetOpType() activities_model.ActionType + GetActUserName() string + GetRepoUserName() string + GetRepoName() string + GetRepoPath() string + GetRepoLink() string + GetBranch() string + GetContent() string + GetCreate() time.Time + GetIssueInfos() []string +} + +// ActionIcon accepts an action operation type and returns an icon class name. +func ActionIcon(opType activities_model.ActionType) string { + switch opType { + case activities_model.ActionCreateRepo, activities_model.ActionTransferRepo, activities_model.ActionRenameRepo: + return "repo" + case activities_model.ActionCommitRepo, activities_model.ActionPushTag, activities_model.ActionDeleteTag, activities_model.ActionDeleteBranch: + return "git-commit" + case activities_model.ActionCreateIssue: + return "issue-opened" + case activities_model.ActionCreatePullRequest: + return "git-pull-request" + case activities_model.ActionCommentIssue, activities_model.ActionCommentPull: + return "comment-discussion" + case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest: + return "git-merge" + case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest: + return "issue-closed" + case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest: + return "issue-reopened" + case activities_model.ActionMirrorSyncPush, activities_model.ActionMirrorSyncCreate, activities_model.ActionMirrorSyncDelete: + return "mirror" + case activities_model.ActionApprovePullRequest: + return "check" + case activities_model.ActionRejectPullRequest: + return "diff" + case activities_model.ActionPublishRelease: + return "tag" + case activities_model.ActionPullReviewDismissed: + return "x" + default: + return "question" + } +} + +// ActionContent2Commits converts action content to push commits +func ActionContent2Commits(act Actioner) *repository.PushCommits { + push := repository.NewPushCommits() + + if act == nil || act.GetContent() == "" { + return push + } + + if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil { + log.Error("json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err) + } + + if push.Len == 0 { + push.Len = len(push.Commits) + } + + return push +} + +// DiffLineTypeToStr returns diff line type name +func DiffLineTypeToStr(diffType int) string { + switch diffType { + case 2: + return "add" + case 3: + return "del" + case 4: + return "tag" + } + return "same" +} + +// MigrationIcon returns a SVG name matching the service an issue/comment was migrated from +func MigrationIcon(hostname string) string { + switch hostname { + case "github.com": + return "octicon-mark-github" + default: + return "gitea-git" + } +} + +type remoteAddress struct { + Address string + Username string + Password string +} + +func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string, ignoreOriginalURL bool) remoteAddress { + a := remoteAddress{} + + remoteURL := m.OriginalURL + if ignoreOriginalURL || remoteURL == "" { + var err error + remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName) + if err != nil { + log.Error("GetRemoteURL %v", err) + return a + } + } + + u, err := giturl.Parse(remoteURL) + if err != nil { + log.Error("giturl.Parse %v", err) + return a + } + + if u.Scheme != "ssh" && u.Scheme != "file" { + if u.User != nil { + a.Username = u.User.Username() + a.Password, _ = u.User.Password() + } + u.User = nil + } + a.Address = u.String() + + return a +} + +func FilenameIsImage(filename string) bool { + mimeType := mime.TypeByExtension(filepath.Ext(filename)) + return strings.HasPrefix(mimeType, "image/") +} + +func TabSizeClass(ec interface{}, filename string) string { + var ( + value *editorconfig.Editorconfig + ok bool + ) + if ec != nil { + if value, ok = ec.(*editorconfig.Editorconfig); !ok || value == nil { + return "tab-size-8" + } + def, err := value.GetDefinitionForFilename(filename) + if err != nil { + log.Error("tab size class: getting definition for filename: %v", err) + return "tab-size-8" + } + if def.TabWidth > 0 { + return fmt.Sprintf("tab-size-%d", def.TabWidth) + } + } + return "tab-size-8" +} diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go new file mode 100644 index 0000000000..a59ddd3f17 --- /dev/null +++ b/modules/templates/util_render.go @@ -0,0 +1,254 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "context" + "encoding/hex" + "fmt" + "html/template" + "math" + "net/url" + "regexp" + "strings" + "unicode" + + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/emoji" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" + "code.gitea.io/gitea/modules/setting" +) + +// RenderCommitMessage renders commit message with XSS-safe and special links. +func RenderCommitMessage(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { + return RenderCommitMessageLink(ctx, msg, urlPrefix, "", metas) +} + +// RenderCommitMessageLink renders commit message as a XXS-safe link to the provided +// default url, handling for special links. +func RenderCommitMessageLink(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { + cleanMsg := template.HTMLEscapeString(msg) + // we can safely assume that it will not return any error, since there + // shouldn't be any special HTML. + fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + DefaultLink: urlDefault, + Metas: metas, + }, cleanMsg) + if err != nil { + log.Error("RenderCommitMessage: %v", err) + return "" + } + msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n") + if len(msgLines) == 0 { + return template.HTML("") + } + return template.HTML(msgLines[0]) +} + +// RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to +// the provided default url, handling for special links without email to links. +func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML { + msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace) + lineEnd := strings.IndexByte(msgLine, '\n') + if lineEnd > 0 { + msgLine = msgLine[:lineEnd] + } + msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace) + if len(msgLine) == 0 { + return template.HTML("") + } + + // we can safely assume that it will not return any error, since there + // shouldn't be any special HTML. + renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + DefaultLink: urlDefault, + Metas: metas, + }, template.HTMLEscapeString(msgLine)) + if err != nil { + log.Error("RenderCommitMessageSubject: %v", err) + return template.HTML("") + } + return template.HTML(renderedMessage) +} + +// RenderCommitBody extracts the body of a commit message without its title. +func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { + msgLine := strings.TrimRightFunc(msg, unicode.IsSpace) + lineEnd := strings.IndexByte(msgLine, '\n') + if lineEnd > 0 { + msgLine = msgLine[lineEnd+1:] + } else { + return template.HTML("") + } + msgLine = strings.TrimLeftFunc(msgLine, unicode.IsSpace) + if len(msgLine) == 0 { + return template.HTML("") + } + + renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + Metas: metas, + }, template.HTMLEscapeString(msgLine)) + if err != nil { + log.Error("RenderCommitMessage: %v", err) + return "" + } + return template.HTML(renderedMessage) +} + +// Match text that is between back ticks. +var codeMatcher = regexp.MustCompile("`([^`]+)`") + +// RenderCodeBlock renders "`…`" as highlighted "" block. +// Intended for issue and PR titles, these containers should have styles for "" elements +func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML { + htmlWithCodeTags := codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), "$1") // replace with HTML tags + return template.HTML(htmlWithCodeTags) +} + +// RenderIssueTitle renders issue/pull title with defined post processors +func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[string]string) template.HTML { + renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + Metas: metas, + }, template.HTMLEscapeString(text)) + if err != nil { + log.Error("RenderIssueTitle: %v", err) + return template.HTML("") + } + return template.HTML(renderedText) +} + +// RenderLabel renders a label +func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML { + labelScope := label.ExclusiveScope() + + textColor := "#111" + if label.UseLightTextColor() { + textColor = "#eee" + } + + description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description)) + + if labelScope == "" { + // Regular label + s := fmt.Sprintf("
%s
", + textColor, label.Color, description, RenderEmoji(ctx, label.Name)) + return template.HTML(s) + } + + // Scoped label + scopeText := RenderEmoji(ctx, labelScope) + itemText := RenderEmoji(ctx, label.Name[len(labelScope)+1:]) + + itemColor := label.Color + scopeColor := label.Color + if r, g, b, err := label.ColorRGB(); err == nil { + // Make scope and item background colors slightly darker and lighter respectively. + // More contrast needed with higher luminance, empirically tweaked. + luminance := (0.299*r + 0.587*g + 0.114*b) / 255 + contrast := 0.01 + luminance*0.03 + // Ensure we add the same amount of contrast also near 0 and 1. + darken := contrast + math.Max(luminance+contrast-1.0, 0.0) + lighten := contrast + math.Max(contrast-luminance, 0.0) + // Compute factor to keep RGB values proportional. + darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0) + lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0) + + scopeBytes := []byte{ + uint8(math.Min(math.Round(r*darkenFactor), 255)), + uint8(math.Min(math.Round(g*darkenFactor), 255)), + uint8(math.Min(math.Round(b*darkenFactor), 255)), + } + itemBytes := []byte{ + uint8(math.Min(math.Round(r*lightenFactor), 255)), + uint8(math.Min(math.Round(g*lightenFactor), 255)), + uint8(math.Min(math.Round(b*lightenFactor), 255)), + } + + itemColor = "#" + hex.EncodeToString(itemBytes) + scopeColor = "#" + hex.EncodeToString(scopeBytes) + } + + s := fmt.Sprintf(""+ + "
%s
"+ + "
%s
"+ + "
", + description, + textColor, scopeColor, scopeText, + textColor, itemColor, itemText) + return template.HTML(s) +} + +// RenderEmoji renders html text with emoji post processors +func RenderEmoji(ctx context.Context, text string) template.HTML { + renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx}, + template.HTMLEscapeString(text)) + if err != nil { + log.Error("RenderEmoji: %v", err) + return template.HTML("") + } + return template.HTML(renderedText) +} + +// ReactionToEmoji renders emoji for use in reactions +func ReactionToEmoji(reaction string) template.HTML { + val := emoji.FromCode(reaction) + if val != nil { + return template.HTML(val.Emoji) + } + val = emoji.FromAlias(reaction) + if val != nil { + return template.HTML(val.Emoji) + } + return template.HTML(fmt.Sprintf(`:%s:`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction))) +} + +// RenderNote renders the contents of a git-notes file as a commit message. +func RenderNote(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML { + cleanMsg := template.HTMLEscapeString(msg) + fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: urlPrefix, + Metas: metas, + }, cleanMsg) + if err != nil { + log.Error("RenderNote: %v", err) + return "" + } + return template.HTML(fullMessage) +} + +func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //nolint:revive + output, err := markdown.RenderString(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: setting.AppSubURL, + }, input) + if err != nil { + log.Error("RenderString: %v", err) + } + return template.HTML(output) +} + +func RenderLabels(ctx context.Context, labels []*issues_model.Label, repoLink string) template.HTML { + htmlCode := `` + for _, label := range labels { + // Protect against nil value in labels - shouldn't happen but would cause a panic if so + if label == nil { + continue + } + htmlCode += fmt.Sprintf("%s ", + repoLink, label.ID, RenderLabel(ctx, label)) + } + htmlCode += "" + return template.HTML(htmlCode) +} diff --git a/modules/templates/util_string.go b/modules/templates/util_string.go index e86bbe9e70..459380aee5 100644 --- a/modules/templates/util_string.go +++ b/modules/templates/util_string.go @@ -3,12 +3,18 @@ package templates -import "strings" +import ( + "strings" + + "code.gitea.io/gitea/modules/base" +) type StringUtils struct{} +var stringUtils = StringUtils{} + func NewStringUtils() *StringUtils { - return &StringUtils{} + return &stringUtils } func (su *StringUtils) HasPrefix(s, prefix string) bool { @@ -18,3 +24,15 @@ func (su *StringUtils) HasPrefix(s, prefix string) bool { func (su *StringUtils) Contains(s, substr string) bool { return strings.Contains(s, substr) } + +func (su *StringUtils) Split(s, sep string) []string { + return strings.Split(s, sep) +} + +func (su *StringUtils) Join(a []string, sep string) string { + return strings.Join(a, sep) +} + +func (su *StringUtils) EllipsisString(s string, max int) string { + return base.EllipsisString(s, max) +} diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go index 80b96fa656..83170b374b 100644 --- a/modules/timeutil/datetime.go +++ b/modules/timeutil/datetime.go @@ -30,13 +30,13 @@ func DateTime(format string, datetime any) template.HTML { var datetimeEscaped, textEscaped string switch v := datetime.(type) { case nil: - return "N/A" + return "-" case string: datetimeEscaped = html.EscapeString(v) textEscaped = datetimeEscaped case time.Time: if v.IsZero() || v.Unix() == 0 { - return "N/A" + return "-" } datetimeEscaped = html.EscapeString(v.Format(time.RFC3339)) if format == "full" { diff --git a/modules/timeutil/datetime_test.go b/modules/timeutil/datetime_test.go index a5639b34f1..f44b7aaae3 100644 --- a/modules/timeutil/datetime_test.go +++ b/modules/timeutil/datetime_test.go @@ -23,10 +23,10 @@ func TestDateTime(t *testing.T) { refTime, _ := time.Parse(time.RFC3339, refTimeStr) refTimeStamp := TimeStamp(refTime.Unix()) - assert.EqualValues(t, "N/A", DateTime("short", nil)) - assert.EqualValues(t, "N/A", DateTime("short", 0)) - assert.EqualValues(t, "N/A", DateTime("short", time.Time{})) - assert.EqualValues(t, "N/A", DateTime("short", TimeStamp(0))) + assert.EqualValues(t, "-", DateTime("short", nil)) + assert.EqualValues(t, "-", DateTime("short", 0)) + assert.EqualValues(t, "-", DateTime("short", time.Time{})) + assert.EqualValues(t, "-", DateTime("short", TimeStamp(0))) actual := DateTime("short", "invalid") assert.EqualValues(t, `invalid`, actual) diff --git a/modules/timeutil/language.go b/modules/timeutil/language.go deleted file mode 100644 index c2f7a0e579..0000000000 --- a/modules/timeutil/language.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package timeutil - -import ( - "time" - - "code.gitea.io/gitea/modules/setting" -) - -var langTimeFormats = map[string]string{ - "zh-CN": "2006年01月02日 15时04分05秒", - "en-US": time.RFC1123, - "lv-LV": "02.01.2006. 15:04:05", -} - -// GetLangTimeFormat represents the default time format for the language -func GetLangTimeFormat(lang string) string { - return langTimeFormats[lang] -} - -// GetTimeFormat represents the -func GetTimeFormat(lang string) string { - if setting.TimeFormat == "" { - format := GetLangTimeFormat(lang) - if format == "" { - format = time.RFC1123 - } - return format - } - return setting.TimeFormat -} diff --git a/modules/translation/translation.go b/modules/translation/translation.go index 56cf1df2d4..49dfa84d1b 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation/i18n" + "code.gitea.io/gitea/modules/util" "golang.org/x/text/language" "golang.org/x/text/message" @@ -241,5 +242,12 @@ func (l *locale) TrN(cnt any, key1, keyN string, args ...any) string { func (l *locale) PrettyNumber(v any) string { // TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format + if s, ok := v.(string); ok { + if num, err := util.ToInt64(s); err == nil { + v = num + } else if num, err := util.ToFloat64(s); err == nil { + v = num + } + } return l.msgPrinter.Sprintf("%v", number.Decimal(v)) } diff --git a/modules/translation/translation_test.go b/modules/translation/translation_test.go index 83a40f1458..464aa32661 100644 --- a/modules/translation/translation_test.go +++ b/modules/translation/translation_test.go @@ -21,7 +21,12 @@ func TestPrettyNumber(t *testing.T) { l := NewLocale("id-ID") assert.EqualValues(t, "1.000.000", l.PrettyNumber(1000000)) + assert.EqualValues(t, "1.000.000,1", l.PrettyNumber(1000000.1)) + assert.EqualValues(t, "1.000.000", l.PrettyNumber("1000000")) + assert.EqualValues(t, "1.000.000", l.PrettyNumber("1000000.0")) + assert.EqualValues(t, "1.000.000,1", l.PrettyNumber("1000000.1")) l = NewLocale("nosuch") assert.EqualValues(t, "1,000,000", l.PrettyNumber(1000000)) + assert.EqualValues(t, "1,000,000.1", l.PrettyNumber(1000000.1)) } diff --git a/modules/util/filebuffer/file_backed_buffer.go b/modules/util/filebuffer/file_backed_buffer.go index bfddf90e92..6b07bd0413 100644 --- a/modules/util/filebuffer/file_backed_buffer.go +++ b/modules/util/filebuffer/file_backed_buffer.go @@ -7,11 +7,10 @@ import ( "bytes" "errors" "io" + "math" "os" ) -const maxInt = int(^uint(0) >> 1) // taken from bytes.Buffer - var ( // ErrInvalidMemorySize occurs if the memory size is not in a valid range ErrInvalidMemorySize = errors.New("Memory size must be greater 0 and lower math.MaxInt32") @@ -37,7 +36,7 @@ type FileBackedBuffer struct { // New creates a file backed buffer with a specific maximum memory size func New(maxMemorySize int) (*FileBackedBuffer, error) { - if maxMemorySize < 0 || maxMemorySize > maxInt { + if maxMemorySize < 0 || maxMemorySize > math.MaxInt32 { return nil, ErrInvalidMemorySize } diff --git a/modules/web/handler.go b/modules/web/handler.go index 8a44673f12..bfb83820c8 100644 --- a/modules/web/handler.go +++ b/modules/web/handler.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" "reflect" - "strings" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/web/routing" @@ -131,16 +130,22 @@ func hasResponseBeenWritten(argsIn []reflect.Value) bool { // toHandlerProvider converts a handler to a handler provider // A handler provider is a function that takes a "next" http.Handler, it can be used as a middleware func toHandlerProvider(handler any) func(next http.Handler) http.Handler { - if hp, ok := handler.(func(next http.Handler) http.Handler); ok { - return hp - } - funcInfo := routing.GetFuncInfo(handler) fn := reflect.ValueOf(handler) if fn.Type().Kind() != reflect.Func { panic(fmt.Sprintf("handler must be a function, but got %s", fn.Type())) } + if hp, ok := handler.(func(next http.Handler) http.Handler); ok { + return func(next http.Handler) http.Handler { + h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + routing.UpdateFuncInfo(req.Context(), funcInfo) + h.ServeHTTP(resp, req) + }) + } + } + provider := func(next http.Handler) http.Handler { return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) { // wrap the response writer to check whether the response has been written @@ -175,26 +180,3 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler { provider(nil).ServeHTTP(nil, nil) // do a pre-check to make sure all arguments and return values are supported return provider } - -// MiddlewareWithPrefix wraps a handler function at a prefix, and make it as a middleware -// TODO: this design is incorrect, the asset handler should not be a middleware -func MiddlewareWithPrefix(pathPrefix string, middleware func(handler http.Handler) http.Handler, handlerFunc http.HandlerFunc) func(next http.Handler) http.Handler { - funcInfo := routing.GetFuncInfo(handlerFunc) - handler := http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { - routing.UpdateFuncInfo(req.Context(), funcInfo) - handlerFunc(resp, req) - }) - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { - if !strings.HasPrefix(req.URL.Path, pathPrefix) { - next.ServeHTTP(resp, req) - return - } - if middleware != nil { - middleware(handler).ServeHTTP(resp, req) - } else { - handler.ServeHTTP(resp, req) - } - }) - } -} diff --git a/modules/web/route.go b/modules/web/route.go index fe35880849..6fd60f4ca7 100644 --- a/modules/web/route.go +++ b/modules/web/route.go @@ -44,23 +44,13 @@ type Route struct { // NewRoute creates a new route func NewRoute() *Route { r := chi.NewRouter() - return &Route{ - R: r, - curGroupPrefix: "", - curMiddlewares: []interface{}{}, - } + return &Route{R: r} } // Use supports two middlewares func (r *Route) Use(middlewares ...interface{}) { - if r.curGroupPrefix != "" { - // FIXME: this behavior is incorrect, should use "With" instead - r.curMiddlewares = append(r.curMiddlewares, middlewares...) - } else { - // FIXME: another misuse, the "Use" with empty middlewares is called after "Mount" - for _, m := range middlewares { - r.R.Use(toHandlerProvider(m)) - } + for _, m := range middlewares { + r.R.Use(toHandlerProvider(m)) } } @@ -116,9 +106,7 @@ func (r *Route) Methods(method, pattern string, h []any) { // Mount attaches another Route along ./pattern/* func (r *Route) Mount(pattern string, subR *Route) { - middlewares := make([]interface{}, len(r.curMiddlewares)) - copy(middlewares, r.curMiddlewares) - subR.Use(middlewares...) + subR.Use(r.curMiddlewares...) r.R.Mount(r.getPattern(pattern), subR.R) } diff --git a/options/license/CC-BY-SA-3.0-IGO b/options/license/CC-BY-SA-3.0-IGO new file mode 100644 index 0000000000..2b8b0f8f23 --- /dev/null +++ b/options/license/CC-BY-SA-3.0-IGO @@ -0,0 +1,107 @@ +Creative Commons Attribution-ShareAlike 3.0 IGO + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. THE LICENSOR IS NOT NECESSARILY AN INTERGOVERNMENTAL ORGANIZATION (IGO), AS DEFINED IN THE LICENSE BELOW. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("LICENSE"). THE LICENSOR (DEFINED BELOW) HOLDS COPYRIGHT AND OTHER RIGHTS IN THE WORK. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION FOR YOUR ACCEPTANCE AND AGREEMENT TO THE TERMS OF THE LICENSE. + +1. Definitions + + a. "IGO" means, solely and exclusively for purposes of this License, an organization established by a treaty or other instrument governed by international law and possessing its own international legal personality. Other organizations established to carry out activities across national borders and that accordingly enjoy immunity from legal process are also IGOs for the sole and exclusive purposes of this License. IGOs may include as members, in addition to states, other entities. + + b. "Work" means the literary and/or artistic work eligible for copyright protection, whatever may be the mode or form of its expression including digital form, and offered under the terms of this License. It is understood that a database, which by reason of the selection and arrangement of its contents constitutes an intellectual creation, is considered a Work. + + c. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License and may be, but is not necessarily, an IGO. + + d. "You" means an individual or entity exercising rights under this License. + + e. "License Elements" means the following high-level license attributes as selected by the Licensor and indicated in the title of this License: Attribution, ShareAlike. + + f. "Reproduce" means to make a copy of the Work in any manner or form, and by any means. + + g. "Distribute" means the activity of making publicly available the Work or Adaptation (or copies of the Work or Adaptation), as applicable, by sale, rental, public lending or any other known form of transfer of ownership or possession of the Work or copy of the Work. + + h. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. + + i. "Adaptation" means a work derived from or based upon the Work, or upon the Work and other pre-existing works. Adaptations may include works such as translations, derivative works, or any alterations and arrangements of any kind involving the Work. For purposes of this License, where the Work is a musical work, performance, or phonogram, the synchronization of the Work in timed-relation with a moving image is an Adaptation. For the avoidance of doubt, including the Work in a Collection is not an Adaptation. + + j. "Collection" means a collection of literary or artistic works or other works or subject matter other than works listed in Section 1(b) which by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. For the avoidance of doubt, a Collection will not be considered as an Adaptation. + + k. "Creative Commons Compatible License" means a license that is listed at https://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. + +2. Scope of this License. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright protection. + +3. License Grant. Subject to the terms and conditions of this License, the Licensor hereby grants You a worldwide, royalty-free, non-exclusive license to exercise the rights in the Work as follows: + + a. to Reproduce, Distribute and Publicly Perform the Work, to incorporate the Work into one or more Collections, and to Reproduce, Distribute and Publicly Perform the Work as incorporated in the Collections; and, + + b. to create, Reproduce, Distribute and Publicly Perform Adaptations, provided that You clearly label, demarcate or otherwise identify that changes were made to the original Work. + + c. For the avoidance of doubt: + + i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; + + ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, + + ii. Voluntary License Schemes. To the extent possible, the Licensor waives the right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary licensing scheme. + +This License lasts for the duration of the term of the copyright in the Work licensed by the Licensor. The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by the Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: + + a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work (see section 8(a)). You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from a Licensor You must, to the extent practicable, remove from the Collection any credit (inclusive of any logo, trademark, official mark or official emblem) as required by Section 4(c), as requested. If You create an Adaptation, upon notice from a Licensor You must, to the extent practicable, remove from the Adaptation any credit (inclusive of any logo, trademark, official mark or official emblem) as required by Section 4(c), as requested. + + b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) either the unported Creative Commons license or a ported Creative Commons license (either this or a later license version) containing the same License Elements; or (iv) a Creative Commons Compatible License. If You license the Adaptation under one of the licenses mentioned in (iv), You must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), You must comply with terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform. (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License. (III) You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform. (IV) When You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. + + c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) any attributions that the Licensor indicates be associated with the Work as indicated in a copyright notice, (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that the Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and, (iv) consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation. The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of an Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributors to the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Licensor or others designated for attribution, of You or Your use of the Work, without the separate, express prior written permission of the Licensor or such others. + + d. Except as otherwise agreed in writing by the Licensor, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the honor or reputation of the Licensor where moral rights apply. + +5. Representations, Warranties and Disclaimer + +THE LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. + +6. Limitation on Liability + +IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + a. Subject to the terms and conditions set forth in this License, the license granted here lasts for the duration of the term of the copyright in the Work licensed by the Licensor as stated in Section 3. Notwithstanding the above, the Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated below. + + b. If You fail to comply with this License, then this License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. Notwithstanding the foregoing, this License reinstates automatically as of the date the violation is cured, provided it is cured within 30 days of You discovering the violation, or upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 7(b) does not affect any rights the Licensor may have to seek remedies for violations of this License by You. + +8. Miscellaneous + + a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. + + b. Each time You Distribute or Publicly Perform an Adaptation, the Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. + + c. If any provision of this License is invalid or unenforceable, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + + d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the Licensor. + + e. This License constitutes the entire agreement between You and the Licensor with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. The Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. + + f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). Interpretation of the scope of the rights granted by the Licensor and the conditions imposed on You under this License, this License, and the rights and conditions set forth herein shall be made with reference to copyright as determined in accordance with general principles of international law, including the above mentioned conventions. + + g. Nothing in this License constitutes or may be interpreted as a limitation upon or waiver of any privileges and immunities that may apply to the Licensor or You, including immunity from the legal processes of any jurisdiction, national court or other authority. + + h. Where the Licensor is an IGO, any and all disputes arising under this License that cannot be settled amicably shall be resolved in accordance with the following procedure: + + i. Pursuant to a notice of mediation communicated by reasonable means by either You or the Licensor to the other, the dispute shall be submitted to non-binding mediation conducted in accordance with rules designated by the Licensor in the copyright notice published with the Work, or if none then in accordance with those communicated in the notice of mediation. The language used in the mediation proceedings shall be English unless otherwise agreed. + + ii. If any such dispute has not been settled within 45 days following the date on which the notice of mediation is provided, either You or the Licensor may, pursuant to a notice of arbitration communicated by reasonable means to the other, elect to have the dispute referred to and finally determined by arbitration. The arbitration shall be conducted in accordance with the rules designated by the Licensor in the copyright notice published with the Work, or if none then in accordance with the UNCITRAL Arbitration Rules as then in force. The arbitral tribunal shall consist of a sole arbitrator and the language of the proceedings shall be English unless otherwise agreed. The place of arbitration shall be where the Licensor has its headquarters. The arbitral proceedings shall be conducted remotely (e.g., via telephone conference or written submissions) whenever practicable. + + iii. Interpretation of this License in any dispute submitted to mediation or arbitration shall be as set forth in Section 8(f), above. + +Creative Commons Notice + +Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of the Licensor. + +Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. + +Creative Commons may be contacted at https://creativecommons.org/. diff --git a/options/license/Latex2e-translated-notice b/options/license/Latex2e-translated-notice new file mode 100644 index 0000000000..5ac100f4cb --- /dev/null +++ b/options/license/Latex2e-translated-notice @@ -0,0 +1,26 @@ +Copyright @copyright{} 1989, 1992, 1993, 1994, 1995, 1996, 2014 Free Software +Foundation, Inc. + +Copyright @copyright{} 1995, 1996 Joseph Arceneaux. + +Copyright @copyright{} 1999, Carlo Wood. + +Copyright @copyright{} 2001, David Ingamells. + +Copyright @copyright{} 2013, Łukasz Stelmach. + +Copyright @copyright{} 2015, Tim Hentenaar. + +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the entire +resulting derived work is distributed under the terms of a permission +notice identical to this one. + +Permission is granted to copy and distribute translations of this manual +into another language, under the above conditions for modified versions, +except that this permission notice may be stated in a translation approved +by the Foundation. diff --git a/options/license/MIT-Festival b/options/license/MIT-Festival new file mode 100644 index 0000000000..6ec072db0a --- /dev/null +++ b/options/license/MIT-Festival @@ -0,0 +1,22 @@ +Permission is hereby granted, free of charge, to use and distribute +this software and its documentation without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of this work, and to +permit persons to whom this work is furnished to do so, subject to +the following conditions: +1. The code must retain the above copyright notice, this list of +conditions and the following disclaimer. +2. Any modifications must be clearly marked as such. +3. Original authors' names are not deleted. +4. The authors' names are not used to endorse or promote products +derived from this software without specific prior written +permission. +THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK +DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT +SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/options/license/NIST-Software b/options/license/NIST-Software new file mode 100644 index 0000000000..0ae22d9052 --- /dev/null +++ b/options/license/NIST-Software @@ -0,0 +1,28 @@ +NIST-developed software is provided by NIST as a public service. +You may use, copy, and distribute copies of the software in any +medium, provided that you keep intact this entire notice. You may +improve, modify, and create derivative works of the software or any +portion of the software, and you may copy and distribute such +modifications or works. Modified works should carry a notice stating +that you changed the software and should note the date and nature of +any such change. Please explicitly acknowledge the National Institute +of Standards and Technology as the source of the software. + +NIST-developed software is expressly provided "AS IS." NIST MAKES NO +WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT, OR ARISING BY OPERATION +OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND DATA ACCURACY. NIST +NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE +UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES +NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR +THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, +RELIABILITY, OR USEFULNESS OF THE SOFTWARE. + +You are solely responsible for determining the appropriateness of using and +distributing the software and you assume all risks associated with its use, +including but not limited to the risks and costs of program errors, compliance +with applicable laws, damage to or loss of data, programs or equipment, and the +unavailability or interruption of operation. This software is not intended to be +used in any situation where a failure could cause risk of injury or damage to +property. The software developed by NIST employees is not subject to copyright +protection within the United States. diff --git a/options/license/OLFL-1.3 b/options/license/OLFL-1.3 new file mode 100644 index 0000000000..77ffc8dc07 --- /dev/null +++ b/options/license/OLFL-1.3 @@ -0,0 +1,220 @@ +Open Logistics Foundation License +Version 1.3, January 2023 +https://www.openlogisticsfoundation.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION AND DISTRIBUTION + +§1 Definitions + +(1) "Subject Matter of the License" shall mean the works of software components +in Source or Object form as well as any other components protected under +copyright, design and/or patent law which are made available under this License. + +(2) "License" shall mean the terms and conditions for the use, reproduction and +distribution of the Subject Matter of the License in accordance with the +provisions of this document. + +(3) "Licensor(s)" shall mean the copyright holder(s) or the entity authorized by +law or contract by the copyright holder(s) to grant the License. + +(4) "You" (or "Your") shall mean a natural or legal person exercising the +permissions granted by this License. + +(5) "Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation source, and +configuration files. + +(6) "Object" form shall mean any form resulting from mechanical transformation +or translation of a Source form, including but not limited to compiled object +code, generated documentation, and conversions to other media types. + +(7) "Derivative Works" shall mean any work, whether in Source or Object form or +any other form, that is based on (or derived from) the Subject Matter of the +License and for which the editorial revisions, annotations, elaborations, or +other modifications represent, as a whole, an original work of authorship. For +the purposes of this License, Derivative Works shall not include works that +remain separable from, or merely link (or bind by name) to the interfaces of, +the Subject Matter of the License and Derivative Works thereof. + +(8) "Contribution" shall mean any proprietary work, including the original +version of the Subject Matter of the License and any changes or additions to +such work, or Derivative Works of such work, that the rights holder, or a +natural or legal person authorized to make submissions, intentionally submits to +a Licensor to be incorporated into the Subject Matter of the License. For the +purposes of this definition, "submit" shall mean any form of electronic or +written communication which is sent to a Licensor or its representatives for the +purpose of discussing or improving the Subject Matter of the License, including +but not limited to communications sent via electronic mailing lists, source code +control systems and issue tracking systems; however, communications that are +clearly marked by the copyright holder as "not a contribution" or otherwise +identified as such in writing are excluded. + +(9) "Contributor" shall mean the Licensor(s) and/or any natural or legal person +on whose behalf the Licensor(s) receive(s) any Contribution subsequently +incorporated into the Subject Matter of the License. + +§2 Grant of usage rights + +Subject to the terms and conditions of this License and compliance with the +provisions of this License, You are hereby granted by each Contributor, insofar +as applicable to the respective Subject Matter of the License the + +- royalty-free and non-exclusive, +- sub-licensable for commercial and non-commercial purposes, +- worldwide and perpetual, +- irrevocable and non-terminable + +right to reproduce, prepare Derivative Works of, publicly display, publicly +perform, and distribute the Subject Matter of the License and such Derivative +Works in any form. This right of use includes but is not limited to the right + +- to use the Subject Matter of the License in any hardware and software + environment (with regard to the software and data components), in particular + to store or load it permanently or temporarily, to display it and run it, + including to the extent reproductions are necessary to that end, +- to otherwise modify, interpret, edit or redesign it, +- to store, reproduce, exhibit, publish, distribute it in tangible or intangible + form, on any medium or in any other way, for commercial and non-commercial + purposes, in particular to communicate it privately or publicly, including via + image, audio and other information carriers, irrespective of whether by wire + or wireless means, +- to use it in databases, data networks and online services, including the right + to make the software and data components of the Subject Matter of the License + available in Source or Object form to users of the aforementioned databases, + networks and online services for research and retrieval purposes, +- to allow third parties to use or operate it, +- to use it for own purposes but also to provide services to third parties, +- to distribute it + +in its original or modified, interpreted, edited or redesigned form. + +The foregoing right of use relates to the Subject Matter of the License, in +particular to its Source and Object form of software components (including +design rights, where applicable). + +§3 Grant of patent license + +Subject to the terms and conditions of this License and compliance with the +provisions of this License, You are hereby granted by each Contributor a +- royalty-free and non-exclusive, +- worldwide and perpetual, +- irrevocable (with the exception of the restrictions set out in this Section 3) + +patent license in all rights deriving from the patents, owned and licensable by +the Contributor at the time of the submission of the Contribution, to + +- produce, +- have produced, +- use, +- offer for sale, +- sell, +- import and otherwise transfer + +the Subject Matter of the License. + +However, said patent license shall cover only those rights deriving from the +patents of the respective Contributors which are indispensable in order not to +infringe that patent and only to the extent that the use of the Contributor’s +respective Contributions, whether alone or in combination with other +Contributions of the Contributors or any third parties together with the Subject +Matter of the License for which these Contributions were submitted, would +otherwise infringe that patent. The grant of license shall not include rights +deriving from the patents which may in future become necessary for their lawful +use due to subsequent modifications to the Subject Matter or Contributions made +by third parties after the original submission. + +In the event that You institute patent litigation against any entity or person +(including a counterclaim or countersuit in a legal action), alleging that the +Subject Matter of the License or a Contribution incorporated or contained +therein constitutes patent infringement or indirect infringement, all patent +licenses which have been granted to You under this License for the Subject +Matter of the License as well as this License itself shall be deemed terminated +as of the date on which the action is filed. + +§4 Distribution + +You may reproduce and distribute copies of the Subject Matter of the License or +Derivative Works on any medium, with or without modifications (with regard to +software components in Source or Object form), provided that You comply with +the following rules: + +- You must provide all other recipients of the Subject Matter of the License or + of Derivative Works with a copy of this License and inform them that the + Subject Matter of the License was originally licensed under this License. +- You must ensure that modified files contain prominent notices indicating that + You have modified the files. +- You must retain all copyright, patent, trademark and attribution notices in + the Subject Matter of the License in the Source form of any Derivative Works + You distribute, with the exception of those notices that do not pertain to any + part of the Derivative Works. + +You may add Your own copyright notices to Your modifications and state any +additional or different license conditions and conditions for the use, +reproduction or distribution of Your modifications or for those Derivative Works +as a whole, provided that Your use, reproduction and distribution of the work +complies with the terms and conditions set out in this License in all other +respects. + +§5 Submission of Contributions + +Unless expressly stated otherwise, every Contribution that You have +intentionally submitted for inclusion in the Subject Matter of the License is +subject to this License without any additional terms or conditions applying. +Irrespective of the above, none of the terms or conditions contained herein may +be interpreted to supersede or modify the terms or conditions of any separate +licensing agreement that You may have concluded with a Licensor for such +Contributions, such as a so-called "Contributor License Agreement" (CLA). + +§6 Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks or product names of the Licensor(s) or of a Contributor. + +§7 Limited warranty + +This License is granted free of charge and thus constitutes a gift. Accordingly, +any warranty is excluded. The Subject Matter of the License is a work in +progress; it is constantly being improved by countless Contributors. The Subject +Matter of the License is not complete and may therefore contain errors ("bugs") +or additional patents of Contributors or third parties, as is inherent in this +type of development. + +§8 Limitation of liability + +Except in cases of intentional and grossly negligent conduct, the Contributors, +their legal representatives, trustees, officers and employees shall not be +liable for direct or indirect, material or immaterial loss or damage of any kind +arising from the License or the use of the Subject Matter of the License; this +applies, among other things, but not exclusively, to loss of goodwill, loss of +production, computer failures or errors, loss of data or economic loss or +damage, even if the Contributor has been notified of the possibility of such +loss or damage. Irrespective of the above, the Licensor shall only be liable +within the scope of statutory product liability to the extent that the +respective provisions are applicable to the Subject Matter of the License or the +Contribution. + +Except in cases of intentional conduct, the Contributors, their legal +representatives, trustees, officers and employees shall not be liable for any +infringement of third-party patent or intellectual property rights arising from +the Contributions nor do they warrant that the Contributions are accurate, +devoid of mistakes, complete and/or fit for any particular purpose. + +§9 Provision of warranties or assumption of additional liability in the event of +distribution of the Subject Matter of the License + +In the event of distribution of the Subject Matter of the License or Derivative +Works, You are free to accept support, warranty, indemnity or other liability +obligations and/or rights consistent with this License and to charge a fee in +return. However, in accepting such obligations, You may act only on Your own +behalf and on Your sole responsibility, not on behalf of any other Contributor, +and You hereby agree to indemnify, defend, and hold each Contributor harmless +for any liability incurred by, or claims asserted against, such Contributor by +reason of Your accepting any such warranty or additional liability. + +§10 Applicable law + +This License is governed by German law, excluding its conflict of laws +provisions and the provisions of the UN Convention on Contracts for the +International Sale of Goods (CISG). + +END OF TERMS AND CONDITIONS diff --git a/options/license/SGP4 b/options/license/SGP4 new file mode 100644 index 0000000000..1b86e057c7 --- /dev/null +++ b/options/license/SGP4 @@ -0,0 +1 @@ +There is no license associated with the code and you may use it for any purpose—personal or commercial—as you wish. We ask only that you include citations in your documentation and source code to show the source of the code and provide links to the main page, to facilitate communications regarding any questions on the theory or source code. diff --git a/options/license/UnixCrypt b/options/license/UnixCrypt new file mode 100644 index 0000000000..280853382b --- /dev/null +++ b/options/license/UnixCrypt @@ -0,0 +1,6 @@ +Copyright (c) 1996 Aki Yoshida. All rights reserved. + +Permission to use, copy, modify and distribute this software +for non-commercial or commercial purposes and without fee is +hereby granted provided that this copyright notice appears in +all copies. diff --git a/options/license/metamail b/options/license/metamail new file mode 100644 index 0000000000..be7a8a4e5a --- /dev/null +++ b/options/license/metamail @@ -0,0 +1,12 @@ +Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore) + +Permission to use, copy, modify, and distribute this material +for any purpose and without fee is hereby granted, provided +that the above copyright notice and this permission notice +appear in all copies, and that the name of Bellcore not be +used in advertising or publicity pertaining to this +material without the specific, prior written permission +of an authorized representative of Bellcore. BELLCORE +MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY +OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS", +WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. diff --git a/options/license/vsftpd-openssl-exception b/options/license/vsftpd-openssl-exception new file mode 100644 index 0000000000..a864761e48 --- /dev/null +++ b/options/license/vsftpd-openssl-exception @@ -0,0 +1,5 @@ +vsftpd is licensed under version 2 of the GNU GPL. +As copyright holder, I give permission for vsftpd to be linked to the OpenSSL +libraries. This includes permission for vsftpd binaries to be distributed +linked against the OpenSSL libraries. All other obligations under the GPL v2 +remain intact. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index d774c79891..0ca1e002e8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -109,6 +109,7 @@ error = Error error404 = The page you are trying to reach either does not exist or you are not authorized to view it. never = Never +unknown = Unknown rss_feed = RSS Feed @@ -993,6 +994,7 @@ template.one_item = Must select at least one template item template.invalid = Must select a template repository archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests. +archive.title_date = This repository has been archived on %s. You can view files and clone it, but cannot push or open issues/pull-requests. archive.issue.nocomment = This repo is archived. You cannot comment on issues. archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. @@ -1353,7 +1355,10 @@ issues.filter_label = Label issues.filter_label_exclude = `Use alt + click/enter to exclude labels` issues.filter_label_no_select = All labels issues.filter_milestone = Milestone -issues.filter_milestone_no_select = All milestones +issues.filter_milestone_all = All milestones +issues.filter_milestone_none = No milestones +issues.filter_milestone_open = Open milestones +issues.filter_milestone_closed = Closed milestones issues.filter_project = Project issues.filter_project_all = All projects issues.filter_project_none = No project @@ -1412,8 +1417,8 @@ issues.context.edit = Edit issues.context.delete = Delete issues.no_content = There is no content yet. issues.close = Close Issue -issues.pull_merged_at = `merged commit %[2]s into %[3]s %[4]s` -issues.manually_pull_merged_at = `merged commit %[2]s into %[3]s manually %[4]s` +issues.comment_pull_merged_at = merged commit %[1]s into %[2]s %[3]s +issues.comment_manually_pull_merged_at = manually merged commit %[1]s into %[2]s %[3]s issues.close_comment_issue = Comment and Close issues.reopen_issue = Reopen issues.reopen_comment_issue = Comment and Reopen @@ -1543,6 +1548,7 @@ issues.dependency.issue_closing_blockedby = Closing this issue is blocked by the issues.dependency.issue_close_blocks = This issue blocks closing of the following issues issues.dependency.pr_close_blocks = This pull request blocks closing of the following issues issues.dependency.issue_close_blocked = You need to close all issues blocking this issue before you can close it. +issues.dependency.issue_batch_close_blocked = "Cannot batch close issues that you choose, because issue #%d still has open dependencies" issues.dependency.pr_close_blocked = You need to close all issues blocking this pull request before you can merge it. issues.dependency.blocks_short = Blocks issues.dependency.blocked_by_short = Depends on @@ -1901,6 +1907,7 @@ settings.sync_mirror = Synchronize Now settings.mirror_sync_in_progress = Mirror synchronization is in progress. Check back in a minute. settings.site = Website settings.update_settings = Update Settings +settings.branches.switch_default_branch = Switch Default Branch settings.branches.update_default_branch = Update Default Branch settings.branches.add_new_rule = Add New Rule settings.advanced_settings = Advanced Settings @@ -2096,6 +2103,8 @@ settings.event_pull_request_review = Pull Request Reviewed settings.event_pull_request_review_desc = Pull request approved, rejected, or review comment. settings.event_pull_request_sync = Pull Request Synchronized settings.event_pull_request_sync_desc = Pull request synchronized. +settings.event_pull_request_approvals = Pull Request Approvals +settings.event_pull_request_merge = Pull Request Merge settings.event_package = Package settings.event_package_desc = Package created or deleted in a repository. settings.branch_filter = Branch filter @@ -2151,13 +2160,15 @@ settings.protected_branch.delete_rule = Delete Rule settings.protected_branch_can_push = Allow push? settings.protected_branch_can_push_yes = You can push settings.protected_branch_can_push_no = You cannot push -settings.branch_protection = Branch Protection for Branch '%s' +settings.branch_protection = Branch Protection Rules for Branch '%s' settings.protect_this_branch = Enable Branch Protection settings.protect_this_branch_desc = Prevents deletion and restricts Git pushing and merging to the branch. settings.protect_disable_push = Disable Push settings.protect_disable_push_desc = No pushing will be allowed to this branch. settings.protect_enable_push = Enable Push settings.protect_enable_push_desc = Anyone with write access will be allowed to push to this branch (but not force push). +settings.protect_enable_merge = Enable Merge +settings.protect_enable_merge_desc = Anyone with write access will be allowed to merge the pull requests into this branch. settings.protect_whitelist_committers = Whitelist Restricted Push settings.protect_whitelist_committers_desc = Only whitelisted users or teams will be allowed to push to this branch (but not force push). settings.protect_whitelist_deploy_keys = Whitelist deploy keys with write access to push. @@ -2183,6 +2194,7 @@ settings.dismiss_stale_approvals_desc = When new commits that change the content settings.require_signed_commits = Require Signed Commits settings.require_signed_commits_desc = Reject pushes to this branch if they are unsigned or unverifiable. settings.protect_branch_name_pattern = Protected Branch Name Pattern +settings.protect_patterns = Patterns settings.protect_protected_file_patterns = "Protected file patterns (separated using semicolon ';'):" settings.protect_protected_file_patterns_desc = "Protected files are not allowed to be changed directly even if user has rights to add, edit, or delete files in this branch. Multiple patterns can be separated using semicolon (';'). See github.com/gobwas/glob documentation for pattern syntax. Examples: .drone.yml, /docs/**/*.txt." settings.protect_unprotected_file_patterns = "Unprotected file patterns (separated using semicolon ';'):" @@ -2380,7 +2392,7 @@ branch.already_exists = A branch named "%s" already exists. branch.delete_head = Delete branch.delete = Delete Branch "%s" branch.delete_html = Delete Branch -branch.delete_desc = Deleting a branch is permanent. It CANNOT be undone. Continue? +branch.delete_desc = Deleting a branch is permanent. Although the deleted branch may exist for a short time before cleaning up, in most cases it CANNOT be undone. Continue? branch.deletion_success = Branch "%s" has been deleted. branch.deletion_failed = Failed to delete branch "%s". branch.delete_branch_has_new_commits = Branch "%s" cannot be deleted because new commits have been added after merging. @@ -2397,10 +2409,13 @@ branch.protected_deletion_failed = Branch "%s" is protected. It cannot be delete branch.default_deletion_failed = Branch "%s" is the default branch. It cannot be deleted. branch.restore = Restore Branch "%s" branch.download = Download Branch "%s" +branch.rename = Rename Branch "%s" branch.included_desc = This branch is part of the default branch branch.included = Included branch.create_new_branch = Create branch from branch: branch.confirm_create_branch = Create branch +branch.rename_branch_to = Rename "%s" to: +branch.confirm_rename_branch = Rename branch branch.create_branch_operation = Create branch branch.new_branch = Create new branch branch.new_branch_from = Create new branch from "%s" @@ -2959,6 +2974,7 @@ config.mailer_sendmail_timeout = Sendmail Timeout config.mailer_use_dummy = Dummy config.test_email_placeholder = Email (e.g. test@example.com) config.send_test_mail = Send Testing Email +config.send_test_mail_submit = Send config.test_mail_failed = Failed to send a testing email to "%s": %v config.test_mail_sent = A testing email has been sent to "%s". @@ -3200,7 +3216,7 @@ error.unit_not_allowed = You are not allowed to access this repository section. title = Packages desc = Manage repository packages. empty = There are no packages yet. -empty.documentation = For more information on the package registry, see the documentation. +empty.documentation = For more information on the package registry, see the documentation. empty.repo = Did you upload a package, but it's not shown here? Go to package settings and link it to this repo. filter.type = Type filter.type.all = All @@ -3227,77 +3243,85 @@ dependency.id = ID dependency.version = Version cargo.registry = Setup this registry in the Cargo configuration file (for example ~/.cargo/config.toml): cargo.install = To install the package using Cargo, run the following command: -cargo.documentation = For more information on the Cargo registry, see the documentation. +cargo.documentation = For more information on the Cargo registry, see the documentation. cargo.details.repository_site = Repository Site cargo.details.documentation_site = Documentation Site chef.registry = Setup this registry in your ~/.chef/config.rb file: chef.install = To install the package, run the following command: -chef.documentation = For more information on the Chef registry, see the documentation. +chef.documentation = For more information on the Chef registry, see the documentation. composer.registry = Setup this registry in your ~/.composer/config.json file: composer.install = To install the package using Composer, run the following command: -composer.documentation = For more information on the Composer registry, see the documentation. +composer.documentation = For more information on the Composer registry, see the documentation. composer.dependencies = Dependencies composer.dependencies.development = Development Dependencies conan.details.repository = Repository conan.registry = Setup this registry from the command line: conan.install = To install the package using Conan, run the following command: -conan.documentation = For more information on the Conan registry, see the documentation. +conan.documentation = For more information on the Conan registry, see the documentation. conda.registry = Setup this registry as a Conda repository in your .condarc file: conda.install = To install the package using Conda, run the following command: -conda.documentation = For more information on the Conda registry, see the documentation. +conda.documentation = For more information on the Conda registry, see the documentation. conda.details.repository_site = Repository Site conda.details.documentation_site = Documentation Site container.details.type = Image Type container.details.platform = Platform container.pull = Pull the image from the command line: container.digest = Digest: -container.documentation = For more information on the Container registry, see the documentation. +container.documentation = For more information on the Container registry, see the documentation. container.multi_arch = OS / Arch container.layers = Image Layers container.labels = Labels container.labels.key = Key container.labels.value = Value +debian.registry = Setup this registry from the command line: +debian.registry.info = Choose $distribution and $component from the list below. +debian.install = To install the package, run the following command: +debian.documentation = For more information on the Debian registry, see the documentation. +debian.repository = Repository Info +debian.repository.distributions = Distributions +debian.repository.components = Components +debian.repository.architectures = Architectures generic.download = Download package from the command line: -generic.documentation = For more information on the generic registry, see the documentation. +generic.documentation = For more information on the generic registry, see the documentation. helm.registry = Setup this registry from the command line: helm.install = To install the package, run the following command: -helm.documentation = For more information on the Helm registry, see the documentation. +helm.documentation = For more information on the Helm registry, see the documentation. maven.registry = Setup this registry in your project pom.xml file: maven.install = To use the package include the following in the dependencies block in the pom.xml file: maven.install2 = Run via command line: maven.download = To download the dependency, run via command line: -maven.documentation = For more information on the Maven registry, see the documentation. +maven.documentation = For more information on the Maven registry, see the documentation. nuget.registry = Setup this registry from the command line: nuget.install = To install the package using NuGet, run the following command: -nuget.documentation = For more information on the NuGet registry, see the documentation. +nuget.documentation = For more information on the NuGet registry, see the documentation. nuget.dependency.framework = Target Framework npm.registry = Setup this registry in your project .npmrc file: npm.install = To install the package using npm, run the following command: npm.install2 = or add it to the package.json file: -npm.documentation = For more information on the npm registry, see the documentation. +npm.documentation = For more information on the npm registry, see the documentation. npm.dependencies = Dependencies npm.dependencies.development = Development Dependencies npm.dependencies.peer = Peer Dependencies npm.dependencies.optional = Optional Dependencies npm.details.tag = Tag pub.install = To install the package using Dart, run the following command: -pub.documentation = For more information on the Pub registry, see the documentation. +pub.documentation = For more information on the Pub registry, see the documentation. pypi.requires = Requires Python pypi.install = To install the package using pip, run the following command: -pypi.documentation = For more information on the PyPI registry, see the documentation. +pypi.documentation = For more information on the PyPI registry, see the documentation. rubygems.install = To install the package using gem, run the following command: rubygems.install2 = or add it to the Gemfile: rubygems.dependencies.runtime = Runtime Dependencies rubygems.dependencies.development = Development Dependencies rubygems.required.ruby = Requires Ruby version rubygems.required.rubygems = Requires RubyGem version -rubygems.documentation = For more information on the RubyGems registry, see the documentation. +rubygems.documentation = For more information on the RubyGems registry, see the documentation. swift.registry = Setup this registry from the command line: swift.install = Add the package in your Package.swift file: swift.install2 = and run the following command: -swift.documentation = For more information on the Swift registry, see the documentation. +swift.documentation = For more information on the Swift registry, see the documentation. vagrant.install = To add a Vagrant box, run the following command: -vagrant.documentation = For more information on the Vagrant registry, see the documentation. +vagrant.documentation = For more information on the Vagrant registry, see the documentation. settings.link = Link this package to a repository settings.link.description = If you link a package with a repository, the package is listed in the repository's package list. settings.link.select = Select Repository @@ -3357,6 +3381,7 @@ deletion = Remove secret deletion.description = Removing a secret is permanent and cannot be undone. Continue? deletion.success = The secret has been removed. deletion.failed = Failed to remove secret. +management = Secrets Management [actions] actions = Actions diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index a52641f7b2..810d580f30 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -125,6 +125,20 @@ less=少 more=多 [editor] +buttons.heading.tooltip=見出し追加 +buttons.bold.tooltip=太字追加 +buttons.italic.tooltip=イタリック体追加 +buttons.quote.tooltip=引用 +buttons.code.tooltip=コード追加 +buttons.link.tooltip=リンク追加 +buttons.list.unordered.tooltip=箇条書き追加 +buttons.list.ordered.tooltip=番号付きリスト追加 +buttons.list.task.tooltip=タスクリスト追加 +buttons.mention.tooltip=ユーザーまたはチームにメンション +buttons.ref.tooltip=イシューまたはプルリクエストを参照 +buttons.switch_to_legacy.tooltip=レガシーエディタを使用する +buttons.enable_monospace_font=等幅フォントを有効にする +buttons.disable_monospace_font=等幅フォントを無効にする [filter] string.asc=A - Z @@ -448,6 +462,7 @@ team_invite.text_3=注: この招待は %[1]s 宛です。 招待に心当たり [modal] yes=はい no=いいえ +confirm=了解 cancel=キャンセル modify=更新 @@ -1606,7 +1621,10 @@ pulls.tab_files=変更されたファイル pulls.reopen_to_merge=このプルリクエストをマージする場合は再オープンしてください。 pulls.cant_reopen_deleted_branch=このプルリクエストはブランチが削除されているため、再オープンできません。 pulls.merged=マージ済み +pulls.merged_success=プルリクエストは正常にマージ、クローズされました +pulls.closed=プルリクエストはクローズされました pulls.manually_merged=手動マージ済み +pulls.merged_info_text=ブランチ %s を削除できるようになりました。 pulls.is_closed=プルリクエストはクローズされています。 pulls.title_wip_desc=`誤ってマージされないようにするには、タイトルの頭に %s を付けます。` pulls.cannot_merge_work_in_progress=このプルリクエストは作業中(WIP)としてマーキングされています。 @@ -2426,6 +2444,7 @@ team_unit_desc=リポジトリのセクションへのアクセスを許可 team_unit_disabled=(無効) form.name_reserved=組織名 "%s" は予約されています。 +form.name_pattern_not_allowed=`"%s" の形式は組織名に使用できません。` form.create_org_not_allowed=組織を作成する権限がありません。 settings=設定 @@ -3392,8 +3411,12 @@ runs.closed_tab=%d クローズ runs.commit=コミット runs.pushed_by=Pushed by runs.invalid_workflow_helper=ワークフロー設定ファイルは無効です。あなたの設定ファイルを確認してください: %s +runs.no_matching_runner_helper=一致するランナーがありません: %s need_approval_desc=フォークプルリクエストのワークフローを実行するには承認が必要です。 [projects] +type-1.display_name=個人プロジェクト +type-2.display_name=リポジトリ プロジェクト +type-3.display_name=組織プロジェクト diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index d000c71cad..82f7effb52 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -95,7 +95,7 @@ license=오픈 소스 [install] install=설치 title=초기 설정 -docker_helper=Gitea를 Docker에서 실행하려면 설정 전에 이 문서를 읽어보세요. +docker_helper="Gitea를 Docker에서 실행하려면 설정 전에 이 문서를 읽어보세요." db_title=데이터베이스 설정 db_type=데이터베이스 유형 host=호스트 @@ -444,8 +444,8 @@ manage_gpg_keys=GPG 키 관리 add_key=키 추가 ssh_desc=이러한 SSH 공용 키는 귀하의 계정과 연결되어 있습니다. 해당 개인 키는 당신의 저장소에 대한 전체 액세스를 가능하게 합니다. gpg_desc=이러한 GPG 공개키는 당신의 계정과 연결되어있습니다. 커밋이 검증될 수 있도록 당신의 개인 키를 안전하게 유지하십시오. -ssh_helper=도움이 필요하세요? GitHub의 설명서를 참조하시기 바랍니다: SSH 키 생성하기 또는 SSH를 사용할 때 일반적인 문제 -gpg_helper=도움이 필요하세요? GitHub의 설명서를 참조하시기 바랍니다: GPG키에 대하여. +ssh_helper="도움이 필요하세요? GitHub의 설명서를 참조하시기 바랍니다: SSH 키 생성하기 또는 SSH를 사용할 때 일반적인 문제" +gpg_helper="도움이 필요하세요? GitHub의 설명서를 참조하시기 바랍니다: GPG키에 대하여." add_new_key=SSH 키 추가 add_new_gpg_key=GPG 키 추가 gpg_key_id_used=같은 ID의 GPG 공개키가 이미 존재합니다. @@ -557,7 +557,7 @@ visibility=가시성 visibility_helper=개인 저장소로 만들기 visibility_helper_forced=사이트 관리자가 새 레포지토리에 대해 비공개로만 생성되도록 하였습니다. visibility_fork_helper=(변경사항을 적용하는 경우 모든 포크가 영향을 받게 됩니다.) -clone_helper=클론하는데에 도움이 필요하면 Help에 방문하세요. +clone_helper="클론하는데에 도움이 필요하면 Help에 방문하세요." fork_repo=저장소 포크 fork_from=원본 프로젝트 : fork_visibility_helper=포크된 저장소의 가시성은 변경하실 수 없습니다. @@ -660,7 +660,7 @@ editor.or=혹은 editor.cancel_lower=취소 editor.commit_changes=변경 내용을 커밋 editor.commit_message_desc=선택적 확장 설명을 추가... -editor.commit_directly_to_this_branch=%s 브랜치에서 직접 커밋해주세요. +editor.commit_directly_to_this_branch="%s 브랜치에서 직접 커밋해주세요." editor.create_new_branch=이 커밋에 대한 새로운 브랜치를 만들고 끌어오기 요청을 시작합니다. editor.new_branch_name_desc=새로운 브랜치 명... editor.cancel=취소 @@ -752,7 +752,7 @@ issues.action_milestone=마일스톤 issues.action_milestone_no_select=마일스톤 없음 issues.action_assignee=담당자 issues.action_assignee_no_select=담당자 없음 -issues.opened_by= %[3]s가 %[1]s을 오픈 +issues.opened_by=" %[3]s가 %[1]s을 오픈" issues.previous=이전 issues.next=다음 issues.open_title=오픈 @@ -772,7 +772,7 @@ issues.commit_ref_at=` 커밋 %[2]s에서 이 issues.poster=포스터 issues.collaborator=협업자 issues.owner=소유자 -issues.sign_in_require_desc=로그인하여 이 대화에 참여 +issues.sign_in_require_desc="로그인하여 이 대화에 참여" issues.edit=수정 issues.cancel=취소 issues.save=저장 @@ -810,19 +810,19 @@ issues.add_time_sum_to_small=시간이 입력되지 않았습니다. issues.time_spent_total=총 경과된 시간 issues.time_spent_from_all_authors=`총 경과된 시간: %s` issues.due_date=마감일 -issues.invalid_due_date_format=마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다. -issues.error_modifying_due_date=마감일 수정을 실패하였습니다. -issues.error_removing_due_date=마감일 삭제를 실패하였습니다. +issues.invalid_due_date_format="마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다." +issues.error_modifying_due_date="마감일 수정을 실패하였습니다." +issues.error_removing_due_date="마감일 삭제를 실패하였습니다." issues.due_date_form=yyyy-mm-dd issues.due_date_form_add=마감일 추가 issues.due_date_form_edit=편집 issues.due_date_form_remove=삭제 -issues.due_date_not_writer=이슈의 마감일을 갱신하려면 저장소 쓰기 권한이 필요합니다. +issues.due_date_not_writer="이슈의 마감일을 갱신하려면 저장소 쓰기 권한이 필요합니다." issues.due_date_not_set=마감일이 설정되지 않았습니다. issues.due_date_added=마감일 %s 를 추가 %s issues.due_date_remove=%s %s 마감일이 삭제되었습니다. -issues.due_date_overdue=기한 초과 -issues.due_date_invalid=기한이 올바르지 않거나 범위를 벗어났습니다. 'yyyy-mm-dd'형식을 사용해주십시오. +issues.due_date_overdue="기한 초과" +issues.due_date_invalid="기한이 올바르지 않거나 범위를 벗어났습니다. 'yyyy-mm-dd'형식을 사용해주십시오." issues.dependency.title=의존성 issues.dependency.add=의존성 추가... issues.dependency.cancel=취소 @@ -840,7 +840,7 @@ issues.dependency.add_error_dep_exists=의존성이 이미 존재합니다. issues.dependency.add_error_dep_not_same_repo=두 이슈는 같은 레포지토리 안에 있어야 합니다. issues.review.self.approval=자신의 풀 리퀘스트를 승인할 수 없습니다. issues.review.self.rejection=자신의 풀 리퀘스트에 대한 변경을 요청할 수 없습니다. -issues.review.approve=이 변경사항을 승인하였습니다. %s +issues.review.approve="이 변경사항을 승인하였습니다. %s" issues.review.comment=검토됨 %s issues.review.pending=보류 issues.review.review=검토 @@ -856,7 +856,7 @@ pulls.compare_compare=다음으로부터 풀 pulls.filter_branch=Filter Branch pulls.no_results=결과 없음 pulls.create=풀 리퀘스트 생성 -pulls.title_desc=%[2]s 에서 %[3]s 로 %[1]d commits 를 머지하려 합니다 +pulls.title_desc="%[2]s 에서 %[3]s 로 %[1]d commits 를 머지하려 합니다" pulls.merged_title_desc=%[2]s 에서 %[3]s 로 %[1]d commits 를 머지했습니다 %[4]s pulls.tab_conversation=대화 pulls.tab_commits=커밋 @@ -885,7 +885,7 @@ milestones.title=타이틀 milestones.desc=설명 milestones.due_date=기한 (선택 사항) milestones.clear=지우기 -milestones.invalid_due_date_format=마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다. +milestones.invalid_due_date_format="마감일은 반드시 'yyyy-mm-dd' 형식이어야 합니다." milestones.edit=마일스톤 편집 milestones.cancel=취소 milestones.modify=마일스톤 갱신 @@ -962,7 +962,7 @@ activity.published_release_label=배포됨 search=검색 search.search_repo=저장소 검색 -search.results=%s 에서 "%s" 에 대한 검색 결과 +search.results="%s 에서 \"%s\" 에 대한 검색 결과" search.code_no_results=검색어와 일치하는 소스코드가 없습니다. settings=설정 diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index c72fa11526..df12f24849 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1833,7 +1833,7 @@ activity.new_issues_count_n=questões novas activity.new_issue_label=Em aberto activity.title.unresolved_conv_1=%d diálogo não concluído activity.title.unresolved_conv_n=%d diálogos não concluídos -activity.unresolved_conv_desc=Estas questões e estes pedidos de integração que foram modificados recentemente ainda não foram concluídos. +activity.unresolved_conv_desc=Estas questões e estes pedidos de integração, que foram modificados recentemente, ainda não foram concluídos. activity.unresolved_conv_label=Em aberto activity.title.releases_1=%d lançamento activity.title.releases_n=%d Lançamentos diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 41f987bb72..f192182834 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -61,7 +61,7 @@ new_project_column=创建列 manage_org=管理我的组织 admin_panel=管理后台 account_settings=帐户设置 -settings=帐户设置 +settings=设置 your_profile=个人信息 your_starred=已点赞 your_settings=设置 @@ -286,7 +286,7 @@ issues.in_your_repos=在您的仓库中 [explore] repos=仓库 users=用户 -organizations=组织管理 +organizations=组织 search=搜索 code=代码 search.type.tooltip=搜索类型 diff --git a/package-lock.json b/package-lock.json index 9a2f5dc578..787c3430ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,6 @@ "esbuild-loader": "3.0.1", "escape-goat": "4.0.0", "fast-glob": "3.2.12", - "font-awesome": "4.7.0", "jquery": "3.6.4", "jquery.are-you-sure": "1.9.0", "katex": "0.16.6", @@ -65,6 +64,7 @@ "eslint-plugin-import": "2.27.5", "eslint-plugin-jquery": "1.5.1", "eslint-plugin-no-jquery": "2.7.0", + "eslint-plugin-regexp": "1.14.0", "eslint-plugin-sonarjs": "0.19.0", "eslint-plugin-unicorn": "46.0.0", "eslint-plugin-vue": "9.11.0", @@ -2927,6 +2927,15 @@ "node": ">= 12" } }, + "node_modules/comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -4560,6 +4569,28 @@ "eslint": ">=2.3.0" } }, + "node_modules/eslint-plugin-regexp": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-1.14.0.tgz", + "integrity": "sha512-5+bBSsRTTtkSf8+/iNSjiOW6qbjAdGyqv88HxPaBNFKxROK+UAdOGDl5Jr+csV5wW2BuOOvaG82zsvTriQBRFA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "comment-parser": "^1.1.2", + "grapheme-splitter": "^1.0.4", + "jsdoctypeparser": "^9.0.0", + "refa": "^0.11.0", + "regexp-ast-analysis": "^0.6.0", + "scslre": "^0.2.0" + }, + "engines": { + "node": "^12 || >=14" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/eslint-plugin-sonarjs": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.19.0.tgz", @@ -4913,14 +4944,6 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", - "engines": { - "node": ">=0.10.3" - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -6038,6 +6061,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoctypeparser": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz", + "integrity": "sha512-jrTA2jJIL6/DAEILBEh2/w9QxCuwmvNXIry39Ay/HVfhE3o2yVV0U44blYkqdHA/OKloJEqvJy0xU+GSdE2SIw==", + "dev": true, + "bin": { + "jsdoctypeparser": "bin/jsdoctypeparser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsdom": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.1.tgz", @@ -7934,11 +7969,36 @@ "node": ">=8" } }, + "node_modules/refa": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.11.0.tgz", + "integrity": "sha512-486O8/pQXwj9jV0mVvUnTsxq0uknpBnNJ0eCUhkZqJRQ8KutrT1PhzmumdCeM1hSBF2eMlFPmwECRER4IbKXlQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/regexp-ast-analysis": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.6.0.tgz", + "integrity": "sha512-OLxjyjPkVH+rQlBLb1I/P/VTmamSjGkvN5PTV5BXP432k3uVz727J7H29GA5IFiY0m7e1xBN7049Wn59FY3DEQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.0", + "refa": "^0.11.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/regexp-tree": { "version": "0.1.25", "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.25.tgz", @@ -8247,6 +8307,17 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/scslre": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.2.0.tgz", + "integrity": "sha512-4hc49fUMmX3jM0XdFUAPBrs1xwEcdHa0KyjEsjFs+Zfc66mpFpq5YmRgDtl+Ffo6AtJIilfei+yKw8fUn3N88w==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.0", + "refa": "^0.11.0", + "regexp-ast-analysis": "^0.6.0" + } + }, "node_modules/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", diff --git a/package.json b/package.json index ae3b7446fe..35d417b0f0 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "esbuild-loader": "3.0.1", "escape-goat": "4.0.0", "fast-glob": "3.2.12", - "font-awesome": "4.7.0", "jquery": "3.6.4", "jquery.are-you-sure": "1.9.0", "katex": "0.16.6", @@ -65,6 +64,7 @@ "eslint-plugin-import": "2.27.5", "eslint-plugin-jquery": "1.5.1", "eslint-plugin-no-jquery": "2.7.0", + "eslint-plugin-regexp": "1.14.0", "eslint-plugin-sonarjs": "0.19.0", "eslint-plugin-unicorn": "46.0.0", "eslint-plugin-vue": "9.11.0", diff --git a/public/img/svg/gitea-debian.svg b/public/img/svg/gitea-debian.svg new file mode 100644 index 0000000000..96f8f468e5 --- /dev/null +++ b/public/img/svg/gitea-debian.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/svg/gitea-empty-checkbox.svg b/public/img/svg/gitea-empty-checkbox.svg new file mode 100644 index 0000000000..dc4083e255 --- /dev/null +++ b/public/img/svg/gitea-empty-checkbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go index 705867a9b1..10f92c29f7 100644 --- a/routers/api/actions/runner/utils.go +++ b/routers/api/actions/runner/utils.go @@ -92,6 +92,21 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { event := map[string]interface{}{} _ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event) + baseRef := "" + headRef := "" + if pullPayload, err := t.Job.Run.GetPullRequestEventPayload(); err == nil && pullPayload.PullRequest != nil && pullPayload.PullRequest.Base != nil && pullPayload.PullRequest.Head != nil { + baseRef = pullPayload.PullRequest.Base.Ref + headRef = pullPayload.PullRequest.Head.Ref + } + refPrefix, refName := git.SplitRefName(t.Job.Run.Ref) + refType := "" + switch refPrefix { + case git.BranchPrefix: + refType = "branch" + case git.TagPrefix: + refType = "tag" + } + taskContext, _ := structpb.NewStruct(map[string]interface{}{ // standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context "action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2. @@ -100,19 +115,19 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { "action_repository": "", // string, For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout. "action_status": "", // string, For a composite action, the current result of the composite action. "actor": t.Job.Run.TriggerUser.Name, // string, The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from github.triggering_actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. - "api_url": "", // string, The URL of the GitHub REST API. - "base_ref": "", // string, The base_ref or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. + "api_url": setting.AppURL + "api/v1", // string, The URL of the GitHub REST API. + "base_ref": baseRef, // string, The base_ref or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. "env": "", // string, Path on the runner to the file that sets environment variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions." "event": event, // object, The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in "Events that trigger workflows." For example, for a workflow run triggered by the push event, this object contains the contents of the push webhook payload. "event_name": t.Job.Run.Event.Event(), // string, The name of the event that triggered the workflow run. "event_path": "", // string, The path to the file on the runner that contains the full event webhook payload. "graphql_url": "", // string, The URL of the GitHub GraphQL API. - "head_ref": "", // string, The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. + "head_ref": headRef, // string, The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. "job": fmt.Sprint(t.JobID), // string, The job_id of the current job. "ref": t.Job.Run.Ref, // string, The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/, for pull requests it is refs/pull//merge, and for tags it is refs/tags/. For example, refs/heads/feature-branch-1. - "ref_name": git.RefEndName(t.Job.Run.Ref), // string, The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. + "ref_name": refName, // string, The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. "ref_protected": false, // boolean, true if branch protections are configured for the ref that triggered the workflow run. - "ref_type": "", // string, The type of ref that triggered the workflow run. Valid values are branch or tag. + "ref_type": refType, // string, The type of ref that triggered the workflow run. Valid values are branch or tag. "path": "", // string, Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions." "repository": t.Job.Run.Repo.OwnerName + "/" + t.Job.Run.Repo.Name, // string, The owner and repository name. For example, Codertocat/Hello-World. "repository_owner": t.Job.Run.Repo.OwnerName, // string, The repository owner's name. For example, Codertocat. diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 8bf5dbab35..04b0c0ab08 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -20,6 +21,7 @@ import ( "code.gitea.io/gitea/routers/api/packages/conan" "code.gitea.io/gitea/routers/api/packages/conda" "code.gitea.io/gitea/routers/api/packages/container" + "code.gitea.io/gitea/routers/api/packages/debian" "code.gitea.io/gitea/routers/api/packages/generic" "code.gitea.io/gitea/routers/api/packages/helm" "code.gitea.io/gitea/routers/api/packages/maven" @@ -36,6 +38,32 @@ import ( func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) { return func(ctx *context.Context) { + if ctx.Data["IsApiToken"] == true { + scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope) + if ok { // it's a personal access token but not oauth2 token + scopeMatched := false + var err error + if accessMode == perm.AccessModeRead { + scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeReadPackage) + if err != nil { + ctx.Error(http.StatusInternalServerError, "HasScope", err.Error()) + return + } + } else if accessMode == perm.AccessModeWrite { + scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeWritePackage) + if err != nil { + ctx.Error(http.StatusInternalServerError, "HasScope", err.Error()) + return + } + } + if !scopeMatched { + ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea Package API"`) + ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin") + return + } + } + } + if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() { ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea Package API"`) ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin") @@ -245,6 +273,24 @@ func CommonRoutes(ctx gocontext.Context) *web.Route { conda.UploadPackageFile(ctx) }) }, reqPackageAccess(perm.AccessModeRead)) + r.Group("/debian", func() { + r.Get("/repository.key", debian.GetRepositoryKey) + r.Group("/dists/{distribution}", func() { + r.Get("/{filename}", debian.GetRepositoryFile) + r.Get("/by-hash/{algorithm}/{hash}", debian.GetRepositoryFileByHash) + r.Group("/{component}/{architecture}", func() { + r.Get("/{filename}", debian.GetRepositoryFile) + r.Get("/by-hash/{algorithm}/{hash}", debian.GetRepositoryFileByHash) + }) + }) + r.Group("/pool/{distribution}/{component}", func() { + r.Get("/{name}_{version}_{architecture}.deb", debian.DownloadPackageFile) + r.Group("", func() { + r.Put("/upload", debian.UploadPackageFile) + r.Delete("/{name}/{version}/{architecture}", debian.DeletePackageFile) + }, reqPackageAccess(perm.AccessModeWrite)) + }) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/generic", func() { r.Group("/{packagename}/{packageversion}", func() { r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage) diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go index e0bf5da13a..18c93d328a 100644 --- a/routers/api/packages/cargo/cargo.go +++ b/routers/api/packages/cargo/cargo.go @@ -173,7 +173,7 @@ func UploadPackage(ctx *context.Context) { return } - buf, err := packages_module.CreateHashedBufferFromReader(cp.Content, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(cp.Content) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/chef/chef.go b/routers/api/packages/chef/chef.go index 28d07dea47..b48b1778c4 100644 --- a/routers/api/packages/chef/chef.go +++ b/routers/api/packages/chef/chef.go @@ -263,7 +263,7 @@ func UploadPackage(ctx *context.Context) { } defer file.Close() - buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(file) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go index a623952aa7..d93b11efdf 100644 --- a/routers/api/packages/composer/composer.go +++ b/routers/api/packages/composer/composer.go @@ -192,7 +192,7 @@ func DownloadPackageFile(ctx *context.Context) { // UploadPackage creates a new package func UploadPackage(ctx *context.Context) { - buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go index d538cc7d39..caeb8c11bc 100644 --- a/routers/api/packages/conan/conan.go +++ b/routers/api/packages/conan/conan.go @@ -318,7 +318,7 @@ func uploadFile(ctx *context.Context, fileFilter container.Set[string], fileKey defer upload.Close() } - buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(upload) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -648,10 +648,7 @@ func deleteRecipeOrPackage(apictx *context.Context, rref *conan_module.RecipeRef } for _, pf := range pfs { - if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil { - return err - } - if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil { + if err := packages_service.DeletePackageFile(ctx, pf); err != nil { return err } } @@ -664,11 +661,7 @@ func deleteRecipeOrPackage(apictx *context.Context, rref *conan_module.RecipeRef if !has { versionDeleted = true - if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeVersion, pv.ID); err != nil { - return err - } - - if err := packages_model.DeleteVersionByID(ctx, pv.ID); err != nil { + if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { return err } } diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go index 2ff619fed4..f778690630 100644 --- a/routers/api/packages/conda/conda.go +++ b/routers/api/packages/conda/conda.go @@ -183,7 +183,7 @@ func UploadPackageFile(ctx *context.Context) { defer upload.Close() } - buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(upload) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/container/blob.go b/routers/api/packages/container/blob.go index f0457c55e1..c8e8dd0545 100644 --- a/routers/api/packages/container/blob.go +++ b/routers/api/packages/container/blob.go @@ -27,10 +27,6 @@ var uploadVersionMutex sync.Mutex // saveAsPackageBlob creates a package blob from an upload // The uploaded blob gets stored in a special upload version to link them to the package/image func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { - if err := packages_service.CheckSizeQuotaExceeded(db.DefaultContext, pci.Creator, pci.Owner, packages_model.TypeContainer, hsr.Size()); err != nil { - return nil, err - } - pb := packages_service.NewPackageBlob(hsr) exists := false @@ -43,6 +39,10 @@ func saveAsPackageBlob(hsr packages_module.HashedSizeReader, pci *packages_servi } err = db.WithTx(db.DefaultContext, func(ctx context.Context) error { + if err := packages_service.CheckSizeQuotaExceeded(ctx, pci.Creator, pci.Owner, packages_model.TypeContainer, hsr.Size()); err != nil { + return err + } + pb, exists, err = packages_model.GetOrInsertBlob(ctx, pb) if err != nil { log.Error("Error inserting package blob: %v", err) diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index 883fe73cbd..63c49809a7 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -219,7 +219,7 @@ func InitiateUploadBlob(ctx *context.Context) { digest := ctx.FormTrim("digest") if digest != "" { - buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -538,7 +538,7 @@ func UploadManifest(ctx *context.Context) { } maxSize := maxManifestSize + 1 - buf, err := packages_module.CreateHashedBufferFromReader(&io.LimitedReader{R: ctx.Req.Body, N: int64(maxSize)}, maxSize) + buf, err := packages_module.CreateHashedBufferFromReaderWithSize(&io.LimitedReader{R: ctx.Req.Body, N: int64(maxSize)}, maxSize) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go new file mode 100644 index 0000000000..cfc03ae522 --- /dev/null +++ b/routers/api/packages/debian/debian.go @@ -0,0 +1,317 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package debian + +import ( + stdctx "context" + "errors" + "fmt" + "io" + "net/http" + "strings" + + "code.gitea.io/gitea/models/db" + packages_model "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/notification" + packages_module "code.gitea.io/gitea/modules/packages" + debian_module "code.gitea.io/gitea/modules/packages/debian" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers/api/packages/helper" + packages_service "code.gitea.io/gitea/services/packages" + debian_service "code.gitea.io/gitea/services/packages/debian" +) + +func apiError(ctx *context.Context, status int, obj interface{}) { + helper.LogAndProcessError(ctx, status, obj, func(message string) { + ctx.PlainText(status, message) + }) +} + +func GetRepositoryKey(ctx *context.Context) { + _, pub, err := debian_service.GetOrCreateKeyPair(ctx.Package.Owner.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{ + ContentType: "application/pgp-keys", + Filename: "repository.key", + }) +} + +// https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files +// https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices +func GetRepositoryFile(ctx *context.Context) { + pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + key := ctx.Params("distribution") + + component := ctx.Params("component") + architecture := strings.TrimPrefix(ctx.Params("architecture"), "binary-") + if component != "" && architecture != "" { + key += "|" + component + "|" + architecture + } + + s, pf, err := packages_service.GetFileStreamByPackageVersion( + ctx, + pv, + &packages_service.PackageFileInfo{ + Filename: ctx.Params("filename"), + CompositeKey: key, + }, + ) + if err != nil { + if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + defer s.Close() + + ctx.ServeContent(s, &context.ServeHeaderOptions{ + Filename: pf.Name, + LastModified: pf.CreatedUnix.AsLocalTime(), + }) +} + +// https://wiki.debian.org/DebianRepository/Format#indices_acquisition_via_hashsums_.28by-hash.29 +func GetRepositoryFileByHash(ctx *context.Context) { + pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + algorithm := strings.ToLower(ctx.Params("algorithm")) + if algorithm == "md5sum" { + algorithm = "md5" + } + + pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ + VersionID: pv.ID, + Hash: strings.ToLower(ctx.Params("hash")), + HashAlgorithm: algorithm, + }) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if len(pfs) != 1 { + apiError(ctx, http.StatusNotFound, nil) + return + } + + s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + defer s.Close() + + ctx.ServeContent(s, &context.ServeHeaderOptions{ + Filename: pf.Name, + LastModified: pf.CreatedUnix.AsLocalTime(), + }) +} + +func UploadPackageFile(ctx *context.Context) { + distribution := strings.TrimSpace(ctx.Params("distribution")) + component := strings.TrimSpace(ctx.Params("component")) + if distribution == "" || component == "" { + apiError(ctx, http.StatusBadRequest, "invalid distribution or component") + return + } + + upload, close, err := ctx.UploadStream() + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if close { + defer upload.Close() + } + + buf, err := packages_module.CreateHashedBufferFromReader(upload) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + defer buf.Close() + + pck, err := debian_module.ParsePackage(buf) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + apiError(ctx, http.StatusBadRequest, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + if _, err := buf.Seek(0, io.SeekStart); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + _, _, err = packages_service.CreatePackageOrAddFileToExisting( + &packages_service.PackageCreationInfo{ + PackageInfo: packages_service.PackageInfo{ + Owner: ctx.Package.Owner, + PackageType: packages_model.TypeDebian, + Name: pck.Name, + Version: pck.Version, + }, + Creator: ctx.Doer, + Metadata: pck.Metadata, + }, + &packages_service.PackageFileCreationInfo{ + PackageFileInfo: packages_service.PackageFileInfo{ + Filename: fmt.Sprintf("%s_%s_%s.deb", pck.Name, pck.Version, pck.Architecture), + CompositeKey: fmt.Sprintf("%s|%s", distribution, component), + }, + Creator: ctx.Doer, + Data: buf, + IsLead: true, + Properties: map[string]string{ + debian_module.PropertyDistribution: distribution, + debian_module.PropertyComponent: component, + debian_module.PropertyArchitecture: pck.Architecture, + debian_module.PropertyControl: pck.Control, + }, + }, + ) + if err != nil { + switch err { + case packages_model.ErrDuplicatePackageVersion: + apiError(ctx, http.StatusBadRequest, err) + case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: + apiError(ctx, http.StatusForbidden, err) + default: + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, pck.Architecture); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + ctx.Status(http.StatusCreated) +} + +func DownloadPackageFile(ctx *context.Context) { + name := ctx.Params("name") + version := ctx.Params("version") + + s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + ctx, + &packages_service.PackageInfo{ + Owner: ctx.Package.Owner, + PackageType: packages_model.TypeDebian, + Name: name, + Version: version, + }, + &packages_service.PackageFileInfo{ + Filename: fmt.Sprintf("%s_%s_%s.deb", name, version, ctx.Params("architecture")), + CompositeKey: fmt.Sprintf("%s|%s", ctx.Params("distribution"), ctx.Params("component")), + }, + ) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + defer s.Close() + + ctx.ServeContent(s, &context.ServeHeaderOptions{ + ContentType: "application/vnd.debian.binary-package", + Filename: pf.Name, + LastModified: pf.CreatedUnix.AsLocalTime(), + }) +} + +func DeletePackageFile(ctx *context.Context) { + distribution := ctx.Params("distribution") + component := ctx.Params("component") + name := ctx.Params("name") + version := ctx.Params("version") + architecture := ctx.Params("architecture") + + owner := ctx.Package.Owner + + var pd *packages_model.PackageDescriptor + + err := db.WithTx(ctx, func(ctx stdctx.Context) error { + pv, err := packages_model.GetVersionByNameAndVersion(ctx, owner.ID, packages_model.TypeDebian, name, version) + if err != nil { + return err + } + + pf, err := packages_model.GetFileForVersionByName( + ctx, + pv.ID, + fmt.Sprintf("%s_%s_%s.deb", name, version, architecture), + fmt.Sprintf("%s|%s", distribution, component), + ) + if err != nil { + return err + } + + if err := packages_service.DeletePackageFile(ctx, pf); err != nil { + return err + } + + has, err := packages_model.HasVersionFileReferences(ctx, pv.ID) + if err != nil { + return err + } + if !has { + pd, err = packages_model.GetPackageDescriptor(ctx, pv) + if err != nil { + return err + } + + if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { + return err + } + } + + return nil + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + if pd != nil { + notification.NotifyPackageDelete(ctx, ctx.Doer, pd) + } + + if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, architecture); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go index 5fba02cacd..0c873119ef 100644 --- a/routers/api/packages/generic/generic.go +++ b/routers/api/packages/generic/generic.go @@ -84,7 +84,7 @@ func UploadPackage(ctx *context.Context) { defer upload.Close() } - buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(upload) if err != nil { log.Error("Error creating hashed buffer: %v", err) apiError(ctx, http.StatusInternalServerError, err) diff --git a/routers/api/packages/helm/helm.go b/routers/api/packages/helm/helm.go index 3bcce6bdf5..b7edc8b7fe 100644 --- a/routers/api/packages/helm/helm.go +++ b/routers/api/packages/helm/helm.go @@ -155,7 +155,7 @@ func UploadPackage(ctx *context.Context) { defer upload.Close() } - buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(upload) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go index a3a23ecfa8..dd270ff0ed 100644 --- a/routers/api/packages/maven/maven.go +++ b/routers/api/packages/maven/maven.go @@ -245,7 +245,7 @@ func UploadPackageFile(ctx *context.Context) { packageName := params.GroupID + "-" + params.ArtifactID - buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index 51b34d3e27..89476a776a 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -189,7 +189,7 @@ func UploadPackage(ctx *context.Context) { } } - buf, err := packages_module.CreateHashedBufferFromReader(bytes.NewReader(npmPackage.Data), 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(bytes.NewReader(npmPackage.Data)) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index 3418bf9959..f6143ce291 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -475,7 +475,7 @@ func UploadSymbolPackage(ctx *context.Context) { Version: np.Version, } - _, _, err = packages_service.AddFileToExistingPackage( + _, err = packages_service.AddFileToExistingPackage( pi, &packages_service.PackageFileCreationInfo{ PackageFileInfo: packages_service.PackageFileInfo{ @@ -501,7 +501,7 @@ func UploadSymbolPackage(ctx *context.Context) { } for _, pdb := range pdbs { - _, _, err := packages_service.AddFileToExistingPackage( + _, err := packages_service.AddFileToExistingPackage( pi, &packages_service.PackageFileCreationInfo{ PackageFileInfo: packages_service.PackageFileInfo{ @@ -545,7 +545,7 @@ func processUploadedFile(ctx *context.Context, expectedType nuget_module.Package closables = append(closables, upload) } - buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(upload) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return nil, nil, closables diff --git a/routers/api/packages/pub/pub.go b/routers/api/packages/pub/pub.go index 1ece4e18ed..ae0c6e7859 100644 --- a/routers/api/packages/pub/pub.go +++ b/routers/api/packages/pub/pub.go @@ -166,7 +166,7 @@ func UploadPackageFile(ctx *context.Context) { } defer file.Close() - buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(file) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/pypi/pypi.go b/routers/api/packages/pypi/pypi.go index 2f71801a8f..90a37ec2a8 100644 --- a/routers/api/packages/pypi/pypi.go +++ b/routers/api/packages/pypi/pypi.go @@ -117,7 +117,7 @@ func UploadPackageFile(ctx *context.Context) { } defer file.Close() - buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(file) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go index af358fb82f..740efa9bab 100644 --- a/routers/api/packages/rubygems/rubygems.go +++ b/routers/api/packages/rubygems/rubygems.go @@ -209,7 +209,7 @@ func UploadPackageFile(ctx *context.Context) { defer upload.Close() } - buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(upload) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go index f78f703778..06f592dd64 100644 --- a/routers/api/packages/swift/swift.go +++ b/routers/api/packages/swift/swift.go @@ -300,7 +300,7 @@ func UploadPackageFile(ctx *context.Context) { } defer file.Close() - buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(file) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/packages/vagrant/vagrant.go b/routers/api/packages/vagrant/vagrant.go index 7b76ab79b0..cefdc45b10 100644 --- a/routers/api/packages/vagrant/vagrant.go +++ b/routers/api/packages/vagrant/vagrant.go @@ -158,7 +158,7 @@ func UploadPackageFile(ctx *context.Context) { defer upload.Close() } - buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(upload) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go index 47fd0ef3c3..ccd8be9171 100644 --- a/routers/api/v1/admin/adopt.go +++ b/routers/api/v1/admin/adopt.go @@ -95,7 +95,7 @@ func AdoptRepository(ctx *context.APIContext) { } // check not a repo - has, err := repo_model.IsRepositoryExist(ctx, ctxUser, repoName) + has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName) if err != nil { ctx.InternalServerError(err) return @@ -157,7 +157,7 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) { } // check not a repo - has, err := repo_model.IsRepositoryExist(ctx, ctxUser, repoName) + has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName) if err != nil { ctx.InternalServerError(err) return diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index ad68a33d6b..9a733b832f 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -719,6 +719,10 @@ func Routes(ctx gocontext.Context) *web.Route { m.Post("/markup", bind(api.MarkupOption{}), misc.Markup) m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown) m.Post("/markdown/raw", misc.MarkdownRaw) + m.Get("/gitignore/templates", misc.ListGitignoresTemplates) + m.Get("/gitignore/templates/{name}", misc.GetGitignoreTemplateInfo) + m.Get("/licenses", misc.ListLicenseTemplates) + m.Get("/licenses/{name}", misc.GetLicenseTemplateInfo) m.Group("/settings", func() { m.Get("/ui", settings.GetGeneralUISettings) m.Get("/api", settings.GetGeneralAPISettings) diff --git a/routers/api/v1/misc/gitignore.go b/routers/api/v1/misc/gitignore.go new file mode 100644 index 0000000000..7c7fe4b125 --- /dev/null +++ b/routers/api/v1/misc/gitignore.go @@ -0,0 +1,56 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package misc + +import ( + "net/http" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/options" + repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" +) + +// Shows a list of all Gitignore templates +func ListGitignoresTemplates(ctx *context.APIContext) { + // swagger:operation GET /gitignore/templates miscellaneous listGitignoresTemplates + // --- + // summary: Returns a list of all gitignore templates + // produces: + // - application/json + // responses: + // "200": + // "$ref": "#/responses/GitignoreTemplateList" + ctx.JSON(http.StatusOK, repo_module.Gitignores) +} + +// SHows information about a gitignore template +func GetGitignoreTemplateInfo(ctx *context.APIContext) { + // swagger:operation GET /gitignore/templates/{name} miscellaneous getGitignoreTemplateInfo + // --- + // summary: Returns information about a gitignore template + // produces: + // - application/json + // parameters: + // - name: name + // in: path + // description: name of the template + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/GitignoreTemplateInfo" + // "404": + // "$ref": "#/responses/notFound" + name := util.PathJoinRelX(ctx.Params("name")) + + text, err := options.Gitignore(name) + if err != nil { + ctx.NotFound() + return + } + + ctx.JSON(http.StatusOK, &structs.GitignoreTemplateInfo{Name: name, Source: string(text)}) +} diff --git a/routers/api/v1/misc/licenses.go b/routers/api/v1/misc/licenses.go new file mode 100644 index 0000000000..65f63468cf --- /dev/null +++ b/routers/api/v1/misc/licenses.go @@ -0,0 +1,76 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package misc + +import ( + "fmt" + "net/http" + "net/url" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/options" + repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" +) + +// Returns a list of all License templates +func ListLicenseTemplates(ctx *context.APIContext) { + // swagger:operation GET /licenses miscellaneous listLicenseTemplates + // --- + // summary: Returns a list of all license templates + // produces: + // - application/json + // responses: + // "200": + // "$ref": "#/responses/LicenseTemplateList" + response := make([]api.LicensesTemplateListEntry, len(repo_module.Licenses)) + for i, license := range repo_module.Licenses { + response[i] = api.LicensesTemplateListEntry{ + Key: license, + Name: license, + URL: fmt.Sprintf("%sapi/v1/licenses/%s", setting.AppURL, url.PathEscape(license)), + } + } + ctx.JSON(http.StatusOK, response) +} + +// Returns information about a gitignore template +func GetLicenseTemplateInfo(ctx *context.APIContext) { + // swagger:operation GET /licenses/{name} miscellaneous getLicenseTemplateInfo + // --- + // summary: Returns information about a license template + // produces: + // - application/json + // parameters: + // - name: name + // in: path + // description: name of the license + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/LicenseTemplateInfo" + // "404": + // "$ref": "#/responses/notFound" + name := util.PathJoinRelX(ctx.Params("name")) + + text, err := options.License(name) + if err != nil { + ctx.NotFound() + return + } + + response := api.LicenseTemplateInfo{ + Key: name, + Name: name, + URL: fmt.Sprintf("%sapi/v1/licenses/%s", setting.AppURL, url.PathEscape(name)), + Body: string(text), + // This is for combatibilty with the GitHub API. This Text is for some reason added to each License response. + Implementation: "Create a text file (typically named LICENSE or LICENSE.txt) in the root of your source code and copy the text of the license into the file", + } + + ctx.JSON(http.StatusOK, response) +} diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go index 301f51eea2..32de2956b7 100644 --- a/routers/api/v1/misc/markup_test.go +++ b/routers/api/v1/misc/markup_test.go @@ -30,7 +30,7 @@ const ( ) func createContext(req *http.Request) (*context.Context, *httptest.ResponseRecorder) { - _, rnd := templates.HTMLRenderer(req.Context()) + rnd := templates.HTMLRenderer() resp := httptest.NewRecorder() c := &context.Context{ Req: req, diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index 200dc5aaf1..ac48eb8a53 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -40,7 +40,7 @@ func ListPackages(ctx *context.APIContext) { // in: query // description: package type filter // type: string - // enum: [cargo, chef, composer, conan, conda, container, generic, helm, maven, npm, nuget, pub, pypi, rubygems, swift, vagrant] + // enum: [cargo, chef, composer, conan, conda, container, debian, generic, helm, maven, npm, nuget, pub, pypi, rubygems, swift, vagrant] // - name: q // in: query // description: name filter diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index 22b013e7dc..401403c83d 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -115,6 +115,10 @@ func GetAllCommits(ctx *context.APIContext) { // in: query // description: page size of results (ignored if used with 'path') // type: integer + // - name: not + // in: query + // description: commits that match the given specifier will not be listed. + // type: string // responses: // "200": // "$ref": "#/responses/CommitList" @@ -181,7 +185,8 @@ func GetAllCommits(ctx *context.APIContext) { } // Query commits - commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize) + not := ctx.FormString("not") + commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not) if err != nil { ctx.Error(http.StatusInternalServerError, "CommitsByRange", err) return diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index 5b564a8066..e1e79c0e78 100644 --- a/routers/api/v1/repo/fork.go +++ b/routers/api/v1/repo/fork.go @@ -141,7 +141,7 @@ func CreateFork(ctx *context.APIContext) { Description: repo.Description, }) if err != nil { - if repo_model.IsErrReachLimitOfRepo(err) || repo_model.IsErrRepoAlreadyExist(err) { + if repo_service.IsErrForkAlreadyExist(err) || repo_model.IsErrRepoAlreadyExist(err) || repo_model.IsErrReachLimitOfRepo(err) { ctx.Error(http.StatusConflict, "ForkRepository", err) } else { ctx.Error(http.StatusInternalServerError, "ForkRepository", err) diff --git a/routers/api/v1/swagger/misc.go b/routers/api/v1/swagger/misc.go index a4052a6a76..5778b659b5 100644 --- a/routers/api/v1/swagger/misc.go +++ b/routers/api/v1/swagger/misc.go @@ -14,6 +14,34 @@ type swaggerResponseServerVersion struct { Body api.ServerVersion `json:"body"` } +// GitignoreTemplateList +// swagger:response GitignoreTemplateList +type swaggerResponseGitignoreTemplateList struct { + // in:body + Body []string `json:"body"` +} + +// GitignoreTemplateInfo +// swagger:response GitignoreTemplateInfo +type swaggerResponseGitignoreTemplateInfo struct { + // in:body + Body api.GitignoreTemplateInfo `json:"body"` +} + +// LicenseTemplateList +// swagger:response LicenseTemplateList +type swaggerResponseLicensesTemplateList struct { + // in:body + Body []api.LicensesTemplateListEntry `json:"body"` +} + +// LicenseTemplateInfo +// swagger:response LicenseTemplateInfo +type swaggerResponseLicenseTemplateInfo struct { + // in:body + Body api.LicenseTemplateInfo `json:"body"` +} + // StringSlice // swagger:response StringSlice type swaggerResponseStringSlice struct { diff --git a/routers/common/middleware.go b/routers/common/middleware.go index 2abdcb583d..28ecf934e1 100644 --- a/routers/common/middleware.go +++ b/routers/common/middleware.go @@ -15,24 +15,23 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/routing" + "gitea.com/go-chi/session" "github.com/chi-middleware/proxy" chi "github.com/go-chi/chi/v5" ) -// Middlewares returns common middlewares -func Middlewares() []func(http.Handler) http.Handler { - handlers := []func(http.Handler) http.Handler{ - func(next http.Handler) http.Handler { - return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { - // First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL - req.URL.RawPath = req.URL.EscapedPath() +// ProtocolMiddlewares returns HTTP protocol related middlewares +func ProtocolMiddlewares() (handlers []any) { + handlers = append(handlers, func(next http.Handler) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + // First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL + req.URL.RawPath = req.URL.EscapedPath() - ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true) - defer finished() - next.ServeHTTP(context.NewResponse(resp), req.WithContext(cache.WithCacheContext(ctx))) - }) - }, - } + ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true) + defer finished() + next.ServeHTTP(context.NewResponse(resp), req.WithContext(cache.WithCacheContext(ctx))) + }) + }) if setting.ReverseProxyLimit > 0 { opt := proxy.NewForwardedHeadersOptions(). @@ -112,3 +111,17 @@ func stripSlashesMiddleware(next http.Handler) http.Handler { next.ServeHTTP(resp, req) }) } + +func Sessioner() func(next http.Handler) http.Handler { + return session.Sessioner(session.Options{ + Provider: setting.SessionConfig.Provider, + ProviderConfig: setting.SessionConfig.ProviderConfig, + CookieName: setting.SessionConfig.CookieName, + CookiePath: setting.SessionConfig.CookiePath, + Gclifetime: setting.SessionConfig.Gclifetime, + Maxlifetime: setting.SessionConfig.Maxlifetime, + Secure: setting.SessionConfig.Secure, + SameSite: setting.SessionConfig.SameSite, + Domain: setting.SessionConfig.Domain, + }) +} diff --git a/routers/init.go b/routers/init.go index af768abbf4..358922b1ae 100644 --- a/routers/init.go +++ b/routers/init.go @@ -175,22 +175,17 @@ func GlobalInitInstalled(ctx context.Context) { // NormalRoutes represents non install routes func NormalRoutes(ctx context.Context) *web.Route { - ctx, _ = templates.HTMLRenderer(ctx) + _ = templates.HTMLRenderer() r := web.NewRoute() - for _, middle := range common.Middlewares() { - r.Use(middle) - } + r.Use(common.ProtocolMiddlewares()...) r.Mount("/", web_routers.Routes(ctx)) r.Mount("/api/v1", apiv1.Routes(ctx)) r.Mount("/api/internal", private.Routes()) if setting.Packages.Enabled { - // Add endpoints to match common package manager APIs - // This implements package support for most package managers r.Mount("/api/packages", packages_router.CommonRoutes(ctx)) - // This implements the OCI API (Note this is not preceded by /api but is instead /v2) r.Mount("/v2", packages_router.ContainerRoutes(ctx)) } diff --git a/routers/install/install.go b/routers/install/install.go index 8f8656230a..c838db6582 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -55,7 +55,7 @@ func getSupportedDbTypeNames() (dbTypeNames []map[string]string) { // Init prepare for rendering installation page func Init(ctx goctx.Context) func(next http.Handler) http.Handler { - _, rnd := templates.HTMLRenderer(ctx) + rnd := templates.HTMLRenderer() dbTypeNames := getSupportedDbTypeNames() return func(next http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { diff --git a/routers/install/routes.go b/routers/install/routes.go index f52539ec1b..88c7e99695 100644 --- a/routers/install/routes.go +++ b/routers/install/routes.go @@ -19,8 +19,6 @@ import ( "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/routers/web/healthcheck" "code.gitea.io/gitea/services/forms" - - "gitea.com/go-chi/session" ) type dataStore map[string]interface{} @@ -30,7 +28,6 @@ func (d *dataStore) GetData() map[string]interface{} { } func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler { - _, rnd := templates.HTMLRenderer(ctx) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { defer func() { @@ -69,6 +66,7 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler { if !setting.IsProd { store["ErrorMsg"] = combinedErr } + rnd := templates.HTMLRenderer() err = rnd.HTML(w, http.StatusInternalServerError, "status/500", templates.BaseVars().Merge(store)) if err != nil { log.Error("%v", err) @@ -83,34 +81,22 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler { // Routes registers the installation routes func Routes(ctx goctx.Context) *web.Route { + base := web.NewRoute() + base.Use(common.ProtocolMiddlewares()...) + base.RouteMethods("/assets/*", "GET, HEAD", public.AssetsHandlerFunc("/assets/")) + r := web.NewRoute() - for _, middle := range common.Middlewares() { - r.Use(middle) - } - - r.Use(web.MiddlewareWithPrefix("/assets/", nil, public.AssetsHandlerFunc("/assets/"))) - - r.Use(session.Sessioner(session.Options{ - Provider: setting.SessionConfig.Provider, - ProviderConfig: setting.SessionConfig.ProviderConfig, - CookieName: setting.SessionConfig.CookieName, - CookiePath: setting.SessionConfig.CookiePath, - Gclifetime: setting.SessionConfig.Gclifetime, - Maxlifetime: setting.SessionConfig.Maxlifetime, - Secure: setting.SessionConfig.Secure, - SameSite: setting.SessionConfig.SameSite, - Domain: setting.SessionConfig.Domain, - })) - + r.Use(common.Sessioner()) r.Use(installRecovery(ctx)) r.Use(Init(ctx)) r.Get("/", Install) // it must be on the root, because the "install.js" use the window.location to replace the "localhost" AppURL r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall) r.Get("/post-install", InstallDone) r.Get("/api/healthz", healthcheck.Check) - r.NotFound(installNotFound) - return r + + base.Mount("", r) + return base } func installNotFound(w http.ResponseWriter, req *http.Request) { diff --git a/routers/install/routes_test.go b/routers/install/routes_test.go index 35b5e5e916..e3d2a42467 100644 --- a/routers/install/routes_test.go +++ b/routers/install/routes_test.go @@ -11,11 +11,14 @@ import ( ) func TestRoutes(t *testing.T) { + // TODO: this test seems not really testing the handlers ctx, cancel := context.WithCancel(context.Background()) defer cancel() - routes := Routes(ctx) - assert.NotNil(t, routes) - assert.EqualValues(t, "/", routes.R.Routes()[0].Pattern) - assert.Nil(t, routes.R.Routes()[0].SubRoutes) - assert.Len(t, routes.R.Routes()[0].Handlers, 2) + base := Routes(ctx) + assert.NotNil(t, base) + r := base.R.Routes()[1] + routes := r.SubRoutes.Routes()[0] + assert.EqualValues(t, "/", routes.Pattern) + assert.Nil(t, routes.SubRoutes) + assert.Len(t, routes.Handlers, 2) } diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go index 9847a6d923..35c387c28b 100644 --- a/routers/web/admin/admin.go +++ b/routers/web/admin/admin.go @@ -113,7 +113,6 @@ func updateSystemStatus() { // Dashboard show admin panel dashboard func Dashboard(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.dashboard") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminDashboard"] = true ctx.Data["Stats"] = activities_model.GetStatistic() ctx.Data["NeedUpdate"] = updatechecker.GetNeedUpdate() @@ -129,7 +128,6 @@ func Dashboard(ctx *context.Context) { func DashboardPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.AdminDashboardForm) ctx.Data["Title"] = ctx.Tr("admin.dashboard") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminDashboard"] = true ctx.Data["Stats"] = activities_model.GetStatistic() updateSystemStatus() @@ -155,7 +153,6 @@ func DashboardPost(ctx *context.Context) { // Monitor show admin monitor page func Monitor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.monitor") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminMonitor"] = true ctx.Data["Processes"], ctx.Data["ProcessCount"] = process.GetManager().Processes(false, true) ctx.Data["Entries"] = cron.ListTasks() @@ -167,7 +164,6 @@ func Monitor(ctx *context.Context) { // GoroutineStacktrace show admin monitor goroutines page func GoroutineStacktrace(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.monitor") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminMonitor"] = true processStacks, processCount, goroutineCount, err := process.GetManager().ProcessStacktraces(false, false) @@ -202,7 +198,6 @@ func Queue(ctx *context.Context) { return } ctx.Data["Title"] = ctx.Tr("admin.monitor.queue", mq.Name) - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminMonitor"] = true ctx.Data["Queue"] = mq ctx.HTML(http.StatusOK, tplQueue) diff --git a/routers/web/admin/applications.go b/routers/web/admin/applications.go index 745d17ff2a..7b27524340 100644 --- a/routers/web/admin/applications.go +++ b/routers/web/admin/applications.go @@ -31,7 +31,6 @@ func newOAuth2CommonHandlers() *user_setting.OAuth2CommonHandlers { // Applications render org applications page (for org, at the moment, there are only OAuth2 applications) func Applications(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings.applications") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminApplications"] = true apps, err := auth.GetOAuth2ApplicationsByUserID(ctx, 0) @@ -47,7 +46,6 @@ func Applications(ctx *context.Context) { // ApplicationsPost response for adding an oauth2 application func ApplicationsPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings.applications") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminApplications"] = true oa := newOAuth2CommonHandlers() @@ -56,7 +54,6 @@ func ApplicationsPost(ctx *context.Context) { // EditApplication displays the given application func EditApplication(ctx *context.Context) { - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminApplications"] = true oa := newOAuth2CommonHandlers() @@ -66,7 +63,6 @@ func EditApplication(ctx *context.Context) { // EditApplicationPost response for editing oauth2 application func EditApplicationPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings.applications") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminApplications"] = true oa := newOAuth2CommonHandlers() @@ -76,7 +72,6 @@ func EditApplicationPost(ctx *context.Context) { // ApplicationsRegenerateSecret handles the post request for regenerating the secret func ApplicationsRegenerateSecret(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminApplications"] = true oa := newOAuth2CommonHandlers() diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 6ebd23b7bb..b6ea3ff403 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -45,7 +45,6 @@ var ( // Authentications show authentication config page func Authentications(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.authentication") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminAuthentications"] = true var err error @@ -89,7 +88,6 @@ var ( // NewAuthSource render adding a new auth source page func NewAuthSource(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.auths.new") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminAuthentications"] = true ctx.Data["type"] = auth.LDAP.Int() @@ -237,7 +235,6 @@ func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi func NewAuthSourcePost(ctx *context.Context) { form := *web.GetForm(ctx).(*forms.AuthenticationForm) ctx.Data["Title"] = ctx.Tr("admin.auths.new") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminAuthentications"] = true ctx.Data["CurrentTypeName"] = auth.Type(form.Type).String() @@ -333,7 +330,6 @@ func NewAuthSourcePost(ctx *context.Context) { // EditAuthSource render editing auth source page func EditAuthSource(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.auths.edit") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminAuthentications"] = true ctx.Data["SecurityProtocols"] = securityProtocols @@ -369,7 +365,6 @@ func EditAuthSource(ctx *context.Context) { func EditAuthSourcePost(ctx *context.Context) { form := *web.GetForm(ctx).(*forms.AuthenticationForm) ctx.Data["Title"] = ctx.Tr("admin.auths.edit") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminAuthentications"] = true ctx.Data["SMTPAuths"] = smtp.Authenticators diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go index 48815dcff8..7460ea24a7 100644 --- a/routers/web/admin/config.go +++ b/routers/web/admin/config.go @@ -100,7 +100,6 @@ func shadowPassword(provider, cfgItem string) string { // Config show admin config page func Config(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.config") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminConfig"] = true systemSettings, err := system_model.GetAllSettings(ctx) diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index c16158c6ae..4618a78c3a 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -24,7 +24,6 @@ const ( // Emails show all emails func Emails(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.emails") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminEmails"] = true opts := &user_model.SearchEmailOptions{ diff --git a/routers/web/admin/notice.go b/routers/web/admin/notice.go index f60cf90b3c..9e4588dd75 100644 --- a/routers/web/admin/notice.go +++ b/routers/web/admin/notice.go @@ -22,7 +22,6 @@ const ( // Notices show notices for admin func Notices(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.notices") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminNotices"] = true total := system_model.CountNotices() diff --git a/routers/web/admin/orgs.go b/routers/web/admin/orgs.go index a4eb0af58d..6a3617d67f 100644 --- a/routers/web/admin/orgs.go +++ b/routers/web/admin/orgs.go @@ -21,7 +21,6 @@ const ( // Organizations show all the organizations func Organizations(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.organizations") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminOrganizations"] = true explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{ diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go index 88fb47ca01..4d5987e780 100644 --- a/routers/web/admin/packages.go +++ b/routers/web/admin/packages.go @@ -64,7 +64,6 @@ func Packages(ctx *context.Context) { } ctx.Data["Title"] = ctx.Tr("packages.title") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminPackages"] = true ctx.Data["Query"] = query ctx.Data["PackageType"] = packageType diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go index 53b609af96..9a0e467b48 100644 --- a/routers/web/admin/repos.go +++ b/routers/web/admin/repos.go @@ -29,7 +29,6 @@ const ( // Repos show all the repositories func Repos(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.repositories") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminRepositories"] = true explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{ @@ -67,7 +66,6 @@ func DeleteRepo(ctx *context.Context) { // UnadoptedRepos lists the unadopted repositories func UnadoptedRepos(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.repositories") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminRepositories"] = true opts := db.ListOptions{ @@ -135,7 +133,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { repoName := dirSplit[1] // check not a repo - has, err := repo_model.IsRepositoryExist(ctx, ctxUser, repoName) + has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, repoName) if err != nil { ctx.ServerError("IsRepositoryExist", err) return diff --git a/routers/web/admin/runners.go b/routers/web/admin/runners.go index a70b53fdcb..eaa268b4f1 100644 --- a/routers/web/admin/runners.go +++ b/routers/web/admin/runners.go @@ -4,75 +4,10 @@ package admin import ( - "net/url" - - actions_model "code.gitea.io/gitea/models/actions" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" - actions_shared "code.gitea.io/gitea/routers/web/shared/actions" ) -const ( - tplRunners base.TplName = "admin/runners/base" - tplRunnerEdit base.TplName = "admin/runners/edit" -) - -// Runners show all the runners -func Runners(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("actions.runners") - ctx.Data["PageIsAdmin"] = true - ctx.Data["PageIsAdminRunners"] = true - - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - opts := actions_model.FindRunnerOptions{ - ListOptions: db.ListOptions{ - Page: page, - PageSize: 100, - }, - Sort: ctx.Req.URL.Query().Get("sort"), - Filter: ctx.Req.URL.Query().Get("q"), - } - - actions_shared.RunnersList(ctx, tplRunners, opts) -} - -// EditRunner show editing runner page -func EditRunner(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("actions.runners.edit_runner") - ctx.Data["PageIsAdmin"] = true - ctx.Data["PageIsAdminRunners"] = true - - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - actions_shared.RunnerDetails(ctx, tplRunnerEdit, page, ctx.ParamsInt64(":runnerid"), 0, 0) -} - -// EditRunnerPost response for editing runner -func EditRunnerPost(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("actions.runners.edit") - ctx.Data["PageIsAdmin"] = true - ctx.Data["PageIsAdminRunners"] = true - actions_shared.RunnerDetailsEditPost(ctx, ctx.ParamsInt64(":runnerid"), 0, 0, - setting.AppSubURL+"/admin/runners/"+url.PathEscape(ctx.Params(":runnerid"))) -} - -// DeleteRunnerPost response for deleting a runner -func DeleteRunnerPost(ctx *context.Context) { - actions_shared.RunnerDeletePost(ctx, ctx.ParamsInt64(":runnerid"), - setting.AppSubURL+"/admin/runners/", - setting.AppSubURL+"/admin/runners/"+url.PathEscape(ctx.Params(":runnerid")), - ) -} - -func ResetRunnerRegistrationToken(ctx *context.Context) { - actions_shared.RunnerResetRegistrationToken(ctx, 0, 0, setting.AppSubURL+"/admin/runners/") +func RedirectToDefaultSetting(ctx *context.Context) { + ctx.Redirect(setting.AppSubURL + "/admin/actions/runners") } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 531f14d086..2150bc42f7 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -37,7 +37,6 @@ const ( // Users show all the users func Users(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.users") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminUsers"] = true extraParamStrings := map[string]string{} @@ -80,7 +79,6 @@ func Users(ctx *context.Context) { // NewUser render adding a new user page func NewUser(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.users.new_account") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminUsers"] = true ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() @@ -102,7 +100,6 @@ func NewUser(ctx *context.Context) { func NewUserPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.AdminCreateUserForm) ctx.Data["Title"] = ctx.Tr("admin.users.new_account") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminUsers"] = true ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() @@ -253,7 +250,6 @@ func prepareUserInfo(ctx *context.Context) *user_model.User { // EditUser show editing user page func EditUser(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.users.edit_account") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminUsers"] = true ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations @@ -271,7 +267,6 @@ func EditUser(ctx *context.Context) { func EditUserPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.AdminEditUserForm) ctx.Data["Title"] = ctx.Tr("admin.users.edit_account") - ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminUsers"] = true ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() diff --git a/routers/web/base.go b/routers/web/base.go index 79991d89db..73607cad06 100644 --- a/routers/web/base.go +++ b/routers/web/base.go @@ -59,12 +59,7 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor return } - http.Redirect( - w, - req, - u.String(), - http.StatusTemporaryRedirect, - ) + http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect) }) } @@ -122,10 +117,10 @@ func (d *dataStore) GetData() map[string]interface{} { return *d } -// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so. +// RecoveryWith500Page returns a middleware that recovers from any panics and writes a 500 and a log if so. // This error will be created with the gitea 500 page. -func Recovery(ctx goctx.Context) func(next http.Handler) http.Handler { - _, rnd := templates.HTMLRenderer(ctx) +func RecoveryWith500Page(ctx goctx.Context) func(next http.Handler) http.Handler { + rnd := templates.HTMLRenderer() return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { defer func() { diff --git a/routers/web/feed/branch.go b/routers/web/feed/branch.go index 22b6e2f14b..f13038ff9b 100644 --- a/routers/web/feed/branch.go +++ b/routers/web/feed/branch.go @@ -16,7 +16,7 @@ import ( // ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) { - commits, err := ctx.Repo.Commit.CommitsByRange(0, 10) + commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "") if err != nil { ctx.ServerError("ShowBranchFeed", err) return diff --git a/routers/web/misc/misc.go b/routers/web/misc/misc.go new file mode 100644 index 0000000000..582179990a --- /dev/null +++ b/routers/web/misc/misc.go @@ -0,0 +1,49 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package misc + +import ( + "net/http" + "os" + "path" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/httpcache" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +func SSHInfo(rw http.ResponseWriter, req *http.Request) { + if !git.SupportProcReceive { + rw.WriteHeader(http.StatusNotFound) + return + } + rw.Header().Set("content-type", "text/json;charset=UTF-8") + _, err := rw.Write([]byte(`{"type":"gitea","version":1}`)) + if err != nil { + log.Error("fail to write result: err: %v", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + rw.WriteHeader(http.StatusOK) +} + +func DummyOK(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusOK) +} + +func RobotsTxt(w http.ResponseWriter, req *http.Request) { + filePath := path.Join(setting.CustomPath, "robots.txt") + fi, err := os.Stat(filePath) + if err == nil && httpcache.HandleTimeCache(req, w, fi) { + return + } + http.ServeFile(w, req, filePath) +} + +func StaticRedirect(target string) func(w http.ResponseWriter, req *http.Request) { + return func(w http.ResponseWriter, req *http.Request) { + http.Redirect(w, req, path.Join(setting.StaticURLPrefix, target), http.StatusMovedPermanently) + } +} diff --git a/routers/web/org/home.go b/routers/web/org/home.go index 201eefa614..7f38ec51ba 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/markup" @@ -143,6 +144,11 @@ func Home(ctx *context.Context) { return } + var isFollowing bool + if ctx.Doer != nil { + isFollowing = user_model.IsFollowing(ctx.Doer.ID, ctx.ContextUser.ID) + } + ctx.Data["Repos"] = repos ctx.Data["Total"] = count ctx.Data["MembersTotal"] = membersCount @@ -150,6 +156,7 @@ func Home(ctx *context.Context) { ctx.Data["Teams"] = ctx.Org.Teams ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull ctx.Data["PageIsViewRepositories"] = true + ctx.Data["IsFollowing"] = isFollowing pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5) pager.SetDefaultParams(ctx) diff --git a/routers/web/org/org_runners.go b/routers/web/org/org_runners.go deleted file mode 100644 index f87a506504..0000000000 --- a/routers/web/org/org_runners.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package org - -import ( - "net/url" - - actions_model "code.gitea.io/gitea/models/actions" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/context" - actions_shared "code.gitea.io/gitea/routers/web/shared/actions" -) - -// Runners render runners page -func Runners(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("org.runners") - ctx.Data["PageIsOrgSettings"] = true - ctx.Data["PageIsOrgSettingsRunners"] = true - - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - opts := actions_model.FindRunnerOptions{ - ListOptions: db.ListOptions{ - Page: page, - PageSize: 100, - }, - Sort: ctx.Req.URL.Query().Get("sort"), - Filter: ctx.Req.URL.Query().Get("q"), - OwnerID: ctx.Org.Organization.ID, - WithAvailable: true, - } - - actions_shared.RunnersList(ctx, tplSettingsRunners, opts) -} - -// ResetRunnerRegistrationToken reset runner registration token -func ResetRunnerRegistrationToken(ctx *context.Context) { - actions_shared.RunnerResetRegistrationToken(ctx, - ctx.Org.Organization.ID, 0, - ctx.Org.OrgLink+"/settings/runners") -} - -// RunnersEdit render runner edit page -func RunnersEdit(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("org.runners.edit") - ctx.Data["PageIsOrgSettings"] = true - ctx.Data["PageIsOrgSettingsRunners"] = true - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - actions_shared.RunnerDetails(ctx, tplSettingsRunnersEdit, page, - ctx.ParamsInt64(":runnerid"), ctx.Org.Organization.ID, 0, - ) -} - -// RunnersEditPost response for editing runner -func RunnersEditPost(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("org.runners.edit") - ctx.Data["PageIsOrgSettings"] = true - ctx.Data["PageIsOrgSettingsRunners"] = true - actions_shared.RunnerDetailsEditPost(ctx, ctx.ParamsInt64(":runnerid"), - ctx.Org.Organization.ID, 0, - ctx.Org.OrgLink+"/settings/runners/"+url.PathEscape(ctx.Params(":runnerid"))) -} - -// RunnerDeletePost response for deleting runner -func RunnerDeletePost(ctx *context.Context) { - actions_shared.RunnerDeletePost(ctx, - ctx.ParamsInt64(":runnerid"), - ctx.Org.OrgLink+"/settings/runners", - ctx.Org.OrgLink+"/settings/runners/"+url.PathEscape(ctx.Params(":runnerid"))) -} diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index db8fc728df..4111b13531 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -37,10 +37,6 @@ const ( tplSettingsHooks base.TplName = "org/settings/hooks" // tplSettingsLabels template path for render labels settings tplSettingsLabels base.TplName = "org/settings/labels" - // tplSettingsRunners template path for render runners settings - tplSettingsRunners base.TplName = "org/settings/runners" - // tplSettingsRunnersEdit template path for render runners edit settings - tplSettingsRunnersEdit base.TplName = "org/settings/runners_edit" ) // Settings render the main settings page diff --git a/routers/web/org/setting/runners.go b/routers/web/org/setting/runners.go new file mode 100644 index 0000000000..c3c771036a --- /dev/null +++ b/routers/web/org/setting/runners.go @@ -0,0 +1,12 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "code.gitea.io/gitea/modules/context" +) + +func RedirectToDefaultSetting(ctx *context.Context) { + ctx.Redirect(ctx.Org.OrgLink + "/settings/actions/runners") +} diff --git a/routers/web/org/setting_secrets.go b/routers/web/org/setting_secrets.go deleted file mode 100644 index 580a14015b..0000000000 --- a/routers/web/org/setting_secrets.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package org - -import ( - "net/http" - - "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/context" - shared "code.gitea.io/gitea/routers/web/shared/secrets" -) - -const ( - tplSettingsSecrets base.TplName = "org/settings/secrets" -) - -// Secrets render organization secrets page -func Secrets(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("secrets.secrets") - ctx.Data["PageIsOrgSettings"] = true - ctx.Data["PageIsOrgSettingsSecrets"] = true - - shared.SetSecretsContext(ctx, ctx.ContextUser.ID, 0) - if ctx.Written() { - return - } - - ctx.HTML(http.StatusOK, tplSettingsSecrets) -} - -// SecretsPost add secrets -func SecretsPost(ctx *context.Context) { - shared.PerformSecretsPost( - ctx, - ctx.ContextUser.ID, - 0, - ctx.Org.OrgLink+"/settings/secrets", - ) -} - -// SecretsDelete delete secrets -func SecretsDelete(ctx *context.Context) { - shared.PerformSecretsDelete( - ctx, - ctx.ContextUser.ID, - 0, - ctx.Org.OrgLink+"/settings/secrets", - ) -} diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index bae8e5a003..2ce4bf5322 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -86,9 +86,17 @@ func TeamsAction(ctx *context.Context) { return } } + + redirect := ctx.Org.OrgLink + "/teams/" + if isOrgMember, err := org_model.IsOrganizationMember(ctx, ctx.Org.Organization.ID, ctx.Doer.ID); err != nil { + ctx.ServerError("IsOrganizationMember", err) + return + } else if !isOrgMember { + redirect = setting.AppSubURL + "/" + } ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Org.OrgLink + "/teams/", + "redirect": redirect, }) return case "remove": diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index c553aef9ae..f96cd2acf8 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -55,6 +55,7 @@ type ViewResponse struct { Status string `json:"status"` CanCancel bool `json:"canCancel"` CanApprove bool `json:"canApprove"` // the run needs an approval and the doer has permission to approve + CanRerun bool `json:"canRerun"` Done bool `json:"done"` Jobs []*ViewJob `json:"jobs"` Commit ViewCommit `json:"commit"` @@ -136,6 +137,7 @@ func ViewPost(ctx *context_module.Context) { resp.State.Run.Link = run.Link() resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions) resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions) + resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions) resp.State.Run.Done = run.Status.IsDone() resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json resp.State.Run.Status = run.Status.String() @@ -238,7 +240,7 @@ func ViewPost(ctx *context_module.Context) { ctx.JSON(http.StatusOK, resp) } -func Rerun(ctx *context_module.Context) { +func RerunOne(ctx *context_module.Context) { runIndex := ctx.ParamsInt64("run") jobIndex := ctx.ParamsInt64("job") @@ -246,10 +248,37 @@ func Rerun(ctx *context_module.Context) { if ctx.Written() { return } + + if err := rerunJob(ctx, job); err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + + ctx.JSON(http.StatusOK, struct{}{}) +} + +func RerunAll(ctx *context_module.Context) { + runIndex := ctx.ParamsInt64("run") + + _, jobs := getRunJobs(ctx, runIndex, 0) + if ctx.Written() { + return + } + + for _, j := range jobs { + if err := rerunJob(ctx, j); err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + } + + ctx.JSON(http.StatusOK, struct{}{}) +} + +func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob) error { status := job.Status if !status.IsDone() { - ctx.JSON(http.StatusOK, struct{}{}) - return + return nil } job.TaskID = 0 @@ -261,13 +290,11 @@ func Rerun(ctx *context_module.Context) { _, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "task_id", "status", "started", "stopped") return err }); err != nil { - ctx.Error(http.StatusInternalServerError, err.Error()) - return + return err } actions_service.CreateCommitStatus(ctx, job) - - ctx.JSON(http.StatusOK, struct{}{}) + return nil } func Cancel(ctx *context_module.Context) { diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go index 9fb9cb00bf..c6ea4e3cdb 100644 --- a/routers/web/repo/attachment.go +++ b/routers/web/repo/attachment.go @@ -110,6 +110,11 @@ func ServeAttachment(ctx *context.Context, uuid string) { return } } else { // If we have the repository we check access + context.CheckRepoScopedToken(ctx, repository) + if ctx.Written() { + return + } + perm, err := access_model.GetUserRepoPermission(ctx, repository, ctx.Doer) if err != nil { ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err.Error()) diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 7439c2411b..93294f8ddd 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -70,7 +70,7 @@ func Commits(ctx *context.Context) { } // Both `git log branchName` and `git log commitId` work. - commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize) + commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "") if err != nil { ctx.ServerError("CommitsByRange", err) return diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index a01bb4f28e..4e45a9b6e2 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -19,7 +19,7 @@ import ( "time" actions_model "code.gitea.io/gitea/models/actions" - "code.gitea.io/gitea/models/auth" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" @@ -152,13 +152,18 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { return } + context.CheckRepoScopedToken(ctx, repo) + if ctx.Written() { + return + } + if ctx.IsBasicAuth && ctx.Data["IsApiToken"] != true && ctx.Data["IsActionsToken"] != true { - _, err = auth.GetTwoFactorByUID(ctx.Doer.ID) + _, err = auth_model.GetTwoFactorByUID(ctx.Doer.ID) if err == nil { // TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented ctx.PlainText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page") return - } else if !auth.IsErrTwoFactorNotEnrolled(err) { + } else if !auth_model.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("IsErrTwoFactorNotEnrolled", err) return } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index bea35785bb..5c96d326a7 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -241,7 +241,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) var mileIDs []int64 - if milestoneID > 0 { + if milestoneID > 0 || milestoneID == db.NoConditionID { // -1 to get those issues which have no any milestone assigned mileIDs = []int64{milestoneID} } @@ -438,14 +438,8 @@ func Issues(ctx *context.Context) { return } - var err error - // Get milestones - ctx.Data["Milestones"], _, err = issues_model.GetMilestones(issues_model.GetMilestonesOption{ - RepoID: ctx.Repo.Repository.ID, - State: api.StateType(ctx.FormString("state")), - }) - if err != nil { - ctx.ServerError("GetAllRepoMilestones", err) + renderMilestones(ctx) + if ctx.Written() { return } @@ -454,6 +448,29 @@ func Issues(ctx *context.Context) { ctx.HTML(http.StatusOK, tplIssues) } +func renderMilestones(ctx *context.Context) { + // Get milestones + milestones, _, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{ + RepoID: ctx.Repo.Repository.ID, + State: api.StateAll, + }) + if err != nil { + ctx.ServerError("GetAllRepoMilestones", err) + return + } + + openMilestones, closedMilestones := issues_model.MilestoneList{}, issues_model.MilestoneList{} + for _, milestone := range milestones { + if milestone.IsClosed { + closedMilestones = append(closedMilestones, milestone) + } else { + openMilestones = append(openMilestones, milestone) + } + } + ctx.Data["OpenMilestones"] = openMilestones + ctx.Data["ClosedMilestones"] = closedMilestones +} + // RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.Repository) { var err error @@ -2689,7 +2706,7 @@ func UpdateIssueStatus(ctx *context.Context) { if err := issue_service.ChangeStatus(issue, ctx.Doer, "", isClosed); err != nil { if issues_model.IsErrDependenciesLeft(err) { ctx.JSON(http.StatusPreconditionFailed, map[string]interface{}{ - "error": "cannot close this issue because it still has open dependencies", + "error": ctx.Tr("repo.issues.dependency.issue_batch_close_blocked", issue.Index), }) return } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index b50c057f5f..88eae46562 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -18,7 +18,9 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -59,6 +61,22 @@ func MustBeAbleToUpload(ctx *context.Context) { } } +func CommitInfoCache(ctx *context.Context) { + var err error + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) + if err != nil { + ctx.ServerError("GetBranchCommit", err) + return + } + ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() + if err != nil { + ctx.ServerError("GetCommitsCount", err) + return + } + ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount + ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache()) +} + func checkContextUser(ctx *context.Context, uid int64) *user_model.User { orgs, err := organization.GetOrgsCanCreateRepoByUserID(ctx.Doer.ID) if err != nil { diff --git a/routers/web/repo/runners.go b/routers/web/repo/runners.go deleted file mode 100644 index d3dbff17fd..0000000000 --- a/routers/web/repo/runners.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package repo - -import ( - "net/url" - - actions_model "code.gitea.io/gitea/models/actions" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/context" - actions_shared "code.gitea.io/gitea/routers/web/shared/actions" -) - -const ( - tplRunners = "repo/settings/runners" - tplRunnerEdit = "repo/settings/runner_edit" -) - -// Runners render runners page -func Runners(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("actions.runners") - ctx.Data["PageIsSettingsRunners"] = true - - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - opts := actions_model.FindRunnerOptions{ - ListOptions: db.ListOptions{ - Page: page, - PageSize: 100, - }, - Sort: ctx.Req.URL.Query().Get("sort"), - Filter: ctx.Req.URL.Query().Get("q"), - RepoID: ctx.Repo.Repository.ID, - WithAvailable: true, - } - - actions_shared.RunnersList(ctx, tplRunners, opts) -} - -func RunnersEdit(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("actions.runners") - ctx.Data["PageIsSettingsRunners"] = true - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - actions_shared.RunnerDetails(ctx, tplRunnerEdit, page, - ctx.ParamsInt64(":runnerid"), 0, ctx.Repo.Repository.ID, - ) -} - -func RunnersEditPost(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("actions.runners") - ctx.Data["PageIsSettingsRunners"] = true - actions_shared.RunnerDetailsEditPost(ctx, ctx.ParamsInt64(":runnerid"), - 0, ctx.Repo.Repository.ID, - ctx.Repo.RepoLink+"/settings/runners/"+url.PathEscape(ctx.Params(":runnerid"))) -} - -func ResetRunnerRegistrationToken(ctx *context.Context) { - actions_shared.RunnerResetRegistrationToken(ctx, - 0, ctx.Repo.Repository.ID, - ctx.Repo.RepoLink+"/settings/runners") -} - -// RunnerDeletePost response for deleting runner -func RunnerDeletePost(ctx *context.Context) { - actions_shared.RunnerDeletePost(ctx, ctx.ParamsInt64(":runnerid"), - ctx.Repo.RepoLink+"/settings/runners", - ctx.Repo.RepoLink+"/settings/runners/"+url.PathEscape(ctx.Params(":runnerid"))) -} diff --git a/routers/web/repo/setting/runners.go b/routers/web/repo/setting/runners.go new file mode 100644 index 0000000000..e8ad635826 --- /dev/null +++ b/routers/web/repo/setting/runners.go @@ -0,0 +1,167 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "errors" + "net/http" + "net/url" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + actions_shared "code.gitea.io/gitea/routers/web/shared/actions" +) + +const ( + // TODO: Separate secrets from runners when layout is ready + tplRepoRunners base.TplName = "repo/settings/actions" + tplOrgRunners base.TplName = "org/settings/actions" + tplAdminRunners base.TplName = "admin/actions" + tplRepoRunnerEdit base.TplName = "repo/settings/runner_edit" + tplOrgRunnerEdit base.TplName = "org/settings/runners_edit" + tplAdminRunnerEdit base.TplName = "admin/runners/edit" +) + +type runnersCtx struct { + OwnerID int64 + RepoID int64 + IsRepo bool + IsOrg bool + IsAdmin bool + RunnersTemplate base.TplName + RunnerEditTemplate base.TplName + RedirectLink string +} + +func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) { + if ctx.Data["PageIsRepoSettings"] == true { + return &runnersCtx{ + RepoID: ctx.Repo.Repository.ID, + OwnerID: 0, + IsRepo: true, + RunnersTemplate: tplRepoRunners, + RunnerEditTemplate: tplRepoRunnerEdit, + RedirectLink: ctx.Repo.RepoLink + "/settings/actions/runners/", + }, nil + } + + if ctx.Data["PageIsOrgSettings"] == true { + return &runnersCtx{ + RepoID: 0, + OwnerID: ctx.Org.Organization.ID, + IsOrg: true, + RunnersTemplate: tplOrgRunners, + RunnerEditTemplate: tplOrgRunnerEdit, + RedirectLink: ctx.Org.OrgLink + "/settings/actions/runners/", + }, nil + } + + if ctx.Data["PageIsAdmin"] == true { + return &runnersCtx{ + RepoID: 0, + OwnerID: 0, + IsAdmin: true, + RunnersTemplate: tplAdminRunners, + RunnerEditTemplate: tplAdminRunnerEdit, + RedirectLink: setting.AppSubURL + "/admin/actions/runners/", + }, nil + } + + return nil, errors.New("unable to set Runners context") +} + +// Runners render settings/actions/runners page for repo level +func Runners(ctx *context.Context) { + ctx.Data["PageIsSharedSettingsRunners"] = true + ctx.Data["Title"] = ctx.Tr("actions.actions") + ctx.Data["PageType"] = "runners" + + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + page := ctx.FormInt("page") + if page <= 1 { + page = 1 + } + + opts := actions_model.FindRunnerOptions{ + ListOptions: db.ListOptions{ + Page: page, + PageSize: 100, + }, + Sort: ctx.Req.URL.Query().Get("sort"), + Filter: ctx.Req.URL.Query().Get("q"), + } + if rCtx.IsRepo { + opts.RepoID = rCtx.RepoID + opts.WithAvailable = true + } else if rCtx.IsOrg { + opts.OwnerID = rCtx.OwnerID + opts.WithAvailable = true + } + actions_shared.RunnersList(ctx, opts) + + ctx.HTML(http.StatusOK, rCtx.RunnersTemplate) +} + +// RunnersEdit renders runner edit page for repository level +func RunnersEdit(ctx *context.Context) { + ctx.Data["PageIsSharedSettingsRunners"] = true + ctx.Data["Title"] = ctx.Tr("actions.runners.edit_runner") + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + + page := ctx.FormInt("page") + if page <= 1 { + page = 1 + } + + actions_shared.RunnerDetails(ctx, page, + ctx.ParamsInt64(":runnerid"), rCtx.OwnerID, rCtx.RepoID, + ) + ctx.HTML(http.StatusOK, rCtx.RunnerEditTemplate) +} + +func RunnersEditPost(ctx *context.Context) { + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + actions_shared.RunnerDetailsEditPost(ctx, ctx.ParamsInt64(":runnerid"), + rCtx.OwnerID, rCtx.RepoID, + rCtx.RedirectLink+url.PathEscape(ctx.Params(":runnerid"))) +} + +func ResetRunnerRegistrationToken(ctx *context.Context) { + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + actions_shared.RunnerResetRegistrationToken(ctx, rCtx.OwnerID, rCtx.RepoID, rCtx.RedirectLink) +} + +// RunnerDeletePost response for deleting runner +func RunnerDeletePost(ctx *context.Context) { + rCtx, err := getRunnersCtx(ctx) + if err != nil { + ctx.ServerError("getRunnersCtx", err) + return + } + actions_shared.RunnerDeletePost(ctx, ctx.ParamsInt64(":runnerid"), rCtx.RedirectLink, rCtx.RedirectLink+url.PathEscape(ctx.Params(":runnerid"))) +} + +func RedirectToDefaultSetting(ctx *context.Context) { + ctx.Redirect(ctx.Repo.RepoLink + "/settings/actions/runners") +} diff --git a/routers/web/repo/setting/secrets.go b/routers/web/repo/setting/secrets.go new file mode 100644 index 0000000000..444f16f86c --- /dev/null +++ b/routers/web/repo/setting/secrets.go @@ -0,0 +1,115 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "errors" + "net/http" + + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + shared "code.gitea.io/gitea/routers/web/shared/secrets" +) + +const ( + // TODO: Separate secrets from runners when layout is ready + tplRepoSecrets base.TplName = "repo/settings/actions" + tplOrgSecrets base.TplName = "org/settings/actions" + tplUserSecrets base.TplName = "user/settings/actions" +) + +type secretsCtx struct { + OwnerID int64 + RepoID int64 + IsRepo bool + IsOrg bool + IsUser bool + SecretsTemplate base.TplName + RedirectLink string +} + +func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) { + if ctx.Data["PageIsRepoSettings"] == true { + return &secretsCtx{ + OwnerID: 0, + RepoID: ctx.Repo.Repository.ID, + IsRepo: true, + SecretsTemplate: tplRepoSecrets, + RedirectLink: ctx.Repo.RepoLink + "/settings/actions/secrets", + }, nil + } + + if ctx.Data["PageIsOrgSettings"] == true { + return &secretsCtx{ + OwnerID: ctx.ContextUser.ID, + RepoID: 0, + IsOrg: true, + SecretsTemplate: tplOrgSecrets, + RedirectLink: ctx.Org.OrgLink + "/settings/actions/secrets", + }, nil + } + + if ctx.Data["PageIsUserSettings"] == true { + return &secretsCtx{ + OwnerID: ctx.Doer.ID, + RepoID: 0, + IsUser: true, + SecretsTemplate: tplUserSecrets, + RedirectLink: setting.AppSubURL + "/user/settings/actions/secrets", + }, nil + } + + return nil, errors.New("unable to set Secrets context") +} + +func Secrets(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("actions.actions") + ctx.Data["PageType"] = "secrets" + ctx.Data["PageIsSharedSettingsSecrets"] = true + + sCtx, err := getSecretsCtx(ctx) + if err != nil { + ctx.ServerError("getSecretsCtx", err) + return + } + + if sCtx.IsRepo { + ctx.Data["DisableSSH"] = setting.SSH.Disabled + } + + shared.SetSecretsContext(ctx, sCtx.OwnerID, sCtx.RepoID) + if ctx.Written() { + return + } + ctx.HTML(http.StatusOK, sCtx.SecretsTemplate) +} + +func SecretsPost(ctx *context.Context) { + sCtx, err := getSecretsCtx(ctx) + if err != nil { + ctx.ServerError("getSecretsCtx", err) + return + } + shared.PerformSecretsPost( + ctx, + sCtx.OwnerID, + sCtx.RepoID, + sCtx.RedirectLink, + ) +} + +func SecretsDelete(ctx *context.Context) { + sCtx, err := getSecretsCtx(ctx) + if err != nil { + ctx.ServerError("getSecretsCtx", err) + return + } + shared.PerformSecretsDelete( + ctx, + sCtx.OwnerID, + sCtx.RepoID, + sCtx.RedirectLink, + ) +} diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index 34e84c4656..932bd373d7 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -130,15 +130,6 @@ func SettingsProtectedBranch(c *context.Context) { } c.Data["branch_status_check_contexts"] = contexts - c.Data["is_context_required"] = func(context string) bool { - for _, c := range rule.StatusCheckContexts { - if c == context { - return true - } - } - return false - } - if c.Repo.Owner.IsOrganization() { teams, err := organization.OrgFromUser(c.Repo.Owner).TeamsWithAccessToRepo(c.Repo.Repository.ID, perm.AccessModeRead) if err != nil { @@ -352,7 +343,7 @@ func RenameBranchPost(ctx *context.Context) { if ctx.HasError() { ctx.Flash.Error(ctx.GetErrMsg()) - ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) return } @@ -364,16 +355,16 @@ func RenameBranchPost(ctx *context.Context) { if msg == "target_exist" { ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_exist", form.To)) - ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) return } if msg == "from_not_exist" { ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_not_exist", form.From)) - ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) return } ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To)) - ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) } diff --git a/routers/web/repo/setting_secrets.go b/routers/web/repo/setting_secrets.go deleted file mode 100644 index 57a4c470dd..0000000000 --- a/routers/web/repo/setting_secrets.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package repo - -import ( - "net/http" - - "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/setting" - shared "code.gitea.io/gitea/routers/web/shared/secrets" -) - -const ( - tplSecrets base.TplName = "repo/settings/secrets" -) - -func Secrets(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("secrets.secrets") - ctx.Data["PageIsSettingsSecrets"] = true - ctx.Data["DisableSSH"] = setting.SSH.Disabled - - shared.SetSecretsContext(ctx, 0, ctx.Repo.Repository.ID) - if ctx.Written() { - return - } - - ctx.HTML(http.StatusOK, tplSecrets) -} - -func SecretsPost(ctx *context.Context) { - shared.PerformSecretsPost( - ctx, - 0, - ctx.Repo.Repository.ID, - ctx.Repo.RepoLink+"/settings/secrets", - ) -} - -func DeleteSecret(ctx *context.Context) { - shared.PerformSecretsDelete( - ctx, - 0, - ctx.Repo.Repository.ID, - ctx.Repo.RepoLink+"/settings/secrets", - ) -} diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index f30588967e..a21b405c96 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -67,7 +67,7 @@ type ownerRepoCtx struct { // getOwnerRepoCtx determines whether this is a repo, owner, or admin (both default and system) context. func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { - if is, ok := ctx.Data["IsRepositoryWebhook"]; ok && is.(bool) { + if ctx.Data["PageIsRepoSettings"] == true { return &ownerRepoCtx{ RepoID: ctx.Repo.Repository.ID, Link: path.Join(ctx.Repo.RepoLink, "settings/hooks"), @@ -76,7 +76,7 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { }, nil } - if is, ok := ctx.Data["IsOrganizationWebhook"]; ok && is.(bool) { + if ctx.Data["PageIsOrgSettings"] == true { return &ownerRepoCtx{ OwnerID: ctx.ContextUser.ID, Link: path.Join(ctx.Org.OrgLink, "settings/hooks"), @@ -85,7 +85,7 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { }, nil } - if is, ok := ctx.Data["IsUserWebhook"]; ok && is.(bool) { + if ctx.Data["PageIsUserSettings"] == true { return &ownerRepoCtx{ OwnerID: ctx.Doer.ID, Link: path.Join(setting.AppSubURL, "/user/settings/hooks"), @@ -94,7 +94,7 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { }, nil } - if ctx.Doer.IsAdmin { + if ctx.Data["PageIsAdmin"] == true { return &ownerRepoCtx{ IsAdmin: true, IsSystemWebhook: ctx.Params(":configType") == "system-hooks", diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go index 2c3614cbbc..9a50da2b49 100644 --- a/routers/web/shared/actions/runners.go +++ b/routers/web/shared/actions/runners.go @@ -10,7 +10,6 @@ import ( actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" @@ -18,8 +17,8 @@ import ( "code.gitea.io/gitea/services/forms" ) -// RunnersList render common runners list page -func RunnersList(ctx *context.Context, tplName base.TplName, opts actions_model.FindRunnerOptions) { +// RunnersList prepares data for runners list +func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) { count, err := actions_model.CountRunners(ctx, opts) if err != nil { ctx.ServerError("CountRunners", err) @@ -58,13 +57,12 @@ func RunnersList(ctx *context.Context, tplName base.TplName, opts actions_model. ctx.Data["RunnerRepoID"] = opts.RepoID pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) - ctx.Data["Page"] = pager - ctx.HTML(http.StatusOK, tplName) + ctx.Data["Page"] = pager } -// RunnerDetails render runner details page -func RunnerDetails(ctx *context.Context, tplName base.TplName, page int, runnerID, ownerID, repoID int64) { +// RunnerDetails prepares data for runners edit page +func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int64) { runner, err := actions_model.GetRunnerByID(ctx, runnerID) if err != nil { ctx.ServerError("GetRunnerByID", err) @@ -111,8 +109,6 @@ func RunnerDetails(ctx *context.Context, tplName base.TplName, page int, runnerI ctx.Data["Tasks"] = tasks pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) ctx.Data["Page"] = pager - - ctx.HTML(http.StatusOK, tplName) } // RunnerDetailsEditPost response for edit runner details diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 1f77379044..1af56e24b0 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -30,6 +30,8 @@ import ( "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers/web/feed" + context_service "code.gitea.io/gitea/services/context" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" @@ -815,3 +817,51 @@ func ShowGPGKeys(ctx *context.Context) { writer.Close() ctx.PlainTextBytes(http.StatusOK, buf.Bytes()) } + +func UsernameSubRoute(ctx *context.Context) { + // WORKAROUND to support usernames with "." in it + // https://github.com/go-chi/chi/issues/781 + username := ctx.Params("username") + reloadParam := func(suffix string) (success bool) { + ctx.SetParams("username", strings.TrimSuffix(username, suffix)) + context_service.UserAssignmentWeb()(ctx) + return !ctx.Written() + } + switch { + case strings.HasSuffix(username, ".png"): + if reloadParam(".png") { + AvatarByUserName(ctx) + } + case strings.HasSuffix(username, ".keys"): + if reloadParam(".keys") { + ShowSSHKeys(ctx) + } + case strings.HasSuffix(username, ".gpg"): + if reloadParam(".gpg") { + ShowGPGKeys(ctx) + } + case strings.HasSuffix(username, ".rss"): + if !setting.Other.EnableFeed { + ctx.Error(http.StatusNotFound) + return + } + if reloadParam(".rss") { + context_service.UserAssignmentWeb()(ctx) + feed.ShowUserFeedRSS(ctx) + } + case strings.HasSuffix(username, ".atom"): + if !setting.Other.EnableFeed { + ctx.Error(http.StatusNotFound) + return + } + if reloadParam(".atom") { + feed.ShowUserFeedAtom(ctx) + } + default: + context_service.UserAssignmentWeb()(ctx) + if !ctx.Written() { + ctx.Data["EnableFeed"] = setting.Other.EnableFeed + Profile(ctx) + } + } +} diff --git a/routers/web/user/package.go b/routers/web/user/package.go index a9acc5281f..37ee0b8631 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -14,8 +14,10 @@ import ( access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" + debian_module "code.gitea.io/gitea/modules/packages/debian" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" @@ -163,6 +165,32 @@ func ViewPackageVersion(ctx *context.Context) { ctx.Data["IsPackagesPage"] = true ctx.Data["PackageDescriptor"] = pd + switch pd.Package.Type { + case packages_model.TypeContainer: + ctx.Data["RegistryHost"] = setting.Packages.RegistryHost + case packages_model.TypeDebian: + distributions := make(container.Set[string]) + components := make(container.Set[string]) + architectures := make(container.Set[string]) + + for _, f := range pd.Files { + for _, pp := range f.Properties { + switch pp.Name { + case debian_module.PropertyDistribution: + distributions.Add(pp.Value) + case debian_module.PropertyComponent: + components.Add(pp.Value) + case debian_module.PropertyArchitecture: + architectures.Add(pp.Value) + } + } + } + + ctx.Data["Distributions"] = distributions.Values() + ctx.Data["Components"] = components.Values() + ctx.Data["Architectures"] = architectures.Values() + } + var ( total int64 pvs []*packages_model.PackageVersion @@ -170,8 +198,6 @@ func ViewPackageVersion(ctx *context.Context) { ) switch pd.Package.Type { case packages_model.TypeContainer: - ctx.Data["RegistryHost"] = setting.Packages.RegistryHost - pvs, total, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{ Paginator: db.NewAbsoluteListOptions(0, 5), PackageID: pd.Package.ID, @@ -183,10 +209,6 @@ func ViewPackageVersion(ctx *context.Context) { PackageID: pd.Package.ID, IsInternal: util.OptionalBoolFalse, }) - if err != nil { - ctx.ServerError("SearchVersions", err) - return - } } if err != nil { ctx.ServerError("", err) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 367bb306b8..ef91d89d14 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -167,30 +167,31 @@ func Profile(ctx *context.Context) { language := ctx.FormTrim("language") ctx.Data["Language"] = language + followers, numFollowers, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ + PageSize: pagingNum, + Page: page, + }) + if err != nil { + ctx.ServerError("GetUserFollowers", err) + return + } + ctx.Data["NumFollowers"] = numFollowers + following, numFollowing, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ + PageSize: pagingNum, + Page: page, + }) + if err != nil { + ctx.ServerError("GetUserFollowing", err) + return + } + ctx.Data["NumFollowing"] = numFollowing + switch tab { case "followers": - items, count, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ - PageSize: pagingNum, - Page: page, - }) - if err != nil { - ctx.ServerError("GetUserFollowers", err) - return - } - ctx.Data["Cards"] = items - + ctx.Data["Cards"] = followers total = int(count) case "following": - items, count, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ - PageSize: pagingNum, - Page: page, - }) - if err != nil { - ctx.ServerError("GetUserFollowing", err) - return - } - ctx.Data["Cards"] = items - + ctx.Data["Cards"] = following total = int(count) case "activity": date := ctx.FormString("date") diff --git a/routers/web/user/setting/adopt.go b/routers/web/user/setting/adopt.go index c9995e7278..01668c3954 100644 --- a/routers/web/user/setting/adopt.go +++ b/routers/web/user/setting/adopt.go @@ -31,7 +31,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { root := user_model.UserPath(ctxUser.LowerName) // check not a repo - has, err := repo_model.IsRepositoryExist(ctx, ctxUser, dir) + has, err := repo_model.IsRepositoryModelExist(ctx, ctxUser, dir) if err != nil { ctx.ServerError("IsRepositoryExist", err) return diff --git a/routers/web/user/setting/secrets.go b/routers/web/user/setting/secrets.go index 2314f3694f..eb09477bdb 100644 --- a/routers/web/user/setting/secrets.go +++ b/routers/web/user/setting/secrets.go @@ -4,44 +4,10 @@ package setting import ( - "net/http" - - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" - shared "code.gitea.io/gitea/routers/web/shared/secrets" ) -const ( - tplSettingsSecrets base.TplName = "user/settings/secrets" -) - -func Secrets(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("secrets.secrets") - ctx.Data["PageIsSettingsSecrets"] = true - - shared.SetSecretsContext(ctx, ctx.Doer.ID, 0) - if ctx.Written() { - return - } - - ctx.HTML(http.StatusOK, tplSettingsSecrets) -} - -func SecretsPost(ctx *context.Context) { - shared.PerformSecretsPost( - ctx, - ctx.Doer.ID, - 0, - setting.AppSubURL+"/user/settings/secrets", - ) -} - -func SecretsDelete(ctx *context.Context) { - shared.PerformSecretsDelete( - ctx, - ctx.Doer.ID, - 0, - setting.AppSubURL+"/user/settings/secrets", - ) +func RedirectToDefaultSetting(ctx *context.Context) { + ctx.Redirect(setting.AppSubURL + "/user/settings/actions/secrets") } diff --git a/routers/web/web.go b/routers/web/web.go index 779499889f..5357c55500 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -6,16 +6,10 @@ package web import ( gocontext "context" "net/http" - "os" - "path" - "strings" "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/unit" - "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/metrics" "code.gitea.io/gitea/modules/public" @@ -26,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/routing" + "code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/routers/web/admin" "code.gitea.io/gitea/routers/web/auth" "code.gitea.io/gitea/routers/web/devtest" @@ -35,8 +30,10 @@ import ( "code.gitea.io/gitea/routers/web/healthcheck" "code.gitea.io/gitea/routers/web/misc" "code.gitea.io/gitea/routers/web/org" + org_setting "code.gitea.io/gitea/routers/web/org/setting" "code.gitea.io/gitea/routers/web/repo" "code.gitea.io/gitea/routers/web/repo/actions" + repo_setting "code.gitea.io/gitea/routers/web/repo/setting" "code.gitea.io/gitea/routers/web/user" user_setting "code.gitea.io/gitea/routers/web/user/setting" "code.gitea.io/gitea/routers/web/user/setting/security" @@ -48,7 +45,6 @@ import ( _ "code.gitea.io/gitea/modules/session" // to registers all internal adapters "gitea.com/go-chi/captcha" - "gitea.com/go-chi/session" "github.com/NYTimes/gziphandler" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" @@ -99,49 +95,31 @@ func buildAuthGroup() *auth_service.Group { return group } +func ctxDataSet(args ...any) func(ctx *context.Context) { + return func(ctx *context.Context) { + for i := 0; i < len(args); i += 2 { + ctx.Data[args[i].(string)] = args[i+1] + } + } +} + // Routes returns all web routes func Routes(ctx gocontext.Context) *web.Route { routes := web.NewRoute() - routes.Use(web.MiddlewareWithPrefix("/assets/", CorsHandler(), public.AssetsHandlerFunc("/assets/"))) - - sessioner := session.Sessioner(session.Options{ - Provider: setting.SessionConfig.Provider, - ProviderConfig: setting.SessionConfig.ProviderConfig, - CookieName: setting.SessionConfig.CookieName, - CookiePath: setting.SessionConfig.CookiePath, - Gclifetime: setting.SessionConfig.Gclifetime, - Maxlifetime: setting.SessionConfig.Maxlifetime, - Secure: setting.SessionConfig.Secure, - SameSite: setting.SessionConfig.SameSite, - Domain: setting.SessionConfig.Domain, - }) - routes.Use(sessioner) - - ctx, _ = templates.HTMLRenderer(ctx) - - routes.Use(Recovery(ctx)) - - // We use r.Route here over r.Use because this prevents requests that are not for avatars having to go through this additional handler + routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler + routes.RouteMethods("/assets/*", "GET, HEAD", CorsHandler(), public.AssetsHandlerFunc("/assets/")) routes.RouteMethods("/avatars/*", "GET, HEAD", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) routes.RouteMethods("/repo-avatars/*", "GET, HEAD", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) + routes.RouteMethods("/apple-touch-icon.png", "GET, HEAD", misc.StaticRedirect("/assets/img/apple-touch-icon.png")) + routes.RouteMethods("/favicon.ico", "GET, HEAD", misc.StaticRedirect("/assets/img/favicon.png")) - // for health check - doesn't need to be passed through gzip handler - routes.Head("/", func(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(http.StatusOK) - }) + _ = templates.HTMLRenderer() - // this png is very likely to always be below the limit for gzip so it doesn't need to pass through gzip - routes.Get("/apple-touch-icon.png", func(w http.ResponseWriter, req *http.Request) { - http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "/assets/img/apple-touch-icon.png"), http.StatusPermanentRedirect) - }) - - // redirect default favicon to the path of the custom favicon with a default as a fallback - routes.Get("/favicon.ico", func(w http.ResponseWriter, req *http.Request) { - http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "/assets/img/favicon.png"), http.StatusMovedPermanently) - }) - - common := []interface{}{} + common := []any{ + common.Sessioner(), + RecoveryWith500Page(ctx), + } if setting.EnableGzip { h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize)) @@ -157,42 +135,18 @@ func Routes(ctx gocontext.Context) *web.Route { } if setting.HasRobotsTxt { - routes.Get("/robots.txt", append(common, func(w http.ResponseWriter, req *http.Request) { - filePath := path.Join(setting.CustomPath, "robots.txt") - fi, err := os.Stat(filePath) - if err == nil && httpcache.HandleTimeCache(req, w, fi) { - return - } - http.ServeFile(w, req, filePath) - })...) + routes.Get("/robots.txt", append(common, misc.RobotsTxt)...) } // prometheus metrics endpoint - do not need to go through contexter if setting.Metrics.Enabled { - c := metrics.NewCollector() - prometheus.MustRegister(c) - + prometheus.MustRegister(metrics.NewCollector()) routes.Get("/metrics", append(common, Metrics)...) } - routes.Get("/ssh_info", func(rw http.ResponseWriter, req *http.Request) { - if !git.SupportProcReceive { - rw.WriteHeader(http.StatusNotFound) - return - } - rw.Header().Set("content-type", "text/json;charset=UTF-8") - _, err := rw.Write([]byte(`{"type":"gitea","version":1}`)) - if err != nil { - log.Error("fail to write result: err: %v", err) - rw.WriteHeader(http.StatusInternalServerError) - return - } - rw.WriteHeader(http.StatusOK) - }) - + routes.Get("/ssh_info", misc.SSHInfo) routes.Get("/api/healthz", healthcheck.Check) - // Removed: toolbox.Toolboxer middleware will provide debug information which seems unnecessary common = append(common, context.Contexter(ctx)) group := buildAuthGroup() @@ -207,7 +161,7 @@ func Routes(ctx gocontext.Context) *web.Route { common = append(common, middleware.GetHead) if setting.API.EnableSwagger { - // Note: The route moved from apiroutes because it's in fact want to render a web page + // Note: The route is here but no in API routes because it renders a web page routes.Get("/api/swagger", append(common, misc.Swagger)...) // Render V1 by default } @@ -217,17 +171,14 @@ func Routes(ctx gocontext.Context) *web.Route { common = append(common, goGet) others := web.NewRoute() - for _, middle := range common { - others.Use(middle) - } - - RegisterRoutes(others) + others.Use(common...) + registerRoutes(others) routes.Mount("", others) return routes } -// RegisterRoutes register routes -func RegisterRoutes(m *web.Route) { +// registerRoutes register routes +func registerRoutes(m *web.Route) { reqSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: true}) ignSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView}) ignExploreSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView}) @@ -342,6 +293,24 @@ func RegisterRoutes(m *web.Route) { m.Post("/packagist/{id}", web.Bind(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost) } + addSettingsSecretsRoutes := func() { + m.Group("/secrets", func() { + m.Get("", repo_setting.Secrets) + m.Post("", web.Bind(forms.AddSecretForm{}), repo_setting.SecretsPost) + m.Post("/delete", repo_setting.SecretsDelete) + }) + } + + addSettingsRunnersRoutes := func() { + m.Group("/runners", func() { + m.Get("", repo_setting.Runners) + m.Combo("/{runnerid}").Get(repo_setting.RunnersEdit). + Post(web.Bind(forms.EditRunnerForm{}), repo_setting.RunnersEditPost) + m.Post("/{runnerid}/delete", repo_setting.RunnerDeletePost) + m.Get("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken) + }) + } + // FIXME: not all routes need go through same middleware. // Especially some AJAX requests, we can reduce middleware number to improve performance. // Routers. @@ -354,8 +323,8 @@ func RegisterRoutes(m *web.Route) { m.Get("/nodeinfo", NodeInfoLinks) m.Get("/webfinger", WebfingerQuery) }, federationEnabled) - m.Get("/change-password", func(w http.ResponseWriter, req *http.Request) { - http.Redirect(w, req, "/user/settings/account", http.StatusTemporaryRedirect) + m.Get("/change-password", func(ctx *context.Context) { + ctx.Redirect(setting.AppSubURL + "/user/settings/account") }) }) @@ -501,11 +470,12 @@ func RegisterRoutes(m *web.Route) { }) m.Post("/chef/regenerate_keypair", user_setting.RegenerateChefKeyPair) }, packagesEnabled) - m.Group("/secrets", func() { - m.Get("", user_setting.Secrets) - m.Post("", web.Bind(forms.AddSecretForm{}), user_setting.SecretsPost) - m.Post("/delete", user_setting.SecretsDelete) - }) + + m.Group("/actions", func() { + m.Get("", user_setting.RedirectToDefaultSetting) + addSettingsSecretsRoutes() + }, actions.MustEnableActions) + m.Get("/organization", user_setting.Organization) m.Get("/repos", user_setting.Repos) m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository) @@ -519,14 +489,8 @@ func RegisterRoutes(m *web.Route) { m.Post("/replay/{uuid}", repo.ReplayWebhook) }) addWebhookEditRoutes() - }, webhooksEnabled, func(ctx *context.Context) { - ctx.Data["IsUserWebhook"] = true - }) - }, reqSignIn, func(ctx *context.Context) { - ctx.Data["PageIsUserSettings"] = true - ctx.Data["AllThemes"] = setting.UI.Themes - ctx.Data["EnablePackages"] = setting.Packages.Enabled - }) + }, webhooksEnabled) + }, reqSignIn, ctxDataSet("PageIsUserSettings", true, "AllThemes", setting.UI.Themes, "EnablePackages", setting.Packages.Enabled)) m.Group("/user", func() { // r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds) @@ -651,66 +615,15 @@ func RegisterRoutes(m *web.Route) { } }) - m.Group("/runners", func() { - m.Get("", admin.Runners) - m.Get("/reset_registration_token", admin.ResetRunnerRegistrationToken) - m.Combo("/{runnerid}").Get(admin.EditRunner).Post(web.Bind(forms.EditRunnerForm{}), admin.EditRunnerPost) - m.Post("/{runnerid}/delete", admin.DeleteRunnerPost) - }, actions.MustEnableActions) - }, func(ctx *context.Context) { - ctx.Data["EnableOAuth2"] = setting.OAuth2.Enable - ctx.Data["EnablePackages"] = setting.Packages.Enabled - }, adminReq) + m.Group("/actions", func() { + m.Get("", admin.RedirectToDefaultSetting) + addSettingsRunnersRoutes() + }) + }, adminReq, ctxDataSet("EnableOAuth2", setting.OAuth2.Enable, "EnablePackages", setting.Packages.Enabled)) // ***** END: Admin ***** m.Group("", func() { - m.Get("/favicon.ico", func(ctx *context.Context) { - ctx.SetServeHeaders(&context.ServeHeaderOptions{ - Filename: "favicon.png", - }) - http.ServeFile(ctx.Resp, ctx.Req, path.Join(setting.StaticRootPath, "public/img/favicon.png")) - }) - m.Get("/{username}", func(ctx *context.Context) { - // WORKAROUND to support usernames with "." in it - // https://github.com/go-chi/chi/issues/781 - username := ctx.Params("username") - reloadParam := func(suffix string) (success bool) { - ctx.SetParams("username", strings.TrimSuffix(username, suffix)) - context_service.UserAssignmentWeb()(ctx) - return !ctx.Written() - } - switch { - case strings.HasSuffix(username, ".png"): - if reloadParam(".png") { - user.AvatarByUserName(ctx) - } - case strings.HasSuffix(username, ".keys"): - if reloadParam(".keys") { - user.ShowSSHKeys(ctx) - } - case strings.HasSuffix(username, ".gpg"): - if reloadParam(".gpg") { - user.ShowGPGKeys(ctx) - } - case strings.HasSuffix(username, ".rss"): - feedEnabled(ctx) - if !ctx.Written() && reloadParam(".rss") { - context_service.UserAssignmentWeb()(ctx) - feed.ShowUserFeedRSS(ctx) - } - case strings.HasSuffix(username, ".atom"): - feedEnabled(ctx) - if !ctx.Written() && reloadParam(".atom") { - feed.ShowUserFeedAtom(ctx) - } - default: - context_service.UserAssignmentWeb()(ctx) - if !ctx.Written() { - ctx.Data["EnableFeed"] = setting.Other.EnableFeed - user.Profile(ctx) - } - } - }) + m.Get("/{username}", user.UsernameSubRoute) m.Get("/attachments/{uuid}", repo.GetAttachment) }, ignSignIn) @@ -830,9 +743,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/replay/{uuid}", repo.ReplayWebhook) }) addWebhookEditRoutes() - }, webhooksEnabled, func(ctx *context.Context) { - ctx.Data["IsOrganizationWebhook"] = true - }) + }, webhooksEnabled) m.Group("/labels", func() { m.Get("", org.RetrieveLabels, org.Labels) @@ -842,20 +753,12 @@ func RegisterRoutes(m *web.Route) { m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), org.InitializeLabels) }) - m.Group("/runners", func() { - m.Get("", org.Runners) - m.Combo("/{runnerid}").Get(org.RunnersEdit). - Post(web.Bind(forms.EditRunnerForm{}), org.RunnersEditPost) - m.Post("/{runnerid}/delete", org.RunnerDeletePost) - m.Get("/reset_registration_token", org.ResetRunnerRegistrationToken) + m.Group("/actions", func() { + m.Get("", org_setting.RedirectToDefaultSetting) + addSettingsRunnersRoutes() + addSettingsSecretsRoutes() }, actions.MustEnableActions) - m.Group("/secrets", func() { - m.Get("", org.Secrets) - m.Post("", web.Bind(forms.AddSecretForm{}), org.SecretsPost) - m.Post("/delete", org.SecretsDelete) - }) - m.RouteMethods("/delete", "GET,POST", org.SettingsDelete) m.Group("/packages", func() { @@ -876,10 +779,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/rebuild", org.RebuildCargoIndex) }) }, packagesEnabled) - }, func(ctx *context.Context) { - ctx.Data["EnableOAuth2"] = setting.OAuth2.Enable - ctx.Data["EnablePackages"] = setting.Packages.Enabled - }) + }, ctxDataSet("EnableOAuth2", setting.OAuth2.Enable, "EnablePackages", setting.Packages.Enabled, "PageIsOrgSettings", true)) }, context.OrgAssignment(true, true)) }, reqSignIn) // ***** END: Organization ***** @@ -1013,9 +913,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/replay/{uuid}", repo.ReplayWebhook) }) addWebhookEditRoutes() - }, webhooksEnabled, func(ctx *context.Context) { - ctx.Data["IsRepositoryWebhook"] = true - }) + }, webhooksEnabled) m.Group("/keys", func() { m.Combo("").Get(repo.DeployKeys). @@ -1023,12 +921,6 @@ func RegisterRoutes(m *web.Route) { m.Post("/delete", repo.DeleteDeployKey) }) - m.Group("/secrets", func() { - m.Get("", repo.Secrets) - m.Post("", web.Bind(forms.AddSecretForm{}), repo.SecretsPost) - m.Post("/delete", repo.DeleteSecret) - }) - m.Group("/lfs", func() { m.Get("/", repo.LFSFiles) m.Get("/show/{oid}", repo.LFSFileGet) @@ -1042,18 +934,12 @@ func RegisterRoutes(m *web.Route) { m.Post("/{lid}/unlock", repo.LFSUnlock) }) }) - - m.Group("/runners", func() { - m.Get("", repo.Runners) - m.Combo("/{runnerid}").Get(repo.RunnersEdit). - Post(web.Bind(forms.EditRunnerForm{}), repo.RunnersEditPost) - m.Post("/{runnerid}/delete", repo.RunnerDeletePost) - m.Get("/reset_registration_token", repo.ResetRunnerRegistrationToken) + m.Group("/actions", func() { + m.Get("", repo_setting.RedirectToDefaultSetting) + addSettingsRunnersRoutes() + addSettingsSecretsRoutes() }, actions.MustEnableActions) - }, func(ctx *context.Context) { - ctx.Data["PageIsSettings"] = true - ctx.Data["LFSStartServer"] = setting.LFS.StartServer - }) + }, ctxDataSet("PageIsRepoSettings", true, "LFSStartServer", setting.LFS.StartServer)) }, reqSignIn, context.RepoAssignment, context.UnitTypes(), reqRepoAdmin, context.RepoRef()) m.Post("/{username}/{reponame}/action/{action}", reqSignIn, context.RepoAssignment, context.UnitTypes(), repo.Action) @@ -1204,12 +1090,11 @@ func RegisterRoutes(m *web.Route) { m.Get("", repo.TagsList) m.Get(".rss", feedEnabled, repo.TagsListFeedRSS) m.Get(".atom", feedEnabled, repo.TagsListFeedAtom) - }, func(ctx *context.Context) { - ctx.Data["EnableFeed"] = setting.Other.EnableFeed - }, repo.MustBeNotEmpty, reqRepoCodeReader, context.RepoRefByType(context.RepoRefTag, true)) + }, ctxDataSet("EnableFeed", setting.Other.EnableFeed), + repo.MustBeNotEmpty, reqRepoCodeReader, context.RepoRefByType(context.RepoRefTag, true)) m.Post("/tags/delete", repo.DeleteTag, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef()) - }, reqSignIn, context.RepoAssignment, context.UnitTypes()) + }, ignSignIn, context.RepoAssignment, context.UnitTypes()) // Releases m.Group("/{username}/{reponame}", func() { @@ -1219,9 +1104,8 @@ func RegisterRoutes(m *web.Route) { m.Get("/latest", repo.LatestRelease) m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS) m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom) - }, func(ctx *context.Context) { - ctx.Data["EnableFeed"] = setting.Other.EnableFeed - }, repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag, true)) + }, ctxDataSet("EnableFeed", setting.Other.EnableFeed), + repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag, true)) m.Get("/releases/attachments/{uuid}", repo.GetAttachment, repo.MustBeNotEmpty, reqRepoReleaseReader) m.Group("/releases", func() { m.Get("/new", repo.NewRelease) @@ -1233,21 +1117,7 @@ func RegisterRoutes(m *web.Route) { m.Group("/releases", func() { m.Get("/edit/*", repo.EditRelease) m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost) - }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, func(ctx *context.Context) { - var err error - ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) - if err != nil { - ctx.ServerError("GetBranchCommit", err) - return - } - ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount() - if err != nil { - ctx.ServerError("GetCommitsCount", err) - return - } - ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount - ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache()) - }) + }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, repo.CommitInfoCache) }, ignSignIn, context.RepoAssignment, context.UnitTypes(), reqRepoReleaseReader) // to maintain compatibility with old attachments @@ -1316,28 +1186,21 @@ func RegisterRoutes(m *web.Route) { m.Combo(""). Get(actions.View). Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) - m.Post("/rerun", reqRepoActionsWriter, actions.Rerun) + m.Post("/rerun", reqRepoActionsWriter, actions.RerunOne) }) m.Post("/cancel", reqRepoActionsWriter, actions.Cancel) m.Post("/approve", reqRepoActionsWriter, actions.Approve) + m.Post("/rerun", reqRepoActionsWriter, actions.RerunAll) }) }, reqRepoActionsReader, actions.MustEnableActions) m.Group("/wiki", func() { m.Combo("/"). Get(repo.Wiki). - Post(context.RepoMustNotBeArchived(), - reqSignIn, - reqRepoWikiWriter, - web.Bind(forms.NewWikiForm{}), - repo.WikiPost) + Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) m.Combo("/*"). Get(repo.Wiki). - Post(context.RepoMustNotBeArchived(), - reqSignIn, - reqRepoWikiWriter, - web.Bind(forms.NewWikiForm{}), - repo.WikiPost) + Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) m.Get("/commit/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff) }, repo.MustEnableWiki, func(ctx *context.Context) { @@ -1468,8 +1331,7 @@ func RegisterRoutes(m *web.Route) { m.Group("", func() { m.Get("/forks", repo.Forks) }, context.RepoRef(), reqRepoCodeReader) - m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}", - repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff) + m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}", repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff) }, ignSignIn, context.RepoAssignment, context.UnitTypes()) m.Post("/{username}/{reponame}/lastcommit/*", ignSignInAndCsrf, context.RepoAssignment, context.UnitTypes(), context.RepoRefByType(context.RepoRefCommit), reqRepoCodeReader, repo.LastCommit) diff --git a/services/auth/basic.go b/services/auth/basic.go index dc03780905..36480568ff 100644 --- a/services/auth/basic.go +++ b/services/auth/basic.go @@ -102,6 +102,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore } store.GetData()["IsApiToken"] = true + store.GetData()["ApiTokenScope"] = token.Scope return u, nil } else if !auth_model.IsErrAccessTokenNotExist(err) && !auth_model.IsErrAccessTokenEmpty(err) { log.Error("GetAccessTokenBySha: %v", err) diff --git a/services/auth/middleware.go b/services/auth/middleware.go index b96810f17f..abeafed9cb 100644 --- a/services/auth/middleware.go +++ b/services/auth/middleware.go @@ -236,7 +236,6 @@ func VerifyAuthWithOptionsAPI(options *VerifyOptions) func(ctx *context.APIConte }) return } - ctx.Data["PageIsAdmin"] = true } } } diff --git a/services/convert/repository.go b/services/convert/repository.go index a2a8570cc9..f470fd1656 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -183,6 +183,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, mode perm.Acc DefaultBranch: repo.DefaultBranch, Created: repo.CreatedUnix.AsTime(), Updated: repo.UpdatedUnix.AsTime(), + ArchivedAt: repo.ArchivedUnix.AsTime(), Permissions: permission, HasIssues: hasIssues, ExternalTracker: externalTracker, diff --git a/services/forms/package_form.go b/services/forms/package_form.go index 699d0fe44f..efe4a0d84a 100644 --- a/services/forms/package_form.go +++ b/services/forms/package_form.go @@ -15,7 +15,7 @@ import ( type PackageCleanupRuleForm struct { ID int64 Enabled bool - Type string `binding:"Required;In(cargo,chef,composer,conan,conda,container,generic,helm,maven,npm,nuget,pub,pypi,rubygems,swift,vagrant)"` + Type string `binding:"Required;In(cargo,chef,composer,conan,conda,container,debian,generic,helm,maven,npm,nuget,pub,pypi,rubygems,swift,vagrant)"` KeepCount int `binding:"In(0,1,5,10,25,50,100)"` KeepPattern string `binding:"RegexPattern"` RemoveDays int `binding:"In(0,7,14,30,60,90,180)"` diff --git a/services/lfs/locks.go b/services/lfs/locks.go index d963d9ab57..1e5db6bd20 100644 --- a/services/lfs/locks.go +++ b/services/lfs/locks.go @@ -58,6 +58,11 @@ func GetListLockHandler(ctx *context.Context) { } repository.MustOwner(ctx) + context.CheckRepoScopedToken(ctx, repository) + if ctx.Written() { + return + } + authenticated := authenticate(ctx, repository, rv.Authorization, true, false) if !authenticated { ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs") @@ -145,6 +150,11 @@ func PostLockHandler(ctx *context.Context) { } repository.MustOwner(ctx) + context.CheckRepoScopedToken(ctx, repository) + if ctx.Written() { + return + } + authenticated := authenticate(ctx, repository, authorization, true, true) if !authenticated { ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs") @@ -212,6 +222,11 @@ func VerifyLockHandler(ctx *context.Context) { } repository.MustOwner(ctx) + context.CheckRepoScopedToken(ctx, repository) + if ctx.Written() { + return + } + authenticated := authenticate(ctx, repository, authorization, true, true) if !authenticated { ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs") @@ -278,6 +293,11 @@ func UnLockHandler(ctx *context.Context) { } repository.MustOwner(ctx) + context.CheckRepoScopedToken(ctx, repository) + if ctx.Written() { + return + } + authenticated := authenticate(ctx, repository, authorization, true, true) if !authenticated { ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs") diff --git a/services/lfs/server.go b/services/lfs/server.go index 44de9ba74f..64e1203394 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -423,6 +423,11 @@ func getAuthenticatedRepository(ctx *context.Context, rc *requestContext, requir return nil } + context.CheckRepoScopedToken(ctx, repository) + if ctx.Written() { + return nil + } + return repository } diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go index 91cc8cb405..3d878b7c8c 100644 --- a/services/mailer/mailer.go +++ b/services/mailer/mailer.go @@ -26,6 +26,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + ntlmssp "github.com/Azure/go-ntlmssp" "github.com/jaytaylor/html2text" "gopkg.in/gomail.v2" ) @@ -145,6 +146,35 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { return nil, nil } +type ntlmAuth struct { + username, password, domain string + domainNeeded bool +} + +// NtlmAuth SMTP AUTH NTLM Auth Handler +func NtlmAuth(username, password string) smtp.Auth { + user, domain, domainNeeded := ntlmssp.GetDomain(username) + return &ntlmAuth{user, password, domain, domainNeeded} +} + +// Start starts SMTP NTLM Auth +func (a *ntlmAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { + negotiateMessage, err := ntlmssp.NewNegotiateMessage(a.domain, "") + return "NTLM", negotiateMessage, err +} + +// Next next step of SMTP ntlm auth +func (a *ntlmAuth) Next(fromServer []byte, more bool) ([]byte, error) { + if more { + if len(fromServer) == 0 { + return nil, fmt.Errorf("ntlm ChallengeMessage is empty") + } + authenticateMessage, err := ntlmssp.ProcessChallenge(fromServer, a.username, a.password, a.domainNeeded) + return authenticateMessage, err + } + return nil, nil +} + // Sender SMTP mail sender type smtpSender struct{} @@ -237,6 +267,8 @@ func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error { } else if strings.Contains(options, "LOGIN") { // Patch for AUTH LOGIN auth = LoginAuth(opts.User, opts.Passwd) + } else if strings.Contains(options, "NTLM") { + auth = NtlmAuth(opts.User, opts.Passwd) } if auth != nil { diff --git a/services/packages/cleanup/cleanup.go b/services/packages/cleanup/cleanup.go index 2d62a028a4..43fbc1ad9b 100644 --- a/services/packages/cleanup/cleanup.go +++ b/services/packages/cleanup/cleanup.go @@ -17,6 +17,7 @@ import ( packages_service "code.gitea.io/gitea/services/packages" cargo_service "code.gitea.io/gitea/services/packages/cargo" container_service "code.gitea.io/gitea/services/packages/container" + debian_service "code.gitea.io/gitea/services/packages/debian" ) // Cleanup removes expired package data @@ -45,6 +46,7 @@ func Cleanup(taskCtx context.Context, olderThan time.Duration) error { return fmt.Errorf("CleanupRule [%d]: GetPackagesByType failed: %w", pcr.ID, err) } + anyVersionDeleted := false for _, p := range packages { pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ PackageID: p.ID, @@ -91,6 +93,7 @@ func Cleanup(taskCtx context.Context, olderThan time.Duration) error { } versionDeleted = true + anyVersionDeleted = true } if versionDeleted { @@ -105,6 +108,14 @@ func Cleanup(taskCtx context.Context, olderThan time.Duration) error { } } } + + if anyVersionDeleted { + if pcr.Type == packages_model.TypeDebian { + if err := debian_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil { + return fmt.Errorf("CleanupRule [%d]: debian.BuildAllRepositoryFiles failed: %w", pcr.ID, err) + } + } + } return nil }) if err != nil { diff --git a/services/packages/debian/repository.go b/services/packages/debian/repository.go new file mode 100644 index 0000000000..69d00086a0 --- /dev/null +++ b/services/packages/debian/repository.go @@ -0,0 +1,443 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package debian + +import ( + "bytes" + "compress/gzip" + "context" + "errors" + "fmt" + "io" + "sort" + "strings" + "time" + + "code.gitea.io/gitea/models/db" + packages_model "code.gitea.io/gitea/models/packages" + debian_model "code.gitea.io/gitea/models/packages/debian" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" + packages_module "code.gitea.io/gitea/modules/packages" + debian_module "code.gitea.io/gitea/modules/packages/debian" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + packages_service "code.gitea.io/gitea/services/packages" + + "github.com/keybase/go-crypto/openpgp" + "github.com/keybase/go-crypto/openpgp/armor" + "github.com/keybase/go-crypto/openpgp/clearsign" + "github.com/keybase/go-crypto/openpgp/packet" + "github.com/ulikunitz/xz" +) + +// GetOrCreateRepositoryVersion gets or creates the internal repository package +// The Debian registry needs multiple index files which are stored in this package. +func GetOrCreateRepositoryVersion(ownerID int64) (*packages_model.PackageVersion, error) { + var repositoryVersion *packages_model.PackageVersion + + return repositoryVersion, db.WithTx(db.DefaultContext, func(ctx context.Context) error { + p := &packages_model.Package{ + OwnerID: ownerID, + Type: packages_model.TypeDebian, + Name: debian_module.RepositoryPackage, + LowerName: debian_module.RepositoryPackage, + IsInternal: true, + } + var err error + if p, err = packages_model.TryInsertPackage(ctx, p); err != nil { + if err != packages_model.ErrDuplicatePackage { + log.Error("Error inserting package: %v", err) + return err + } + } + + pv := &packages_model.PackageVersion{ + PackageID: p.ID, + CreatorID: ownerID, + Version: debian_module.RepositoryVersion, + LowerVersion: debian_module.RepositoryVersion, + IsInternal: true, + MetadataJSON: "null", + } + if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil { + if err != packages_model.ErrDuplicatePackageVersion { + log.Error("Error inserting package version: %v", err) + return err + } + } + + repositoryVersion = pv + + return nil + }) +} + +// GetOrCreateKeyPair gets or creates the PGP keys used to sign repository files +func GetOrCreateKeyPair(ownerID int64) (string, string, error) { + priv, err := user_model.GetSetting(ownerID, debian_module.SettingKeyPrivate) + if err != nil && !errors.Is(err, util.ErrNotExist) { + return "", "", err + } + + pub, err := user_model.GetSetting(ownerID, debian_module.SettingKeyPublic) + if err != nil && !errors.Is(err, util.ErrNotExist) { + return "", "", err + } + + if priv == "" || pub == "" { + priv, pub, err = generateKeypair() + if err != nil { + return "", "", err + } + + if err := user_model.SetUserSetting(ownerID, debian_module.SettingKeyPrivate, priv); err != nil { + return "", "", err + } + + if err := user_model.SetUserSetting(ownerID, debian_module.SettingKeyPublic, pub); err != nil { + return "", "", err + } + } + + return priv, pub, nil +} + +func generateKeypair() (string, string, error) { + e, err := openpgp.NewEntity(setting.AppName, "Debian Registry", "", nil) + if err != nil { + return "", "", err + } + + var priv strings.Builder + var pub strings.Builder + + w, err := armor.Encode(&priv, openpgp.PrivateKeyType, nil) + if err != nil { + return "", "", err + } + if err := e.SerializePrivate(w, nil); err != nil { + return "", "", err + } + w.Close() + + w, err = armor.Encode(&pub, openpgp.PublicKeyType, nil) + if err != nil { + return "", "", err + } + if err := e.Serialize(w); err != nil { + return "", "", err + } + w.Close() + + return priv.String(), pub.String(), nil +} + +// BuildAllRepositoryFiles (re)builds all repository files for every available distributions, components and architectures +func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error { + pv, err := GetOrCreateRepositoryVersion(ownerID) + if err != nil { + return err + } + + // 1. Delete all existing repository files + pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID) + if err != nil { + return err + } + + for _, pf := range pfs { + if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil { + return err + } + if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil { + return err + } + } + + // 2. (Re)Build repository files for existing packages + distributions, err := debian_model.GetDistributions(ctx, ownerID) + if err != nil { + return err + } + for _, distribution := range distributions { + components, err := debian_model.GetComponents(ctx, ownerID, distribution) + if err != nil { + return err + } + architectures, err := debian_model.GetArchitectures(ctx, ownerID, distribution) + if err != nil { + return err + } + + for _, component := range components { + for _, architecture := range architectures { + if err := buildRepositoryFiles(ctx, ownerID, pv, distribution, component, architecture); err != nil { + return fmt.Errorf("failed to build repository files [%s/%s/%s]: %w", distribution, component, architecture, err) + } + } + } + } + + return nil +} + +// BuildSpecificRepositoryFiles builds index files for the repository +func BuildSpecificRepositoryFiles(ctx context.Context, ownerID int64, distribution, component, architecture string) error { + pv, err := GetOrCreateRepositoryVersion(ownerID) + if err != nil { + return err + } + + return buildRepositoryFiles(ctx, ownerID, pv, distribution, component, architecture) +} + +func buildRepositoryFiles(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, distribution, component, architecture string) error { + if err := buildPackagesIndices(ctx, ownerID, repoVersion, distribution, component, architecture); err != nil { + return err + } + + return buildReleaseFiles(ctx, ownerID, repoVersion, distribution) +} + +// https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices +func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, distribution, component, architecture string) error { + pfds, err := debian_model.SearchLatestPackages(ctx, &debian_model.PackageSearchOptions{ + OwnerID: ownerID, + Distribution: distribution, + Component: component, + Architecture: architecture, + }) + if err != nil { + return err + } + + // Delete the package indices if there are no packages + if len(pfds) == 0 { + key := fmt.Sprintf("%s|%s|%s", distribution, component, architecture) + for _, filename := range []string{"Packages", "Packages.gz", "Packages.xz"} { + pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, key) + if err != nil && !errors.Is(err, util.ErrNotExist) { + return err + } + + if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil { + return err + } + if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil { + return err + } + } + + return nil + } + + packagesContent, _ := packages_module.NewHashedBuffer() + + packagesGzipContent, _ := packages_module.NewHashedBuffer() + gzw := gzip.NewWriter(packagesGzipContent) + + packagesXzContent, _ := packages_module.NewHashedBuffer() + xzw, _ := xz.NewWriter(packagesXzContent) + + w := io.MultiWriter(packagesContent, gzw, xzw) + + addSeparator := false + for _, pfd := range pfds { + if addSeparator { + fmt.Fprintln(w) + } + addSeparator = true + + fmt.Fprint(w, pfd.Properties.GetByName(debian_module.PropertyControl)) + + fmt.Fprintf(w, "Filename: pool/%s/%s/%s\n", distribution, component, pfd.File.Name) + fmt.Fprintf(w, "Size: %d\n", pfd.Blob.Size) + fmt.Fprintf(w, "MD5sum: %s\n", pfd.Blob.HashMD5) + fmt.Fprintf(w, "SHA1: %s\n", pfd.Blob.HashSHA1) + fmt.Fprintf(w, "SHA256: %s\n", pfd.Blob.HashSHA256) + fmt.Fprintf(w, "SHA512: %s\n", pfd.Blob.HashSHA512) + } + + gzw.Close() + xzw.Close() + + for _, file := range []struct { + Name string + Data packages_module.HashedSizeReader + }{ + {"Packages", packagesContent}, + {"Packages.gz", packagesGzipContent}, + {"Packages.xz", packagesXzContent}, + } { + _, err = packages_service.AddFileToPackageVersionInternal( + repoVersion, + &packages_service.PackageFileCreationInfo{ + PackageFileInfo: packages_service.PackageFileInfo{ + Filename: file.Name, + CompositeKey: fmt.Sprintf("%s|%s|%s", distribution, component, architecture), + }, + Creator: user_model.NewGhostUser(), + Data: file.Data, + IsLead: false, + OverwriteExisting: true, + Properties: map[string]string{ + debian_module.PropertyRepositoryIncludeInRelease: "", + debian_module.PropertyDistribution: distribution, + debian_module.PropertyComponent: component, + debian_module.PropertyArchitecture: architecture, + }, + }, + ) + if err != nil { + return err + } + } + + return nil +} + +// https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files +func buildReleaseFiles(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, distribution string) error { + pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ + VersionID: repoVersion.ID, + Properties: map[string]string{ + debian_module.PropertyRepositoryIncludeInRelease: "", + debian_module.PropertyDistribution: distribution, + }, + }) + if err != nil { + return err + } + + // Delete the release files if there are no packages + if len(pfs) == 0 { + for _, filename := range []string{"Release", "Release.gpg", "InRelease"} { + pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, distribution) + if err != nil && !errors.Is(err, util.ErrNotExist) { + return err + } + + if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil { + return err + } + if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil { + return err + } + } + + return nil + } + + components, err := debian_model.GetComponents(ctx, ownerID, distribution) + if err != nil { + return err + } + + sort.Strings(components) + + architectures, err := debian_model.GetArchitectures(ctx, ownerID, distribution) + if err != nil { + return err + } + + sort.Strings(architectures) + + priv, _, err := GetOrCreateKeyPair(ownerID) + if err != nil { + return err + } + + block, err := armor.Decode(strings.NewReader(priv)) + if err != nil { + return err + } + + e, err := openpgp.ReadEntity(packet.NewReader(block.Body)) + if err != nil { + return err + } + + inReleaseContent, _ := packages_module.NewHashedBuffer() + sw, err := clearsign.Encode(inReleaseContent, e.PrivateKey, nil) + if err != nil { + return err + } + + var buf bytes.Buffer + + w := io.MultiWriter(sw, &buf) + + fmt.Fprintf(w, "Origin: %s\n", setting.AppName) + fmt.Fprintf(w, "Label: %s\n", setting.AppName) + fmt.Fprintf(w, "Suite: %s\n", distribution) + fmt.Fprintf(w, "Codename: %s\n", distribution) + fmt.Fprintf(w, "Components: %s\n", strings.Join(components, " ")) + fmt.Fprintf(w, "Architectures: %s\n", strings.Join(architectures, " ")) + fmt.Fprintf(w, "Date: %s\n", time.Now().UTC().Format(time.RFC1123)) + fmt.Fprint(w, "Acquire-By-Hash: yes") + + pfds, err := packages_model.GetPackageFileDescriptors(ctx, pfs) + if err != nil { + return err + } + + var md5, sha1, sha256, sha512 strings.Builder + for _, pfd := range pfds { + path := fmt.Sprintf("%s/binary-%s/%s", pfd.Properties.GetByName(debian_module.PropertyComponent), pfd.Properties.GetByName(debian_module.PropertyArchitecture), pfd.File.Name) + fmt.Fprintf(&md5, " %s %d %s\n", pfd.Blob.HashMD5, pfd.Blob.Size, path) + fmt.Fprintf(&sha1, " %s %d %s\n", pfd.Blob.HashSHA1, pfd.Blob.Size, path) + fmt.Fprintf(&sha256, " %s %d %s\n", pfd.Blob.HashSHA256, pfd.Blob.Size, path) + fmt.Fprintf(&sha512, " %s %d %s\n", pfd.Blob.HashSHA512, pfd.Blob.Size, path) + } + + fmt.Fprintln(w, "MD5Sum:") + fmt.Fprint(w, md5.String()) + fmt.Fprintln(w, "SHA1:") + fmt.Fprint(w, sha1.String()) + fmt.Fprintln(w, "SHA256:") + fmt.Fprint(w, sha256.String()) + fmt.Fprintln(w, "SHA512:") + fmt.Fprint(w, sha512.String()) + + sw.Close() + + releaseGpgContent, _ := packages_module.NewHashedBuffer() + if err := openpgp.ArmoredDetachSign(releaseGpgContent, e, bytes.NewReader(buf.Bytes()), nil); err != nil { + return err + } + + releaseContent, _ := packages_module.CreateHashedBufferFromReader(&buf) + + for _, file := range []struct { + Name string + Data packages_module.HashedSizeReader + }{ + {"Release", releaseContent}, + {"Release.gpg", releaseGpgContent}, + {"InRelease", inReleaseContent}, + } { + _, err = packages_service.AddFileToPackageVersionInternal( + repoVersion, + &packages_service.PackageFileCreationInfo{ + PackageFileInfo: packages_service.PackageFileInfo{ + Filename: file.Name, + CompositeKey: distribution, + }, + Creator: user_model.NewGhostUser(), + Data: file.Data, + IsLead: false, + OverwriteExisting: true, + Properties: map[string]string{ + debian_module.PropertyDistribution: distribution, + }, + }, + ) + if err != nil { + return err + } + } + + return nil +} diff --git a/services/packages/packages.go b/services/packages/packages.go index dd5c63470b..735e52c854 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -187,19 +187,33 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all } // AddFileToExistingPackage adds a file to an existing package. If the package does not exist, ErrPackageNotExist is returned -func AddFileToExistingPackage(pvi *PackageInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageVersion, *packages_model.PackageFile, error) { +func AddFileToExistingPackage(pvi *PackageInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, error) { + return addFileToPackageWrapper(func(ctx context.Context) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) { + pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version) + if err != nil { + return nil, nil, false, err + } + + return addFileToPackageVersion(ctx, pv, pvi, pfci) + }) +} + +// AddFileToPackageVersionInternal adds a file to the package +// This method skips quota checks and should only be used for system-managed packages. +func AddFileToPackageVersionInternal(pv *packages_model.PackageVersion, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, error) { + return addFileToPackageWrapper(func(ctx context.Context) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) { + return addFileToPackageVersionUnchecked(ctx, pv, pfci) + }) +} + +func addFileToPackageWrapper(fn func(ctx context.Context) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error)) (*packages_model.PackageFile, error) { ctx, committer, err := db.TxContext(db.DefaultContext) if err != nil { - return nil, nil, err + return nil, err } defer committer.Close() - pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version) - if err != nil { - return nil, nil, err - } - - pf, pb, blobCreated, err := addFileToPackageVersion(ctx, pv, pvi, pfci) + pf, pb, blobCreated, err := fn(ctx) removeBlob := false defer func() { if removeBlob { @@ -211,15 +225,15 @@ func AddFileToExistingPackage(pvi *PackageInfo, pfci *PackageFileCreationInfo) ( }() if err != nil { removeBlob = blobCreated - return nil, nil, err + return nil, err } if err := committer.Commit(); err != nil { removeBlob = blobCreated - return nil, nil, err + return nil, err } - return pv, pf, nil + return pf, nil } // NewPackageBlob creates a package blob instance @@ -236,12 +250,16 @@ func NewPackageBlob(hsr packages_module.HashedSizeReader) *packages_model.Packag } func addFileToPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pvi *PackageInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) { - log.Trace("Adding package file: %v, %s", pv.ID, pfci.Filename) - if err := CheckSizeQuotaExceeded(ctx, pfci.Creator, pvi.Owner, pvi.PackageType, pfci.Data.Size()); err != nil { return nil, nil, false, err } + return addFileToPackageVersionUnchecked(ctx, pv, pfci) +} + +func addFileToPackageVersionUnchecked(ctx context.Context, pv *packages_model.PackageVersion, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) { + log.Trace("Adding package file: %v, %s", pv.ID, pfci.Filename) + pb, exists, err := packages_model.GetOrInsertBlob(ctx, NewPackageBlob(pfci.Data)) if err != nil { log.Error("Error inserting package blob: %v", err) @@ -345,6 +363,8 @@ func CheckSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, p typeSpecificSize = setting.Packages.LimitSizeConda case packages_model.TypeContainer: typeSpecificSize = setting.Packages.LimitSizeContainer + case packages_model.TypeDebian: + typeSpecificSize = setting.Packages.LimitSizeDebian case packages_model.TypeGeneric: typeSpecificSize = setting.Packages.LimitSizeGeneric case packages_model.TypeHelm: diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 94b2c3f3d5..55e77a78a7 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -206,7 +206,7 @@ func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, re } } - if exist, err := repo_model.IsRepositoryExist(ctx, u, repoName); err != nil { + if exist, err := repo_model.IsRepositoryModelExist(ctx, u, repoName); err != nil { return err } else if exist { return repo_model.ErrRepoAlreadyExist{ diff --git a/services/repository/files/update.go b/services/repository/files/update.go index 45a4692396..25014f4418 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -458,6 +458,11 @@ func CreateOrUpdateRepoFile(ctx context.Context, repo *repo_model.Repository, do if err != nil { return nil, err } + + if repo.IsEmpty { + _ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false}, "is_empty") + } + return file, nil } diff --git a/templates/admin/runners/base.tmpl b/templates/admin/actions.tmpl similarity index 77% rename from templates/admin/runners/base.tmpl rename to templates/admin/actions.tmpl index 36495b6a0b..9640e0fd1f 100644 --- a/templates/admin/runners/base.tmpl +++ b/templates/admin/actions.tmpl @@ -1,5 +1,7 @@ -{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin runners")}} +{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin actions")}}
+ {{if eq .PageType "runners"}} {{template "shared/actions/runner_list" .}} + {{end}}
{{template "admin/layout_footer" .}} diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 0aed59ffab..2c8fe724e2 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -334,7 +334,7 @@
- +
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 136ad38f16..c4f77ec1ae 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -238,16 +238,18 @@
{{.Mailer.SMTPPort}}
{{end}}
{{.locale.Tr "admin.config.mailer_user"}}
-
{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}

-
- {{.CsrfTokenHtml}} -
-
+
{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}
+
+
{{.locale.Tr "admin.config.send_test_mail"}}
+
+ + {{.CsrfTokenHtml}} +
-
- - + + + {{end}}
@@ -363,7 +365,7 @@
{{$.locale.Tr "admin.config.log_mode"}}
{{.Name}} ({{.Provider}})
{{$.locale.Tr "admin.config.log_config"}}
-
{{.Config | JsonPrettyPrint}}
+
{{JsonUtils.PrettyIndent .Config}}
{{end}}
{{$.locale.Tr "admin.config.router_log_mode"}}
@@ -376,7 +378,7 @@
{{$.locale.Tr "admin.config.log_mode"}}
{{.Name}} ({{.Provider}})
{{$.locale.Tr "admin.config.log_config"}}
-
{{.Config | JsonPrettyPrint}}
+
{{JsonUtils.PrettyIndent .Config}}
{{end}} {{else}}
{{$.locale.Tr "admin.config.routes_to_default_logger"}}
@@ -391,7 +393,7 @@
{{$.locale.Tr "admin.config.log_mode"}}
{{.Name}} ({{.Provider}})
{{$.locale.Tr "admin.config.log_config"}}
-
{{.Config | JsonPrettyPrint}}
+
{{JsonUtils.PrettyIndent .Config}}
{{end}} {{else}}
{{$.locale.Tr "admin.config.routes_to_default_logger"}}
@@ -410,7 +412,7 @@
{{$.locale.Tr "admin.config.log_mode"}}
{{.Name}} ({{.Provider}})
{{$.locale.Tr "admin.config.log_config"}}
-
{{.Config | JsonPrettyPrint}}
+
{{JsonUtils.PrettyIndent .Config}}
{{end}} {{else}}
{{$.locale.Tr "admin.config.routes_to_default_logger"}}
diff --git a/templates/admin/cron.tmpl b/templates/admin/cron.tmpl index 267da00759..a7f4405620 100644 --- a/templates/admin/cron.tmpl +++ b/templates/admin/cron.tmpl @@ -21,8 +21,8 @@ {{$.locale.Tr (printf "admin.dashboard.%s" .Name)}} {{.Spec}} - {{DateTime "full" (DateFmtLong .Next)}} - {{if gt .Prev.Year 1}}{{DateTime "full" .Prev}}{{else}}N/A{{end}} + {{DateTime "full" .Next}} + {{if gt .Prev.Year 1}}{{DateTime "full" .Prev}}{{else}}-{{end}} {{.ExecTimes}} {{if eq .Status ""}}—{{else if eq .Status "finished"}}{{svg "octicon-check" 16}}{{else}}{{svg "octicon-x" 16}}{{end}} diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl index 93fcae4ca4..91a84aebf5 100644 --- a/templates/admin/dashboard.tmpl +++ b/templates/admin/dashboard.tmpl @@ -16,63 +16,63 @@

{{.locale.Tr "admin.dashboard.operations"}}

-
- {{.CsrfTokenHtml}} -
- +
+ + {{.CsrfTokenHtml}} +
- + - + - + - + {{if and (not .SSH.Disabled) (not .SSH.StartBuiltinServer)}} - + - + {{end}} - + - + - + - + - +
{{.locale.Tr "admin.dashboard.delete_inactive_accounts"}}
{{.locale.Tr "admin.dashboard.delete_repo_archives"}}
{{.locale.Tr "admin.dashboard.delete_missing_repos"}}
{{.locale.Tr "admin.dashboard.git_gc_repos"}}
{{.locale.Tr "admin.dashboard.resync_all_sshkeys"}}
{{.locale.Tr "admin.dashboard.resync_all_sshkeys.desc"}}
{{.locale.Tr "admin.dashboard.resync_all_sshprincipals"}}
{{.locale.Tr "admin.dashboard.resync_all_sshprincipals.desc"}}
{{.locale.Tr "admin.dashboard.resync_all_hooks"}}
{{.locale.Tr "admin.dashboard.reinit_missing_repos"}}
{{.locale.Tr "admin.dashboard.sync_external_users"}}
{{.locale.Tr "admin.dashboard.repo_health_check"}}
{{.locale.Tr "admin.dashboard.delete_generated_repository_avatars"}}
-
-
+ +

{{.locale.Tr "admin.dashboard.system_status"}} diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl index e8eb54e58d..51a93bacd7 100644 --- a/templates/admin/navbar.tmpl +++ b/templates/admin/navbar.tmpl @@ -35,9 +35,14 @@ {{end}} {{if .EnableActions}} - - {{.locale.Tr "actions.runners"}} - +
+ {{.locale.Tr "actions.actions"}} + +
{{end}} {{.locale.Tr "admin.config"}} diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index bd6b74dd2b..3dbae04625 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -3,67 +3,60 @@

{{.locale.Tr "admin.notices.system_notice_list"}} ({{.locale.Tr "admin.total" .Total}})

-
- - +
+ + + + + + + + + + + + {{range .Notices}} - - - - - - + + + + + + - - - {{range .Notices}} + {{end}} + + {{if .Notices}} + - - - - - - - - {{end}} - - {{if .Notices}} - - - - + - - - {{end}} -
ID{{.locale.Tr "admin.notices.type"}}{{.locale.Tr "admin.notices.desc"}}{{.locale.Tr "admin.users.created"}}{{.locale.Tr "admin.notices.op"}}
ID{{.locale.Tr "admin.notices.type"}}{{.locale.Tr "admin.notices.desc"}}{{.locale.Tr "admin.users.created"}}{{.locale.Tr "admin.notices.op"}}
{{.ID}}{{$.locale.Tr .TrStr}}{{.Description}}{{DateTime "short" .CreatedUnix}}{{svg "octicon-note" 16}}
-
- -
-
{{.ID}}{{$.locale.Tr .TrStr}}{{.Description}}{{DateTime "short" .CreatedUnix}}{{svg "octicon-note" 16 "view-detail"}}
-
- {{.CsrfTokenHtml}} - -
-
+
+ {{.CsrfTokenHtml}} + +
+
-
- + + + + + + {{end}} + {{template "base/paginate" .}} diff --git a/templates/admin/queue.tmpl b/templates/admin/queue.tmpl index 3de01a32ab..84eb8892ef 100644 --- a/templates/admin/queue.tmpl +++ b/templates/admin/queue.tmpl @@ -174,7 +174,7 @@ {{.locale.Tr "admin.monitor.queue.configuration"}}

-
{{.Queue.Configuration | JsonPrettyPrint}}
+
{{JsonUtils.PrettyIndent .Queue.Configuration}}
diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index 3d09f2de23..f485784d0c 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -1,4 +1,4 @@ -{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin user")}} +{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin")}}

{{.locale.Tr "admin.repos.repo_manage_panel"}} ({{.locale.Tr "admin.total" .Total}}) @@ -33,6 +33,7 @@ {{.locale.Tr "admin.repos.size"}} {{SortArrow "size" "reversesize" $.SortType false}} + {{.locale.Tr "admin.auths.updated"}} {{.locale.Tr "admin.users.created"}} {{.locale.Tr "admin.notices.op"}} @@ -80,6 +81,7 @@ {{.NumForks}} {{.NumIssues}} {{FileSize .Size}} + {{DateTime "short" .UpdatedUnix}} {{DateTime "short" .CreatedUnix}} {{svg "octicon-trash"}} diff --git a/templates/admin/repo/unadopted.tmpl b/templates/admin/repo/unadopted.tmpl index 3e47447178..27898a1854 100644 --- a/templates/admin/repo/unadopted.tmpl +++ b/templates/admin/repo/unadopted.tmpl @@ -1,4 +1,4 @@ -{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin user")}} +{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin")}}

{{.locale.Tr "admin.repos.unadopted"}} @@ -18,47 +18,44 @@ {{if .search}}
{{if .Dirs}} -
+
{{range $dirI, $dir := .Dirs}} -
-
- {{svg "octicon-file-directory-fill"}} - {{$dir}} -
- -
diff --git a/templates/package/content/debian.tmpl b/templates/package/content/debian.tmpl new file mode 100644 index 0000000000..8b60b33bcc --- /dev/null +++ b/templates/package/content/debian.tmpl @@ -0,0 +1,65 @@ +{{if eq .PackageDescriptor.Package.Type "debian"}} +

{{.locale.Tr "packages.installation"}}

+
+
+
+ +
sudo curl  -o /etc/apt/trusted.gpg.d/gitea-{{$.PackageDescriptor.Owner.Name}}.asc
+echo "deb  $distribution $component" | sudo tee -a /etc/apt/sources.list.d/gitea.list
+sudo apt update
+

{{.locale.Tr "packages.debian.registry.info" | Safe}}

+
+
+ +
+
sudo apt install {{$.PackageDescriptor.Package.Name}}={{$.PackageDescriptor.Version.Version}}
+
+
+
+ +
+
+
+ +

{{.locale.Tr "packages.debian.repository"}}

+
+ + + + + + + + + + + + + + + +
{{.locale.Tr "packages.debian.repository.distributions"}}
{{StringUtils.Join .Distributions ", "}}
{{.locale.Tr "packages.debian.repository.components"}}
{{StringUtils.Join .Components ", "}}
{{.locale.Tr "packages.debian.repository.architectures"}}
{{StringUtils.Join .Architectures ", "}}
+
+ + {{if .PackageDescriptor.Metadata.Description}} +

{{.locale.Tr "packages.about"}}

+
+ {{.PackageDescriptor.Metadata.Description}} +
+ {{end}} + + {{if .PackageDescriptor.Metadata.Dependencies}} +

{{.locale.Tr "packages.dependencies"}}

+
+ + + {{range .PackageDescriptor.Metadata.Dependencies}} + + + + {{end}} + +
{{.}}
+
+ {{end}} +{{end}} diff --git a/templates/package/content/generic.tmpl b/templates/package/content/generic.tmpl index 2674a155d3..556e31971c 100644 --- a/templates/package/content/generic.tmpl +++ b/templates/package/content/generic.tmpl @@ -11,7 +11,7 @@ curl - +
diff --git a/templates/package/content/helm.tmpl b/templates/package/content/helm.tmpl index fc190e0e45..9b4f57538a 100644 --- a/templates/package/content/helm.tmpl +++ b/templates/package/content/helm.tmpl @@ -4,15 +4,15 @@
-
helm repo add gitea 
+				
helm repo add {{AppDomain}} 
 helm repo update
-
helm install {{.PackageDescriptor.Package.Name}} gitea/{{.PackageDescriptor.Package.Name}}
+
helm install {{.PackageDescriptor.Package.Name}} {{AppDomain}}/{{.PackageDescriptor.Package.Name}}
- +
diff --git a/templates/package/content/maven.tmpl b/templates/package/content/maven.tmpl index ff89145fd0..f18f12091b 100644 --- a/templates/package/content/maven.tmpl +++ b/templates/package/content/maven.tmpl @@ -40,7 +40,7 @@
mvn dependency:get -DremoteRepositories= -Dartifact={{.PackageDescriptor.Metadata.GroupID}}:{{.PackageDescriptor.Metadata.ArtifactID}}:{{.PackageDescriptor.Version.Version}}
- +

diff --git a/templates/package/content/npm.tmpl b/templates/package/content/npm.tmpl index d0c037b38a..a8d3f40316 100644 --- a/templates/package/content/npm.tmpl +++ b/templates/package/content/npm.tmpl @@ -15,7 +15,7 @@
"{{.PackageDescriptor.Package.Name}}": "{{.PackageDescriptor.Version.Version}}"

- +
diff --git a/templates/package/content/nuget.tmpl b/templates/package/content/nuget.tmpl index d3cf9b09de..f3013adc76 100644 --- a/templates/package/content/nuget.tmpl +++ b/templates/package/content/nuget.tmpl @@ -11,7 +11,7 @@
dotnet add package --source {{.PackageDescriptor.Owner.Name}} --version {{.PackageDescriptor.Version.Version}} {{.PackageDescriptor.Package.Name}}
- +
diff --git a/templates/package/content/pub.tmpl b/templates/package/content/pub.tmpl index d5676fca5a..28aa29abe9 100644 --- a/templates/package/content/pub.tmpl +++ b/templates/package/content/pub.tmpl @@ -7,7 +7,7 @@
dart pub add {{.PackageDescriptor.Package.Name}}:{{.PackageDescriptor.Version.Version}} --hosted-url=
- +
diff --git a/templates/package/content/pypi.tmpl b/templates/package/content/pypi.tmpl index 2576bdd9de..07c387ecac 100644 --- a/templates/package/content/pypi.tmpl +++ b/templates/package/content/pypi.tmpl @@ -7,7 +7,7 @@
pip install --index-url  {{.PackageDescriptor.Package.Name}}
- +
diff --git a/templates/package/content/rubygems.tmpl b/templates/package/content/rubygems.tmpl index d8190ba4f5..6e6ee7105f 100644 --- a/templates/package/content/rubygems.tmpl +++ b/templates/package/content/rubygems.tmpl @@ -13,7 +13,7 @@ end
- +
diff --git a/templates/package/content/swift.tmpl b/templates/package/content/swift.tmpl index 3ff06483b8..ea87599fcc 100644 --- a/templates/package/content/swift.tmpl +++ b/templates/package/content/swift.tmpl @@ -17,7 +17,7 @@
swift package resolve
- +
diff --git a/templates/package/content/vagrant.tmpl b/templates/package/content/vagrant.tmpl index a6a92f5ecd..b6ec2eaf32 100644 --- a/templates/package/content/vagrant.tmpl +++ b/templates/package/content/vagrant.tmpl @@ -7,7 +7,7 @@
vagrant box add --box-version {{.PackageDescriptor.Version.Version}} ""
- +
diff --git a/templates/package/metadata/debian.tmpl b/templates/package/metadata/debian.tmpl new file mode 100644 index 0000000000..93b6db3bd2 --- /dev/null +++ b/templates/package/metadata/debian.tmpl @@ -0,0 +1,4 @@ +{{if eq .PackageDescriptor.Package.Type "debian"}} + {{if .PackageDescriptor.Metadata.Maintainer}}
{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Maintainer}}
{{end}} + {{if .PackageDescriptor.Metadata.ProjectURL}}
{{end}} +{{end}} diff --git a/templates/package/settings.tmpl b/templates/package/settings.tmpl index a443a8d27f..7beb3da062 100644 --- a/templates/package/settings.tmpl +++ b/templates/package/settings.tmpl @@ -20,7 +20,7 @@ {{$repoID = .PackageDescriptor.Repository.ID}} {{end}} - + {{svg "octicon-triangle-down" 14 "dropdown icon"}}
{{.locale.Tr "packages.settings.link.select"}}
diff --git a/templates/package/shared/cleanup_rules/list.tmpl b/templates/package/shared/cleanup_rules/list.tmpl index 09f95e4f4a..10a073eb55 100644 --- a/templates/package/shared/cleanup_rules/list.tmpl +++ b/templates/package/shared/cleanup_rules/list.tmpl @@ -22,9 +22,9 @@ {{.Type.Name}}
{{if .Enabled}}{{$.locale.Tr "enabled"}}{{else}}{{$.locale.Tr "disabled"}}{{end}}
{{if .KeepCount}}
{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.count"}}: {{if eq .KeepCount 1}}{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.count.1"}}{{else}}{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.count.n" .KeepCount}}{{end}}
{{end}} - {{if .KeepPattern}}
{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.pattern"}}: {{EllipsisString .KeepPattern 100}}
{{end}} + {{if .KeepPattern}}
{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.pattern"}}: {{StringUtils.EllipsisString .KeepPattern 100}}
{{end}} {{if .RemoveDays}}
{{$.locale.Tr "packages.owner.settings.cleanuprules.remove.days"}}: {{$.locale.Tr "tool.days" .RemoveDays}}
{{end}} - {{if .RemovePattern}}
{{$.locale.Tr "packages.owner.settings.cleanuprules.remove.pattern"}}: {{EllipsisString .RemovePattern 100}}
{{end}} + {{if .RemovePattern}}
{{$.locale.Tr "packages.owner.settings.cleanuprules.remove.pattern"}}: {{StringUtils.EllipsisString .RemovePattern 100}}
{{end}} {{else}} diff --git a/templates/package/shared/list.tmpl b/templates/package/shared/list.tmpl index 2b6398fcd6..707fbe357f 100644 --- a/templates/package/shared/list.tmpl +++ b/templates/package/shared/list.tmpl @@ -44,7 +44,7 @@ {{$packagesUrl := URLJoin .Owner.HomeLink "-" "packages"}}

{{.locale.Tr "packages.empty.repo" $packagesUrl | Safe}}

{{end}} -

{{.locale.Tr "packages.empty.documentation" | Safe}}

+

{{.locale.Tr "packages.empty.documentation" "https://docs.gitea.io/en-us/usage/packages/overview/" | Safe}}

{{else}}

{{.locale.Tr "packages.filter.no_result"}}

diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl index 3cb130851f..30e737d09c 100644 --- a/templates/package/view.tmpl +++ b/templates/package/view.tmpl @@ -25,6 +25,7 @@ {{template "package/content/conan" .}} {{template "package/content/conda" .}} {{template "package/content/container" .}} + {{template "package/content/debian" .}} {{template "package/content/generic" .}} {{template "package/content/helm" .}} {{template "package/content/maven" .}} @@ -52,6 +53,7 @@ {{template "package/metadata/conan" .}} {{template "package/metadata/conda" .}} {{template "package/metadata/container" .}} + {{template "package/metadata/debian" .}} {{template "package/metadata/generic" .}} {{template "package/metadata/helm" .}} {{template "package/metadata/maven" .}} diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index ac4b34a960..1ef0b0be97 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -3,14 +3,14 @@ {{if .CanWriteProjects}}
{{end}} {{template "base/alert" .}} -