fix migrations.go conflict

This commit is contained in:
DmitryFrolovTri
2022-11-24 17:13:26 +00:00
161 changed files with 3460 additions and 1899 deletions
+1 -11
View File
@@ -39,16 +39,6 @@ steps:
- make lint-frontend
depends_on: [deps-frontend]
- name: security-check
image: golang:1.19
pull: always
commands:
- make security-check
depends_on: [deps-backend]
volumes:
- name: deps
path: /go
- name: lint-backend
image: gitea/test_env:linux-amd64 # https://gitea.com/gitea/test-env
pull: always
@@ -561,7 +551,7 @@ steps:
# TODO: We should probably build all dependencies into a test image
- name: test-e2e
image: mcr.microsoft.com/playwright:v1.27.1-focal
image: mcr.microsoft.com/playwright:v1.28.0-focal
commands:
- curl -sLO https://go.dev/dl/go1.19.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz
- groupadd --gid 1001 gitea && useradd -m --gid 1001 --uid 1001 gitea
+6 -1
View File
@@ -199,7 +199,7 @@ rules:
newline-per-chained-call: [0]
no-alert: [0]
no-array-constructor: [2]
no-async-promise-executor: [2]
no-async-promise-executor: [0]
no-await-in-loop: [0]
no-bitwise: [0]
no-buffer-constructor: [0]
@@ -229,6 +229,7 @@ rules:
no-empty-character-class: [2]
no-empty-function: [0]
no-empty-pattern: [2]
no-empty-static-block: [2]
no-empty: [2, {allowEmptyCatch: true}]
no-eq-null: [2]
no-eval: [2]
@@ -269,6 +270,7 @@ rules:
no-negated-condition: [0]
no-nested-ternary: [0]
no-new-func: [2]
no-new-native-nonconstructor: [2]
no-new-object: [2]
no-new-symbol: [2]
no-new-wrappers: [2]
@@ -443,6 +445,7 @@ rules:
unicorn/no-invalid-remove-event-listener: [2]
unicorn/no-keyword-prefix: [0]
unicorn/no-lonely-if: [2]
unicorn/no-negated-condition: [0]
unicorn/no-nested-ternary: [0]
unicorn/no-new-array: [0]
unicorn/no-new-buffer: [0]
@@ -453,6 +456,7 @@ rules:
unicorn/no-static-only-class: [2]
unicorn/no-thenable: [2]
unicorn/no-this-assignment: [2]
unicorn/no-typeof-undefined: [2]
unicorn/no-unnecessary-await: [2]
unicorn/no-unreadable-array-destructuring: [0]
unicorn/no-unreadable-iife: [2]
@@ -503,6 +507,7 @@ rules:
unicorn/prefer-regexp-test: [2]
unicorn/prefer-replace-all: [0]
unicorn/prefer-set-has: [0]
unicorn/prefer-set-size: [2]
unicorn/prefer-spread: [0]
unicorn/prefer-starts-ends-with: [2]
unicorn/prefer-string-slice: [0]
+11
View File
@@ -1,8 +1,19 @@
extends: stylelint-config-standard
plugins:
- stylelint-declaration-strict-value
overrides:
- files: ["**/*.less"]
customSyntax: postcss-less
- files: ["**/*.less"]
rules:
scale-unlimited/declaration-strict-value: [color, {
ignoreValues: /^(inherit|transparent|unset|initial)$/
}]
- files: ["**/chroma/*", "**/codemirror/*", "**/standalone/*", "**/console/*"]
rules:
scale-unlimited/declaration-strict-value: null
rules:
alpha-value-notation: null
+2 -2
View File
@@ -333,7 +333,7 @@ checks: checks-frontend checks-backend
checks-frontend: lockfile-check svg-check
.PHONY: checks-backend
checks-backend: tidy-check swagger-check fmt-check misspell-check swagger-validate
checks-backend: tidy-check swagger-check fmt-check misspell-check swagger-validate security-check
.PHONY: lint
lint: lint-frontend lint-backend
@@ -745,7 +745,7 @@ generate-go: $(TAGS_PREREQ)
.PHONY: security-check
security-check:
govulncheck -v ./...
go run $(GOVULNCHECK_PACKAGE) -v ./...
$(EXECUTABLE): $(GO_SOURCES) $(TAGS_PREREQ)
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
+6 -1
View File
@@ -759,6 +759,9 @@ ROUTER = console
;; Enable captcha validation for registration
;ENABLE_CAPTCHA = false
;;
;; Enable this to require captcha validation for login
;REQUIRE_CAPTCHA_FOR_LOGIN = false
;;
;; Type of captcha you want to use. Options: image, recaptcha, hcaptcha, mcaptcha.
;CAPTCHA_TYPE = image
;;
@@ -2234,7 +2237,9 @@ ROUTER = console
;; Show template execution time in the footer
;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
;; Generate sitemap. Defaults to `true`.
; ENABLE_SITEMAP = true
;ENABLE_SITEMAP = true
;; Enable/Disable RSS/Atom feed
;ENABLE_FEED = true
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -289,8 +289,13 @@ The following configuration set `Content-Type: application/vnd.android.package-a
This includes CSS files, images, JS files and web fonts.
Avatar images are dynamic resources and still served by Gitea.
The option can be just a different path, as in `/static`, or another domain, as in `https://cdn.example.com`.
Requests are then made as `%(ROOT_URL)s/static/css/index.css` and `https://cdn.example.com/css/index.css` respective.
Requests are then made as `%(ROOT_URL)s/static/assets/css/index.css` or `https://cdn.example.com/assets/css/index.css` respectively.
The static files are located in the `public/` directory of the Gitea source repository.
You can proxy the STATIC_URL_PREFIX requests to Gitea server to serve the static
assets, or copy the manually built Gitea assets from `$GITEA_BUILD/public` to
the assets location, eg: `/var/www/assets`, make sure `$STATIC_URL_PREFIX/assets/css/index.css`
points to `/var/www/assets/css/index.css`.
- `HTTP_ADDR`: **0.0.0.0**: HTTP listen address.
- If `PROTOCOL` is set to `fcgi`, Gitea will listen for FastCGI requests on TCP socket
defined by `HTTP_ADDR` and `HTTP_PORT` configuration settings.
@@ -629,6 +634,7 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o
- `ENABLE_REVERSE_PROXY_FULL_NAME`: **false**: Enable this to allow to auto-registration with a
provided full name for the user.
- `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration.
- `REQUIRE_CAPTCHA_FOR_LOGIN`: **false**: Enable this to require captcha validation for login. You also must enable `ENABLE_CAPTCHA`.
- `REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA`: **false**: Enable this to force captcha validation
even for External Accounts (i.e. GitHub, OpenID Connect, etc). You also must enable `ENABLE_CAPTCHA`.
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha, hcaptcha, mcaptcha\]
@@ -1283,3 +1289,4 @@ PROXY_HOSTS = *.github.com
- `SHOW_FOOTER_VERSION`: **true**: Show Gitea and Go version information in the footer.
- `SHOW_FOOTER_TEMPLATE_LOAD_TIME`: **true**: Show time of template execution in the footer.
- `ENABLE_SITEMAP`: **true**: Generate sitemap.
- `ENABLE_FEED`: **true**: Enable/Disable RSS/Atom feed.
@@ -145,7 +145,8 @@ menu:
- `ENABLE_NOTIFY_MAIL`: 是否发送工单创建等提醒邮件,需要 `Mailer` 被激活。
- `ENABLE_REVERSE_PROXY_AUTHENTICATION`: 允许反向代理认证,更多细节见:https://github.com/gogits/gogs/issues/165
- `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION`: 允许通过反向认证做自动注册。
- `ENABLE_CAPTCHA`: 注册时使用图片验证码。
- `ENABLE_CAPTCHA`: **false**: 注册时使用图片验证码。
- `REQUIRE_CAPTCHA_FOR_LOGIN`: **false**: 登录时需要图片验证码。需要同时开启 `ENABLE_CAPTCHA`
### Service - Expore (`service.explore`)
@@ -15,6 +15,14 @@ menu:
# Logging Configuration
The logging configuration of Gitea mainly consists of 3 types of components:
- The `[log]` section for general configuration
- `[log.<sublogger>]` sections for the configuration of different log outputs
- `[log.<sublogger>.<group>]` sections for output specific configuration of a log group
As mentioned below, there is a fully functional log output by default, so it is not necessary to define one.
**Table of Contents**
{{< toc >}}
@@ -23,6 +31,166 @@ menu:
To collect logs for help and issue report, see [Support Options]({{< relref "doc/help/seek-help.en-us.md" >}}).
## The `[log]` section
Configuration of logging facilities in Gitea happen in the `[log]` section and it's subsections.
In the top level `[log]` section the following configurations can be placed:
- `ROOT_PATH`: (Default: **%(GITEA_WORK_DIR)/log**): Base path for log files
- `MODE`: (Default: **console**) List of log outputs to use for the Default logger.
- `ROUTER`: (Default: **console**): List of log outputs to use for the Router logger.
- `ACCESS`: List of log outputs to use for the Access logger.
- `XORM`: (Default: **,**) List of log outputs to use for the XORM logger.
- `ENABLE_ACCESS_LOG`: (Default: **false**): whether the Access logger is allowed to emit logs
- `ENABLE_XORM_LOG`: (Default: **true**): whether the XORM logger is allowed to emit logs
For details on the loggers check the "Log Groups" section.
Important: log outputs won't be used if you don't enable them for the desired loggers in the corresponding list value.
Lists are specified as comma separated values. This format also works in subsection.
This section may be used for defining default values for subsections.
Examples:
- `LEVEL`: (Default: **Info**) Least severe log events to persist. Case insensitive. The full list of levels as of v1.17.3 can be read [here](https://github.com/go-gitea/gitea/blob/v1.17.3/custom/conf/app.example.ini#L507).
- `STACKTRACE_LEVEL`: (Default: **None**) For this and more severe events the stacktrace will be printed upon getting logged.
Some values are not inherited by subsections. For details see the "Non-inherited default values" section.
## Log outputs
Log outputs are the targets to which log messages will be sent.
The content and the format of the log messages to be saved can be configured in these.
Log outputs are also called subloggers.
Gitea provides 4 possible log outputs:
- `console` - Log to `os.Stdout` or `os.Stderr`
- `file` - Log to a file
- `conn` - Log to a socket (network or unix)
- `smtp` - Log via email
By default, Gitea has a `console` output configured, which is used by the loggers as seen in the section "The log section" above.
### Common configuration
Certain configuration is common to all modes of log output:
- `MODE` is the mode of the log output. It will default to the sublogger
name, thus `[log.console.router]` will default to `MODE = console`.
For mode specific confgurations read further.
- `LEVEL` is the lowest level that this output will log. This value
is inherited from `[log]` and in the case of the non-default loggers
from `[log.sublogger]`.
- `STACKTRACE_LEVEL` is the lowest level that this output will print
a stacktrace. This value is inherited.
- `COLORIZE` will default to `true` for `console` as
described, otherwise it will default to `false`.
### Non-inherited default values
There are several values which are not inherited as described above but
rather default to those specific to type of logger, these are:
`EXPRESSION`, `FLAGS`, `PREFIX` and `FILE_NAME`.
#### `EXPRESSION`
`EXPRESSION` represents a regular expression that log events must match to be logged by the sublogger. Either the log message, (with colors removed), must match or the `longfilename:linenumber:functionname` must match. NB: the whole message or string doesn't need to completely match.
Please note this expression will be run in the sublogger's goroutine
not the logging event subroutine. Therefore it can be complicated.
#### `FLAGS`
`FLAGS` represents the preceding logging context information that is
printed before each message. It is a comma-separated string set. The order of values does not matter.
Possible values are:
- `none` or `,` - No flags.
- `date` - the date in the local time zone: `2009/01/23`.
- `time` - the time in the local time zone: `01:23:23`.
- `microseconds` - microsecond resolution: `01:23:23.123123`. Assumes
time.
- `longfile` - full file name and line number: `/a/b/c/d.go:23`.
- `shortfile` - final file name element and line number: `d.go:23`.
- `funcname` - function name of the caller: `runtime.Caller()`.
- `shortfuncname` - last part of the function name. Overrides
`funcname`.
- `utc` - if date or time is set, use UTC rather than the local time
zone.
- `levelinitial` - Initial character of the provided level in brackets eg. `[I]` for info.
- `level` - Provided level in brackets `[INFO]`
- `medfile` - Last 20 characters of the filename - equivalent to
`shortfile,longfile`.
- `stdflags` - Equivalent to `date,time,medfile,shortfuncname,levelinitial`
### Console mode
In this mode the logger will forward log messages to the stdout and
stderr streams attached to the Gitea process.
For loggers in console mode, `COLORIZE` will default to `true` if not
on windows, or the windows terminal can be set into ANSI mode or is a
cygwin or Msys pipe.
Settings:
- `STDERR`: **false**: Whether the logger should print to `stderr` instead of `stdout`.
### File mode
In this mode the logger will save log messages to a file.
Settings:
- `FILE_NAME`: The file to write the log events to. For details see below.
- `MAX_SIZE_SHIFT`: **28**: Maximum size shift of a single file. 28 represents 256Mb. For details see below.
- `LOG_ROTATE` **true**: Whether to rotate the log files. TODO: if false, will it delete instead on daily rotate, or do nothing?.
- `DAILY_ROTATE`: **true**: Whether to rotate logs daily.
- `MAX_DAYS`: **7**: Delete rotated log files after this number of days.
- `COMPRESS`: **true**: Whether to compress old log files by default with gzip.
- `COMPRESSION_LEVEL`: **-1**: Compression level. For details see below.
The default value of `FILE_NAME` depends on the respective logger facility.
If unset, their own default will be used.
If set it will be relative to the provided `ROOT_PATH` in the master `[log]` section.
`MAX_SIZE_SHIFT` defines the maximum size of a file by left shifting 1 the given number of times (`1 << x`).
The exact behavior at the time of v1.17.3 can be seen [here](https://github.com/go-gitea/gitea/blob/v1.17.3/modules/setting/log.go#L185).
The useful values of `COMPRESSION_LEVEL` are from 1 to (and including) 9, where higher numbers mean better compression.
Beware that better compression might come with higher resource usage.
Must be preceded with a `-` sign.
### Conn mode
In this mode the logger will send log messages over a network socket.
Settings:
- `ADDR`: **:7020**: Sets the address to connect to.
- `PROTOCOL`: **tcp**: Set the protocol, either "tcp", "unix" or "udp".
- `RECONNECT`: **false**: Try to reconnect when connection is lost.
- `RECONNECT_ON_MSG`: **false**: Reconnect host for every single message.
### SMTP mode
In this mode the logger will send log messages in email.
It is not recommended to use this logger to send general logging
messages. However, you could perhaps set this logger to work on `FATAL` messages only.
Settings:
- `HOST`: **127.0.0.1:25**: The SMTP host to connect to.
- `USER`: User email address to send from.
- `PASSWD`: Password for the smtp server.
- `RECEIVERS`: Email addresses to send to.
- `SUBJECT`: **Diagnostic message from Gitea**. The content of the email's subject field.
## Log Groups
The fundamental thing to be aware of in Gitea is that there are several
@@ -172,106 +340,6 @@ which will not be inherited from the `[log]` or relevant
- `EXPRESSION` will default to `""`
- `PREFIX` will default to `""`
## Log outputs
Gitea provides 4 possible log outputs:
- `console` - Log to `os.Stdout` or `os.Stderr`
- `file` - Log to a file
- `conn` - Log to a keep-alive TCP connection
- `smtp` - Log via email
Certain configuration is common to all modes of log output:
- `LEVEL` is the lowest level that this output will log. This value
is inherited from `[log]` and in the case of the non-default loggers
from `[log.sublogger]`.
- `STACKTRACE_LEVEL` is the lowest level that this output will print
a stacktrace. This value is inherited.
- `MODE` is the mode of the log output. It will default to the sublogger
name. Thus `[log.console.router]` will default to `MODE = console`.
- `COLORIZE` will default to `true` for `console` as
described, otherwise it will default to `false`.
### Non-inherited default values
There are several values which are not inherited as described above but
rather default to those specific to type of logger, these are:
`EXPRESSION`, `FLAGS`, `PREFIX` and `FILE_NAME`.
#### `EXPRESSION`
`EXPRESSION` represents a regular expression that log events must match to be logged by the sublogger. Either the log message, (with colors removed), must match or the `longfilename:linenumber:functionname` must match. NB: the whole message or string doesn't need to completely match.
Please note this expression will be run in the sublogger's goroutine
not the logging event subroutine. Therefore it can be complicated.
#### `FLAGS`
`FLAGS` represents the preceding logging context information that is
printed before each message. It is a comma-separated string set. The order of values does not matter.
Possible values are:
- `none` or `,` - No flags.
- `date` - the date in the local time zone: `2009/01/23`.
- `time` - the time in the local time zone: `01:23:23`.
- `microseconds` - microsecond resolution: `01:23:23.123123`. Assumes
time.
- `longfile` - full file name and line number: `/a/b/c/d.go:23`.
- `shortfile` - final file name element and line number: `d.go:23`.
- `funcname` - function name of the caller: `runtime.Caller()`.
- `shortfuncname` - last part of the function name. Overrides
`funcname`.
- `utc` - if date or time is set, use UTC rather than the local time
zone.
- `levelinitial` - Initial character of the provided level in brackets eg. `[I]` for info.
- `level` - Provided level in brackets `[INFO]`
- `medfile` - Last 20 characters of the filename - equivalent to
`shortfile,longfile`.
- `stdflags` - Equivalent to `date,time,medfile,shortfuncname,levelinitial`
### Console mode
For loggers in console mode, `COLORIZE` will default to `true` if not
on windows, or the windows terminal can be set into ANSI mode or is a
cygwin or Msys pipe.
If `STDERR` is set to `true` the logger will use `os.Stderr` instead of
`os.Stdout`.
### File mode
The `FILE_NAME` defaults as described above. If set it will be relative
to the provided `ROOT_PATH` in the master `[log]` section.
Other values:
- `LOG_ROTATE`: **true**: Rotate the log files.
- `MAX_SIZE_SHIFT`: **28**: Maximum size shift of a single file, 28 represents 256Mb.
- `DAILY_ROTATE`: **true**: Rotate logs daily.
- `MAX_DAYS`: **7**: Delete the log file after n days
- `COMPRESS`: **true**: Compress old log files by default with gzip
- `COMPRESSION_LEVEL`: **-1**: Compression level
### Conn mode
- `RECONNECT_ON_MSG`: **false**: Reconnect host for every single message.
- `RECONNECT`: **false**: Try to reconnect when connection is lost.
- `PROTOCOL`: **tcp**: Set the protocol, either "tcp", "unix" or "udp".
- `ADDR`: **:7020**: Sets the address to connect to.
### SMTP mode
It is not recommended to use this logger to send general logging
messages. However, you could perhaps set this logger to work on `FATAL`.
- `USER`: User email address to send from.
- `PASSWD`: Password for the smtp server.
- `HOST`: **127.0.0.1:25**: The SMTP host to connect to.
- `RECEIVERS`: Email addresses to send to.
- `SUBJECT`: **Diagnostic message from Gitea**
## Debugging problems
When submitting logs in Gitea issues it is often helpful to submit
@@ -166,11 +166,47 @@ Uses the following fields:
## PAM (Pluggable Authentication Module)
To configure PAM, set the 'PAM Service Name' to a filename in `/etc/pam.d/`. To
work with normal Linux passwords, the user running Gitea must have read access
to `/etc/shadow`.
This procedure enables PAM authentication. Users may still be added to the
system manually using the user administration. PAM provides a mechanism to
automatically add users to the current database by testing them against PAM
authentication. To work with normal Linux passwords, the user running Gitea
must also have read access to `/etc/shadow` in order to check the validity of
the account when logging in using a public key.
**Note**: PAM support is added via [build-time flags](https://docs.gitea.io/en-us/install-from-source/#build), and the official binaries provided do not have this enabled.
**Note**: If a user has added SSH public keys into Gitea, the use of these
keys _may_ bypass the login check system. Therefore, if you wish to disable a user who
authenticates with PAM, you _should_ also manually disable the account in Gitea using the
built-in user manager.
1. Configure and prepare the installation.
- It is recommended that you create an administrative user.
- Deselecting automatic sign-up may also be desired.
1. Once the database has been initialized, log in as the newly created
administrative user.
1. Navigate to the user setting (icon in top-right corner), and select
`Site Administration` -> `Authentication Sources`, and select
`Add Authentication Source`.
1. Fill out the field as follows:
- `Authentication Type` : `PAM`
- `Name` : Any value should be valid here, use "System Authentication" if
you'd like.
- `PAM Service Name` : Select the appropriate file listed under `/etc/pam.d/`
that performs the authentication desired.[^1]
- `PAM Email Domain` : The e-mail suffix to append to user authentication.
For example, if the login system expects a user called `gituser`, and this
field is set to `mail.com`, then Gitea will expect the `user email` field
for an authenticated GIT instance to be `gituser@mail.com`.[^2]
**Note**: PAM support is added via [build-time flags](https://docs.gitea.io/en-us/install-from-source/#build),
and the official binaries provided do not have this enabled. PAM requires that
the necessary libpam dynamic library be available and the necessary PAM
development headers be accessible to the compiler.
[^1]: For example, using standard Linux log-in on Debian "Bullseye" use
`common-session-noninteractive` - this value may be valid for other flavors of
Debian including Ubuntu and Mint, consult your distribution's documentation.
[^2]: **This is a required field for PAM**. Be aware: In the above example, the
user will log into the Gitea web interface as `gituser` and not `gituser@mail.com`
## SMTP (Simple Mail Transfer Protocol)
@@ -29,10 +29,16 @@ the official [install instructions](https://docs.docker.com/compose/install/).
The most simple setup just creates a volume and a network and starts the `gitea/gitea:latest-rootless`
image as a service. Since there is no database available, one can be initialized using SQLite3.
Create a directory for `data` and `config` then paste the following content into a file named `docker-compose.yml`.
Note that the volume should be owned by the user/group with the UID/GID specified in the config file. By default Gitea in docker will use uid:1000 gid:1000. If needed you can set ownership on those folders with the command: `sudo chown 1000:1000 config/ data/`
If you don't give the volume correct permissions, the container may not start.
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev-rootless` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev-rootless`)
Create a directory for `data` and `config`:
```sh
mkdir -p gitea/{data,config}
cd gitea
touch docker-compose.yml
```
Then paste the following content into a file named `docker-compose.yml`:
```yaml
version: "2"
@@ -51,6 +57,16 @@ services:
- "2222:2222"
```
Note that the volume should be owned by the user/group with the UID/GID specified in the config file. By default Gitea in docker will use uid:1000 gid:1000. If needed you can set ownership on those folders with the command:
```sh
sudo chown 1000:1000 config/ data/
```
> If you don't give the volume correct permissions, the container may not start.
For a stable release you could use `:latest-rootless`, `:1-rootless` or specify a certain release like `:{{< version >}}-rootless`, but if you'd like to use the latest development version then `:dev-rootless` would be an appropriate tag. If you'd like to run the latest commit from a release branch you can use the `:1.x-dev-rootless` tag, where x is the minor version of Gitea. (e.g. `:1.16-dev-rootless`)
## Custom port
To bind the integrated ssh and the webserver on a different port, adjust
@@ -0,0 +1,84 @@
---
date: "2022-11-01T00:00:00+00:00"
title: "Storage"
slug: "packages/storage"
draft: false
toc: false
menu:
sidebar:
parent: "packages"
name: "storage"
weight: 5
identifier: "storage"
---
# Storage
This document describes the storage of the package registry and how it can be managed.
**Table of Contents**
{{< toc >}}
## Deduplication
The package registry has a build-in deduplication of uploaded blobs.
If two identical files are uploaded only one blob is saved on the filesystem.
This ensures no space is wasted for duplicated files.
If two packages are uploaded with identical files, both packages will display the same size but on the filesystem they require only half of the size.
Whenever a package gets deleted only the references to the underlaying blobs are removed.
The blobs get not removed at this moment, so they still require space on the filesystem.
When a new package gets uploaded the existing blobs may get referenced again.
These unreferenced blobs get deleted by a [clean up job]({{< relref "doc/advanced/config-cheat-sheet.en-us.md#cron---cleanup-expired-packages-croncleanup_packages" >}}).
The config setting `OLDER_THAN` configures how long unreferenced blobs are kept before they get deleted.
## Cleanup Rules
Package registries can become large over time without cleanup.
It's recommended to delete unnecessary packages and set up cleanup rules to automatically manage the package registry usage.
Every package owner (user or organization) manages the cleanup rules which are applied to their packages.
|Setting|Description|
|-|-|
|Enabled|Turn the cleanup rule on or off.|
|Type|Every rule manages a specific package type.|
|Apply pattern to full package name|If enabled, the patterns below are applied to the full package name (`package/version`). Otherwise only the version (`version`) is used.|
|Keep the most recent|How many versions to *always* keep for each package.|
|Keep versions matching|The regex pattern that determines which versions to keep. An empty pattern keeps no version while `.+` keeps all versions. The container registry will always keep the `latest` version even if not configured.|
|Remove versions older than|Remove only versions older than the selected days.|
|Remove versions matching|The regex pattern that determines which versions to remove. An empty pattern or `.+` leads to the removal of every package if no other setting tells otherwise.|
Every cleanup rule can show a preview of the affected packages.
This can be used to check if the cleanup rules is proper configured.
### Regex examples
Regex patterns are automatically surrounded with `\A` and `\z` anchors.
Do not include any `\A`, `\z`, `^` or `$` token in the regex patterns as they are not necessary.
The patterns are case-insensitive which matches the behaviour of the package registry in Gitea.
|Pattern|Description|
|-|-|
|`.*`|Match every possible version.|
|`v.+`|Match versions that start with `v`.|
|`release`|Match only the version `release`.|
|`release.*`|Match versions that are either named or start with `release`.|
|`.+-temp-.+`|Match versions that contain `-temp-`.|
|`v.+\|release`|Match versions that either start with `v` or are named `release`.|
|`package/v.+\|other/release`|Match versions of the package `package` that start with `v` or the version `release` of the package `other`. This needs the setting *Apply pattern to full package name* enabled.|
### How the cleanup rules work
The cleanup rules are part of the [clean up job]({{< relref "doc/advanced/config-cheat-sheet.en-us.md#cron---cleanup-expired-packages-croncleanup_packages" >}}) and run periodicly.
The cleanup rule:
1. Collects all packages of the package type for the owners registry.
1. For every package it collects all versions.
1. Excludes from the list the # versions based on the *Keep the most recent* value.
1. Excludes from the list any versions matching the *Keep versions matching* value.
1. Excludes from the list the versions more recent than the *Remove versions older than* value.
1. Excludes from the list any versions not matching the *Remove versions matching* value.
1. Deletes the remaining versions.
+1 -1
View File
@@ -7,7 +7,7 @@ toc: false
menu:
sidebar:
parent: "packages"
name: "vagrant"
name: "Vagrant"
weight: 120
identifier: "vagrant"
---
+1 -1
View File
@@ -102,7 +102,6 @@ require (
golang.org/x/tools v0.1.12
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.4.0
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
@@ -293,6 +292,7 @@ require (
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
+1 -1
View File
@@ -66,7 +66,7 @@ type AccessToken struct {
Token string `xorm:"-"`
TokenHash string `xorm:"UNIQUE"` // sha256 of token
TokenSalt string
TokenLastEight string `xorm:"token_last_eight"`
TokenLastEight string `xorm:"INDEX token_last_eight"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+6 -2
View File
@@ -20,8 +20,12 @@ import (
"code.gitea.io/gitea/modules/setting"
)
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
const DefaultAvatarPixelSize = 28
const (
// DefaultAvatarClass is the default class of a rendered avatar
DefaultAvatarClass = "ui avatar vm"
// DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
DefaultAvatarPixelSize = 28
)
// EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
type EmailHash struct {
+4
View File
@@ -439,6 +439,10 @@ var migrations = []Migration{
NewMigration("Alter package_version.metadata_json to LONGTEXT", v1_19.AlterPackageVersionMetadataToLongText),
// v233 -> v234
NewMigration("Add header_authorization_encrypted column to webhook table", v1_19.AddHeaderAuthorizationEncryptedColWebhook),
// v234 -> v235
NewMigration("Add package cleanup rule table", v1_19.CreatePackageCleanupRuleTable),
// v235 -> v236
NewMigration("Add index for access_token", v1_19.AddIndexForAccessToken),
// to modify later
NewMigration("add size limit on repository", addSizeLimitOnRepo),
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package v1_19 //nolint
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func CreatePackageCleanupRuleTable(x *xorm.Engine) error {
type PackageCleanupRule struct {
ID int64 `xorm:"pk autoincr"`
Enabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
OwnerID int64 `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT 0"`
Type string `xorm:"UNIQUE(s) INDEX NOT NULL"`
KeepCount int `xorm:"NOT NULL DEFAULT 0"`
KeepPattern string `xorm:"NOT NULL DEFAULT ''"`
RemoveDays int `xorm:"NOT NULL DEFAULT 0"`
RemovePattern string `xorm:"NOT NULL DEFAULT ''"`
MatchFullName bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL DEFAULT 0"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL DEFAULT 0"`
}
return x.Sync2(new(PackageCleanupRule))
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package v1_19 //nolint
import (
"xorm.io/xorm"
)
func AddIndexForAccessToken(x *xorm.Engine) error {
type AccessToken struct {
TokenLastEight string `xorm:"INDEX token_last_eight"`
}
return x.Sync(new(AccessToken))
}
+15
View File
@@ -45,6 +45,21 @@ const (
TypeVagrant Type = "vagrant"
)
var TypeList = []Type{
TypeComposer,
TypeConan,
TypeContainer,
TypeGeneric,
TypeHelm,
TypeMaven,
TypeNpm,
TypeNuGet,
TypePub,
TypePyPI,
TypeRubyGems,
TypeVagrant,
}
// Name gets the name of the package type
func (pt Type) Name() string {
switch pt {
+110
View File
@@ -0,0 +1,110 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package packages
import (
"context"
"errors"
"fmt"
"regexp"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
)
var ErrPackageCleanupRuleNotExist = errors.New("Package blob does not exist")
func init() {
db.RegisterModel(new(PackageCleanupRule))
}
// PackageCleanupRule represents a rule which describes when to clean up package versions
type PackageCleanupRule struct {
ID int64 `xorm:"pk autoincr"`
Enabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
OwnerID int64 `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT 0"`
Type Type `xorm:"UNIQUE(s) INDEX NOT NULL"`
KeepCount int `xorm:"NOT NULL DEFAULT 0"`
KeepPattern string `xorm:"NOT NULL DEFAULT ''"`
KeepPatternMatcher *regexp.Regexp `xorm:"-"`
RemoveDays int `xorm:"NOT NULL DEFAULT 0"`
RemovePattern string `xorm:"NOT NULL DEFAULT ''"`
RemovePatternMatcher *regexp.Regexp `xorm:"-"`
MatchFullName bool `xorm:"NOT NULL DEFAULT false"`
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL DEFAULT 0"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL DEFAULT 0"`
}
func (pcr *PackageCleanupRule) CompiledPattern() error {
if pcr.KeepPatternMatcher != nil || pcr.RemovePatternMatcher != nil {
return nil
}
if pcr.KeepPattern != "" {
var err error
pcr.KeepPatternMatcher, err = regexp.Compile(fmt.Sprintf(`(?i)\A%s\z`, pcr.KeepPattern))
if err != nil {
return err
}
}
if pcr.RemovePattern != "" {
var err error
pcr.RemovePatternMatcher, err = regexp.Compile(fmt.Sprintf(`(?i)\A%s\z`, pcr.RemovePattern))
if err != nil {
return err
}
}
return nil
}
func InsertCleanupRule(ctx context.Context, pcr *PackageCleanupRule) (*PackageCleanupRule, error) {
return pcr, db.Insert(ctx, pcr)
}
func GetCleanupRuleByID(ctx context.Context, id int64) (*PackageCleanupRule, error) {
pcr := &PackageCleanupRule{}
has, err := db.GetEngine(ctx).ID(id).Get(pcr)
if err != nil {
return nil, err
}
if !has {
return nil, ErrPackageCleanupRuleNotExist
}
return pcr, nil
}
func UpdateCleanupRule(ctx context.Context, pcr *PackageCleanupRule) error {
_, err := db.GetEngine(ctx).ID(pcr.ID).AllCols().Update(pcr)
return err
}
func GetCleanupRulesByOwner(ctx context.Context, ownerID int64) ([]*PackageCleanupRule, error) {
pcrs := make([]*PackageCleanupRule, 0, 10)
return pcrs, db.GetEngine(ctx).Where("owner_id = ?", ownerID).Find(&pcrs)
}
func DeleteCleanupRuleByID(ctx context.Context, ruleID int64) error {
_, err := db.GetEngine(ctx).ID(ruleID).Delete(&PackageCleanupRule{})
return err
}
func HasOwnerCleanupRuleForPackageType(ctx context.Context, ownerID int64, packageType Type) (bool, error) {
return db.GetEngine(ctx).
Where("owner_id = ? AND type = ?", ownerID, packageType).
Exist(&PackageCleanupRule{})
}
func IterateEnabledCleanupRules(ctx context.Context, callback func(context.Context, *PackageCleanupRule) error) error {
return db.Iterate(
ctx,
builder.Eq{"enabled": true},
callback,
)
}
+9
View File
@@ -320,6 +320,15 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P
return pvs, count, err
}
// ExistVersion checks if a version matching the search options exist
func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error) {
return db.GetEngine(ctx).
Where(opts.toConds()).
Table("package_version").
Join("INNER", "package", "package.id = package_version.package_id").
Exist(new(PackageVersion))
}
// CountVersions counts all versions of packages matching the search options
func CountVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) {
return db.GetEngine(ctx).
+19 -3
View File
@@ -233,14 +233,30 @@ func ReplayHookTask(ctx context.Context, hookID int64, uuid string) (*HookTask,
return newTask, db.Insert(ctx, newTask)
}
// FindUndeliveredHookTasks represents find the undelivered hook tasks
func FindUndeliveredHookTasks(ctx context.Context) ([]*HookTask, error) {
tasks := make([]*HookTask, 0, 10)
// FindUndeliveredHookTaskIDs will find the next 100 undelivered hook tasks with ID greater than the provided lowerID
func FindUndeliveredHookTaskIDs(ctx context.Context, lowerID int64) ([]int64, error) {
const batchSize = 100
tasks := make([]int64, 0, batchSize)
return tasks, db.GetEngine(ctx).
Select("id").
Table(new(HookTask)).
Where("is_delivered=?", false).
And("id > ?", lowerID).
Asc("id").
Limit(batchSize).
Find(&tasks)
}
func MarkTaskDelivered(ctx context.Context, task *HookTask) (bool, error) {
count, err := db.GetEngine(ctx).ID(task.ID).Where("is_delivered = ?", false).Cols("is_delivered").Update(&HookTask{
ID: task.ID,
IsDelivered: true,
})
return count != 0, err
}
// CleanupHookTaskTable deletes rows from hook_task as needed.
func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, olderThan time.Duration, numberToKeep int) error {
log.Trace("Doing: CleanupHookTaskTable")
+59
View File
@@ -5,9 +5,15 @@
package context
import (
"fmt"
"sync"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"
"gitea.com/go-chi/captcha"
@@ -28,3 +34,56 @@ func GetImageCaptcha() *captcha.Captcha {
})
return cpt
}
// SetCaptchaData sets common captcha data
func SetCaptchaData(ctx *Context) {
if !setting.Service.EnableCaptcha {
return
}
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
}
const (
gRecaptchaResponseField = "g-recaptcha-response"
hCaptchaResponseField = "h-captcha-response"
mCaptchaResponseField = "m-captcha-response"
)
// VerifyCaptcha verifies Captcha data
// No-op if captchas are not enabled
func VerifyCaptcha(ctx *Context, tpl base.TplName, form interface{}) {
if !setting.Service.EnableCaptcha {
return
}
var valid bool
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx, ctx.Req.Form.Get(gRecaptchaResponseField))
case setting.HCaptcha:
valid, err = hcaptcha.Verify(ctx, ctx.Req.Form.Get(hCaptchaResponseField))
case setting.MCaptcha:
valid, err = mcaptcha.Verify(ctx, ctx.Req.Form.Get(mCaptchaResponseField))
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%v", err)
}
if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tpl, form)
}
}
+13 -5
View File
@@ -349,9 +349,11 @@ func (ctx *Context) RespHeader() http.Header {
type ServeHeaderOptions struct {
ContentType string // defaults to "application/octet-stream"
ContentTypeCharset string
ContentLength *int64
Disposition string // defaults to "attachment"
Filename string
CacheDuration time.Duration // defaults to 5 minutes
LastModified time.Time
}
// SetServeHeaders sets necessary content serve headers
@@ -369,6 +371,10 @@ func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
header.Set("Content-Type", contentType)
header.Set("X-Content-Type-Options", "nosniff")
if opts.ContentLength != nil {
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
}
if opts.Filename != "" {
disposition := opts.Disposition
if disposition == "" {
@@ -385,14 +391,16 @@ func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
duration = 5 * time.Minute
}
httpcache.AddCacheControlToHeader(header, duration)
if !opts.LastModified.IsZero() {
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
}
}
// ServeContent serves content to http request
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, modTime time.Time) {
ctx.SetServeHeaders(&ServeHeaderOptions{
Filename: name,
})
http.ServeContent(ctx.Resp, ctx.Req, name, modTime, r)
func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
ctx.SetServeHeaders(opts)
http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r)
}
// UploadStream returns the request body or the first form file
+7 -2
View File
@@ -441,8 +441,10 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
userName := ctx.Params(":username")
repoName := ctx.Params(":reponame")
repoName = strings.TrimSuffix(repoName, ".git")
repoName = strings.TrimSuffix(repoName, ".rss")
repoName = strings.TrimSuffix(repoName, ".atom")
if setting.EnableFeed {
repoName = strings.TrimSuffix(repoName, ".rss")
repoName = strings.TrimSuffix(repoName, ".atom")
}
// Check if the user is the same as the repository owner
if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(userName) {
@@ -1087,6 +1089,9 @@ func (ctx *Context) IssueTemplatesErrorsFromDefaultBranch() ([]*api.IssueTemplat
if it, err := template.UnmarshalFromEntry(entry, dirName); err != nil {
invalidFiles[fullName] = err
} else {
if !strings.HasPrefix(it.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
it.Ref = git.BranchPrefix + it.Ref
}
issueTemplates = append(issueTemplates, it)
}
}
+4 -1
View File
@@ -10,6 +10,7 @@ package git
import (
"bytes"
"strconv"
"strings"
"time"
"github.com/go-git/go-git/v5/plumbing/object"
@@ -30,7 +31,9 @@ type Signature = object.Signature
func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
sig := new(Signature)
emailStart := bytes.IndexByte(line, '<')
sig.Name = string(line[:emailStart-1])
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
}
emailEnd := bytes.IndexByte(line, '>')
sig.Email = string(line[emailStart+1 : emailEnd])
+4 -1
View File
@@ -11,6 +11,7 @@ import (
"bytes"
"fmt"
"strconv"
"strings"
"time"
)
@@ -51,7 +52,9 @@ func newSignatureFromCommitline(line []byte) (sig *Signature, err error) {
return
}
sig.Name = string(line[:emailStart-1])
if emailStart > 0 { // Empty name has already occurred, even if it shouldn't
sig.Name = strings.TrimSpace(string(line[:emailStart-1]))
}
sig.Email = string(line[emailStart+1 : emailEnd])
hasTime := emailEnd+2 < len(line)
+3
View File
@@ -100,6 +100,9 @@ func RefURL(repoURL, ref string) string {
return repoURL + "/src/branch/" + refName
case strings.HasPrefix(ref, TagPrefix):
return repoURL + "/src/tag/" + refName
case !IsValidSHAPattern(ref):
// assume they mean a branch
return repoURL + "/src/branch/" + refName
default:
return repoURL + "/src/commit/" + refName
}
+5 -6
View File
@@ -8,12 +8,13 @@ import (
"fmt"
"os"
"strings"
"time"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"github.com/santhosh-tekuri/jsonschema/v5"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
// Load project data from file, with optional validation
@@ -84,13 +85,9 @@ func validate(bs []byte, datatype interface{}, isJSON bool) error {
func toStringKeys(val interface{}) (interface{}, error) {
var err error
switch val := val.(type) {
case map[interface{}]interface{}:
case map[string]interface{}:
m := make(map[string]interface{})
for k, v := range val {
k, ok := k.(string)
if !ok {
return nil, fmt.Errorf("found non-string key %T %s", k, k)
}
m[k], err = toStringKeys(v)
if err != nil {
return nil, err
@@ -106,6 +103,8 @@ func toStringKeys(val interface{}) (interface{}, error) {
}
}
return l, nil
case time.Time:
return val.Format(time.RFC3339), nil
default:
return val, nil
}
+1 -1
View File
@@ -14,7 +14,7 @@ import (
"code.gitea.io/gitea/modules/validation"
"github.com/hashicorp/go-version"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
var (
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"code.gitea.io/gitea/modules/validation"
"github.com/hashicorp/go-version"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
var (
+2 -2
View File
@@ -14,7 +14,7 @@ import (
"code.gitea.io/gitea/modules/validation"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
var (
@@ -120,7 +120,7 @@ func (r requirement) AsVersionRequirement() []VersionRequirement {
if !ok {
continue
}
vm, ok := req[1].(map[interface{}]interface{})
vm, ok := req[1].(map[string]interface{})
if !ok {
continue
}
+2
View File
@@ -40,6 +40,7 @@ var Service = struct {
EnableReverseProxyEmail bool
EnableReverseProxyFullName bool
EnableCaptcha bool
RequireCaptchaForLogin bool
RequireExternalRegistrationCaptcha bool
RequireExternalRegistrationPassword bool
CaptchaType string
@@ -130,6 +131,7 @@ func newService() {
Service.EnableReverseProxyEmail = sec.Key("ENABLE_REVERSE_PROXY_EMAIL").MustBool()
Service.EnableReverseProxyFullName = sec.Key("ENABLE_REVERSE_PROXY_FULL_NAME").MustBool()
Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool(false)
Service.RequireCaptchaForLogin = sec.Key("REQUIRE_CAPTCHA_FOR_LOGIN").MustBool(false)
Service.RequireExternalRegistrationCaptcha = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA").MustBool(Service.EnableCaptcha)
Service.RequireExternalRegistrationPassword = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_PASSWORD").MustBool()
Service.CaptchaType = sec.Key("CAPTCHA_TYPE").MustString(ImageCaptcha)
+2
View File
@@ -440,6 +440,7 @@ var (
ShowFooterBranding bool
ShowFooterVersion bool
ShowFooterTemplateLoadTime bool
EnableFeed bool
// Global setting objects
Cfg *ini.File
@@ -1102,6 +1103,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
ShowFooterVersion = Cfg.Section("other").Key("SHOW_FOOTER_VERSION").MustBool(true)
ShowFooterTemplateLoadTime = Cfg.Section("other").Key("SHOW_FOOTER_TEMPLATE_LOAD_TIME").MustBool(true)
EnableSitemap = Cfg.Section("other").Key("ENABLE_SITEMAP").MustBool(true)
EnableFeed = Cfg.Section("other").Key("ENABLE_FEED").MustBool(true)
UI.ShowUserEmail = Cfg.Section("ui").Key("SHOW_USER_EMAIL").MustBool(true)
UI.DefaultShowFullName = Cfg.Section("ui").Key("DEFAULT_SHOW_FULL_NAME").MustBool(false)
+3 -3
View File
@@ -607,7 +607,7 @@ func AvatarHTML(src string, size int, class, name string) template.HTML {
// Avatar renders user avatars. args: user, size (int), class (string)
func Avatar(item interface{}, others ...interface{}) template.HTML {
size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, "ui avatar vm", others...)
size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
switch t := item.(type) {
case *user_model.User:
@@ -638,7 +638,7 @@ func AvatarByAction(action *activities_model.Action, others ...interface{}) temp
// 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, "ui avatar", others...)
size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
src := repo.RelAvatarLink()
if src != "" {
@@ -649,7 +649,7 @@ func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTM
// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string)
func AvatarByEmail(email, name string, others ...interface{}) template.HTML {
size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, "ui avatar", others...)
size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor)
if src != "" {
+26 -1
View File
@@ -86,12 +86,16 @@ remove = Remove
remove_all = Remove All
edit = Edit
enabled = Enabled
disabled = Disabled
copy = Copy
copy_url = Copy URL
copy_content = Copy content
copy_branch = Copy branch name
copy_success = Copied!
copy_error = Copy failed
copy_type_unsupported = This file type can not be copied
write = Write
preview = Preview
@@ -1096,7 +1100,6 @@ editor.cannot_edit_non_text_files = Binary files cannot be edited in the web int
editor.edit_this_file = Edit File
editor.this_file_locked = File is locked
editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file.
editor.only_copy_raw = You may only copy raw text files.
editor.fork_before_edit = You must fork this repository to make or propose changes to this file.
editor.delete_this_file = Delete File
editor.must_have_write_access = You must have write access to make or propose changes to this file.
@@ -1630,6 +1633,8 @@ pulls.reopened_at = `reopened this pull request <a id="%[1]s" href="#%[1]s">%[2]
pulls.merge_instruction_hint = `You can also view <a class="show-instruction">command line instructions</a>.`
pulls.merge_instruction_step1_desc = From your project repository, check out a new branch and test the changes.
pulls.merge_instruction_step2_desc = Merge the changes and update on Gitea.
pulls.clear_merge_message = Clear merge message
pulls.clear_merge_message_hint = Clearing the merge message will only remove the commit message content and keep generated git trailers such as "Co-Authored-By …".
pulls.auto_merge_button_when_succeed = (When checks succeed)
pulls.auto_merge_when_succeed = Auto merge when all checks succeed
@@ -3189,3 +3194,23 @@ settings.delete.description = Deleting a package is permanent and cannot be undo
settings.delete.notice = You are about to delete %s (%s). This operation is irreversible, are you sure?
settings.delete.success = The package has been deleted.
settings.delete.error = Failed to delete the package.
owner.settings.cleanuprules.title = Manage Cleanup Rules
owner.settings.cleanuprules.add = Add Cleanup Rule
owner.settings.cleanuprules.edit = Edit Cleanup Rule
owner.settings.cleanuprules.none = No cleanup rules available. Read the docs to learn more.
owner.settings.cleanuprules.preview = Cleanup Rule Preview
owner.settings.cleanuprules.preview.overview = %d packages are scheduled to be removed.
owner.settings.cleanuprules.preview.none = Cleanup rule does not match any packages.
owner.settings.cleanuprules.enabled = Enabled
owner.settings.cleanuprules.pattern_full_match = Apply pattern to full package name
owner.settings.cleanuprules.keep.title = Versions that match these rules are kept, even if they match a removal rule below.
owner.settings.cleanuprules.keep.count = Keep the most recent
owner.settings.cleanuprules.keep.count.1 = 1 version per package
owner.settings.cleanuprules.keep.count.n = %d versions per package
owner.settings.cleanuprules.keep.pattern = Keep versions matching
owner.settings.cleanuprules.keep.pattern.container = The <code>latest</code> version is always kept for Container packages.
owner.settings.cleanuprules.remove.title = Versions that match these rules are removed, unless a rule above says to keep them.
owner.settings.cleanuprules.remove.days = Remove versions older than
owner.settings.cleanuprules.remove.pattern = Remove versions matching
owner.settings.cleanuprules.success.update = Cleanup rule has been updated.
owner.settings.cleanuprules.success.delete = Cleanup rule has been deleted.
+1151 -1302
View File
File diff suppressed because it is too large Load Diff
+22 -21
View File
@@ -9,14 +9,14 @@
"dependencies": {
"@citation-js/core": "0.6.1",
"@citation-js/plugin-bibtex": "0.6.1",
"@citation-js/plugin-csl": "0.6.3",
"@citation-js/plugin-csl": "0.6.4",
"@citation-js/plugin-software-formats": "0.6.0",
"@claviska/jquery-minicolors": "2.3.6",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "17.7.0",
"@vue/compiler-sfc": "3.2.41",
"@primer/octicons": "17.9.0",
"@vue/compiler-sfc": "3.2.45",
"add-asset-webpack-plugin": "2.0.1",
"css-loader": "6.7.1",
"css-loader": "6.7.2",
"dropzone": "6.0.0-beta.2",
"easymde": "2.18.0",
"esbuild-loader": "2.20.0",
@@ -29,45 +29,46 @@
"less": "4.1.3",
"less-loader": "11.1.0",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "9.1.7",
"mini-css-extract-plugin": "2.6.1",
"mermaid": "9.2.2",
"mini-css-extract-plugin": "2.7.0",
"monaco-editor": "0.34.1",
"monaco-editor-webpack-plugin": "7.0.1",
"pretty-ms": "8.0.0",
"sortablejs": "1.15.0",
"swagger-ui-dist": "4.15.0",
"swagger-ui-dist": "4.15.5",
"tippy.js": "6.3.7",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
"vue": "3.2.41",
"vue": "3.2.45",
"vue-bar-graph": "2.0.0",
"vue-loader": "17.0.0",
"vue-loader": "17.0.1",
"vue3-calendar-heatmap": "2.0.0",
"webpack": "5.74.0",
"webpack-cli": "4.10.0",
"webpack": "5.75.0",
"webpack-cli": "5.0.0",
"workbox-routing": "6.5.4",
"workbox-strategies": "6.5.4",
"worker-loader": "3.0.8",
"wrap-ansi": "8.0.1"
},
"devDependencies": {
"@playwright/test": "1.27.1",
"@playwright/test": "1.28.0",
"@rollup/pluginutils": "5.0.2",
"@stoplight/spectral-cli": "6.5.1",
"eslint": "8.26.0",
"@stoplight/spectral-cli": "6.6.0",
"eslint": "8.28.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jquery": "1.5.1",
"eslint-plugin-sonarjs": "0.16.0",
"eslint-plugin-unicorn": "44.0.2",
"eslint-plugin-vue": "9.6.0",
"jsdom": "20.0.1",
"eslint-plugin-unicorn": "45.0.0",
"eslint-plugin-vue": "9.7.0",
"jsdom": "20.0.3",
"markdownlint-cli": "0.32.2",
"postcss-less": "6.0.0",
"stylelint": "14.14.0",
"stylelint": "14.15.0",
"stylelint-config-standard": "29.0.0",
"svgo": "3.0.0",
"updates": "13.1.10",
"vitest": "0.24.3"
"stylelint-declaration-strict-value": "1.9.1",
"svgo": "3.0.2",
"updates": "13.2.1",
"vitest": "0.25.2"
},
"browserslist": [
"defaults",
+1
View File
@@ -0,0 +1 @@
<svg xml:space="preserve" style="enable-background:new 0 0 520 520" viewBox="0 0 520 520" class="svg gitea-matrix" width="16" height="16" aria-hidden="true"><path d="M13.7 11.9v496.2h35.7V520H0V0h49.4v11.9H13.7zM166.3 169.2v25.1h.7c6.7-9.6 14.8-17 24.2-22.2 9.4-5.3 20.3-7.9 32.5-7.9 11.7 0 22.4 2.3 32.1 6.8 9.7 4.5 17 12.6 22.1 24 5.5-8.1 13-15.3 22.4-21.5 9.4-6.2 20.6-9.3 33.5-9.3 9.8 0 18.9 1.2 27.3 3.6 8.4 2.4 15.5 6.2 21.5 11.5s10.6 12.1 14 20.6c3.3 8.5 5 18.7 5 30.7v124.1h-50.9V249.6c0-6.2-.2-12.1-.7-17.6-.5-5.5-1.8-10.3-3.9-14.3-2.2-4.1-5.3-7.3-9.5-9.7-4.2-2.4-9.9-3.6-17-3.6-7.2 0-13 1.4-17.4 4.1-4.4 2.8-7.9 6.3-10.4 10.8-2.5 4.4-4.2 9.4-5 15.1-.8 5.6-1.3 11.3-1.3 17v103.3h-50.9v-104c0-5.5-.1-10.9-.4-16.3-.2-5.4-1.3-10.3-3.1-14.9-1.8-4.5-4.8-8.2-9-10.9-4.2-2.7-10.3-4.1-18.5-4.1-2.4 0-5.6.5-9.5 1.6-3.9 1.1-7.8 3.1-11.5 6.1-3.7 3-6.9 7.3-9.5 12.9-2.6 5.6-3.9 13-3.9 22.1v107.6h-50.9V169.2h48zM506.3 508.1V11.9h-35.7V0H520v520h-49.4v-11.9h35.7z"/></svg>

After

Width:  |  Height:  |  Size: 968 B

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-arrow-down-left" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M11.78 4.22a.75.75 0 0 1 0 1.06l-5.26 5.26h4.2a.75.75 0 0 1 0 1.5H4.71a.75.75 0 0 1-.75-.75V5.28a.75.75 0 1 1 1.5 0v4.2l5.26-5.26a.75.75 0 0 1 1.06 0z"/></svg>

After

Width:  |  Height:  |  Size: 291 B

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-arrow-down-right" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4.22 4.179a.75.75 0 0 1 1.06 0l5.26 5.26v-4.2a.75.75 0 0 1 1.5 0v6.01a.75.75 0 0 1-.75.75H5.28a.75.75 0 0 1 0-1.5h4.2L4.22 5.24a.75.75 0 0 1 0-1.06z"/></svg>

After

Width:  |  Height:  |  Size: 291 B

+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-arrow-up-left" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M3.96 4.75A.75.75 0 0 1 4.71 4h6.01a.75.75 0 0 1 0 1.5h-4.2l5.26 5.26a.75.75 0 0 1-1.06 1.061l-5.26-5.26v4.2a.75.75 0 0 1-1.5 0V4.75z"/></svg>

After

Width:  |  Height:  |  Size: 272 B

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-arrow-up-right" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4.53 4.75A.75.75 0 0 1 5.28 4h6.01a.75.75 0 0 1 .75.75v6.01a.75.75 0 1 1-1.5 0v-4.2l-5.26 5.261a.75.75 0 1 1-1.06-1.06L9.48 5.5h-4.2a.75.75 0 0 1-.75-.75z"/></svg>

After

Width:  |  Height:  |  Size: 295 B

+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-clock-fill" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8.575-3.25a.825.825 0 1 0-1.65 0v3.5c0 .337.205.64.519.766l2.5 1a.825.825 0 0 0 .612-1.532l-1.981-.793V4.75z"/></svg>

After

Width:  |  Height:  |  Size: 279 B

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-issue-tracked-by" width="16" height="16" aria-hidden="true"><path d="M1.5 8a6.5 6.5 0 0 1 13 0A.75.75 0 0 0 16 8a8 8 0 1 0-8 8 .75.75 0 0 0 0-1.5A6.5 6.5 0 0 1 1.5 8z"/><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm1.5 1.75a.75.75 0 0 1 .75-.75h5a.75.75 0 0 1 0 1.5h-5a.75.75 0 0 1-.75-.75zm2.75 2.25a.75.75 0 0 0 0 1.5h3a.75.75 0 0 0 0-1.5h-3z"/></svg>

After

Width:  |  Height:  |  Size: 404 B

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-issue-tracked-in" width="16" height="16" aria-hidden="true"><path d="M1.5 8a6.5 6.5 0 0 1 13 0A.75.75 0 0 0 16 8a8 8 0 1 0-8 8 .75.75 0 0 0 0-1.5A6.5 6.5 0 0 1 1.5 8z"/><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm3.573 5.823-2.896-2.896a.25.25 0 0 1 0-.354l2.896-2.896a.25.25 0 0 1 .427.177V11.5h3.25a.75.75 0 0 1 0 1.5H12v2.146a.25.25 0 0 1-.427.177z"/></svg>

After

Width:  |  Height:  |  Size: 413 B

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-project-roadmap" width="16" height="16" aria-hidden="true"><path d="M4.75 7a.75.75 0 0 0 0 1.5h4.5a.75.75 0 0 0 0-1.5h-4.5zM5 4.75A.75.75 0 0 1 5.75 4h5.5a.75.75 0 0 1 0 1.5h-5.5A.75.75 0 0 1 5 4.75zM6.75 10a.75.75 0 0 0 0 1.5h4.5a.75.75 0 0 0 0-1.5h-4.5z"/><path fill-rule="evenodd" d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75zm1.75-.25h12.5a.25.25 0 0 1 .25.25v12.5a.25.25 0 0 1-.25.25H1.75a.25.25 0 0 1-.25-.25V1.75a.25.25 0 0 1 .25-.25z"/></svg>

After

Width:  |  Height:  |  Size: 574 B

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-project-symlink" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16h-8.5a.75.75 0 0 1 0-1.5h8.5a.25.25 0 0 0 .25-.25V6.5h-13v1.75a.75.75 0 0 1-1.5 0v-6.5zM1.5 5H5V1.5H1.75a.25.25 0 0 0-.25.25V5zm5 0h8V1.75a.25.25 0 0 0-.25-.25H6.5V5z"/><path d="M1.5 13.737a2.25 2.25 0 0 1 2.262-2.25L4 11.49v1.938c0 .218.26.331.42.183l2.883-2.677a.25.25 0 0 0 0-.366L4.42 7.89a.25.25 0 0 0-.42.183V9.99l-.23-.001A3.75 3.75 0 0 0 0 13.738v1.012a.75.75 0 0 0 1.5 0v-1.013z"/></svg>

After

Width:  |  Height:  |  Size: 615 B

+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" class="svg octicon-skip-fill" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm11.333-2.167a.825.825 0 0 0-1.166-1.166l-5.5 5.5a.825.825 0 0 0 1.166 1.166l5.5-5.5z"/></svg>

After

Width:  |  Height:  |  Size: 254 B

+1
View File
@@ -181,6 +181,7 @@ func CommonRoutes(ctx gocontext.Context) *web.Route {
r.Group("/maven", func() {
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), maven.UploadPackageFile)
r.Get("/*", maven.DownloadPackageFile)
r.Head("/*", maven.ProvidePackageFileHeader)
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/nuget", func() {
r.Group("", func() { // Needs to be unauthenticated for the NuGet client.
+4 -1
View File
@@ -184,7 +184,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// UploadPackage creates a new package
+4 -1
View File
@@ -477,7 +477,10 @@ func downloadFile(ctx *context.Context, fileFilter container.Set[string], fileKe
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// DeleteRecipeV1 deletes the requested recipe(s)
+4 -1
View File
@@ -53,7 +53,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// UploadPackage uploads the specific generic package.
+5 -2
View File
@@ -23,7 +23,7 @@ import (
"code.gitea.io/gitea/routers/api/packages/helper"
packages_service "code.gitea.io/gitea/services/packages"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
func apiError(ctx *context.Context, status int, obj interface{}) {
@@ -138,7 +138,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// UploadPackage creates a new package
+1 -6
View File
@@ -6,7 +6,6 @@ package maven
import (
"encoding/xml"
"sort"
"strings"
packages_model "code.gitea.io/gitea/models/packages"
@@ -23,12 +22,8 @@ type MetadataResponse struct {
Version []string `xml:"versioning>versions>version"`
}
// pds is expected to be sorted ascending by CreatedUnix
func createMetadataResponse(pds []*packages_model.PackageDescriptor) *MetadataResponse {
sort.Slice(pds, func(i, j int) bool {
// Maven and Gradle order packages by their creation timestamp and not by their version string
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
})
var release *packages_model.PackageDescriptor
versions := make([]string, 0, len(pds))
+52 -5
View File
@@ -16,6 +16,8 @@ import (
"net/http"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
packages_model "code.gitea.io/gitea/models/packages"
@@ -34,6 +36,10 @@ const (
extensionSHA1 = ".sha1"
extensionSHA256 = ".sha256"
extensionSHA512 = ".sha512"
extensionPom = ".pom"
extensionJar = ".jar"
contentTypeJar = "application/java-archive"
contentTypeXML = "text/xml"
)
var (
@@ -49,6 +55,15 @@ func apiError(ctx *context.Context, status int, obj interface{}) {
// DownloadPackageFile serves the content of a package
func DownloadPackageFile(ctx *context.Context) {
handlePackageFile(ctx, true)
}
// ProvidePackageFileHeader provides only the headers describing a package
func ProvidePackageFileHeader(ctx *context.Context) {
handlePackageFile(ctx, false)
}
func handlePackageFile(ctx *context.Context, serveContent bool) {
params, err := extractPathParameters(ctx)
if err != nil {
apiError(ctx, http.StatusBadRequest, err)
@@ -58,7 +73,7 @@ func DownloadPackageFile(ctx *context.Context) {
if params.IsMeta && params.Version == "" {
serveMavenMetadata(ctx, params)
} else {
servePackageFile(ctx, params)
servePackageFile(ctx, params, serveContent)
}
}
@@ -82,6 +97,11 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
return
}
sort.Slice(pds, func(i, j int) bool {
// Maven and Gradle order packages by their creation timestamp and not by their version string
return pds[i].Version.CreatedUnix < pds[j].Version.CreatedUnix
})
xmlMetadata, err := xml.Marshal(createMetadataResponse(pds))
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -89,6 +109,9 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
}
xmlMetadataWithHeader := append([]byte(xml.Header), xmlMetadata...)
latest := pds[len(pds)-1]
ctx.Resp.Header().Set("Last-Modified", latest.Version.CreatedUnix.Format(http.TimeFormat))
ext := strings.ToLower(filepath.Ext(params.Filename))
if isChecksumExtension(ext) {
var hash []byte
@@ -110,10 +133,15 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
return
}
ctx.PlainTextBytes(http.StatusOK, xmlMetadataWithHeader)
ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
ctx.Resp.Header().Set("Content-Type", contentTypeXML)
if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil {
log.Error("write bytes failed: %v", err)
}
}
func servePackageFile(ctx *context.Context, params parameters) {
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
packageName := params.GroupID + "-" + params.ArtifactID
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeMaven, packageName, params.Version)
@@ -165,6 +193,23 @@ func servePackageFile(ctx *context.Context, params parameters) {
return
}
opts := &context.ServeHeaderOptions{
ContentLength: &pb.Size,
LastModified: pf.CreatedUnix.AsLocalTime(),
}
switch ext {
case extensionJar:
opts.ContentType = contentTypeJar
case extensionPom:
opts.ContentType = contentTypeXML
}
if !serveContent {
ctx.SetServeHeaders(opts)
ctx.Status(http.StatusOK)
return
}
s, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(pb.HashSHA256))
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -177,7 +222,9 @@ func servePackageFile(ctx *context.Context, params parameters) {
}
}
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
opts.Filename = pf.Name
ctx.ServeContent(s, opts)
}
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
@@ -273,7 +320,7 @@ func UploadPackageFile(ctx *context.Context) {
}
// If it's the package pom file extract the metadata
if ext == ".pom" {
if ext == extensionPom {
pfci.IsLead = true
var err error
+8 -2
View File
@@ -103,7 +103,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// DownloadPackageFileByName finds the version and serves the contents of a package
@@ -146,7 +149,10 @@ func DownloadPackageFileByName(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// UploadPackage creates a new package
+8 -2
View File
@@ -342,7 +342,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
@@ -562,7 +565,10 @@ func DownloadSymbolFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// DeletePackage hard deletes the package
+4 -1
View File
@@ -275,5 +275,8 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
+4 -1
View File
@@ -95,7 +95,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
+4 -1
View File
@@ -192,7 +192,10 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
// UploadPackageFile adds a file to the package. If the package does not exist, it gets created.
+4 -1
View File
@@ -239,5 +239,8 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
+5 -1
View File
@@ -341,7 +341,11 @@ func download(ctx *context.APIContext, archiveName string, archiver *repo_model.
return
}
defer fr.Close()
ctx.ServeContent(downloadName, fr, archiver.CreatedUnix.AsLocalTime())
ctx.ServeContent(fr, &context.ServeHeaderOptions{
Filename: downloadName,
LastModified: archiver.CreatedUnix.AsLocalTime(),
})
}
// GetEditorconfig get editor config of a repository
+6 -7
View File
@@ -5,7 +5,6 @@
package common
import (
"fmt"
"io"
"path"
"path/filepath"
@@ -52,16 +51,16 @@ func ServeData(ctx *context.Context, filePath string, size int64, reader io.Read
buf = buf[:n]
}
if size >= 0 {
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
} else {
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
}
opts := &context.ServeHeaderOptions{
Filename: path.Base(filePath),
}
if size >= 0 {
opts.ContentLength = &size
} else {
log.Error("ServeData called to serve data: %s with size < 0: %d", filePath, size)
}
sniffedType := typesniffer.DetectContentType(buf)
isPlain := sniffedType.IsText() || ctx.FormBool("render")
+19 -44
View File
@@ -17,11 +17,8 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@@ -163,6 +160,10 @@ func SignIn(ctx *context.Context) {
ctx.Data["PageIsLogin"] = true
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled()
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
context.SetCaptchaData(ctx)
}
ctx.HTML(http.StatusOK, tplSignIn)
}
@@ -189,6 +190,16 @@ func SignInPost(ctx *context.Context) {
}
form := web.GetForm(ctx).(*forms.SignInForm)
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
context.SetCaptchaData(ctx)
context.VerifyCaptcha(ctx, tplSignIn, form)
if ctx.Written() {
return
}
}
u, source, err := auth_service.UserSignIn(form.UserName, form.Password)
if err != nil {
if user_model.IsErrUserNotExist(err) || user_model.IsErrEmailAddressNotExist(err) {
@@ -383,14 +394,7 @@ func SignUp(ctx *context.Context) {
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = context.GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
context.SetCaptchaData(ctx)
ctx.Data["PageIsSignUp"] = true
// Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true
@@ -406,14 +410,7 @@ func SignUpPost(ctx *context.Context) {
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = context.GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
context.SetCaptchaData(ctx)
ctx.Data["PageIsSignUp"] = true
// Permission denied if DisableRegistration or AllowOnlyExternalRegistration options are true
@@ -427,31 +424,9 @@ func SignUpPost(ctx *context.Context) {
return
}
if setting.Service.EnableCaptcha {
var valid bool
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
case setting.HCaptcha:
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
case setting.MCaptcha:
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%s", err.Error())
}
if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form)
return
}
context.VerifyCaptcha(ctx, tplSignUp, form)
if ctx.Written() {
return
}
if !form.IsEmailDomainAllowed() {
+2 -26
View File
@@ -14,10 +14,6 @@ import (
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/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
auth_service "code.gitea.io/gitea/services/auth"
@@ -221,28 +217,8 @@ func LinkAccountPostRegister(ctx *context.Context) {
}
if setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha {
var valid bool
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
case setting.HCaptcha:
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
case setting.MCaptcha:
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%s", err.Error())
}
if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form)
context.VerifyCaptcha(ctx, tplLinkAccount, form)
if ctx.Written() {
return
}
}
+4 -45
View File
@@ -13,10 +13,7 @@ import (
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/mcaptcha"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
@@ -357,14 +354,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
ctx.Data["PageIsSignIn"] = true
ctx.Data["PageIsOpenIDRegister"] = true
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
ctx.Data["Captcha"] = context.GetImageCaptcha()
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
context.SetCaptchaData(ctx)
ctx.Data["OpenID"] = oid
if setting.Service.AllowOnlyInternalRegistration {
@@ -373,42 +363,11 @@ func RegisterOpenIDPost(ctx *context.Context) {
}
if setting.Service.EnableCaptcha {
var valid bool
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
if err := ctx.Req.ParseForm(); err != nil {
ctx.ServerError("", err)
return
}
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
case setting.HCaptcha:
if err := ctx.Req.ParseForm(); err != nil {
ctx.ServerError("", err)
return
}
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
case setting.MCaptcha:
if err := ctx.Req.ParseForm(); err != nil {
ctx.ServerError("", err)
return
}
valid, err = mcaptcha.Verify(ctx, form.McaptchaResponse)
default:
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
return
}
if err != nil {
log.Debug("%s", err.Error())
}
if !valid {
ctx.Data["Err_Captcha"] = true
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form)
if err := ctx.Req.ParseForm(); err != nil {
ctx.ServerError("", err)
return
}
context.VerifyCaptcha(ctx, tplSignUpOID, form)
}
length := setting.MinPasswordLength
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package org
import (
"fmt"
"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/packages"
)
const (
tplSettingsPackages base.TplName = "org/settings/packages"
tplSettingsPackagesRuleEdit base.TplName = "org/settings/packages_cleanup_rules_edit"
tplSettingsPackagesRulePreview base.TplName = "org/settings/packages_cleanup_rules_preview"
)
func Packages(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true
shared.SetPackagesContext(ctx, ctx.ContextUser)
ctx.HTML(http.StatusOK, tplSettingsPackages)
}
func PackagesRuleAdd(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true
shared.SetRuleAddContext(ctx)
ctx.HTML(http.StatusOK, tplSettingsPackagesRuleEdit)
}
func PackagesRuleEdit(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true
shared.SetRuleEditContext(ctx, ctx.ContextUser)
ctx.HTML(http.StatusOK, tplSettingsPackagesRuleEdit)
}
func PackagesRuleAddPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true
shared.PerformRuleAddPost(
ctx,
ctx.ContextUser,
fmt.Sprintf("%s/org/%s/settings/packages", setting.AppSubURL, ctx.ContextUser.Name),
tplSettingsPackagesRuleEdit,
)
}
func PackagesRuleEditPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true
shared.PerformRuleEditPost(
ctx,
ctx.ContextUser,
fmt.Sprintf("%s/org/%s/settings/packages", setting.AppSubURL, ctx.ContextUser.Name),
tplSettingsPackagesRuleEdit,
)
}
func PackagesRulePreview(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsPackages"] = true
shared.SetRulePreviewContext(ctx, ctx.ContextUser)
ctx.HTML(http.StatusOK, tplSettingsPackagesRulePreview)
}
+4
View File
@@ -784,6 +784,10 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles
}
}
}
}
if !strings.HasPrefix(template.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
template.Ref = git.BranchPrefix + template.Ref
}
ctx.Data["HasSelectedLabel"] = len(labelIDs) > 0
ctx.Data["label_ids"] = strings.Join(labelIDs, ",")
+4 -1
View File
@@ -427,7 +427,10 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep
}
defer fr.Close()
ctx.ServeContent(downloadName, fr, archiver.CreatedUnix.AsLocalTime())
ctx.ServeContent(fr, &context.ServeHeaderOptions{
Filename: downloadName,
LastModified: archiver.CreatedUnix.AsLocalTime(),
})
}
// InitiateDownload will enqueue an archival request, as needed. It may submit
+17 -7
View File
@@ -443,7 +443,12 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["IsRepresentableAsText"] = isRepresentableAsText
ctx.Data["IsDisplayingSource"] = isDisplayingSource
ctx.Data["IsDisplayingRendered"] = isDisplayingRendered
ctx.Data["IsTextSource"] = isTextFile || isDisplayingSource
isTextSource := isTextFile || isDisplayingSource
ctx.Data["IsTextSource"] = isTextSource
if isTextSource {
ctx.Data["CanCopyContent"] = true
}
// Check LFS Lock
lfsLock, err := git_model.GetTreePathLock(ctx.Repo.Repository.ID, ctx.Repo.TreePath)
@@ -474,6 +479,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
case isRepresentableAsText:
if st.IsSvgImage() {
ctx.Data["IsImageFile"] = true
ctx.Data["CanCopyContent"] = true
ctx.Data["HasSourceRenderedToggle"] = true
}
@@ -608,6 +614,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["IsAudioFile"] = true
case st.IsImage() && (setting.UI.SVG.Enabled || !st.IsSvgImage()):
ctx.Data["IsImageFile"] = true
ctx.Data["CanCopyContent"] = true
default:
if fileSize >= setting.UI.MaxDisplayFileSize {
ctx.Data["IsFileTooLarge"] = true
@@ -771,13 +778,16 @@ func checkCitationFile(ctx *context.Context, entry *git.TreeEntry) {
// Home render repository home page
func Home(ctx *context.Context) {
isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req)
if isFeed {
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
return
}
if setting.EnableFeed {
isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req)
if isFeed {
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
return
}
ctx.Data["FeedURL"] = ctx.Repo.Repository.HTMLURL()
ctx.Data["EnableFeed"] = true
ctx.Data["FeedURL"] = ctx.Repo.Repository.HTMLURL()
}
checkHomeCodeViewable(ctx)
if ctx.Written() {
+226
View File
@@ -0,0 +1,226 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package packages
import (
"fmt"
"net/http"
"time"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
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/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms"
container_service "code.gitea.io/gitea/services/packages/container"
)
func SetPackagesContext(ctx *context.Context, owner *user_model.User) {
pcrs, err := packages_model.GetCleanupRulesByOwner(ctx, owner.ID)
if err != nil {
ctx.ServerError("GetCleanupRulesByOwner", err)
return
}
ctx.Data["CleanupRules"] = pcrs
}
func SetRuleAddContext(ctx *context.Context) {
setRuleEditContext(ctx, nil)
}
func SetRuleEditContext(ctx *context.Context, owner *user_model.User) {
pcr := getCleanupRuleByContext(ctx, owner)
if pcr == nil {
return
}
setRuleEditContext(ctx, pcr)
}
func setRuleEditContext(ctx *context.Context, pcr *packages_model.PackageCleanupRule) {
ctx.Data["IsEditRule"] = pcr != nil
if pcr == nil {
pcr = &packages_model.PackageCleanupRule{}
}
ctx.Data["CleanupRule"] = pcr
ctx.Data["AvailableTypes"] = packages_model.TypeList
}
func PerformRuleAddPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
performRuleEditPost(ctx, owner, nil, redirectURL, template)
}
func PerformRuleEditPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
pcr := getCleanupRuleByContext(ctx, owner)
if pcr == nil {
return
}
form := web.GetForm(ctx).(*forms.PackageCleanupRuleForm)
if form.Action == "remove" {
if err := packages_model.DeleteCleanupRuleByID(ctx, pcr.ID); err != nil {
ctx.ServerError("DeleteCleanupRuleByID", err)
return
}
ctx.Flash.Success(ctx.Tr("packages.owner.settings.cleanuprules.success.delete"))
ctx.Redirect(redirectURL)
} else {
performRuleEditPost(ctx, owner, pcr, redirectURL, template)
}
}
func performRuleEditPost(ctx *context.Context, owner *user_model.User, pcr *packages_model.PackageCleanupRule, redirectURL string, template base.TplName) {
isEditRule := pcr != nil
if pcr == nil {
pcr = &packages_model.PackageCleanupRule{}
}
form := web.GetForm(ctx).(*forms.PackageCleanupRuleForm)
pcr.Enabled = form.Enabled
pcr.OwnerID = owner.ID
pcr.KeepCount = form.KeepCount
pcr.KeepPattern = form.KeepPattern
pcr.RemoveDays = form.RemoveDays
pcr.RemovePattern = form.RemovePattern
pcr.MatchFullName = form.MatchFullName
ctx.Data["IsEditRule"] = isEditRule
ctx.Data["CleanupRule"] = pcr
ctx.Data["AvailableTypes"] = packages_model.TypeList
if ctx.HasError() {
ctx.HTML(http.StatusOK, template)
return
}
if isEditRule {
if err := packages_model.UpdateCleanupRule(ctx, pcr); err != nil {
ctx.ServerError("UpdateCleanupRule", err)
return
}
} else {
pcr.Type = packages_model.Type(form.Type)
if has, err := packages_model.HasOwnerCleanupRuleForPackageType(ctx, owner.ID, pcr.Type); err != nil {
ctx.ServerError("HasOwnerCleanupRuleForPackageType", err)
return
} else if has {
ctx.Data["Err_Type"] = true
ctx.HTML(http.StatusOK, template)
return
}
var err error
if pcr, err = packages_model.InsertCleanupRule(ctx, pcr); err != nil {
ctx.ServerError("InsertCleanupRule", err)
return
}
}
ctx.Flash.Success(ctx.Tr("packages.owner.settings.cleanuprules.success.update"))
ctx.Redirect(fmt.Sprintf("%s/rules/%d", redirectURL, pcr.ID))
}
func SetRulePreviewContext(ctx *context.Context, owner *user_model.User) {
pcr := getCleanupRuleByContext(ctx, owner)
if pcr == nil {
return
}
if err := pcr.CompiledPattern(); err != nil {
ctx.ServerError("CompiledPattern", err)
return
}
olderThan := time.Now().AddDate(0, 0, -pcr.RemoveDays)
packages, err := packages_model.GetPackagesByType(ctx, pcr.OwnerID, pcr.Type)
if err != nil {
ctx.ServerError("GetPackagesByType", err)
return
}
versionsToRemove := make([]*packages_model.PackageDescriptor, 0, 10)
for _, p := range packages {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: p.ID,
IsInternal: util.OptionalBoolFalse,
Sort: packages_model.SortCreatedDesc,
Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200),
})
if err != nil {
ctx.ServerError("SearchVersions", err)
return
}
for _, pv := range pvs {
if skip, err := container_service.ShouldBeSkipped(ctx, pcr, p, pv); err != nil {
ctx.ServerError("ShouldBeSkipped", err)
return
} else if skip {
continue
}
toMatch := pv.LowerVersion
if pcr.MatchFullName {
toMatch = p.LowerName + "/" + pv.LowerVersion
}
if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) {
continue
}
if pv.CreatedUnix.AsLocalTime().After(olderThan) {
continue
}
if pcr.RemovePatternMatcher != nil && !pcr.RemovePatternMatcher.MatchString(toMatch) {
continue
}
pd, err := packages_model.GetPackageDescriptor(ctx, pv)
if err != nil {
ctx.ServerError("GetPackageDescriptor", err)
return
}
versionsToRemove = append(versionsToRemove, pd)
}
}
ctx.Data["CleanupRule"] = pcr
ctx.Data["VersionsToRemove"] = versionsToRemove
}
func getCleanupRuleByContext(ctx *context.Context, owner *user_model.User) *packages_model.PackageCleanupRule {
id := ctx.FormInt64("id")
if id == 0 {
id = ctx.ParamsInt64("id")
}
pcr, err := packages_model.GetCleanupRuleByID(ctx, id)
if err != nil {
if err == packages_model.ErrPackageCleanupRuleNotExist {
ctx.NotFound("", err)
} else {
ctx.ServerError("GetCleanupRuleByID", err)
}
return nil
}
if pcr != nil && pcr.OwnerID == owner.ID {
return pcr
}
ctx.NotFound("", fmt.Errorf("PackageCleanupRule[%v] not associated to owner %v", id, owner))
return nil
}
+4 -1
View File
@@ -402,5 +402,8 @@ func DownloadPackageFile(ctx *context.Context) {
}
defer s.Close()
ctx.ServeContent(pf.Name, s, pf.CreatedUnix.AsLocalTime())
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}
+80
View File
@@ -0,0 +1,80 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package 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/packages"
)
const (
tplSettingsPackages base.TplName = "user/settings/packages"
tplSettingsPackagesRuleEdit base.TplName = "user/settings/packages_cleanup_rules_edit"
tplSettingsPackagesRulePreview base.TplName = "user/settings/packages_cleanup_rules_preview"
)
func Packages(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
shared.SetPackagesContext(ctx, ctx.Doer)
ctx.HTML(http.StatusOK, tplSettingsPackages)
}
func PackagesRuleAdd(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
shared.SetRuleAddContext(ctx)
ctx.HTML(http.StatusOK, tplSettingsPackagesRuleEdit)
}
func PackagesRuleEdit(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
shared.SetRuleEditContext(ctx, ctx.Doer)
ctx.HTML(http.StatusOK, tplSettingsPackagesRuleEdit)
}
func PackagesRuleAddPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsPackages"] = true
shared.PerformRuleAddPost(
ctx,
ctx.Doer,
setting.AppSubURL+"/user/settings/packages",
tplSettingsPackagesRuleEdit,
)
}
func PackagesRuleEditPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
shared.PerformRuleEditPost(
ctx,
ctx.Doer,
setting.AppSubURL+"/user/settings/packages",
tplSettingsPackagesRuleEdit,
)
}
func PackagesRulePreview(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("packages.title")
ctx.Data["PageIsSettingsPackages"] = true
shared.SetRulePreviewContext(ctx, ctx.Doer)
ctx.HTML(http.StatusOK, tplSettingsPackagesRulePreview)
}
+53 -8
View File
@@ -303,6 +303,20 @@ func RegisterRoutes(m *web.Route) {
}
}
packagesEnabled := func(ctx *context.Context) {
if !setting.Packages.Enabled {
ctx.Error(http.StatusForbidden)
return
}
}
feedEnabled := func(ctx *context.Context) {
if !setting.EnableFeed {
ctx.Error(http.StatusNotFound)
return
}
}
// FIXME: not all routes need go through same middleware.
// Especially some AJAX requests, we can reduce middleware number to improve performance.
// Routers.
@@ -443,12 +457,27 @@ func RegisterRoutes(m *web.Route) {
m.Combo("/keys").Get(user_setting.Keys).
Post(bindIgnErr(forms.AddKeyForm{}), user_setting.KeysPost)
m.Post("/keys/delete", user_setting.DeleteKey)
m.Group("/packages", func() {
m.Get("", user_setting.Packages)
m.Group("/rules", func() {
m.Group("/add", func() {
m.Get("", user_setting.PackagesRuleAdd)
m.Post("", bindIgnErr(forms.PackageCleanupRuleForm{}), user_setting.PackagesRuleAddPost)
})
m.Group("/{id}", func() {
m.Get("", user_setting.PackagesRuleEdit)
m.Post("", bindIgnErr(forms.PackageCleanupRuleForm{}), user_setting.PackagesRuleEditPost)
m.Get("/preview", user_setting.PackagesRulePreview)
})
})
}, packagesEnabled)
m.Get("/organization", user_setting.Organization)
m.Get("/repos", user_setting.Repos)
m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository)
}, reqSignIn, func(ctx *context.Context) {
ctx.Data["PageIsUserSettings"] = true
ctx.Data["AllThemes"] = setting.UI.Themes
ctx.Data["EnablePackages"] = setting.Packages.Enabled
})
m.Group("/user", func() {
@@ -526,12 +555,10 @@ func RegisterRoutes(m *web.Route) {
m.Post("/delete", admin.DeleteRepo)
})
if setting.Packages.Enabled {
m.Group("/packages", func() {
m.Get("", admin.Packages)
m.Post("/delete", admin.DeletePackageVersion)
})
}
m.Group("/packages", func() {
m.Get("", admin.Packages)
m.Post("/delete", admin.DeletePackageVersion)
}, packagesEnabled)
m.Group("/hooks", func() {
m.Get("", admin.DefaultOrSystemWebhooks)
@@ -613,9 +640,11 @@ func RegisterRoutes(m *web.Route) {
m.Get(".png", func(ctx *context.Context) { ctx.Error(http.StatusNotFound) })
m.Get(".keys", user.ShowSSHKeys)
m.Get(".gpg", user.ShowGPGKeys)
m.Get(".rss", feed.ShowUserFeedRSS)
m.Get(".atom", feed.ShowUserFeedAtom)
m.Get(".rss", feedEnabled, feed.ShowUserFeedRSS)
m.Get(".atom", feedEnabled, feed.ShowUserFeedAtom)
m.Get("", user.Profile)
}, func(ctx *context.Context) {
ctx.Data["EnableFeed"] = setting.EnableFeed
}, context_service.UserAssignmentWeb())
m.Get("/attachments/{uuid}", repo.GetAttachment)
}, ignSignIn)
@@ -750,8 +779,24 @@ func RegisterRoutes(m *web.Route) {
})
m.Route("/delete", "GET,POST", org.SettingsDelete)
m.Group("/packages", func() {
m.Get("", org.Packages)
m.Group("/rules", func() {
m.Group("/add", func() {
m.Get("", org.PackagesRuleAdd)
m.Post("", bindIgnErr(forms.PackageCleanupRuleForm{}), org.PackagesRuleAddPost)
})
m.Group("/{id}", func() {
m.Get("", org.PackagesRuleEdit)
m.Post("", bindIgnErr(forms.PackageCleanupRuleForm{}), org.PackagesRuleEditPost)
m.Get("/preview", org.PackagesRulePreview)
})
})
}, packagesEnabled)
}, func(ctx *context.Context) {
ctx.Data["EnableOAuth2"] = setting.OAuth2.Enable
ctx.Data["EnablePackages"] = setting.Packages.Enabled
})
}, context.OrgAssignment(true, true))
}, reqSignIn)
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package forms
import (
"net/http"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/web/middleware"
"gitea.com/go-chi/binding"
)
type PackageCleanupRuleForm struct {
ID int64
Enabled bool
Type string `binding:"Required;In(composer,conan,container,generic,helm,maven,npm,nuget,pub,pypi,rubygems,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)"`
RemovePattern string `binding:"RegexPattern"`
MatchFullName bool
Action string `binding:"Required;In(save,remove)"`
}
func (f *PackageCleanupRuleForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
ctx := context.GetContext(req)
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}
+4 -7
View File
@@ -91,13 +91,10 @@ func (f *InstallForm) Validate(req *http.Request, errs binding.Errors) binding.E
// RegisterForm form for registering
type RegisterForm struct {
UserName string `binding:"Required;Username;MaxSize(40)"`
Email string `binding:"Required;MaxSize(254)"`
Password string `binding:"MaxSize(255)"`
Retype string
GRecaptchaResponse string `form:"g-recaptcha-response"`
HcaptchaResponse string `form:"h-captcha-response"`
McaptchaResponse string `form:"m-captcha-response"`
UserName string `binding:"Required;Username;MaxSize(40)"`
Email string `binding:"Required;MaxSize(254)"`
Password string `binding:"MaxSize(255)"`
Retype string
}
// Validate validates the fields
+2 -5
View File
@@ -27,11 +27,8 @@ func (f *SignInOpenIDForm) Validate(req *http.Request, errs binding.Errors) bind
// SignUpOpenIDForm form for signin up with OpenID
type SignUpOpenIDForm struct {
UserName string `binding:"Required;Username;MaxSize(40)"`
Email string `binding:"Required;Email;MaxSize(254)"`
GRecaptchaResponse string `form:"g-recaptcha-response"`
HcaptchaResponse string `form:"h-captcha-response"`
McaptchaResponse string `form:"m-captcha-response"`
UserName string `binding:"Required;Username;MaxSize(40)"`
Email string `binding:"Required;Email;MaxSize(254)"`
}
// Validate validates the fields
+1 -2
View File
@@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
)
// NewIssue creates new issue with labels for repository.
@@ -201,7 +200,7 @@ func GetRefEndNamesAndURLs(issues []*issues_model.Issue, repoLink string) (map[i
for _, issue := range issues {
if issue.Ref != "" {
issueRefEndNames[issue.ID] = git.RefEndName(issue.Ref)
issueRefURLs[issue.ID] = git.RefURL(repoLink, util.PathEscapeSegments(issue.Ref))
issueRefURLs[issue.ID] = git.RefURL(repoLink, issue.Ref)
}
}
return issueRefEndNames, issueRefURLs
+1 -1
View File
@@ -26,7 +26,7 @@ import (
"code.gitea.io/gitea/modules/structs"
"github.com/google/uuid"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
var _ base.Uploader = &RepositoryDumper{}

Some files were not shown because too many files have changed in this diff Show More