0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-09 03:27:52 +02:00

Merge branch 'main' into zzc/dev/act_runner_exec

This commit is contained in:
a1012112796 2023-06-30 06:35:01 +00:00
commit 62d3f35cb3
No known key found for this signature in database
GPG Key ID: 8304C4283E1B3141
216 changed files with 3365 additions and 1550 deletions

View File

@ -25,10 +25,11 @@ env:
es2022: true
node: true
globals:
__webpack_public_path__: true
overrides:
- files: ["web_src/**/*"]
globals:
__webpack_public_path__: true
process: false # https://github.com/webpack/webpack/issues/15833
- files: ["web_src/**/*", "docs/**/*"]
env:
browser: true

View File

@ -15,6 +15,8 @@ on:
value: ${{ jobs.detect.outputs.templates }}
docker:
value: ${{ jobs.detect.outputs.docker }}
swagger:
value: ${{ jobs.detect.outputs.swagger }}
jobs:
detect:
@ -27,6 +29,7 @@ jobs:
actions: ${{ steps.changes.outputs.actions }}
templates: ${{ steps.changes.outputs.templates }}
docker: ${{ steps.changes.outputs.docker }}
swagger: ${{ steps.changes.outputs.swagger }}
steps:
- uses: actions/checkout@v3
- uses: dorny/paths-filter@v2
@ -37,6 +40,7 @@ jobs:
backend:
- "**/*.go"
- "templates/**/*.tmpl"
- "assets/emoji.json"
- "go.mod"
- "go.sum"
- "Makefile"
@ -44,6 +48,7 @@ jobs:
frontend:
- "**/*.js"
- "web_src/**"
- "assets/emoji.json"
- "package.json"
- "package-lock.json"
- "Makefile"
@ -64,22 +69,6 @@ jobs:
- "Dockerfile.rootless"
- "docker/**"
- "Makefile"
- name: set outputs result
id: changes
run: |
if [ "$ACT_EXEC" == 'true' ]; then
# set default value for 'act_runner exec'
{ echo "backend=true" ; \
echo "frontend=true" ; \
echo "docs=true" ; \
echo "actions=true" ; \
echo "templates=true" ; \
echo "docker=true" ; } >> "$GITHUB_OUTPUT"
else
{ echo "backend=${{ steps.changes_check.outputs.backend }}" ; \
echo "frontend=${{ steps.changes_check.outputs.frontend }}" ; \
echo "docs=${{ steps.changes_check.outputs.docs }}" ; \
echo "actions=${{ steps.changes_check.outputs.actions }}" ; \
echo "actions=${{ steps.changes_check.outputs.templates }}" ; \
echo "actions=${{ steps.changes_check.outputs.docker }}" ; } >> "$GITHUB_OUTPUT"
fi
swagger:
- "templates/swagger/v1_json.tmpl"

View File

@ -39,6 +39,18 @@ jobs:
- run: make deps-py
- run: make lint-templates
lint-swagger:
if: needs.files-changed.outputs.swagger == 'true'
needs: files-changed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: make deps-frontend
- run: make lint-swagger
lint-go-windows:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed

View File

@ -50,7 +50,7 @@ rules:
declaration-no-important: null
declaration-property-max-values: null
declaration-property-unit-allowed-list: null
declaration-property-unit-disallowed-list: null
declaration-property-unit-disallowed-list: {line-height: [em]}
declaration-property-value-allowed-list: null
declaration-property-value-disallowed-list: null
declaration-property-value-no-unknown: true

View File

@ -226,6 +226,8 @@ help:
@echo " - test-frontend test frontend files"
@echo " - test-backend test backend files"
@echo " - test-e2e[\#TestSpecificName] test end to end using playwright"
@echo " - update-js update js dependencies"
@echo " - update-py update py dependencies"
@echo " - webpack build webpack files"
@echo " - svg build svg files"
@echo " - fomantic build fomantic files"
@ -358,10 +360,10 @@ lint: lint-frontend lint-backend
lint-fix: lint-frontend-fix lint-backend-fix
.PHONY: lint-frontend
lint-frontend: lint-js lint-css lint-md lint-swagger
lint-frontend: lint-js lint-css
.PHONY: lint-frontend-fix
lint-frontend-fix: lint-js-fix lint-css-fix lint-md lint-swagger
lint-frontend-fix: lint-js-fix lint-css-fix
.PHONY: lint-backend
lint-backend: lint-go lint-go-vet lint-editorconfig
@ -924,13 +926,20 @@ node_modules: package-lock.json
poetry install
@touch .venv
.PHONY: npm-update
npm-update: node-check | node_modules
npx updates -cu
.PHONY: update-js
update-js: node-check | node_modules
npx updates -u -f package.json
rm -rf node_modules package-lock.json
npm install --package-lock
@touch node_modules
.PHONY: update-py
update-py: node-check | node_modules
npx updates -u -f pyproject.toml
rm -rf .venv poetry.lock
poetry install
@touch .venv
.PHONY: fomantic
fomantic:
rm -rf $(FOMANTIC_WORK_DIR)/build

2
assets/emoji.json generated

File diff suppressed because one or more lines are too long

View File

@ -25,7 +25,7 @@ import (
const (
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
maxUnicodeVersion = 14
maxUnicodeVersion = 15
)
var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")

View File

@ -106,5 +106,21 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) {
WriterOption: log.WriterConsoleOption{Stderr: out == os.Stderr},
}
writer := log.NewEventWriterConsole("console-default", writeMode)
log.GetManager().GetLogger(log.DEFAULT).RemoveAllWriters().AddWriters(writer)
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
}
// PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout.
// Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever.
func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error {
return func(c *cli.Context) error {
level := defaultLevel
if c.Bool("quiet") || c.GlobalBoolT("quiet") {
level = log.FATAL
}
if c.Bool("debug") || c.GlobalBool("debug") || c.Bool("verbose") || c.GlobalBool("verbose") {
level = log.TRACE
}
log.SetConsoleLogger(log.DEFAULT, "console-default", level)
return nil
}
}

View File

@ -151,7 +151,7 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) {
log.FallbackErrorf("unable to create file log writer: %v", err)
return
}
log.GetManager().GetLogger(log.DEFAULT).RemoveAllWriters().AddWriters(writer)
log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer)
}
}

View File

@ -22,9 +22,9 @@ import (
"github.com/urfave/cli"
)
// Cmdembedded represents the available extract sub-command.
// CmdEmbedded represents the available extract sub-command.
var (
Cmdembedded = cli.Command{
CmdEmbedded = cli.Command{
Name: "embedded",
Usage: "Extract embedded resources",
Description: "A command for extracting embedded resources, like templates and images",

View File

@ -15,6 +15,7 @@ import (
"time"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
@ -32,6 +33,7 @@ var (
Name: "hook",
Usage: "Delegate commands to corresponding Git hooks",
Description: "This should only be called by Git",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Subcommands: []cli.Command{
subcmdHookPreReceive,
subcmdHookUpdate,

View File

@ -8,6 +8,7 @@ import (
"fmt"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"github.com/urfave/cli"
@ -17,6 +18,7 @@ import (
var CmdKeys = cli.Command{
Name: "keys",
Usage: "This command queries the Gitea database to get the authorized command for a given ssh key fingerprint",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runKeys,
Flags: []cli.Flag{
cli.StringFlag{

View File

@ -44,6 +44,7 @@ var CmdServ = cli.Command{
Name: "serv",
Usage: "This command should only be called by SSH shell",
Description: "Serv provides access auth for repositories",
Before: PrepareConsoleLoggerLevel(log.FATAL),
Action: runServ,
Flags: []cli.Flag{
cli.BoolFlag{

View File

@ -35,6 +35,7 @@ var CmdWeb = cli.Command{
Usage: "Start Gitea web server",
Description: `Gitea web server is the only thing you need to run,
and it takes care of all the other things for you`,
Before: PrepareConsoleLoggerLevel(log.INFO),
Action: runWeb,
Flags: []cli.Flag{
cli.StringFlag{
@ -206,11 +207,6 @@ func servePprof() {
}
func runWeb(ctx *cli.Context) error {
if ctx.Bool("verbose") {
setupConsoleLogger(log.TRACE, log.CanColorStdout, os.Stdout)
} else if ctx.Bool("quiet") {
setupConsoleLogger(log.FATAL, log.CanColorStdout, os.Stdout)
}
defer func() {
if panicked := recover(); panicked != nil {
log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))

View File

@ -164,3 +164,23 @@ Although we would like to provide more options, our limited manpower means that
However, both Gitea and act runner are completely open source, so anyone can create a new/better implementation.
We support your choice, no matter how you decide.
In case you fork act runner to create your own version: Please contribute the changes back if you can and if you think your changes will help others as well.
## What workflow trigger events does Gitea support?
All events listed in this table are supported events and are compatible with GitHub.
For events supported only by GitHub, see GitHub's [documentation](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows).
| trigger event | activity types |
|-----------------------------|--------------------------------------------------------------------------------------------------------------------------|
| create | not applicable |
| delete | not applicable |
| fork | not applicable |
| gollum | not applicable |
| push | not applicable |
| issues | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `milestoned`, `demilestoned`, `labeled`, `unlabeled` |
| issue_comment | `created`, `edited`, `deleted` |
| pull_request | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `synchronize`, `labeled`, `unlabeled` |
| pull_request_review | `submitted`, `edited` |
| pull_request_review_comment | `created`, `edited` |
| release | `published`, `edited` |
| registry_package | `published` |

View File

@ -164,3 +164,23 @@ defaults:
然而无论您如何决定Gitea 和act runner都是完全开源的所以任何人都可以创建一个新的/更好的实现。
我们支持您的选择,无论您如何决定。
如果您选择分支act runner来创建自己的版本请在您认为您的更改对其他人也有帮助的情况下贡献这些更改。
## Gitea 支持哪些工作流触发事件?
表格中列出的所有事件都是支持的,并且与 GitHub 兼容。
对于仅 GitHub 支持的事件,请参阅 GitHub 的[文档](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows)。
| 触发事件 | 活动类型 |
|-----------------------------|--------------------------------------------------------------------------------------------------------------------------|
| create | 不适用 |
| delete | 不适用 |
| fork | 不适用 |
| gollum | 不适用 |
| push | 不适用 |
| issues | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `milestoned`, `demilestoned`, `labeled`, `unlabeled` |
| issue_comment | `created`, `edited`, `deleted` |
| pull_request | `opened`, `edited`, `closed`, `reopened`, `assigned`, `unassigned`, `synchronize`, `labeled`, `unlabeled` |
| pull_request_review | `submitted`, `edited` |
| pull_request_review_comment | `created`, `edited` |
| release | `published`, `edited` |
| registry_package | `published` |

30
main.go
View File

@ -87,28 +87,36 @@ func main() {
app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
app.Version = Version + formatBuiltWith()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
// these sub-commands need to use config file
subCmdWithIni := []cli.Command{
cmd.CmdWeb,
cmd.CmdServ,
cmd.CmdHook,
cmd.CmdDump,
cmd.CmdCert,
cmd.CmdAdmin,
cmd.CmdGenerate,
cmd.CmdMigrate,
cmd.CmdKeys,
cmd.CmdConvert,
cmd.CmdDoctor,
cmd.CmdManager,
cmd.Cmdembedded,
cmd.CmdEmbedded,
cmd.CmdMigrateStorage,
cmd.CmdDocs,
cmd.CmdDumpRepository,
cmd.CmdRestoreRepository,
cmd.CmdActions,
cmdHelp, // TODO: the "help" sub-command was used to show the more information for "work path" and "custom config", in the future, it should avoid doing so
}
// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []cli.Command{
cmd.CmdCert,
cmd.CmdGenerate,
cmd.CmdDocs,
}
// default configuration flags
// shared configuration flags, they are for global and for each sub-command at the same time
// eg: such command is valid: "./gitea --config /tmp/app.ini web --config /tmp/app.ini", while it's discouraged indeed
// keep in mind that the short flags like "-C", "-c" and "-w" are globally polluted, they can't be used for sub-commands anymore.
globalFlags := []cli.Flag{
cli.HelpFlag,
cli.StringFlag{
@ -128,13 +136,15 @@ func main() {
// Set the default to be equivalent to cmdWeb and add the default flags
app.Flags = append(app.Flags, globalFlags...)
app.Flags = append(app.Flags, cmd.CmdWeb.Flags...)
app.Flags = append(app.Flags, cmd.CmdWeb.Flags...) // TODO: the web flags polluted the global flags, they are not really global flags
app.Action = prepareWorkPathAndCustomConf(cmd.CmdWeb.Action)
app.HideHelp = true // use our own help action to show helps (with more information like default config)
app.Commands = append(app.Commands, cmdHelp)
for i := range app.Commands {
prepareSubcommands(&app.Commands[i], globalFlags)
app.Before = cmd.PrepareConsoleLoggerLevel(log.INFO)
for i := range subCmdWithIni {
prepareSubcommands(&subCmdWithIni[i], globalFlags)
}
app.Commands = append(app.Commands, subCmdWithIni...)
app.Commands = append(app.Commands, subCmdStandalone...)
err := app.Run(os.Args)
if err != nil {

View File

@ -344,6 +344,9 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
return err
}
// UpdateTaskByState updates the task by the state.
// It will always update the task if the state is not final, even there is no change.
// So it will update ActionTask.Updated to avoid the task being judged as a zombie task.
func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionTask, error) {
stepStates := map[int64]*runnerv1.StepState{}
for _, v := range state.Steps {
@ -384,6 +387,12 @@ func UpdateTaskByState(ctx context.Context, state *runnerv1.TaskState) (*ActionT
}, nil); err != nil {
return nil, err
}
} else {
// Force update ActionTask.Updated to avoid the task being judged as a zombie task
task.Updated = timeutil.TimeStampNow()
if err := UpdateTask(ctx, task, "updated"); err != nil {
return nil, err
}
}
if err := task.LoadAttributes(ctx); err != nil {

View File

@ -20,6 +20,10 @@ const (
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
SearchOrderBySize SearchOrderBy = "size ASC"
SearchOrderBySizeReverse SearchOrderBy = "size DESC"
SearchOrderByGitSize SearchOrderBy = "git_size ASC"
SearchOrderByGitSizeReverse SearchOrderBy = "git_size DESC"
SearchOrderByLFSSize SearchOrderBy = "lfs_size ASC"
SearchOrderByLFSSizeReverse SearchOrderBy = "lfs_size DESC"
SearchOrderByID SearchOrderBy = "id ASC"
SearchOrderByIDReverse SearchOrderBy = "id DESC"
SearchOrderByStars SearchOrderBy = "num_stars ASC"

View File

@ -7,6 +7,7 @@ import (
"context"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"strconv"
@ -21,6 +22,7 @@ var defaultFileBlockSize int64 = 32 * 1024
type File interface {
io.ReadWriteCloser
io.Seeker
fs.File
}
type file struct {
@ -193,10 +195,26 @@ func (f *file) Close() error {
return nil
}
func (f *file) Stat() (os.FileInfo, error) {
if f.metaID == 0 {
return nil, os.ErrInvalid
}
fileMeta, err := findFileMetaByID(f.ctx, f.metaID)
if err != nil {
return nil, err
}
return fileMeta, nil
}
func timeToFileTimestamp(t time.Time) int64 {
return t.UnixMicro()
}
func fileTimestampToTime(timestamp int64) time.Time {
return time.UnixMicro(timestamp)
}
func (f *file) loadMetaByPath() (*dbfsMeta, error) {
var fileMeta dbfsMeta
if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil {

View File

@ -5,7 +5,10 @@ package dbfs
import (
"context"
"io/fs"
"os"
"path"
"time"
"code.gitea.io/gitea/models/db"
)
@ -100,3 +103,29 @@ func Remove(ctx context.Context, name string) error {
defer f.Close()
return f.delete()
}
var _ fs.FileInfo = (*dbfsMeta)(nil)
func (m *dbfsMeta) Name() string {
return path.Base(m.FullPath)
}
func (m *dbfsMeta) Size() int64 {
return m.FileSize
}
func (m *dbfsMeta) Mode() fs.FileMode {
return os.ModePerm
}
func (m *dbfsMeta) ModTime() time.Time {
return fileTimestampToTime(m.ModifyTimestamp)
}
func (m *dbfsMeta) IsDir() bool {
return false
}
func (m *dbfsMeta) Sys() any {
return nil
}

View File

@ -111,6 +111,19 @@ func TestDbfsBasic(t *testing.T) {
_, err = OpenFile(db.DefaultContext, "test2.txt", os.O_RDONLY)
assert.Error(t, err)
// test stat
f, err = OpenFile(db.DefaultContext, "test/test.txt", os.O_RDWR|os.O_CREATE)
assert.NoError(t, err)
stat, err := f.Stat()
assert.NoError(t, err)
assert.EqualValues(t, "test.txt", stat.Name())
assert.EqualValues(t, 0, stat.Size())
_, err = f.Write([]byte("0123456789"))
assert.NoError(t, err)
stat, err = f.Stat()
assert.NoError(t, err)
assert.EqualValues(t, 10, stat.Size())
}
func TestDbfsReadWrite(t *testing.T) {

View File

@ -318,90 +318,6 @@ func (err ErrFilePathProtected) Unwrap() error {
return util.ErrPermissionDenied
}
// __________ .__
// \______ \____________ ____ ____ | |__
// | | _/\_ __ \__ \ / \_/ ___\| | \
// | | \ | | \// __ \| | \ \___| Y \
// |______ / |__| (____ /___| /\___ >___| /
// \/ \/ \/ \/ \/
// ErrBranchDoesNotExist represents an error that branch with such name does not exist.
type ErrBranchDoesNotExist struct {
BranchName string
}
// IsErrBranchDoesNotExist checks if an error is an ErrBranchDoesNotExist.
func IsErrBranchDoesNotExist(err error) bool {
_, ok := err.(ErrBranchDoesNotExist)
return ok
}
func (err ErrBranchDoesNotExist) Error() string {
return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName)
}
func (err ErrBranchDoesNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrBranchAlreadyExists represents an error that branch with such name already exists.
type ErrBranchAlreadyExists struct {
BranchName string
}
// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists.
func IsErrBranchAlreadyExists(err error) bool {
_, ok := err.(ErrBranchAlreadyExists)
return ok
}
func (err ErrBranchAlreadyExists) Error() string {
return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
}
func (err ErrBranchAlreadyExists) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrBranchNameConflict represents an error that branch name conflicts with other branch.
type ErrBranchNameConflict struct {
BranchName string
}
// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict.
func IsErrBranchNameConflict(err error) bool {
_, ok := err.(ErrBranchNameConflict)
return ok
}
func (err ErrBranchNameConflict) Error() string {
return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
}
func (err ErrBranchNameConflict) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrBranchesEqual represents an error that branch name conflicts with other branch.
type ErrBranchesEqual struct {
BaseBranchName string
HeadBranchName string
}
// IsErrBranchesEqual checks if an error is an ErrBranchesEqual.
func IsErrBranchesEqual(err error) bool {
_, ok := err.(ErrBranchesEqual)
return ok
}
func (err ErrBranchesEqual) Error() string {
return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
}
func (err ErrBranchesEqual) Unwrap() error {
return util.ErrInvalidArgument
}
// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it.
type ErrDisallowedToMerge struct {
Reason string

View File

@ -0,0 +1,47 @@
-
id: 1
repo_id: 1
name: 'foo'
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
commit_message: 'first commit'
commit_time: 978307100
pusher_id: 1
is_deleted: true
deleted_by_id: 1
deleted_unix: 978307200
-
id: 2
repo_id: 1
name: 'bar'
commit_id: '62fb502a7172d4453f0322a2cc85bddffa57f07a'
commit_message: 'second commit'
commit_time: 978307100
pusher_id: 1
is_deleted: true
deleted_by_id: 99
deleted_unix: 978307200
-
id: 3
repo_id: 1
name: 'branch2'
commit_id: '985f0301dba5e7b34be866819cd15ad3d8f508ee'
commit_message: 'make pull5 outdated'
commit_time: 1579166279
pusher_id: 1
is_deleted: false
deleted_by_id: 0
deleted_unix: 0
-
id: 4
repo_id: 1
name: 'master'
commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d'
commit_message: 'Initial commit'
commit_time: 1489927679
pusher_id: 1
is_deleted: false
deleted_by_id: 0
deleted_unix: 0

View File

@ -1,15 +0,0 @@
-
id: 1
repo_id: 1
name: foo
commit: 1213212312313213213132131
deleted_by_id: 1
deleted_unix: 978307200
-
id: 2
repo_id: 1
name: bar
commit: 5655464564554545466464655
deleted_by_id: 99
deleted_unix: 978307200

379
models/git/branch.go Normal file
View File

@ -0,0 +1,379 @@
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"context"
"fmt"
"time"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
)
// ErrBranchNotExist represents an error that branch with such name does not exist.
type ErrBranchNotExist struct {
RepoID int64
BranchName string
}
// IsErrBranchNotExist checks if an error is an ErrBranchDoesNotExist.
func IsErrBranchNotExist(err error) bool {
_, ok := err.(ErrBranchNotExist)
return ok
}
func (err ErrBranchNotExist) Error() string {
return fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", err.RepoID, err.BranchName)
}
func (err ErrBranchNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrBranchAlreadyExists represents an error that branch with such name already exists.
type ErrBranchAlreadyExists struct {
BranchName string
}
// IsErrBranchAlreadyExists checks if an error is an ErrBranchAlreadyExists.
func IsErrBranchAlreadyExists(err error) bool {
_, ok := err.(ErrBranchAlreadyExists)
return ok
}
func (err ErrBranchAlreadyExists) Error() string {
return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
}
func (err ErrBranchAlreadyExists) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrBranchNameConflict represents an error that branch name conflicts with other branch.
type ErrBranchNameConflict struct {
BranchName string
}
// IsErrBranchNameConflict checks if an error is an ErrBranchNameConflict.
func IsErrBranchNameConflict(err error) bool {
_, ok := err.(ErrBranchNameConflict)
return ok
}
func (err ErrBranchNameConflict) Error() string {
return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
}
func (err ErrBranchNameConflict) Unwrap() error {
return util.ErrAlreadyExist
}
// ErrBranchesEqual represents an error that base branch is equal to the head branch.
type ErrBranchesEqual struct {
BaseBranchName string
HeadBranchName string
}
// IsErrBranchesEqual checks if an error is an ErrBranchesEqual.
func IsErrBranchesEqual(err error) bool {
_, ok := err.(ErrBranchesEqual)
return ok
}
func (err ErrBranchesEqual) Error() string {
return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
}
func (err ErrBranchesEqual) Unwrap() error {
return util.ErrInvalidArgument
}
// Branch represents a branch of a repository
// For those repository who have many branches, stored into database is a good choice
// for pagination, keyword search and filtering
type Branch struct {
ID int64
RepoID int64 `xorm:"UNIQUE(s)"`
Name string `xorm:"UNIQUE(s) NOT NULL"`
CommitID string
CommitMessage string `xorm:"TEXT"`
PusherID int64
Pusher *user_model.User `xorm:"-"`
IsDeleted bool `xorm:"index"`
DeletedByID int64
DeletedBy *user_model.User `xorm:"-"`
DeletedUnix timeutil.TimeStamp `xorm:"index"`
CommitTime timeutil.TimeStamp // The commit
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}
func (b *Branch) LoadDeletedBy(ctx context.Context) (err error) {
if b.DeletedBy == nil {
b.DeletedBy, err = user_model.GetUserByID(ctx, b.DeletedByID)
if user_model.IsErrUserNotExist(err) {
b.DeletedBy = user_model.NewGhostUser()
err = nil
}
}
return err
}
func (b *Branch) LoadPusher(ctx context.Context) (err error) {
if b.Pusher == nil && b.PusherID > 0 {
b.Pusher, err = user_model.GetUserByID(ctx, b.PusherID)
if user_model.IsErrUserNotExist(err) {
b.Pusher = user_model.NewGhostUser()
err = nil
}
}
return err
}
func init() {
db.RegisterModel(new(Branch))
db.RegisterModel(new(RenamedBranch))
}
func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, error) {
var branch Branch
has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch)
if err != nil {
return nil, err
} else if !has {
return nil, ErrBranchNotExist{
RepoID: repoID,
BranchName: branchName,
}
}
return &branch, nil
}
func AddBranches(ctx context.Context, branches []*Branch) error {
for _, branch := range branches {
if _, err := db.GetEngine(ctx).Insert(branch); err != nil {
return err
}
}
return nil
}
func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, error) {
var branch Branch
has, err := db.GetEngine(ctx).ID(branchID).Get(&branch)
if err != nil {
return nil, err
} else if !has {
return nil, ErrBranchNotExist{
RepoID: repoID,
}
}
if branch.RepoID != repoID {
return nil, ErrBranchNotExist{
RepoID: repoID,
}
}
if !branch.IsDeleted {
return nil, ErrBranchNotExist{
RepoID: repoID,
}
}
return &branch, nil
}
func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error {
return db.WithTx(ctx, func(ctx context.Context) error {
branches := make([]*Branch, 0, len(branchIDs))
if err := db.GetEngine(ctx).In("id", branchIDs).Find(&branches); err != nil {
return err
}
for _, branch := range branches {
if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil {
return err
}
}
return nil
})
}
// UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information
// If it doest not exist, insert a new record into database
func UpdateBranch(ctx context.Context, repoID int64, branchName, commitID, commitMessage string, pusherID int64, commitTime time.Time) error {
cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName).
Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix").
Update(&Branch{
CommitID: commitID,
CommitMessage: commitMessage,
PusherID: pusherID,
CommitTime: timeutil.TimeStamp(commitTime.Unix()),
IsDeleted: false,
})
if err != nil {
return err
}
if cnt > 0 {
return nil
}
return db.Insert(ctx, &Branch{
RepoID: repoID,
Name: branchName,
CommitID: commitID,
CommitMessage: commitMessage,
PusherID: pusherID,
CommitTime: timeutil.TimeStamp(commitTime.Unix()),
})
}
// AddDeletedBranch adds a deleted branch to the database
func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error {
branch, err := GetBranch(ctx, repoID, branchName)
if err != nil {
return err
}
if branch.IsDeleted {
return nil
}
cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=? AND is_deleted=?", repoID, branchName, false).
Cols("is_deleted, deleted_by_id, deleted_unix").
Update(&Branch{
IsDeleted: true,
DeletedByID: deletedByID,
DeletedUnix: timeutil.TimeStampNow(),
})
if err != nil {
return err
}
if cnt == 0 {
return fmt.Errorf("branch %s not found or has been deleted", branchName)
}
return err
}
func RemoveDeletedBranchByID(ctx context.Context, repoID, branchID int64) error {
_, err := db.GetEngine(ctx).Where("repo_id=? AND id=? AND is_deleted = ?", repoID, branchID, true).Delete(new(Branch))
return err
}
// RemoveOldDeletedBranches removes old deleted branches
func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) {
// Nothing to do for shutdown or terminate
log.Trace("Doing: DeletedBranchesCleanup")
deleteBefore := time.Now().Add(-olderThan)
_, err := db.GetEngine(ctx).Where("is_deleted=? AND deleted_unix < ?", true, deleteBefore.Unix()).Delete(new(Branch))
if err != nil {
log.Error("DeletedBranchesCleanup: %v", err)
}
}
// RenamedBranch provide renamed branch log
// will check it when a branch can't be found
type RenamedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX NOT NULL"`
From string
To string
CreatedUnix timeutil.TimeStamp `xorm:"created"`
}
// FindRenamedBranch check if a branch was renamed
func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) {
branch = &RenamedBranch{
RepoID: repoID,
From: from,
}
exist, err = db.GetEngine(ctx).Get(branch)
return branch, exist, err
}
// RenameBranch rename a branch
func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
sess := db.GetEngine(ctx)
// 1. update branch in database
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
Name: to,
}); err != nil {
return err
} else if n <= 0 {
return ErrBranchNotExist{
RepoID: repo.ID,
BranchName: from,
}
}
// 2. update default branch if needed
isDefault := repo.DefaultBranch == from
if isDefault {
repo.DefaultBranch = to
_, err = sess.ID(repo.ID).Cols("default_branch").Update(repo)
if err != nil {
return err
}
}
// 3. Update protected branch if needed
protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
if err != nil {
return err
}
if protectedBranch != nil {
// there is a protect rule for this branch
protectedBranch.RuleName = to
_, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
if err != nil {
return err
}
} else {
// some glob protect rules may match this branch
protected, err := IsBranchProtected(ctx, repo.ID, from)
if err != nil {
return err
}
if protected {
return ErrBranchIsProtected
}
}
// 4. Update all not merged pull request base branch name
_, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
repo.ID, from, false).
Update(map[string]interface{}{"base_branch": to})
if err != nil {
return err
}
// 5. do git action
if err = gitAction(isDefault); err != nil {
return err
}
// 6. insert renamed branch record
renamedBranch := &RenamedBranch{
RepoID: repo.ID,
From: from,
To: to,
}
err = db.Insert(ctx, renamedBranch)
if err != nil {
return err
}
return committer.Commit()
}

132
models/git/branch_list.go Normal file
View File

@ -0,0 +1,132 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"context"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
"xorm.io/xorm"
)
type BranchList []*Branch
func (branches BranchList) LoadDeletedBy(ctx context.Context) error {
ids := container.Set[int64]{}
for _, branch := range branches {
if !branch.IsDeleted {
continue
}
ids.Add(branch.DeletedByID)
}
usersMap := make(map[int64]*user_model.User, len(ids))
if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
return err
}
for _, branch := range branches {
if !branch.IsDeleted {
continue
}
branch.DeletedBy = usersMap[branch.DeletedByID]
if branch.DeletedBy == nil {
branch.DeletedBy = user_model.NewGhostUser()
}
}
return nil
}
func (branches BranchList) LoadPusher(ctx context.Context) error {
ids := container.Set[int64]{}
for _, branch := range branches {
if branch.PusherID > 0 { // pusher_id maybe zero because some branches are sync by backend with no pusher
ids.Add(branch.PusherID)
}
}
usersMap := make(map[int64]*user_model.User, len(ids))
if err := db.GetEngine(ctx).In("id", ids.Values()).Find(&usersMap); err != nil {
return err
}
for _, branch := range branches {
if branch.PusherID <= 0 {
continue
}
branch.Pusher = usersMap[branch.PusherID]
if branch.Pusher == nil {
branch.Pusher = user_model.NewGhostUser()
}
}
return nil
}
const (
BranchOrderByNameAsc = "name ASC"
BranchOrderByCommitTimeDesc = "commit_time DESC"
)
type FindBranchOptions struct {
db.ListOptions
RepoID int64
ExcludeBranchNames []string
IsDeletedBranch util.OptionalBool
OrderBy string
}
func (opts *FindBranchOptions) Cond() builder.Cond {
cond := builder.NewCond()
if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
}
if len(opts.ExcludeBranchNames) > 0 {
cond = cond.And(builder.NotIn("name", opts.ExcludeBranchNames))
}
if !opts.IsDeletedBranch.IsNone() {
cond = cond.And(builder.Eq{"is_deleted": opts.IsDeletedBranch.IsTrue()})
}
return cond
}
func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) {
return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{})
}
func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session {
if !opts.IsDeletedBranch.IsFalse() { // if deleted branch included, put them at the end
sess = sess.OrderBy("is_deleted ASC")
}
if opts.OrderBy == "" {
opts.OrderBy = BranchOrderByCommitTimeDesc
}
return sess.OrderBy(opts.OrderBy)
}
func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) {
sess := db.GetEngine(ctx).Where(opts.Cond())
if opts.PageSize > 0 && !opts.IsListAll() {
sess = db.SetSessionPagination(sess, &opts.ListOptions)
}
sess = orderByBranches(sess, opts)
var branches []*Branch
return branches, sess.Find(&branches)
}
func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) {
sess := db.GetEngine(ctx).Select("name").Where(opts.Cond())
if opts.PageSize > 0 && !opts.IsListAll() {
sess = db.SetSessionPagination(sess, &opts.ListOptions)
}
sess = orderByBranches(sess, opts)
var branches []string
if err := sess.Table("branch").Find(&branches); err != nil {
return nil, err
}
return branches, nil
}

View File

@ -11,6 +11,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
@ -18,24 +19,37 @@ import (
func TestAddDeletedBranch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
assert.Error(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.Commit, firstBranch.DeletedByID))
assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "test", "5655464564554545466464656", int64(1)))
assert.True(t, firstBranch.IsDeleted)
assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, firstBranch.Name, firstBranch.DeletedByID))
assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "branch2", int64(1)))
secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"})
assert.True(t, secondBranch.IsDeleted)
err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.Name, secondBranch.CommitID, secondBranch.CommitMessage, secondBranch.PusherID, secondBranch.CommitTime.AsLocalTime())
assert.NoError(t, err)
}
func TestGetDeletedBranches(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
branches, err := git_model.GetDeletedBranches(db.DefaultContext, repo.ID)
branches, err := git_model.FindBranches(db.DefaultContext, git_model.FindBranchOptions{
ListOptions: db.ListOptions{
ListAll: true,
},
RepoID: repo.ID,
IsDeletedBranch: util.OptionalBoolTrue,
})
assert.NoError(t, err)
assert.Len(t, branches, 2)
}
func TestGetDeletedBranch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
assert.NotNil(t, getDeletedBranch(t, firstBranch))
}
@ -43,18 +57,18 @@ func TestGetDeletedBranch(t *testing.T) {
func TestDeletedBranchLoadUser(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2})
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2})
branch := getDeletedBranch(t, firstBranch)
assert.Nil(t, branch.DeletedBy)
branch.LoadUser(db.DefaultContext)
branch.LoadDeletedBy(db.DefaultContext)
assert.NotNil(t, branch.DeletedBy)
assert.Equal(t, "user1", branch.DeletedBy.Name)
branch = getDeletedBranch(t, secondBranch)
assert.Nil(t, branch.DeletedBy)
branch.LoadUser(db.DefaultContext)
branch.LoadDeletedBy(db.DefaultContext)
assert.NotNil(t, branch.DeletedBy)
assert.Equal(t, "Ghost", branch.DeletedBy.Name)
}
@ -63,22 +77,22 @@ func TestRemoveDeletedBranch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1})
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
err := git_model.RemoveDeletedBranchByID(db.DefaultContext, repo.ID, 1)
assert.NoError(t, err)
unittest.AssertNotExistsBean(t, firstBranch)
unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2})
unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2})
}
func getDeletedBranch(t *testing.T, branch *git_model.DeletedBranch) *git_model.DeletedBranch {
func getDeletedBranch(t *testing.T, branch *git_model.Branch) *git_model.Branch {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID)
assert.NoError(t, err)
assert.Equal(t, branch.ID, deletedBranch.ID)
assert.Equal(t, branch.Name, deletedBranch.Name)
assert.Equal(t, branch.Commit, deletedBranch.Commit)
assert.Equal(t, branch.CommitID, deletedBranch.CommitID)
assert.Equal(t, branch.DeletedByID, deletedBranch.DeletedByID)
return deletedBranch
@ -146,8 +160,8 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1)
// Expect no error, and the returned branch is nil.
assert.NoError(t, err)
// Expect error, and the returned branch is nil.
assert.Error(t, err)
assert.Nil(t, deletedBranch)
// Now get the deletedBranch with ID of 1 on repo with ID 1.

View File

@ -1,197 +0,0 @@
// Copyright 2016 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"context"
"fmt"
"time"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
)
// DeletedBranch struct
type DeletedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
Name string `xorm:"UNIQUE(s) NOT NULL"`
Commit string `xorm:"UNIQUE(s) NOT NULL"`
DeletedByID int64 `xorm:"INDEX"`
DeletedBy *user_model.User `xorm:"-"`
DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
func init() {
db.RegisterModel(new(DeletedBranch))
db.RegisterModel(new(RenamedBranch))
}
// AddDeletedBranch adds a deleted branch to the database
func AddDeletedBranch(ctx context.Context, repoID int64, branchName, commit string, deletedByID int64) error {
deletedBranch := &DeletedBranch{
RepoID: repoID,
Name: branchName,
Commit: commit,
DeletedByID: deletedByID,
}
_, err := db.GetEngine(ctx).Insert(deletedBranch)
return err
}
// GetDeletedBranches returns all the deleted branches
func GetDeletedBranches(ctx context.Context, repoID int64) ([]*DeletedBranch, error) {
deletedBranches := make([]*DeletedBranch, 0)
return deletedBranches, db.GetEngine(ctx).Where("repo_id = ?", repoID).Desc("deleted_unix").Find(&deletedBranches)
}
// GetDeletedBranchByID get a deleted branch by its ID
func GetDeletedBranchByID(ctx context.Context, repoID, id int64) (*DeletedBranch, error) {
deletedBranch := &DeletedBranch{}
has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).And("id = ?", id).Get(deletedBranch)
if err != nil {
return nil, err
}
if !has {
return nil, nil
}
return deletedBranch, nil
}
// RemoveDeletedBranchByID removes a deleted branch from the database
func RemoveDeletedBranchByID(ctx context.Context, repoID, id int64) (err error) {
deletedBranch := &DeletedBranch{
RepoID: repoID,
ID: id,
}
if affected, err := db.GetEngine(ctx).Delete(deletedBranch); err != nil {
return err
} else if affected != 1 {
return fmt.Errorf("remove deleted branch ID(%v) failed", id)
}
return nil
}
// LoadUser loads the user that deleted the branch
// When there's no user found it returns a user_model.NewGhostUser
func (deletedBranch *DeletedBranch) LoadUser(ctx context.Context) {
user, err := user_model.GetUserByID(ctx, deletedBranch.DeletedByID)
if err != nil {
user = user_model.NewGhostUser()
}
deletedBranch.DeletedBy = user
}
// RemoveDeletedBranchByName removes all deleted branches
func RemoveDeletedBranchByName(ctx context.Context, repoID int64, branch string) error {
_, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branch).Delete(new(DeletedBranch))
return err
}
// RemoveOldDeletedBranches removes old deleted branches
func RemoveOldDeletedBranches(ctx context.Context, olderThan time.Duration) {
// Nothing to do for shutdown or terminate
log.Trace("Doing: DeletedBranchesCleanup")
deleteBefore := time.Now().Add(-olderThan)
_, err := db.GetEngine(ctx).Where("deleted_unix < ?", deleteBefore.Unix()).Delete(new(DeletedBranch))
if err != nil {
log.Error("DeletedBranchesCleanup: %v", err)
}
}
// RenamedBranch provide renamed branch log
// will check it when a branch can't be found
type RenamedBranch struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX NOT NULL"`
From string
To string
CreatedUnix timeutil.TimeStamp `xorm:"created"`
}
// FindRenamedBranch check if a branch was renamed
func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch *RenamedBranch, exist bool, err error) {
branch = &RenamedBranch{
RepoID: repoID,
From: from,
}
exist, err = db.GetEngine(ctx).Get(branch)
return branch, exist, err
}
// RenameBranch rename a branch
func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
sess := db.GetEngine(ctx)
// 1. update default branch if needed
isDefault := repo.DefaultBranch == from
if isDefault {
repo.DefaultBranch = to
_, err = sess.ID(repo.ID).Cols("default_branch").Update(repo)
if err != nil {
return err
}
}
// 2. Update protected branch if needed
protectedBranch, err := GetProtectedBranchRuleByName(ctx, repo.ID, from)
if err != nil {
return err
}
if protectedBranch != nil {
protectedBranch.RuleName = to
_, err = sess.ID(protectedBranch.ID).Cols("branch_name").Update(protectedBranch)
if err != nil {
return err
}
} else {
protected, err := IsBranchProtected(ctx, repo.ID, from)
if err != nil {
return err
}
if protected {
return ErrBranchIsProtected
}
}
// 3. Update all not merged pull request base branch name
_, err = sess.Table("pull_request").Where("base_repo_id=? AND base_branch=? AND has_merged=?",
repo.ID, from, false).
Update(map[string]interface{}{"base_branch": to})
if err != nil {
return err
}
// 4. do git action
if err = gitAction(isDefault); err != nil {
return err
}
// 5. insert renamed branch record
renamedBranch := &RenamedBranch{
RepoID: repo.ID,
From: from,
To: to,
}
err = db.Insert(ctx, renamedBranch)
if err != nil {
return err
}
return committer.Commit()
}

View File

@ -8,7 +8,7 @@ import (
"sort"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
)
@ -47,19 +47,32 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB
}
// FindAllMatchedBranches find all matched branches
func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) {
// FIXME: how many should we get?
branches, _, err := gitRepo.GetBranchNames(0, 9999999)
if err != nil {
return nil, err
}
rule := glob.MustCompile(ruleName)
results := make([]string, 0, len(branches))
for _, branch := range branches {
if rule.Match(branch) {
results = append(results, branch)
func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) {
results := make([]string, 0, 10)
for page := 1; ; page++ {
brancheNames, err := FindBranchNames(ctx, FindBranchOptions{
ListOptions: db.ListOptions{
PageSize: 100,
Page: page,
},
RepoID: repoID,
IsDeletedBranch: util.OptionalBoolFalse,
})
if err != nil {
return nil, err
}
rule := glob.MustCompile(ruleName)
for _, branch := range brancheNames {
if rule.Match(branch) {
results = append(results, branch)
}
}
if len(brancheNames) < 100 {
break
}
}
return results, nil
}

View File

@ -507,6 +507,10 @@ var migrations = []Migration{
NewMigration("Add variable table", v1_21.CreateVariableTable),
// v262 -> v263
NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun),
// v263 -> v264
NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable),
// v264 -> v265
NewMigration("Add branch table", v1_21.AddBranchTable),
}
// GetCurrentDBVersion returns the current db version

View File

@ -0,0 +1,41 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_21 //nolint
import (
"fmt"
"xorm.io/xorm"
)
// AddGitSizeAndLFSSizeToRepositoryTable: add GitSize and LFSSize columns to Repository
func AddGitSizeAndLFSSizeToRepositoryTable(x *xorm.Engine) error {
type Repository struct {
GitSize int64 `xorm:"NOT NULL DEFAULT 0"`
LFSSize int64 `xorm:"NOT NULL DEFAULT 0"`
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := sess.Sync2(new(Repository)); err != nil {
return fmt.Errorf("Sync2: %w", err)
}
_, err := sess.Exec(`UPDATE repository SET lfs_size=(SELECT SUM(size) FROM lfs_meta_object WHERE lfs_meta_object.repository_id=repository.ID) WHERE EXISTS (SELECT 1 FROM lfs_meta_object WHERE lfs_meta_object.repository_id=repository.ID)`)
if err != nil {
return err
}
_, err = sess.Exec(`UPDATE repository SET git_size = size - lfs_size`)
if err != nil {
return err
}
return sess.Commit()
}

View File

@ -0,0 +1,93 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_21 //nolint
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func AddBranchTable(x *xorm.Engine) error {
type Branch struct {
ID int64
RepoID int64 `xorm:"UNIQUE(s)"`
Name string `xorm:"UNIQUE(s) NOT NULL"`
CommitID string
CommitMessage string `xorm:"TEXT"`
PusherID int64
IsDeleted bool `xorm:"index"`
DeletedByID int64
DeletedUnix timeutil.TimeStamp `xorm:"index"`
CommitTime timeutil.TimeStamp // The commit
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}
if err := x.Sync(new(Branch)); err != nil {
return err
}
if exist, err := x.IsTableExist("deleted_branches"); err != nil {
return err
} else if !exist {
return nil
}
type DeletedBranch struct {
ID int64
RepoID int64 `xorm:"index UNIQUE(s)"`
Name string `xorm:"UNIQUE(s) NOT NULL"`
Commit string
DeletedByID int64
DeletedUnix timeutil.TimeStamp
}
var adminUserID int64
has, err := x.Table("user").
Select("id").
Where("is_admin=?", true).
Asc("id"). // Reliably get the admin with the lowest ID.
Get(&adminUserID)
if err != nil {
return err
} else if !has {
return fmt.Errorf("no admin user found")
}
branches := make([]Branch, 0, 100)
if err := db.Iterate(context.Background(), nil, func(ctx context.Context, deletedBranch *DeletedBranch) error {
branches = append(branches, Branch{
RepoID: deletedBranch.RepoID,
Name: deletedBranch.Name,
CommitID: deletedBranch.Commit,
PusherID: adminUserID,
IsDeleted: true,
DeletedByID: deletedBranch.DeletedByID,
DeletedUnix: deletedBranch.DeletedUnix,
})
if len(branches) >= 100 {
_, err := x.Insert(&branches)
if err != nil {
return err
}
branches = branches[:0]
}
return nil
}); err != nil {
return err
}
if len(branches) > 0 {
if _, err := x.Insert(&branches); err != nil {
return err
}
}
return x.DropTables("deleted_branches")
}

View File

@ -147,7 +147,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
&repo_model.Collaboration{RepoID: repoID},
&issues_model.Comment{RefRepoID: repoID},
&git_model.CommitStatus{RepoID: repoID},
&git_model.DeletedBranch{RepoID: repoID},
&git_model.Branch{RepoID: repoID},
&git_model.LFSLock{RepoID: repoID},
&repo_model.LanguageStat{RepoID: repoID},
&issues_model.Milestone{RepoID: repoID},

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
@ -163,6 +164,8 @@ type Repository struct {
IsTemplate bool `xorm:"INDEX NOT NULL DEFAULT false"`
TemplateID int64 `xorm:"INDEX"`
Size int64 `xorm:"NOT NULL DEFAULT 0"`
GitSize int64 `xorm:"NOT NULL DEFAULT 0"`
LFSSize int64 `xorm:"NOT NULL DEFAULT 0"`
CodeIndexerStatus *RepoIndexerStatus `xorm:"-"`
StatsIndexerStatus *RepoIndexerStatus `xorm:"-"`
IsFsckEnabled bool `xorm:"NOT NULL DEFAULT true"`
@ -196,6 +199,42 @@ func (repo *Repository) SanitizedOriginalURL() string {
return u.String()
}
// text representations to be returned in SizeDetail.Name
const (
SizeDetailNameGit = "git"
SizeDetailNameLFS = "lfs"
)
type SizeDetail struct {
Name string
Size int64
}
// SizeDetails forms a struct with various size details about repository
func (repo *Repository) SizeDetails() []SizeDetail {
sizeDetails := []SizeDetail{
{
Name: SizeDetailNameGit,
Size: repo.GitSize,
},
{
Name: SizeDetailNameLFS,
Size: repo.LFSSize,
},
}
return sizeDetails
}
// SizeDetailsString returns a concatenation of all repository size details as a string
func (repo *Repository) SizeDetailsString() string {
var str strings.Builder
sizeDetails := repo.SizeDetails()
for _, detail := range sizeDetails {
str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size)))
}
return strings.TrimSuffix(str.String(), ", ")
}
func (repo *Repository) LogString() string {
if repo == nil {
return "<Repository nil>"

View File

@ -185,9 +185,11 @@ func ChangeRepositoryName(doer *user_model.User, repo *Repository, newRepoName s
}
// UpdateRepoSize updates the repository size, calculating it using getDirectorySize
func UpdateRepoSize(ctx context.Context, repoID, size int64) error {
_, err := db.GetEngine(ctx).ID(repoID).Cols("size").NoAutoTime().Update(&Repository{
Size: size,
func UpdateRepoSize(ctx context.Context, repoID, gitSize, lfsSize int64) error {
_, err := db.GetEngine(ctx).ID(repoID).Cols("size", "git_size", "lfs_size").NoAutoTime().Update(&Repository{
Size: gitSize + lfsSize,
GitSize: gitSize,
LFSSize: lfsSize,
})
return err
}

View File

@ -1171,9 +1171,9 @@ func GetUserByOpenID(uri string) (*User, error) {
}
// GetAdminUser returns the first administrator
func GetAdminUser() (*User, error) {
func GetAdminUser(ctx context.Context) (*User, error) {
var admin User
has, err := db.GetEngine(db.DefaultContext).
has, err := db.GetEngine(ctx).
Where("is_admin=?", true).
Asc("id"). // Reliably get the admin with the lowest ID.
Get(&admin)

View File

@ -29,12 +29,28 @@ const (
)
func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) {
flag := os.O_WRONLY
if offset == 0 {
// Create file only if offset is 0, or it could result in content holes if the file doesn't exist.
flag |= os.O_CREATE
}
name := DBFSPrefix + filename
f, err := dbfs.OpenFile(ctx, name, os.O_WRONLY|os.O_CREATE)
f, err := dbfs.OpenFile(ctx, name, flag)
if err != nil {
return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err)
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return nil, fmt.Errorf("dbfs Stat %q: %w", name, err)
}
if stat.Size() < offset {
// If the size is less than offset, refuse to write, or it could result in content holes.
// However, if the size is greater than offset, we can still write to overwrite the content.
return nil, fmt.Errorf("size of %q is less than offset", name)
}
if _, err := f.Seek(offset, io.SeekStart); err != nil {
return nil, fmt.Errorf("dbfs Seek %q: %w", name, err)
}
@ -57,7 +73,7 @@ func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runne
}
func ReadLogs(ctx context.Context, inStorage bool, filename string, offset, limit int64) ([]*runnerv1.LogRow, error) {
f, err := openLogs(ctx, inStorage, filename)
f, err := OpenLogs(ctx, inStorage, filename)
if err != nil {
return nil, err
}
@ -125,7 +141,7 @@ func RemoveLogs(ctx context.Context, inStorage bool, filename string) error {
return nil
}
func openLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) {
func OpenLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) {
if !inStorage {
name := DBFSPrefix + filename
f, err := dbfs.Open(ctx, name)

View File

@ -667,13 +667,38 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
}
ctx.Data["Tags"] = tags
brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
branchOpts := git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: util.OptionalBoolFalse,
ListOptions: db.ListOptions{
ListAll: true,
},
}
branchesTotal, err := git_model.CountBranches(ctx, branchOpts)
if err != nil {
ctx.ServerError("CountBranches", err)
return
}
// non empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
if branchesTotal == 0 { // fallback to do a sync immediately
branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
ctx.ServerError("SyncRepoBranches", err)
return
}
}
// FIXME: use paganation and async loading
branchOpts.ExcludeBranchNames = []string{ctx.Repo.Repository.DefaultBranch}
brs, err := git_model.FindBranchNames(ctx, branchOpts)
if err != nil {
ctx.ServerError("GetBranches", err)
return
}
ctx.Data["Branches"] = brs
ctx.Data["BranchesCount"] = len(brs)
// always put default branch on the top
ctx.Data["Branches"] = append(branchOpts.ExcludeBranchNames, brs...)
ctx.Data["BranchesCount"] = branchesTotal
// If not branch selected, try default one.
// If default branch doesn't exist, fall back to some other branch.
@ -897,9 +922,9 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
if len(ctx.Params("*")) == 0 {
refName = ctx.Repo.Repository.DefaultBranch
if !ctx.Repo.GitRepo.IsBranchExist(refName) {
brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1)
if err == nil && len(brs) != 0 {
refName = brs[0]
refName = brs[0].Name
} else if len(brs) == 0 {
log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
ctx.Repo.Repository.MarkAsBrokenEmpty()

View File

@ -230,6 +230,7 @@ var GemojiData = Gemoji{
{"\U0001f382", "birthday cake", []string{"birthday"}, "6.0", false},
{"\U0001f9ac", "bison", []string{"bison"}, "13.0", false},
{"\U0001fae6", "biting lip", []string{"biting_lip"}, "14.0", false},
{"\U0001f426\u200d\u2b1b", "black bird", []string{"black_bird"}, "15.0", false},
{"\U0001f408\u200d\u2b1b", "black cat", []string{"black_cat"}, "13.0", false},
{"\u26ab", "black circle", []string{"black_circle"}, "4.1", false},
{"\U0001f3f4", "black flag", []string{"black_flag"}, "7.0", false},
@ -748,6 +749,7 @@ var GemojiData = Gemoji{
{"\U0001f42c", "dolphin", []string{"dolphin", "flipper"}, "6.0", false},
{"\U0001f1e9\U0001f1f2", "flag: Dominica", []string{"dominica"}, "6.0", false},
{"\U0001f1e9\U0001f1f4", "flag: Dominican Republic", []string{"dominican_republic"}, "6.0", false},
{"\U0001facf", "donkey", []string{"donkey"}, "15.0", false},
{"\U0001f6aa", "door", []string{"door"}, "6.0", false},
{"\U0001fae5", "dotted line face", []string{"dotted_line_face"}, "14.0", false},
{"\U0001f369", "doughnut", []string{"doughnut"}, "6.0", false},
@ -982,11 +984,13 @@ var GemojiData = Gemoji{
{"\U0001f4be", "floppy disk", []string{"floppy_disk"}, "6.0", false},
{"\U0001f3b4", "flower playing cards", []string{"flower_playing_cards"}, "6.0", false},
{"\U0001f633", "flushed face", []string{"flushed"}, "6.0", false},
{"\U0001fa88", "flute", []string{"flute"}, "15.0", false},
{"\U0001fab0", "fly", []string{"fly"}, "13.0", false},
{"\U0001f94f", "flying disc", []string{"flying_disc"}, "11.0", false},
{"\U0001f6f8", "flying saucer", []string{"flying_saucer"}, "11.0", false},
{"\U0001f32b\ufe0f", "fog", []string{"fog"}, "7.0", false},
{"\U0001f301", "foggy", []string{"foggy"}, "6.0", false},
{"\U0001faad", "folding hand fan", []string{"folding_hand_fan"}, "15.0", false},
{"\U0001fad5", "fondue", []string{"fondue"}, "13.0", false},
{"\U0001f9b6", "foot", []string{"foot"}, "11.0", true},
{"\U0001f9b6\U0001f3ff", "foot: Dark Skin Tone", []string{"foot_Dark_Skin_Tone"}, "12.0", false},
@ -1054,6 +1058,7 @@ var GemojiData = Gemoji{
{"\U0001f1ec\U0001f1ee", "flag: Gibraltar", []string{"gibraltar"}, "6.0", false},
{"\U0001f381", "wrapped gift", []string{"gift"}, "6.0", false},
{"\U0001f49d", "heart with ribbon", []string{"gift_heart"}, "6.0", false},
{"\U0001fada", "ginger root", []string{"ginger_root"}, "15.0", false},
{"\U0001f992", "giraffe", []string{"giraffe"}, "11.0", false},
{"\U0001f467", "girl", []string{"girl"}, "6.0", true},
{"\U0001f467\U0001f3ff", "girl: Dark Skin Tone", []string{"girl_Dark_Skin_Tone"}, "12.0", false},
@ -1085,6 +1090,7 @@ var GemojiData = Gemoji{
{"\U0001f3cc\U0001f3fe\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium-Dark Skin Tone", []string{"golfing_woman_Medium-Dark_Skin_Tone"}, "12.0", false},
{"\U0001f3cc\U0001f3fc\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium-Light Skin Tone", []string{"golfing_woman_Medium-Light_Skin_Tone"}, "12.0", false},
{"\U0001f3cc\U0001f3fd\ufe0f\u200d\u2640\ufe0f", "woman golfing: Medium Skin Tone", []string{"golfing_woman_Medium_Skin_Tone"}, "12.0", false},
{"\U0001fabf", "goose", []string{"goose"}, "15.0", false},
{"\U0001f98d", "gorilla", []string{"gorilla"}, "9.0", false},
{"\U0001f347", "grapes", []string{"grapes"}, "6.0", false},
{"\U0001f1ec\U0001f1f7", "flag: Greece", []string{"greece"}, "6.0", false},
@ -1097,6 +1103,7 @@ var GemojiData = Gemoji{
{"\U0001f1ec\U0001f1f1", "flag: Greenland", []string{"greenland"}, "6.0", false},
{"\U0001f1ec\U0001f1e9", "flag: Grenada", []string{"grenada"}, "6.0", false},
{"\u2755", "white exclamation mark", []string{"grey_exclamation"}, "6.0", false},
{"\U0001fa76", "grey heart", []string{"grey_heart"}, "15.0", false},
{"\u2754", "white question mark", []string{"grey_question"}, "6.0", false},
{"\U0001f62c", "grimacing face", []string{"grimacing"}, "6.1", false},
{"\U0001f601", "beaming face with smiling eyes", []string{"grin"}, "6.0", false},
@ -1129,6 +1136,7 @@ var GemojiData = Gemoji{
{"\U0001f3b8", "guitar", []string{"guitar"}, "6.0", false},
{"\U0001f52b", "water pistol", []string{"gun"}, "6.0", false},
{"\U0001f1ec\U0001f1fe", "flag: Guyana", []string{"guyana"}, "6.0", false},
{"\U0001faae", "hair pick", []string{"hair_pick"}, "15.0", false},
{"\U0001f487", "person getting haircut", []string{"haircut"}, "6.0", true},
{"\U0001f487\U0001f3ff", "person getting haircut: Dark Skin Tone", []string{"haircut_Dark_Skin_Tone"}, "12.0", false},
{"\U0001f487\U0001f3fb", "person getting haircut: Light Skin Tone", []string{"haircut_Light_Skin_Tone"}, "12.0", false},
@ -1253,6 +1261,7 @@ var GemojiData = Gemoji{
{"\U0001f1ed\U0001f1fa", "flag: Hungary", []string{"hungary"}, "6.0", false},
{"\U0001f62f", "hushed face", []string{"hushed"}, "6.1", false},
{"\U0001f6d6", "hut", []string{"hut"}, "13.0", false},
{"\U0001fabb", "hyacinth", []string{"hyacinth"}, "15.0", false},
{"\U0001f368", "ice cream", []string{"ice_cream"}, "6.0", false},
{"\U0001f9ca", "ice", []string{"ice_cube"}, "12.0", false},
{"\U0001f3d2", "ice hockey", []string{"ice_hockey"}, "8.0", false},
@ -1293,6 +1302,7 @@ var GemojiData = Gemoji{
{"\U0001f479", "ogre", []string{"japanese_ogre"}, "6.0", false},
{"\U0001fad9", "jar", []string{"jar"}, "14.0", false},
{"\U0001f456", "jeans", []string{"jeans"}, "6.0", false},
{"\U0001fabc", "jellyfish", []string{"jellyfish"}, "15.0", false},
{"\U0001f1ef\U0001f1ea", "flag: Jersey", []string{"jersey"}, "6.0", false},
{"\U0001f9e9", "puzzle piece", []string{"jigsaw"}, "11.0", false},
{"\U0001f1ef\U0001f1f4", "flag: Jordan", []string{"jordan"}, "6.0", false},
@ -1319,6 +1329,7 @@ var GemojiData = Gemoji{
{"\U0001f511", "key", []string{"key"}, "6.0", false},
{"\u2328\ufe0f", "keyboard", []string{"keyboard"}, "", false},
{"\U0001f51f", "keycap: 10", []string{"keycap_ten"}, "6.0", false},
{"\U0001faaf", "khanda", []string{"khanda"}, "15.0", false},
{"\U0001f6f4", "kick scooter", []string{"kick_scooter"}, "9.0", false},
{"\U0001f458", "kimono", []string{"kimono"}, "6.0", false},
{"\U0001f1f0\U0001f1ee", "flag: Kiribati", []string{"kiribati"}, "6.0", false},
@ -1383,6 +1394,12 @@ var GemojiData = Gemoji{
{"\U0001faf2\U0001f3fe", "leftwards hand: Medium-Dark Skin Tone", []string{"leftwards_hand_Medium-Dark_Skin_Tone"}, "12.0", false},
{"\U0001faf2\U0001f3fc", "leftwards hand: Medium-Light Skin Tone", []string{"leftwards_hand_Medium-Light_Skin_Tone"}, "12.0", false},
{"\U0001faf2\U0001f3fd", "leftwards hand: Medium Skin Tone", []string{"leftwards_hand_Medium_Skin_Tone"}, "12.0", false},
{"\U0001faf7", "leftwards pushing hand", []string{"leftwards_pushing_hand"}, "15.0", true},
{"\U0001faf7\U0001f3ff", "leftwards pushing hand: Dark Skin Tone", []string{"leftwards_pushing_hand_Dark_Skin_Tone"}, "12.0", false},
{"\U0001faf7\U0001f3fb", "leftwards pushing hand: Light Skin Tone", []string{"leftwards_pushing_hand_Light_Skin_Tone"}, "12.0", false},
{"\U0001faf7\U0001f3fe", "leftwards pushing hand: Medium-Dark Skin Tone", []string{"leftwards_pushing_hand_Medium-Dark_Skin_Tone"}, "12.0", false},
{"\U0001faf7\U0001f3fc", "leftwards pushing hand: Medium-Light Skin Tone", []string{"leftwards_pushing_hand_Medium-Light_Skin_Tone"}, "12.0", false},
{"\U0001faf7\U0001f3fd", "leftwards pushing hand: Medium Skin Tone", []string{"leftwards_pushing_hand_Medium_Skin_Tone"}, "12.0", false},
{"\U0001f9b5", "leg", []string{"leg"}, "11.0", true},
{"\U0001f9b5\U0001f3ff", "leg: Dark Skin Tone", []string{"leg_Dark_Skin_Tone"}, "12.0", false},
{"\U0001f9b5\U0001f3fb", "leg: Light Skin Tone", []string{"leg_Light_Skin_Tone"}, "12.0", false},
@ -1398,6 +1415,7 @@ var GemojiData = Gemoji{
{"\u264e", "Libra", []string{"libra"}, "", false},
{"\U0001f1f1\U0001f1fe", "flag: Libya", []string{"libya"}, "6.0", false},
{"\U0001f1f1\U0001f1ee", "flag: Liechtenstein", []string{"liechtenstein"}, "6.0", false},
{"\U0001fa75", "light blue heart", []string{"light_blue_heart"}, "15.0", false},
{"\U0001f688", "light rail", []string{"light_rail"}, "6.0", false},
{"\U0001f517", "link", []string{"link"}, "6.0", false},
{"\U0001f981", "lion", []string{"lion"}, "8.0", false},
@ -1695,6 +1713,7 @@ var GemojiData = Gemoji{
{"\U0001f570\ufe0f", "mantelpiece clock", []string{"mantelpiece_clock"}, "7.0", false},
{"\U0001f9bd", "manual wheelchair", []string{"manual_wheelchair"}, "12.0", false},
{"\U0001f341", "maple leaf", []string{"maple_leaf"}, "6.0", false},
{"\U0001fa87", "maracas", []string{"maracas"}, "15.0", false},
{"\U0001f1f2\U0001f1ed", "flag: Marshall Islands", []string{"marshall_islands"}, "6.0", false},
{"\U0001f94b", "martial arts uniform", []string{"martial_arts_uniform"}, "9.0", false},
{"\U0001f1f2\U0001f1f6", "flag: Martinique", []string{"martinique"}, "6.0", false},
@ -1799,6 +1818,7 @@ var GemojiData = Gemoji{
{"\U0001f1f2\U0001f1f8", "flag: Montserrat", []string{"montserrat"}, "6.0", false},
{"\U0001f314", "waxing gibbous moon", []string{"moon", "waxing_gibbous_moon"}, "6.0", false},
{"\U0001f96e", "moon cake", []string{"moon_cake"}, "11.0", false},
{"\U0001face", "moose", []string{"moose"}, "15.0", false},
{"\U0001f1f2\U0001f1e6", "flag: Morocco", []string{"morocco"}, "6.0", false},
{"\U0001f393", "graduation cap", []string{"mortar_board"}, "6.0", false},
{"\U0001f54c", "mosque", []string{"mosque"}, "8.0", false},
@ -2076,6 +2096,7 @@ var GemojiData = Gemoji{
{"\U0001f6f3\ufe0f", "passenger ship", []string{"passenger_ship"}, "7.0", false},
{"\U0001f6c2", "passport control", []string{"passport_control"}, "6.0", false},
{"\u23f8\ufe0f", "pause button", []string{"pause_button"}, "7.0", false},
{"\U0001fadb", "pea pod", []string{"pea_pod"}, "15.0", false},
{"\u262e\ufe0f", "peace symbol", []string{"peace_symbol"}, "", false},
{"\U0001f351", "peach", []string{"peach"}, "6.0", false},
{"\U0001f99a", "peacock", []string{"peacock"}, "11.0", false},
@ -2085,7 +2106,12 @@ var GemojiData = Gemoji{
{"\u270f\ufe0f", "pencil", []string{"pencil2"}, "", false},
{"\U0001f427", "penguin", []string{"penguin"}, "6.0", false},
{"\U0001f614", "pensive face", []string{"pensive"}, "6.0", false},
{"\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands", []string{"people_holding_hands"}, "12.0", false},
{"\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands", []string{"people_holding_hands"}, "12.0", true},
{"\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Dark Skin Tone", []string{"people_holding_hands_Dark_Skin_Tone"}, "12.0", false},
{"\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Light Skin Tone", []string{"people_holding_hands_Light_Skin_Tone"}, "12.0", false},
{"\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium-Dark Skin Tone", []string{"people_holding_hands_Medium-Dark_Skin_Tone"}, "12.0", false},
{"\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium-Light Skin Tone", []string{"people_holding_hands_Medium-Light_Skin_Tone"}, "12.0", false},
{"\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1", "people holding hands: Medium Skin Tone", []string{"people_holding_hands_Medium_Skin_Tone"}, "12.0", false},
{"\U0001fac2", "people hugging", []string{"people_hugging"}, "13.0", false},
{"\U0001f3ad", "performing arts", []string{"performing_arts"}, "6.0", false},
{"\U0001f623", "persevering face", []string{"persevere"}, "6.0", false},
@ -2194,6 +2220,7 @@ var GemojiData = Gemoji{
{"\U0001f90f\U0001f3fd", "pinching hand: Medium Skin Tone", []string{"pinching_hand_Medium_Skin_Tone"}, "12.0", false},
{"\U0001f34d", "pineapple", []string{"pineapple"}, "6.0", false},
{"\U0001f3d3", "ping pong", []string{"ping_pong"}, "8.0", false},
{"\U0001fa77", "pink heart", []string{"pink_heart"}, "15.0", false},
{"\U0001f3f4\u200d\u2620\ufe0f", "pirate flag", []string{"pirate_flag"}, "11.0", false},
{"\u2653", "Pisces", []string{"pisces"}, "", false},
{"\U0001f1f5\U0001f1f3", "flag: Pitcairn Islands", []string{"pitcairn_islands"}, "6.0", false},
@ -2346,7 +2373,7 @@ var GemojiData = Gemoji{
{"\U0001f4fb", "radio", []string{"radio"}, "6.0", false},
{"\U0001f518", "radio button", []string{"radio_button"}, "6.0", false},
{"\u2622\ufe0f", "radioactive", []string{"radioactive"}, "", false},
{"\U0001f621", "pouting face", []string{"rage", "pout"}, "6.0", false},
{"\U0001f621", "enraged face", []string{"rage", "pout"}, "6.0", false},
{"\U0001f683", "railway car", []string{"railway_car"}, "6.0", false},
{"\U0001f6e4\ufe0f", "railway track", []string{"railway_track"}, "7.0", false},
{"\U0001f308", "rainbow", []string{"rainbow"}, "6.0", false},
@ -2434,6 +2461,12 @@ var GemojiData = Gemoji{
{"\U0001faf1\U0001f3fe", "rightwards hand: Medium-Dark Skin Tone", []string{"rightwards_hand_Medium-Dark_Skin_Tone"}, "12.0", false},
{"\U0001faf1\U0001f3fc", "rightwards hand: Medium-Light Skin Tone", []string{"rightwards_hand_Medium-Light_Skin_Tone"}, "12.0", false},
{"\U0001faf1\U0001f3fd", "rightwards hand: Medium Skin Tone", []string{"rightwards_hand_Medium_Skin_Tone"}, "12.0", false},
{"\U0001faf8", "rightwards pushing hand", []string{"rightwards_pushing_hand"}, "15.0", true},
{"\U0001faf8\U0001f3ff", "rightwards pushing hand: Dark Skin Tone", []string{"rightwards_pushing_hand_Dark_Skin_Tone"}, "12.0", false},
{"\U0001faf8\U0001f3fb", "rightwards pushing hand: Light Skin Tone", []string{"rightwards_pushing_hand_Light_Skin_Tone"}, "12.0", false},
{"\U0001faf8\U0001f3fe", "rightwards pushing hand: Medium-Dark Skin Tone", []string{"rightwards_pushing_hand_Medium-Dark_Skin_Tone"}, "12.0", false},
{"\U0001faf8\U0001f3fc", "rightwards pushing hand: Medium-Light Skin Tone", []string{"rightwards_pushing_hand_Medium-Light_Skin_Tone"}, "12.0", false},
{"\U0001faf8\U0001f3fd", "rightwards pushing hand: Medium Skin Tone", []string{"rightwards_pushing_hand_Medium_Skin_Tone"}, "12.0", false},
{"\U0001f48d", "ring", []string{"ring"}, "6.0", false},
{"\U0001f6df", "ring buoy", []string{"ring_buoy"}, "14.0", false},
{"\U0001fa90", "ringed planet", []string{"ringed_planet"}, "12.0", false},
@ -2566,6 +2599,7 @@ var GemojiData = Gemoji{
{"7\ufe0f\u20e3", "keycap: 7", []string{"seven"}, "", false},
{"\U0001faa1", "sewing needle", []string{"sewing_needle"}, "13.0", false},
{"\U0001f1f8\U0001f1e8", "flag: Seychelles", []string{"seychelles"}, "6.0", false},
{"\U0001fae8", "shaking face", []string{"shaking_face"}, "15.0", false},
{"\U0001f958", "shallow pan of food", []string{"shallow_pan_of_food"}, "", false},
{"\u2618\ufe0f", "shamrock", []string{"shamrock"}, "4.1", false},
{"\U0001f988", "shark", []string{"shark"}, "9.0", false},
@ -3125,7 +3159,9 @@ var GemojiData = Gemoji{
{"\U0001f32c\ufe0f", "wind face", []string{"wind_face"}, "7.0", false},
{"\U0001fa9f", "window", []string{"window"}, "13.0", false},
{"\U0001f377", "wine glass", []string{"wine_glass"}, "6.0", false},
{"\U0001fabd", "wing", []string{"wing"}, "15.0", false},
{"\U0001f609", "winking face", []string{"wink"}, "6.0", false},
{"\U0001f6dc", "wireless", []string{"wireless"}, "15.0", false},
{"\U0001f43a", "wolf", []string{"wolf"}, "6.0", false},
{"\U0001f469", "woman", []string{"woman"}, "6.0", true},
{"\U0001f469\U0001f3ff", "woman: Dark Skin Tone", []string{"woman_Dark_Skin_Tone"}, "12.0", false},
@ -3364,5 +3400,5 @@ var GemojiData = Gemoji{
{"\U0001f9df", "zombie", []string{"zombie"}, "11.0", false},
{"\U0001f9df\u200d\u2642\ufe0f", "man zombie", []string{"zombie_man"}, "11.0", false},
{"\U0001f9df\u200d\u2640\ufe0f", "woman zombie", []string{"zombie_woman"}, "11.0", false},
{"\U0001f4a4", "zzz", []string{"zzz"}, "6.0", false},
{"\U0001f4a4", "ZZZ", []string{"zzz"}, "6.0", false},
}

View File

@ -79,5 +79,5 @@ func SetConsoleLogger(loggerName, writerName string, level Level) {
Colorize: CanColorStdout,
WriterOption: WriterConsoleOption{},
})
GetManager().GetLogger(loggerName).RemoveAllWriters().AddWriters(writer)
GetManager().GetLogger(loggerName).ReplaceAllWriters(writer)
}

View File

@ -96,7 +96,10 @@ func (l *LoggerImpl) removeWriterInternal(w EventWriter) {
func (l *LoggerImpl) AddWriters(writer ...EventWriter) {
l.eventWriterMu.Lock()
defer l.eventWriterMu.Unlock()
l.addWritersInternal(writer...)
}
func (l *LoggerImpl) addWritersInternal(writer ...EventWriter) {
for _, w := range writer {
if old, ok := l.eventWriters[w.GetWriterName()]; ok {
l.removeWriterInternal(old)
@ -126,8 +129,8 @@ func (l *LoggerImpl) RemoveWriter(modeName string) error {
return nil
}
// RemoveAllWriters removes all writers from the logger, non-shared writers are closed and flushed
func (l *LoggerImpl) RemoveAllWriters() *LoggerImpl {
// ReplaceAllWriters replaces all writers from the logger, non-shared writers are closed and flushed
func (l *LoggerImpl) ReplaceAllWriters(writer ...EventWriter) {
l.eventWriterMu.Lock()
defer l.eventWriterMu.Unlock()
@ -135,8 +138,7 @@ func (l *LoggerImpl) RemoveAllWriters() *LoggerImpl {
l.removeWriterInternal(w)
}
l.eventWriters = map[string]EventWriter{}
l.syncLevelInternal()
return l
l.addWritersInternal(writer...)
}
// DumpWriters dumps the writers as a JSON map, it's used for debugging and display purposes.
@ -161,7 +163,7 @@ func (l *LoggerImpl) DumpWriters() map[string]any {
// Close closes the logger, non-shared writers are closed and flushed
func (l *LoggerImpl) Close() {
l.RemoveAllWriters()
l.ReplaceAllWriters()
l.ctxCancel()
}
@ -233,7 +235,6 @@ func NewLoggerWithWriters(ctx context.Context, name string, writer ...EventWrite
l.ctx, l.ctxCancel = newProcessTypedContext(ctx, "Logger: "+name)
l.LevelLogger = BaseLoggerToGeneralLogger(l)
l.eventWriters = map[string]EventWriter{}
l.syncLevelInternal()
l.AddWriters(writer...)
return l
}

View File

@ -23,7 +23,7 @@ func TestSharedWorker(t *testing.T) {
loggerTest := m.GetLogger("test")
loggerTest.AddWriters(w)
loggerTest.Info("msg-1")
loggerTest.RemoveAllWriters() // the shared writer is not closed here
loggerTest.ReplaceAllWriters() // the shared writer is not closed here
loggerTest.Info("never seen")
// the shared writer can still be used later

View File

@ -0,0 +1,135 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"context"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
)
// SyncRepoBranches synchronizes branch table with repository branches
func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error) {
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
if err != nil {
return 0, err
}
log.Debug("SyncRepoBranches: in Repo[%d:%s]", repo.ID, repo.FullName())
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
log.Error("OpenRepository[%s]: %w", repo.RepoPath(), err)
return 0, err
}
defer gitRepo.Close()
return SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doerID)
}
func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) {
allBranches := container.Set[string]{}
{
branches, _, err := gitRepo.GetBranchNames(0, 0)
if err != nil {
return 0, err
}
log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches)
for _, branch := range branches {
allBranches.Add(branch)
}
}
dbBranches := make(map[string]*git_model.Branch)
{
branches, err := git_model.FindBranches(ctx, git_model.FindBranchOptions{
ListOptions: db.ListOptions{
ListAll: true,
},
RepoID: repo.ID,
})
if err != nil {
return 0, err
}
for _, branch := range branches {
dbBranches[branch.Name] = branch
}
}
var toAdd []*git_model.Branch
var toUpdate []*git_model.Branch
var toRemove []int64
for branch := range allBranches {
dbb := dbBranches[branch]
commit, err := gitRepo.GetBranchCommit(branch)
if err != nil {
return 0, err
}
if dbb == nil {
toAdd = append(toAdd, &git_model.Branch{
RepoID: repo.ID,
Name: branch,
CommitID: commit.ID.String(),
CommitMessage: commit.CommitMessage,
PusherID: doerID,
CommitTime: timeutil.TimeStamp(commit.Author.When.Unix()),
})
} else if commit.ID.String() != dbb.CommitID {
toUpdate = append(toUpdate, &git_model.Branch{
ID: dbb.ID,
RepoID: repo.ID,
Name: branch,
CommitID: commit.ID.String(),
CommitMessage: commit.CommitMessage,
PusherID: doerID,
CommitTime: timeutil.TimeStamp(commit.Author.When.Unix()),
})
}
}
for _, dbBranch := range dbBranches {
if !allBranches.Contains(dbBranch.Name) && !dbBranch.IsDeleted {
toRemove = append(toRemove, dbBranch.ID)
}
}
log.Trace("SyncRepoBranches[%s]: toAdd: %v, toUpdate: %v, toRemove: %v", repo.FullName(), toAdd, toUpdate, toRemove)
if len(toAdd) == 0 && len(toRemove) == 0 && len(toUpdate) == 0 {
return int64(len(allBranches)), nil
}
if err := db.WithTx(ctx, func(subCtx context.Context) error {
if len(toAdd) > 0 {
if err := git_model.AddBranches(subCtx, toAdd); err != nil {
return err
}
}
for _, b := range toUpdate {
if _, err := db.GetEngine(subCtx).ID(b.ID).
Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted").
Update(b); err != nil {
return err
}
}
if len(toRemove) > 0 {
if err := git_model.DeleteBranches(subCtx, repo.ID, doerID, toRemove); err != nil {
return err
}
}
return nil
}); err != nil {
return 0, err
}
return int64(len(allBranches)), nil
}

View File

@ -330,7 +330,7 @@ func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error {
return fmt.Errorf("updateSize: GetLFSMetaObjects: %w", err)
}
return repo_model.UpdateRepoSize(ctx, repo.ID, size+lfsSize)
return repo_model.UpdateRepoSize(ctx, repo.ID, size, lfsSize)
}
// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon...

View File

@ -351,6 +351,12 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err)
}
if !repo.IsEmpty {
if _, err := SyncRepoBranches(ctx, repo.ID, u.ID); err != nil {
return fmt.Errorf("SyncRepoBranches: %w", err)
}
}
}
if err = UpdateRepository(ctx, repo, false); err != nil {

View File

@ -151,6 +151,10 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
}
}
if _, err := SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
}
if !opts.Releases {
// note: this will greatly improve release (tag) sync
// for pull-mirrors with many tags
@ -169,7 +173,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
}
}
ctx, committer, err := db.TxContext(db.DefaultContext)
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}

View File

@ -244,7 +244,7 @@ func initLoggerByName(manager *log.LoggerManager, rootCfg ConfigProvider, logger
eventWriters = append(eventWriters, eventWriter)
}
manager.GetLogger(loggerName).RemoveAllWriters().AddWriters(eventWriters...)
manager.GetLogger(loggerName).ReplaceAllWriters(eventWriters...)
}
func InitSQLLoggersForCli(level log.Level) {

View File

@ -116,6 +116,10 @@ func loadOAuth2From(rootCfg ConfigProvider) {
return
}
if !OAuth2.Enable {
return
}
OAuth2.JWTSecretBase64 = loadSecret(rootCfg.Section("oauth2"), "JWT_SECRET_URI", "JWT_SECRET")
if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {

View File

@ -380,3 +380,9 @@ type NewIssuePinsAllowed struct {
Issues bool `json:"issues"`
PullRequests bool `json:"pull_requests"`
}
// UpdateRepoAvatarUserOption options when updating the repo avatar
type UpdateRepoAvatarOption struct {
// image must be base64 encoded
Image string `json:"image" binding:"Required"`
}

View File

@ -102,3 +102,9 @@ type RenameUserOption struct {
// unique: true
NewName string `json:"new_username" binding:"Required"`
}
// UpdateUserAvatarUserOption options when updating the user avatar
type UpdateUserAvatarOption struct {
// image must be base64 encoded
Image string `json:"image" binding:"Required"`
}

View File

@ -129,6 +129,7 @@ concept_user_organization = Organization
show_timestamps = Show timestamps
show_log_seconds = Show seconds
show_full_screen = Show full screen
download_logs = Download logs
confirm_delete_selected = Confirm to delete all selected items?
@ -2659,6 +2660,7 @@ dashboard.delete_repo_archives.started = Delete all repository archives task sta
dashboard.delete_missing_repos = Delete all repositories missing their Git files
dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started.
dashboard.delete_generated_repository_avatars = Delete generated repository avatars
dashboard.sync_repo_branches = Sync missed branches from git data to databases
dashboard.update_mirrors = Update Mirrors
dashboard.repo_health_check = Health check all repositories
dashboard.check_repo_stats = Check all repository statistics
@ -2712,6 +2714,7 @@ dashboard.gc_lfs = Garbage collect LFS meta objects
dashboard.stop_zombie_tasks = Stop zombie tasks
dashboard.stop_endless_tasks = Stop endless tasks
dashboard.cancel_abandoned_jobs = Cancel abandoned jobs
dashboard.sync_branch.started = Branches Sync started
users.user_manage_panel = User Account Management
users.new_account = Create User Account
@ -2797,6 +2800,7 @@ repos.stars = Stars
repos.forks = Forks
repos.issues = Issues
repos.size = Size
repos.lfs_size = LFS Size
packages.package_manage_panel = Package Management
packages.total_size = Total Size: %s

View File

@ -3122,7 +3122,7 @@ notices.delete_success=システム通知を削除しました。
[action]
create_repo=がリポジトリ <a href="%s">%s</a> を作成しました
rename_repo=がリポジトリ名を <code>%[1]s</code> から <a href="%[2]s">[3]s</a> へ変更しました
rename_repo=がリポジトリ名を <code>%[1]s</code> から <a href="%[2]s">%[3]s</a> へ変更しました
commit_repo=が <a href="%[1]s">%[4]s</a> の <a href="%[2]s">%[3]s</a> にプッシュしました
create_issue=`がイシュー <a href="%[1]s">%[3]s#%[2]s</a> をオープンしました`
close_issue=`がイシュー <a href="%[1]s">%[3]s#%[2]s</a> をクローズしました`

View File

@ -79,6 +79,8 @@ milestones=Marcos
ok=Ok
cancel=Cancelar
rerun=Reexecutar
rerun_all=Reexecutar todas as tarefas
save=Salvar
add=Adicionar
add_all=Adicionar todos
@ -113,11 +115,18 @@ unknown=Desconhecido
rss_feed=Feed RSS
pin=Fixar
unpin=Desfixar
artifacts=Artefatos
concept_system_global=Global
concept_user_individual=Individual
concept_code_repository=Repositório
concept_user_organization=Organização
show_log_seconds=Mostrar segundos
show_full_screen=Mostrar tela cheia
[aria]
navbar=Barra de navegação
@ -142,6 +151,7 @@ buttons.list.unordered.tooltip=Adicionar uma lista com marcadores
buttons.list.ordered.tooltip=Adicionar uma lista numerada
buttons.list.task.tooltip=Adicionar uma lista de tarefas
buttons.mention.tooltip=Mencionar um usuário ou equipe
buttons.ref.tooltip=Referenciar um issue ou um pull request
buttons.switch_to_legacy.tooltip=Em vez disso, usar o editor legado
buttons.enable_monospace_font=Habilitar fonte mono espaçada
buttons.disable_monospace_font=Desabilitar fonte mono espaçada
@ -247,6 +257,7 @@ openid_signup_popup=Habilitar o auto-cadastro com base no OpenID.
enable_captcha=Habilitar CAPTCHA ao registrar
enable_captcha_popup=Obrigar validação por CAPTCHA para auto-cadastro de usuários.
require_sign_in_view=Exigir acesso do usuário para a visualização de páginas
require_sign_in_view_popup=Limitar o acesso de página aos usuários autenticados. Os visitantes só verão as páginas de autenticação e cadastro.
admin_setting_desc=Criar uma conta de administrador é opcional. O primeiro usuário cadastrado automaticamente se tornará um administrador.
admin_title=Configurações da conta de administrador
admin_name=Nome do usuário administrador
@ -312,6 +323,7 @@ repos=Repositórios
users=Usuários
organizations=Organizações
search=Pesquisar
go_to=Ir para
code=Código
search.type.tooltip=Tipo de pesquisa
search.fuzzy=Similar
@ -467,6 +479,7 @@ team_invite.text_3=Nota: este convite foi destinado a %[1]s. Se você não estav
[modal]
yes=Sim
no=Não
confirm=Confirmar
cancel=Cancelar
modify=Atualizar
@ -514,6 +527,7 @@ lang_select_error=Selecione um idioma da lista.
username_been_taken=O nome de usuário já está sendo usado.
username_change_not_local_user=Usuários não-locais não são autorizados a alterar nome de usuário.
username_has_not_been_changed=Nome de usuário não foi alterado
repo_name_been_taken=O nome de repositório já está sendo usado.
repository_force_private=Forçar Privado está ativado: repositórios privados não podem ser tornados públicos.
repository_files_already_exist=Arquivos já existem neste repositório. Contate o administrador.
@ -555,11 +569,14 @@ auth_failed=Autenticação falhou: %v
still_own_repo=Sua conta possui um ou mais repositórios, exclua ou transfira-os primeiro.
still_has_org=Sua conta é um membro de uma ou mais organizações, deixe-as primeiro.
still_own_packages=Sua conta possui um ou mais pacotes, exclua-os primeiro.
org_still_own_repo=Esta organização ainda possui repositórios, exclua ou transfira-os primeiro.
org_still_own_packages=Esta organização ainda possui pacotes, exclua-os primeiro.
target_branch_not_exist=O branch de destino não existe.
[user]
change_avatar=Altere seu avatar...
joined_on=Inscreveu-se em %s
repositories=Repositórios
activity=Atividade pública
followers=Seguidores
@ -684,10 +701,12 @@ add_new_email=Adicionar novo endereço de e-mail
add_new_openid=Adicionar novo URI OpenID
add_email=Adicionar novo endereço de e-mail
add_openid=Adicionar URI OpenID
add_email_confirmation_sent=Um e-mail de confirmação foi enviado para "%s". Verifique sua caixa de entrada nos próximos %s para confirmar seu endereço de e-mail.
add_email_success=O novo endereço de e-mail foi adicionado.
email_preference_set_success=Preferência de e-mail definida com sucesso.
add_openid_success=O novo endereço de OpenID foi adicionado.
keep_email_private=Ocultar endereço de e-mail
keep_email_private_popup=Seu endereço de e-mail ficará visível apenas para você e para os administradores
openid_desc=OpenID permite delegar autenticação para um provedor externo.
manage_ssh_keys=Gerenciar Chaves SSH
@ -721,6 +740,7 @@ gpg_token_help=Você pode gerar uma assinatura usando:
gpg_token_code=echo "%s" | gpg -a --default-key %s --detach-sig
gpg_token_signature=Assinatura GPG blindada
key_signature_gpg_placeholder=Começa com '-----BEGIN PGP SIGNATURE-----'
verify_gpg_key_success=A chave GPG "%s" foi validada.
ssh_key_verified=Chave validada
ssh_key_verified_long=A chave foi validada com um token e pode ser usada para validar commits que correspondam a qualquer dos endereços de e-mail ativados deste usuário.
ssh_key_verify=Validar
@ -730,11 +750,14 @@ ssh_token=Token
ssh_token_help=Você pode gerar uma assinatura usando:
ssh_token_signature=Assinatura SSH blindada
key_signature_ssh_placeholder=Começa com '-----BEGIN SSH SIGNATURE-----'
verify_ssh_key_success=A chave SSH "%s" foi validada.
subkeys=Subchaves
key_id=ID da chave
key_name=Nome da Chave
key_content=Conteúdo
principal_content=Conteúdo
add_key_success=A chave SSH "%s" foi adicionada.
add_gpg_key_success=A chave GPG "%s" foi adicionada.
delete_key=Remover
ssh_key_deletion=Remover a chave SSH
gpg_key_deletion=Remover a chave GPG
@ -745,6 +768,8 @@ ssh_principal_deletion_desc=A exclusão de um Nome Principal de um Certificado S
ssh_key_deletion_success=A chave SSH foi removida.
gpg_key_deletion_success=A chave GPG foi removida.
ssh_principal_deletion_success=O nome principal foi removido.
added_on=Adicionado em %s
valid_until_date=Válido até %s
valid_forever=Válido para sempre
last_used=Última vez usado em
no_activity=Nenhuma atividade recente
@ -756,6 +781,7 @@ principal_state_desc=Este nome principal foi utilizado nos últimos 7 dias
show_openid=Mostrar no perfil
hide_openid=Ocultar no perfil
ssh_disabled=SSH desabilitado
ssh_signonly=O SSH está desativado no momento, portanto, essas chaves são usadas apenas para verificação de assinatura de confirmação.
ssh_externally_managed=Esta chave SSH para este usuário é gerenciada externamente
manage_social=Gerenciar contas sociais associadas
social_desc=Essas contas sociais estão vinculadas à sua conta do Gitea. Certifique-se de reconhecer todas elas, pois elas podem ser usadas para acessar a sua conta do Gitea.
@ -775,6 +801,11 @@ access_token_deletion_cancel_action=Cancelar
access_token_deletion_confirm_action=Excluir
access_token_deletion_desc=A exclusão de um token revoga o acesso à sua conta para aplicativos que o usam. Continuar?
delete_token_success=O token foi excluído. Os aplicativos que o utilizam já não têm acesso à sua conta.
repo_and_org_access=Acesso ao Repositório e Organização
permissions_public_only=Apenas público
permissions_access_all=Todos (público, privado e limitado)
select_permissions=Selecionar permissões
permissions_list=Permissões:
manage_oauth2_applications=Gerenciar aplicativos OAuth2
edit_oauth2_application=Editar aplicativo OAuth2
@ -859,6 +890,7 @@ visibility=Visibilidade do usuário
visibility.public=Pública
visibility.public_tooltip=Visível para todos
visibility.limited=Limitada
visibility.limited_tooltip=Visível apenas para usuários autenticados
visibility.private=Privada
visibility.private_tooltip=Visível apenas para membros da organização
@ -1012,6 +1044,7 @@ migrated_from_fake=Migrado de %[1]s
migrate.migrate=Migrar de %s
migrate.migrating=Migrando a partir de <b>%s</b> ...
migrate.migrating_failed=Migração a partir de <b>%s</b> falhou.
migrate.migrating_failed.error=Falha ao migrar: %s
migrate.migrating_failed_no_addr=A migração falhou.
migrate.github.description=Migrar dados de github.com ou de outras instâncias do GitHub.
migrate.git.description=Migrar um repositório somente de qualquer serviço Git.
@ -1028,6 +1061,8 @@ migrate.migrating_labels=Migrando Rótulos
migrate.migrating_releases=Migrando Versões
migrate.migrating_issues=Migrando Issues
migrate.migrating_pulls=Migrando Pull Requests
migrate.cancel_migrating_title=Cancelar migração
migrate.cancel_migrating_confirm=Você quer cancelar essa migração?
mirror_from=espelhamento de
forked_from=feito fork de
@ -1112,6 +1147,7 @@ download_file=Baixar arquivo
normal_view=Visão normal
line=linha
lines=linhas
from_comment=(comentário)
editor.add_file=Adicionar Arquivo
editor.new_file=Novo arquivo
@ -1133,6 +1169,9 @@ editor.cancel_lower=Cancelar
editor.commit_signed_changes=Commit de alteradores assinadas
editor.commit_changes=Aplicar commit das alterações
editor.add_tmpl=Adicionar '<filename>'
editor.add=Adicionar %s
editor.update=Atualizar %s
editor.delete=Excluir %s
editor.patch=Aplicar Correção
editor.patching=Corrigindo:
editor.new_patch=Nova correção
@ -1295,6 +1334,10 @@ issues.filter_label_exclude=`Use <code>alt</code> + <code>clique/enter</code> pa
issues.filter_label_no_select=Todas as etiquetas
issues.filter_label_select_no_label=Sem etiqueta
issues.filter_milestone=Marco
issues.filter_milestone_all=Todos os marcos
issues.filter_milestone_none=Sem marcos
issues.filter_milestone_open=Marcos abertos
issues.filter_milestone_closed=Marcos fechados
issues.filter_project=Projeto
issues.filter_project_all=Todos os projetos
issues.filter_project_none=Sem projeto
@ -1353,6 +1396,7 @@ issues.context.reference_issue=Referência em uma nova issue
issues.context.edit=Editar
issues.context.delete=Excluir
issues.no_content=Ainda não há conteúdo.
issues.close=Fechar issue
issues.close_comment_issue=Comentar e fechar
issues.reopen_issue=Reabrir
issues.reopen_comment_issue=Comentar e reabrir
@ -1403,6 +1447,7 @@ issues.attachment.open_tab=`Clique para ver "%s" em uma nova aba`
issues.attachment.download=`Clique para baixar "%s"`
issues.subscribe=Inscrever-se
issues.unsubscribe=Desinscrever
issues.unpin_issue=Desfixar issue
issues.lock=Bloquear conversação
issues.unlock=Desbloquear conversação
issues.lock.unknown_reason=Não pode-se bloquear uma issue com um motivo desconhecido.
@ -1630,6 +1675,7 @@ pulls.update_branch_rebase=Atualizar branch por rebase
pulls.update_branch_success=Atualização do branch foi bem-sucedida
pulls.update_not_allowed=Você não tem permissão para atualizar o branch
pulls.outdated_with_base_branch=Este branch está desatualizado com o branch base
pulls.close=Fechar pull request
pulls.closed_at=`fechou este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.reopened_at=`reabriu este pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
pulls.merge_instruction_hint=`Você também pode ver as <a class="show-instruction">instruções para a linha de comandos</a>.`
@ -1666,10 +1712,12 @@ milestones.desc=Descrição
milestones.due_date=Data limite (opcional)
milestones.clear=Limpar
milestones.invalid_due_date_format=Formato da data limite deve ser 'dd/mm/aaaa'.
milestones.create_success=O marco "%s" foi criado.
milestones.edit=Editar marco
milestones.edit_subheader=Marcos organizam as issues e acompanham o progresso.
milestones.cancel=Cancelar
milestones.modify=Atualizar marco
milestones.edit_success=O marco "%s" foi atualizado.
milestones.deletion=Excluir marco
milestones.deletion_desc=A exclusão deste marco irá removê-lo de todas as issues. Tem certeza que deseja continuar?
milestones.deletion_success=O marco foi excluído.
@ -2100,8 +2148,11 @@ settings.dismiss_stale_approvals_desc=Quando novos commits que mudam o conteúdo
settings.require_signed_commits=Exibir commits assinados
settings.require_signed_commits_desc=Rejeitar pushes para este branch se não estiverem assinados ou não forem validáveis.
settings.protect_branch_name_pattern=Padrão de Nome de Branch Protegida
settings.protect_protected_file_patterns=Padrões de arquivos protegidos (separados usando ponto e vírgula ';'):
settings.add_protected_branch=Habilitar proteção
settings.delete_protected_branch=Desabilitar proteção
settings.remove_protected_branch_success=Proteção do branch "%s" foi desabilitada.
settings.remove_protected_branch_failed=Removendo regra de proteção de branch "%s" falhou.
settings.protected_branch_deletion=Desabilitar proteção de branch
settings.protected_branch_deletion_desc=Desabilitar a proteção de branch permite que os usuários com permissão de escrita realizem push. Continuar?
settings.block_rejected_reviews=Bloquear merge em revisões rejeitadas
@ -2224,7 +2275,9 @@ diff.review.header=Enviar revisão
diff.review.placeholder=Comentário da revisão
diff.review.comment=Comentar
diff.review.approve=Aprovar
diff.review.self_reject=Os autores do pull request não podem solicitar alterações em seus próprios pull request
diff.review.reject=Solicitar alterações
diff.review.self_approve=Os autores do pull request não podem aprovar seu próprio pull request
diff.committed_by=commit de
diff.protected=Protegido
diff.image.side_by_side=Lado a Lado

186
package-lock.json generated
View File

@ -18,7 +18,7 @@
"@webcomponents/custom-elements": "1.6.0",
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "5.2.1",
"asciinema-player": "3.4.0",
"asciinema-player": "3.5.0",
"clippie": "4.0.1",
"css-loader": "6.8.1",
"dropzone": "6.0.0-beta.2",
@ -28,26 +28,27 @@
"fast-glob": "3.2.12",
"jquery": "3.7.0",
"jquery.are-you-sure": "1.9.0",
"katex": "0.16.7",
"katex": "0.16.8",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "10.2.3",
"mini-css-extract-plugin": "2.7.6",
"minimatch": "9.0.1",
"minimatch": "9.0.2",
"monaco-editor": "0.39.0",
"monaco-editor-webpack-plugin": "7.0.1",
"pdfobject": "2.2.12",
"pretty-ms": "8.0.0",
"sortablejs": "1.15.0",
"swagger-ui-dist": "5.0.0",
"swagger-ui-dist": "5.1.0",
"throttle-debounce": "5.0.0",
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
"vue": "3.3.4",
"vue-bar-graph": "2.0.0",
"vue-loader": "17.2.2",
"vue3-calendar-heatmap": "2.0.5",
"webpack": "5.87.0",
"webpack": "5.88.0",
"webpack-cli": "5.1.4",
"wrap-ansi": "8.1.0"
},
@ -67,17 +68,17 @@
"eslint-plugin-regexp": "1.15.0",
"eslint-plugin-sonarjs": "0.19.0",
"eslint-plugin-unicorn": "47.0.0",
"eslint-plugin-vue": "9.14.1",
"eslint-plugin-vue": "9.15.1",
"eslint-plugin-wc": "1.5.0",
"jsdom": "22.1.0",
"markdownlint-cli": "0.35.0",
"postcss-html": "1.5.0",
"stylelint": "15.8.0",
"stylelint": "15.9.0",
"stylelint-declaration-block-no-ignored-properties": "2.7.0",
"stylelint-declaration-strict-value": "1.9.2",
"stylelint-stylistic": "0.4.2",
"svgo": "3.0.2",
"updates": "14.2.4",
"updates": "14.2.8",
"vitest": "0.32.2"
},
"engines": {
@ -402,9 +403,9 @@
}
},
"node_modules/@csstools/media-query-list-parser": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.0.tgz",
"integrity": "sha512-MXkR+TeaS2q9IkpyO6jVCdtA/bfpABJxIrfkLswThFN8EZZgI2RfAHhm6sDNDuYV25d5+b8Lj1fpTccIcSLPsQ==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.1.tgz",
"integrity": "sha512-pUjtFbaKbiFNjJo8pprrIaXLvQvWIlwPiFnRI4sEnc4F0NIGTOsw8kaJSR3CmZAKEvV8QYckovgAnWQC0bgLLQ==",
"dev": true,
"funding": [
{
@ -420,7 +421,7 @@
"node": "^14 || ^16 || >=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^2.1.1",
"@csstools/css-parser-algorithms": "^2.2.0",
"@csstools/css-tokenizer": "^2.1.1"
}
},
@ -1858,9 +1859,9 @@
"integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
},
"node_modules/@types/node": {
"version": "20.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz",
"integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg=="
"version": "20.3.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz",
"integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw=="
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.1",
@ -2604,9 +2605,9 @@
}
},
"node_modules/asciinema-player": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.4.0.tgz",
"integrity": "sha512-dX6jt5S3K6daItsVWzyY9mRDK+ivC2QgqCxFkdSiNslo0vY/ZqA4upcTzqIKZqBtxppovOZk44ltg9VnHG9QVg==",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/asciinema-player/-/asciinema-player-3.5.0.tgz",
"integrity": "sha512-o4B2AscBuCZo4+JB9TBGrfZ7GQL99wsbm08WwmuNJTPd1lyLQJq8wgacnBsdvb2sC0K875ScYr8T5XmfeH/6dg==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"solid-js": "^1.3.0"
@ -2876,9 +2877,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001504",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz",
"integrity": "sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==",
"version": "1.0.30001508",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001508.tgz",
"integrity": "sha512-sdQZOJdmt3GJs1UMNpCCCyeuS2IEGLXnHyAo9yIO5JJDjbjoVRij4M1qep6P6gFpptD1PqIYgzM+gwJbOi92mw==",
"funding": [
{
"type": "opencollective",
@ -4161,9 +4162,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.433",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz",
"integrity": "sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ=="
"version": "1.4.441",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.441.tgz",
"integrity": "sha512-LlCgQ8zgYZPymf5H4aE9itwiIWH4YlCiv1HFLmmcBeFYi5E+3eaIFnjHzYtcFQbaKfAW+CqZ9pgxo33DZuoqPg=="
},
"node_modules/elkjs": {
"version": "0.8.2",
@ -4204,10 +4205,22 @@
"node": ">=10.13.0"
}
},
"node_modules/entities": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/envinfo": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz",
"integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==",
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz",
"integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==",
"bin": {
"envinfo": "dist/cli.js"
},
@ -4806,9 +4819,9 @@
}
},
"node_modules/eslint-plugin-vue": {
"version": "9.14.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.1.tgz",
"integrity": "sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==",
"version": "9.15.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz",
"integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.3.0",
@ -5364,9 +5377,9 @@
}
},
"node_modules/get-tsconfig": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.0.tgz",
"integrity": "sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==",
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz",
"integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
@ -6576,9 +6589,9 @@
"integrity": "sha512-b+z6yF1d4EOyDgylzQo5IminlUmzSeqR1hs/bzjBNjuGras4FXq/6TrzjxfN0j+TmI0ltJzTNlqXUMCniciwKQ=="
},
"node_modules/katex": {
"version": "0.16.7",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.7.tgz",
"integrity": "sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA==",
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.8.tgz",
"integrity": "sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
@ -6881,18 +6894,6 @@
"markdown-it": "bin/markdown-it.js"
}
},
"node_modules/markdown-it/node_modules/entities": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
"integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
"dev": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/markdownlint": {
"version": "0.29.0",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.29.0.tgz",
@ -7655,9 +7656,9 @@
}
},
"node_modules/minimatch": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
"integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz",
"integrity": "sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
@ -7701,13 +7702,13 @@
}
},
"node_modules/mlly": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.3.0.tgz",
"integrity": "sha512-HT5mcgIQKkOrZecOjOX3DJorTikWXwsBfpcr/MGBkhfWcjiqvnaL/9ppxvIUXfjT6xt4DVIAsN9fMUz1ev4bIw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz",
"integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==",
"dev": true,
"dependencies": {
"acorn": "^8.8.2",
"pathe": "^1.1.0",
"acorn": "^8.9.0",
"pathe": "^1.1.1",
"pkg-types": "^1.0.3",
"ufo": "^1.1.2"
}
@ -9062,14 +9063,14 @@
"dev": true
},
"node_modules/run-con": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.11.tgz",
"integrity": "sha512-NEMGsUT+cglWkzEr4IFK21P4Jca45HqiAbIIZIBdX5+UZTB24Mb/21iNGgz9xZa8tL6vbW7CXmq7MFN42+VjNQ==",
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/run-con/-/run-con-1.2.12.tgz",
"integrity": "sha512-5257ILMYIF4RztL9uoZ7V9Q97zHtNHn5bN3NobeAnzB1P3ASLgg8qocM2u+R18ttp+VEM78N2LK8XcNVtnSRrg==",
"dev": true,
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~3.0.0",
"minimist": "^1.2.6",
"minimist": "^1.2.8",
"strip-json-comments": "~3.1.1"
},
"bin": {
@ -9209,9 +9210,9 @@
}
},
"node_modules/semver": {
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz",
"integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==",
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@ -9414,9 +9415,9 @@
"dev": true
},
"node_modules/solid-js": {
"version": "1.7.6",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.6.tgz",
"integrity": "sha512-DXVOTjUh/bIAhE0fIqu3ezGLyQaez7v8EOw3uPLIi87DmLjg+hsuCAgKyNIZ+o4jUetOk3ZORccvJmE1yZUk8g==",
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.7.tgz",
"integrity": "sha512-SPdYVke/Z6Za24PBTbULyQYPrhGO1ZbPany76atO2zF2dmYn2pCotbsw1JtlgWnr9dK2JbwPGnA3ODTGPLhZNw==",
"dependencies": {
"csstype": "^3.1.0",
"seroval": "^0.5.0"
@ -9712,9 +9713,9 @@
"dev": true
},
"node_modules/stylelint": {
"version": "15.8.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.8.0.tgz",
"integrity": "sha512-x9qBk84F3MEjMEUNCE7MtWmfj9G9y5XzJ0cpQeJdy2l/IoqjC8Ih0N0ytmOTnXE4Yv0J7I1cmVRQUVNSPCxTsA==",
"version": "15.9.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.9.0.tgz",
"integrity": "sha512-sXtAZi64CllWr6A+8ymDWnlIaYwuAa7XRmGnJxLQXFNnLjd3Izm4HAD+loKVaZ7cpK6SLxhAUX1lwPJKGCn0mg==",
"dev": true,
"dependencies": {
"@csstools/css-parser-algorithms": "^2.2.0",
@ -9826,9 +9827,9 @@
}
},
"node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz",
"integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ=="
},
"node_modules/superstruct": {
"version": "0.10.13",
@ -9910,9 +9911,9 @@
}
},
"node_modules/swagger-ui-dist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.0.0.tgz",
"integrity": "sha512-bwl6og9I9CAHKGSnYLKydjhBuH7d3oU6RX6uKN8oDCkLusTHXOW3sZMyBWjRtjGFnCMmN085oZoaR/4Wm9nIaQ=="
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.1.0.tgz",
"integrity": "sha512-c1KmAjuVODxw+vwkNLALQZrgdlBAuBbr2xSPfYrJgseEi7gFKcTvShysPmyuDI4kcUa1+5rFpjWvXdusKY74mg=="
},
"node_modules/symbol-tree": {
"version": "3.2.4",
@ -9957,9 +9958,9 @@
}
},
"node_modules/terser": {
"version": "5.18.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz",
"integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==",
"version": "5.18.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.18.2.tgz",
"integrity": "sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@ -10122,6 +10123,11 @@
"node": ">=8.0"
}
},
"node_modules/toastify-js": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz",
"integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ=="
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -10214,9 +10220,9 @@
}
},
"node_modules/tslib": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
"integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==",
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==",
"dev": true
},
"node_modules/type-check": {
@ -10363,9 +10369,9 @@
}
},
"node_modules/updates": {
"version": "14.2.4",
"resolved": "https://registry.npmjs.org/updates/-/updates-14.2.4.tgz",
"integrity": "sha512-r54h4Q12lUAmQ9dENy7BnY22AnTfW4YGEZw73gv6RvNEWgcZ3qS88jPLc1ckPAzt/8TPKWwLkSVpbEpgGwglJw==",
"version": "14.2.8",
"resolved": "https://registry.npmjs.org/updates/-/updates-14.2.8.tgz",
"integrity": "sha512-Ca+M1vKKBBRiQSi3yrN8OdncmP9osIf1oJM/HpEIHeDvyGLs/noTi9X2LS4zl50VXRTSCqssF5CZN0XWzSPigg==",
"dev": true,
"bin": {
"updates": "bin/updates.js"
@ -10528,9 +10534,9 @@
}
},
"node_modules/vite/node_modules/rollup": {
"version": "3.25.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
"integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
"version": "3.25.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.3.tgz",
"integrity": "sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@ -10766,9 +10772,9 @@
}
},
"node_modules/webpack": {
"version": "5.87.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.87.0.tgz",
"integrity": "sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==",
"version": "5.88.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.0.tgz",
"integrity": "sha512-O3jDhG5e44qIBSi/P6KpcCcH7HD+nYIHVBhdWFxcLOcIGN8zGo5nqF3BjyNCxIh4p1vFdNnreZv2h2KkoAw3lw==",
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",

View File

@ -17,7 +17,7 @@
"@webcomponents/custom-elements": "1.6.0",
"add-asset-webpack-plugin": "2.0.1",
"ansi_up": "5.2.1",
"asciinema-player": "3.4.0",
"asciinema-player": "3.5.0",
"clippie": "4.0.1",
"css-loader": "6.8.1",
"dropzone": "6.0.0-beta.2",
@ -27,26 +27,27 @@
"fast-glob": "3.2.12",
"jquery": "3.7.0",
"jquery.are-you-sure": "1.9.0",
"katex": "0.16.7",
"katex": "0.16.8",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "10.2.3",
"mini-css-extract-plugin": "2.7.6",
"minimatch": "9.0.1",
"minimatch": "9.0.2",
"monaco-editor": "0.39.0",
"monaco-editor-webpack-plugin": "7.0.1",
"pdfobject": "2.2.12",
"pretty-ms": "8.0.0",
"sortablejs": "1.15.0",
"swagger-ui-dist": "5.0.0",
"swagger-ui-dist": "5.1.0",
"throttle-debounce": "5.0.0",
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
"vue": "3.3.4",
"vue-bar-graph": "2.0.0",
"vue-loader": "17.2.2",
"vue3-calendar-heatmap": "2.0.5",
"webpack": "5.87.0",
"webpack": "5.88.0",
"webpack-cli": "5.1.4",
"wrap-ansi": "8.1.0"
},
@ -66,17 +67,17 @@
"eslint-plugin-regexp": "1.15.0",
"eslint-plugin-sonarjs": "0.19.0",
"eslint-plugin-unicorn": "47.0.0",
"eslint-plugin-vue": "9.14.1",
"eslint-plugin-vue": "9.15.1",
"eslint-plugin-wc": "1.5.0",
"jsdom": "22.1.0",
"markdownlint-cli": "0.35.0",
"postcss-html": "1.5.0",
"stylelint": "15.8.0",
"stylelint": "15.9.0",
"stylelint-declaration-block-no-ignored-properties": "2.7.0",
"stylelint-declaration-strict-value": "1.9.2",
"stylelint-stylistic": "0.4.2",
"svgo": "3.0.2",
"updates": "14.2.4",
"updates": "14.2.8",
"vitest": "0.32.2"
},
"browserslist": [

8
poetry.lock generated
View File

@ -42,13 +42,13 @@ six = ">=1.13.0"
[[package]]
name = "djlint"
version = "1.31.0"
version = "1.31.1"
description = "HTML Template Linter and Formatter"
optional = false
python-versions = ">=3.8.0,<4.0.0"
files = [
{file = "djlint-1.31.0-py3-none-any.whl", hash = "sha256:2b9200c67103b79835b7547ff732e910888d1f0ef684f5b329eb64b14d09c046"},
{file = "djlint-1.31.0.tar.gz", hash = "sha256:8acb4b751b429c5aabb1aef5b6007bdf53224eceda25c5fbe04c42cc57c0a7ba"},
{file = "djlint-1.31.1-py3-none-any.whl", hash = "sha256:9b2e2fc3a059a8e5a62f309edea15c1aeee331a279ab2699b9fb51a31d8c0934"},
{file = "djlint-1.31.1.tar.gz", hash = "sha256:a11739e2f919f760b3986eb13d06e00171f3bd342b8d88e9bd914a4260eaa8ce"},
]
[package.dependencies]
@ -328,4 +328,4 @@ telegram = ["requests"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "22c4af11eadd8784b613951d6160d67be0f33500238a450741c3d75beb218dad"
content-hash = "f03ad8e7c4f6e797ac3c04630db8cc16438cd59642653c26fd401633cd62d696"

View File

@ -8,7 +8,7 @@ authors = []
python = "^3.8"
[tool.poetry.group.dev.dependencies]
djlint = "1.31.0"
djlint = "1.31.1"
[tool.djlint]
profile="golang"

View File

@ -777,11 +777,11 @@ func Routes() *web.Route {
m.Group("/notifications", func() {
m.Combo("").
Get(notify.ListNotifications).
Put(notify.ReadNotifications, reqToken())
Put(reqToken(), notify.ReadNotifications)
m.Get("/new", notify.NewAvailable)
m.Combo("/threads/{id}").
Get(notify.GetThread).
Patch(notify.ReadThread, reqToken())
Patch(reqToken(), notify.ReadThread)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
// Users (requires user scope)
@ -899,6 +899,11 @@ func Routes() *web.Route {
Patch(bind(api.EditHookOption{}), user.EditHook).
Delete(user.DeleteHook)
}, reqWebhooksEnabled())
m.Group("/avatar", func() {
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
m.Delete("", user.DeleteAvatar)
}, reqToken())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Repositories (requires repo scope, org scope)
@ -1134,6 +1139,10 @@ func Routes() *web.Route {
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed)
m.Group("/avatar", func() {
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
m.Delete("", repo.DeleteAvatar)
}, reqAdmin(), reqToken())
}, repoAssignment())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
@ -1314,6 +1323,10 @@ func Routes() *web.Route {
Patch(bind(api.EditHookOption{}), org.EditHook).
Delete(org.DeleteHook)
}, reqToken(), reqOrgOwnership(), reqWebhooksEnabled())
m.Group("/avatar", func() {
m.Post("", bind(api.UpdateUserAvatarOption{}), org.UpdateAvatar)
m.Delete("", org.DeleteAvatar)
}, reqToken(), reqOrgOwnership())
m.Get("/activities/feeds", org.ListOrgActivityFeeds)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
m.Group("/teams/{teamid}", func() {

View File

@ -183,7 +183,7 @@ func ReadRepoNotifications(ctx *context.APIContext) {
if len(qLastRead) > 0 {
tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
if err != nil {
ctx.InternalServerError(err)
ctx.Error(http.StatusBadRequest, "Parse", err)
return
}
if !tmpLastRead.IsZero() {

View File

@ -132,7 +132,7 @@ func ReadNotifications(ctx *context.APIContext) {
if len(qLastRead) > 0 {
tmpLastRead, err := time.Parse(time.RFC3339, qLastRead)
if err != nil {
ctx.InternalServerError(err)
ctx.Error(http.StatusBadRequest, "Parse", err)
return
}
if !tmpLastRead.IsZero() {

View File

@ -0,0 +1,74 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package org
import (
"encoding/base64"
"net/http"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
user_service "code.gitea.io/gitea/services/user"
)
// UpdateAvatarupdates the Avatar of an Organisation
func UpdateAvatar(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/avatar organization orgUpdateAvatar
// ---
// summary: Update Avatar
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateUserAvatarOption"
// responses:
// "204":
// "$ref": "#/responses/empty"
form := web.GetForm(ctx).(*api.UpdateUserAvatarOption)
content, err := base64.StdEncoding.DecodeString(form.Image)
if err != nil {
ctx.Error(http.StatusBadRequest, "DecodeImage", err)
return
}
err = user_service.UploadAvatar(ctx.Org.Organization.AsUser(), content)
if err != nil {
ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
}
ctx.Status(http.StatusNoContent)
}
// DeleteAvatar deletes the Avatar of an Organisation
func DeleteAvatar(ctx *context.APIContext) {
// swagger:operation DELETE /orgs/{org}/avatar organization orgDeleteAvatar
// ---
// summary: Delete Avatar
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
err := user_service.DeleteAvatar(ctx.Org.Organization.AsUser())
if err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
}
ctx.Status(http.StatusNoContent)
}

View File

@ -0,0 +1,84 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"encoding/base64"
"net/http"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
repo_service "code.gitea.io/gitea/services/repository"
)
// UpdateVatar updates the Avatar of an Repo
func UpdateAvatar(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/avatar repository repoUpdateAvatar
// ---
// summary: Update avatar
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateRepoAvatarOption"
// responses:
// "204":
// "$ref": "#/responses/empty"
form := web.GetForm(ctx).(*api.UpdateRepoAvatarOption)
content, err := base64.StdEncoding.DecodeString(form.Image)
if err != nil {
ctx.Error(http.StatusBadRequest, "DecodeImage", err)
return
}
err = repo_service.UploadAvatar(ctx, ctx.Repo.Repository, content)
if err != nil {
ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
}
ctx.Status(http.StatusNoContent)
}
// UpdateAvatar deletes the Avatar of an Repo
func DeleteAvatar(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/avatar repository repoDeleteAvatar
// ---
// summary: Delete avatar
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// responses:
// "204":
// "$ref": "#/responses/empty"
err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
}
ctx.Status(http.StatusNoContent)
}

View File

@ -15,7 +15,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/convert"
@ -76,7 +78,7 @@ func GetBranch(ctx *context.APIContext) {
return
}
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
@ -118,6 +120,37 @@ func DeleteBranch(ctx *context.APIContext) {
branchName := ctx.Params("*")
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusForbidden, "", "Git Repository is empty.")
return
}
// check whether branches of this repository has been synced
totalNumOfBranches, err := git_model.CountBranches(ctx, git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: util.OptionalBoolFalse,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
return
}
if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
_, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
ctx.ServerError("SyncRepoBranches", err)
return
}
}
if ctx.Repo.Repository.IsArchived {
ctx.Error(http.StatusForbidden, "IsArchived", fmt.Errorf("can not delete branch of an archived repository"))
return
}
if ctx.Repo.Repository.IsMirror {
ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository"))
return
}
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
switch {
case git.IsErrBranchNotExist(err):
@ -203,14 +236,14 @@ func CreateBranch(ctx *context.APIContext) {
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
if err != nil {
if models.IsErrBranchDoesNotExist(err) {
if git_model.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
}
if models.IsErrTagAlreadyExists(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
} else if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Error(http.StatusConflict, "", "The branch already exists.")
} else if models.IsErrBranchNameConflict(err) {
} else if git_model.IsErrBranchNameConflict(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
} else {
ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
@ -236,7 +269,7 @@ func CreateBranch(ctx *context.APIContext) {
return
}
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
@ -275,20 +308,38 @@ func ListBranches(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/BranchList"
var totalNumOfBranches int
var totalNumOfBranches int64
var apiBranches []*api.Branch
listOptions := utils.GetListOptions(ctx)
if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil {
branchOpts := git_model.FindBranchOptions{
ListOptions: listOptions,
RepoID: ctx.Repo.Repository.ID,
IsDeletedBranch: util.OptionalBoolFalse,
}
var err error
totalNumOfBranches, err = git_model.CountBranches(ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountBranches", err)
return
}
if totalNumOfBranches == 0 { // sync branches immediately because non-empty repository should have at least 1 branch
totalNumOfBranches, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
if err != nil {
ctx.ServerError("SyncRepoBranches", err)
return
}
}
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
return
}
skip, _ := listOptions.GetStartEnd()
branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
branches, err := git_model.FindBranches(ctx, branchOpts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return
@ -296,11 +347,11 @@ func ListBranches(ctx *context.APIContext) {
apiBranches = make([]*api.Branch, 0, len(branches))
for i := range branches {
c, err := branches[i].GetCommit()
c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
if err != nil {
// Skip if this branch doesn't exist anymore.
if git.IsErrNotExist(err) {
total--
totalNumOfBranches--
continue
}
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
@ -308,19 +359,17 @@ func ListBranches(ctx *context.APIContext) {
}
branchProtection := rules.GetFirstMatched(branches[i].Name)
apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i], c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
apiBranch, err := convert.ToBranch(ctx, ctx.Repo.Repository, branches[i].Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin())
if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return
}
apiBranches = append(apiBranches, apiBranch)
}
totalNumOfBranches = total
}
ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
ctx.SetTotalCountHeader(int64(totalNumOfBranches))
ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
ctx.SetTotalCountHeader(totalNumOfBranches)
ctx.JSON(http.StatusOK, apiBranches)
}
@ -580,7 +629,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
}()
}
// FIXME: since we only need to recheck files protected rules, we could improve this
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, ruleName)
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
return
@ -851,7 +900,7 @@ func EditBranchProtection(ctx *context.APIContext) {
}
// FIXME: since we only need to recheck files protected rules, we could improve this
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
return

View File

@ -687,12 +687,12 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
ctx.Error(http.StatusForbidden, "Access", err)
return
}
if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
return
}
if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) {
if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
return
}
@ -843,7 +843,7 @@ func DeleteFile(ctx *context.APIContext) {
if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
ctx.Error(http.StatusNotFound, "DeleteFile", err)
return
} else if models.IsErrBranchAlreadyExists(err) ||
} else if git_model.IsErrBranchAlreadyExists(err) ||
models.IsErrFilenameInvalid(err) ||
models.IsErrSHADoesNotMatch(err) ||
models.IsErrCommitIDDoesNotMatch(err) ||

View File

@ -258,7 +258,7 @@ func AddPushMirror(ctx *context.APIContext) {
// schema:
// "$ref": "#/definitions/CreatePushMirrorOption"
// responses:
// "201":
// "200":
// "$ref": "#/responses/PushMirror"
// "403":
// "$ref": "#/responses/forbidden"

View File

@ -8,6 +8,7 @@ import (
"time"
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
@ -91,12 +92,12 @@ func ApplyDiffPatch(ctx *context.APIContext) {
ctx.Error(http.StatusForbidden, "Access", err)
return
}
if models.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
return
}
if models.IsErrBranchDoesNotExist(err) || git.IsErrBranchNotExist(err) {
if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
return
}

View File

@ -181,4 +181,10 @@ type swaggerParameterBodies struct {
// in:body
CreatePushMirrorOption api.CreatePushMirrorOption
// in:body
UpdateUserAvatarOptions api.UpdateUserAvatarOption
// in:body
UpdateRepoAvatarOptions api.UpdateRepoAvatarOption
}

View File

@ -0,0 +1,63 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"encoding/base64"
"net/http"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
user_service "code.gitea.io/gitea/services/user"
)
// UpdateAvatar updates the Avatar of an User
func UpdateAvatar(ctx *context.APIContext) {
// swagger:operation POST /user/avatar user userUpdateAvatar
// ---
// summary: Update Avatar
// produces:
// - application/json
// parameters:
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UpdateUserAvatarOption"
// responses:
// "204":
// "$ref": "#/responses/empty"
form := web.GetForm(ctx).(*api.UpdateUserAvatarOption)
content, err := base64.StdEncoding.DecodeString(form.Image)
if err != nil {
ctx.Error(http.StatusBadRequest, "DecodeImage", err)
return
}
err = user_service.UploadAvatar(ctx.Doer, content)
if err != nil {
ctx.Error(http.StatusInternalServerError, "UploadAvatar", err)
}
ctx.Status(http.StatusNoContent)
}
// DeleteAvatar deletes the Avatar of an User
func DeleteAvatar(ctx *context.APIContext) {
// swagger:operation DELETE /user/avatar user userDeleteAvatar
// ---
// summary: Delete Avatar
// produces:
// - application/json
// responses:
// "204":
// "$ref": "#/responses/empty"
err := user_service.DeleteAvatar(ctx.Doer)
if err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteAvatar", err)
}
ctx.Status(http.StatusNoContent)
}

View File

@ -14,12 +14,15 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/cron"
"code.gitea.io/gitea/services/forms"
repo_service "code.gitea.io/gitea/services/repository"
)
const (
@ -133,12 +136,22 @@ func DashboardPost(ctx *context.Context) {
// Run operation.
if form.Op != "" {
task := cron.GetTask(form.Op)
if task != nil {
go task.RunWithUser(ctx.Doer, nil)
ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op)))
} else {
ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
switch form.Op {
case "sync_repo_branches":
go func() {
if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), ctx.Doer.ID); err != nil {
log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err)
}
}()
ctx.Flash.Success(ctx.Tr("admin.dashboard.sync_branch.started"))
default:
task := cron.GetTask(form.Op)
if task != nil {
go task.RunWithUser(ctx.Doer, nil)
ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op)))
} else {
ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op))
}
}
}
if form.From == "monitor" {

View File

@ -383,7 +383,7 @@ func SignOut(ctx *context.Context) {
})
}
HandleSignOut(ctx)
ctx.Redirect(setting.AppSubURL + "/")
ctx.JSONRedirect(setting.AppSubURL + "/")
}
// SignUp render the register page

View File

@ -73,6 +73,14 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
orderBy = db.SearchOrderBySizeReverse
case "size":
orderBy = db.SearchOrderBySize
case "reversegitsize":
orderBy = db.SearchOrderByGitSizeReverse
case "gitsize":
orderBy = db.SearchOrderByGitSize
case "reverselfssize":
orderBy = db.SearchOrderByLFSSizeReverse
case "lfssize":
orderBy = db.SearchOrderByLFSSize
case "moststars":
orderBy = db.SearchOrderByStarsReverse
case "feweststars":

View File

@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"net/http"
"strings"
"time"
actions_model "code.gitea.io/gitea/models/actions"
@ -310,6 +311,55 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob) erro
return nil
}
func Logs(ctx *context_module.Context) {
runIndex := ctx.ParamsInt64("run")
jobIndex := ctx.ParamsInt64("job")
job, _ := getRunJobs(ctx, runIndex, jobIndex)
if ctx.Written() {
return
}
if job.TaskID == 0 {
ctx.Error(http.StatusNotFound, "job is not started")
return
}
err := job.LoadRun(ctx)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
task, err := actions_model.GetTaskByID(ctx, job.TaskID)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
if task.LogExpired {
ctx.Error(http.StatusNotFound, "logs have been cleaned up")
return
}
reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename)
if err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
defer reader.Close()
workflowName := job.Run.WorkflowID
if p := strings.Index(workflowName, "."); p > 0 {
workflowName = workflowName[0:p]
}
ctx.ServeContent(reader, &context_module.ServeHeaderOptions{
Filename: fmt.Sprintf("%v-%v-%v.log", workflowName, job.Name, task.ID),
ContentLength: &task.LogSize,
ContentType: "text/plain",
ContentTypeCharset: "utf-8",
Disposition: "attachment",
})
}
func Cancel(ctx *context_module.Context) {
runIndex := ctx.ParamsInt64("run")

View File

@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
@ -28,32 +27,16 @@ import (
"code.gitea.io/gitea/services/forms"
release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files"
)
const (
tplBranch base.TplName = "repo/branch/list"
)
// Branch contains the branch information
type Branch struct {
Name string
Commit *git.Commit
IsProtected bool
IsDeleted bool
IsIncluded bool
DeletedBranch *git_model.DeletedBranch
CommitsAhead int
CommitsBehind int
LatestPullRequest *issues_model.PullRequest
MergeMovedOn bool
}
// Branches render repository branch page
func Branches(ctx *context.Context) {
ctx.Data["Title"] = "Branches"
ctx.Data["IsRepoToolbarBranches"] = true
ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls()
ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode)
ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
@ -68,15 +51,15 @@ func Branches(ctx *context.Context) {
}
pageSize := setting.Git.BranchesRangeSize
skip := (page - 1) * pageSize
log.Debug("Branches: skip: %d limit: %d", skip, pageSize)
defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, pageSize)
if ctx.Written() {
defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, util.OptionalBoolNone, page, pageSize)
if err != nil {
ctx.ServerError("LoadBranches", err)
return
}
ctx.Data["Branches"] = branches
ctx.Data["DefaultBranchBranch"] = defaultBranchBranch
pager := context.NewPagination(branchesCount, pageSize, page, 5)
ctx.Data["DefaultBranchBranch"] = defaultBranch
pager := context.NewPagination(int(branchesCount), pageSize, page, 5)
pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager
@ -130,7 +113,7 @@ func RestoreBranchPost(ctx *context.Context) {
if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{
Remote: ctx.Repo.Repository.RepoPath(),
Branch: fmt.Sprintf("%s:%s%s", deletedBranch.Commit, git.BranchPrefix, deletedBranch.Name),
Branch: fmt.Sprintf("%s:%s%s", deletedBranch.CommitID, git.BranchPrefix, deletedBranch.Name),
Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
}); err != nil {
if strings.Contains(err.Error(), "already exists") {
@ -148,7 +131,7 @@ func RestoreBranchPost(ctx *context.Context) {
&repo_module.PushUpdateOptions{
RefFullName: git.RefNameFromBranch(deletedBranch.Name),
OldCommitID: git.EmptySHA,
NewCommitID: deletedBranch.Commit,
NewCommitID: deletedBranch.CommitID,
PusherID: ctx.Doer.ID,
PusherName: ctx.Doer.Name,
RepoUserName: ctx.Repo.Owner.Name,
@ -166,180 +149,6 @@ func redirect(ctx *context.Context) {
})
}
// loadBranches loads branches from the repository limited by page & pageSize.
// NOTE: May write to context on error.
func loadBranches(ctx *context.Context, skip, limit int) (*Branch, []*Branch, int) {
defaultBranch, err := ctx.Repo.GitRepo.GetBranch(ctx.Repo.Repository.DefaultBranch)
if err != nil {
if !git.IsErrBranchNotExist(err) {
log.Error("loadBranches: get default branch: %v", err)
ctx.ServerError("GetDefaultBranch", err)
return nil, nil, 0
}
log.Warn("loadBranches: missing default branch %s for %-v", ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository)
}
rawBranches, totalNumOfBranches, err := ctx.Repo.GitRepo.GetBranches(skip, limit)
if err != nil {
log.Error("GetBranches: %v", err)
ctx.ServerError("GetBranches", err)
return nil, nil, 0
}
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.ServerError("FindRepoProtectedBranchRules", err)
return nil, nil, 0
}
repoIDToRepo := map[int64]*repo_model.Repository{}
repoIDToRepo[ctx.Repo.Repository.ID] = ctx.Repo.Repository
repoIDToGitRepo := map[int64]*git.Repository{}
repoIDToGitRepo[ctx.Repo.Repository.ID] = ctx.Repo.GitRepo
var branches []*Branch
for i := range rawBranches {
if defaultBranch != nil && rawBranches[i].Name == defaultBranch.Name {
// Skip default branch
continue
}
branch := loadOneBranch(ctx, rawBranches[i], defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
if branch == nil {
return nil, nil, 0
}
branches = append(branches, branch)
}
var defaultBranchBranch *Branch
if defaultBranch != nil {
// Always add the default branch
log.Debug("loadOneBranch: load default: '%s'", defaultBranch.Name)
defaultBranchBranch = loadOneBranch(ctx, defaultBranch, defaultBranch, &rules, repoIDToRepo, repoIDToGitRepo)
branches = append(branches, defaultBranchBranch)
}
if ctx.Repo.CanWrite(unit.TypeCode) {
deletedBranches, err := getDeletedBranches(ctx)
if err != nil {
ctx.ServerError("getDeletedBranches", err)
return nil, nil, 0
}
branches = append(branches, deletedBranches...)
}
return defaultBranchBranch, branches, totalNumOfBranches
}
func loadOneBranch(ctx *context.Context, rawBranch, defaultBranch *git.Branch, protectedBranches *git_model.ProtectedBranchRules,
repoIDToRepo map[int64]*repo_model.Repository,
repoIDToGitRepo map[int64]*git.Repository,
) *Branch {
log.Trace("loadOneBranch: '%s'", rawBranch.Name)
commit, err := rawBranch.GetCommit()
if err != nil {
ctx.ServerError("GetCommit", err)
return nil
}
branchName := rawBranch.Name
p := protectedBranches.GetFirstMatched(branchName)
isProtected := p != nil
divergence := &git.DivergeObject{
Ahead: -1,
Behind: -1,
}
if defaultBranch != nil {
divergence, err = files_service.CountDivergingCommits(ctx, ctx.Repo.Repository, git.BranchPrefix+branchName)
if err != nil {
log.Error("CountDivergingCommits", err)
}
}
pr, err := issues_model.GetLatestPullRequestByHeadInfo(ctx.Repo.Repository.ID, branchName)
if err != nil {
ctx.ServerError("GetLatestPullRequestByHeadInfo", err)
return nil
}
headCommit := commit.ID.String()
mergeMovedOn := false
if pr != nil {
pr.HeadRepo = ctx.Repo.Repository
if err := pr.LoadIssue(ctx); err != nil {
ctx.ServerError("LoadIssue", err)
return nil
}
if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
pr.BaseRepo = repo
} else if err := pr.LoadBaseRepo(ctx); err != nil {
ctx.ServerError("LoadBaseRepo", err)
return nil
} else {
repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
}
pr.Issue.Repo = pr.BaseRepo
if pr.HasMerged {
baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
if !ok {
baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
return nil
}
defer baseGitRepo.Close()
repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
}
pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("GetBranchCommitID", err)
return nil
}
if err == nil && headCommit != pullCommit {
// the head has moved on from the merge - we shouldn't delete
mergeMovedOn = true
}
}
}
isIncluded := divergence.Ahead == 0 && ctx.Repo.Repository.DefaultBranch != branchName
return &Branch{
Name: branchName,
Commit: commit,
IsProtected: isProtected,
IsIncluded: isIncluded,
CommitsAhead: divergence.Ahead,
CommitsBehind: divergence.Behind,
LatestPullRequest: pr,
MergeMovedOn: mergeMovedOn,
}
}
func getDeletedBranches(ctx *context.Context) ([]*Branch, error) {
branches := []*Branch{}
deletedBranches, err := git_model.GetDeletedBranches(ctx, ctx.Repo.Repository.ID)
if err != nil {
return branches, err
}
for i := range deletedBranches {
deletedBranches[i].LoadUser(ctx)
branches = append(branches, &Branch{
Name: deletedBranches[i].Name,
IsDeleted: true,
DeletedBranch: deletedBranches[i],
})
}
return branches, nil
}
// CreateBranch creates new branch in repository
func CreateBranch(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewBranchForm)
@ -380,13 +189,13 @@ func CreateBranch(ctx *context.Context) {
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return
}
if models.IsErrBranchNameConflict(err) {
e := err.(models.ErrBranchNameConflict)
if git_model.IsErrBranchNameConflict(err) {
e := err.(git_model.ErrBranchNameConflict)
ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return

View File

@ -9,6 +9,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@ -124,9 +125,9 @@ func CherryPickPost(ctx *context.Context) {
// First lets try the simple plain read-tree -m approach
opts.Content = sha
if _, err := files.CherryPick(ctx, ctx.Repo.Repository, ctx.Doer, form.Revert, opts); err != nil {
if models.IsErrBranchAlreadyExists(err) {
if git_model.IsErrBranchAlreadyExists(err) {
// User has specified a branch that already exists
branchErr := err.(models.ErrBranchAlreadyExists)
branchErr := err.(git_model.ErrBranchAlreadyExists)
ctx.Data["Err_NewBranchName"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
return
@ -161,9 +162,9 @@ func CherryPickPost(ctx *context.Context) {
ctx.Data["FileContent"] = opts.Content
if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
if models.IsErrBranchAlreadyExists(err) {
if git_model.IsErrBranchAlreadyExists(err) {
// User has specified a branch that already exists
branchErr := err.(models.ErrBranchAlreadyExists)
branchErr := err.(git_model.ErrBranchAlreadyExists)
ctx.Data["Err_NewBranchName"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplCherryPick, &form)
return

View File

@ -16,6 +16,7 @@ import (
"path/filepath"
"strings"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
@ -683,7 +684,13 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor
}
defer gitRepo.Close()
branches, _, err = gitRepo.GetBranchNames(0, 0)
branches, err = git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
RepoID: repo.ID,
ListOptions: db.ListOptions{
ListAll: true,
},
IsDeletedBranch: util.OptionalBoolFalse,
})
if err != nil {
return nil, nil, err
}
@ -734,7 +741,13 @@ func CompareDiff(ctx *context.Context) {
return
}
headBranches, _, err := ci.HeadGitRepo.GetBranchNames(0, 0)
headBranches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
RepoID: ci.HeadRepo.ID,
ListOptions: db.ListOptions{
ListAll: true,
},
IsDeletedBranch: util.OptionalBoolFalse,
})
if err != nil {
ctx.ServerError("GetBranches", err)
return

View File

@ -327,10 +327,10 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
} else {
ctx.Error(http.StatusInternalServerError, err.Error())
}
} else if models.IsErrBranchAlreadyExists(err) {
} else if git_model.IsErrBranchAlreadyExists(err) {
// For when a user specifies a new branch that already exists
ctx.Data["Err_NewBranchName"] = true
if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok {
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
} else {
ctx.Error(http.StatusInternalServerError, err.Error())
@ -529,9 +529,9 @@ func DeleteFilePost(ctx *context.Context) {
} else {
ctx.Error(http.StatusInternalServerError, err.Error())
}
} else if models.IsErrBranchAlreadyExists(err) {
} else if git_model.IsErrBranchAlreadyExists(err) {
// For when a user specifies a new branch that already exists
if branchErr, ok := err.(models.ErrBranchAlreadyExists); ok {
if branchErr, ok := err.(git_model.ErrBranchAlreadyExists); ok {
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form)
} else {
ctx.Error(http.StatusInternalServerError, err.Error())
@ -731,10 +731,10 @@ func UploadFilePost(ctx *context.Context) {
} else if git.IsErrBranchNotExist(err) {
branchErr := err.(git.ErrBranchNotExist)
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplUploadFile, &form)
} else if models.IsErrBranchAlreadyExists(err) {
} else if git_model.IsErrBranchAlreadyExists(err) {
// For when a user specifies a new branch that already exists
ctx.Data["Err_NewBranchName"] = true
branchErr := err.(models.ErrBranchAlreadyExists)
branchErr := err.(git_model.ErrBranchAlreadyExists)
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form)
} else if git.IsErrPushOutOfDate(err) {
ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(ctx.Repo.CommitID)+"..."+util.PathEscapeSegments(form.NewBranchName)), tplUploadFile, &form)

View File

@ -785,7 +785,13 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull
return nil
}
brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 0)
brs, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
RepoID: ctx.Repo.Repository.ID,
ListOptions: db.ListOptions{
ListAll: true,
},
IsDeletedBranch: util.OptionalBoolFalse,
})
if err != nil {
ctx.ServerError("GetBranches", err)
return nil

View File

@ -20,14 +20,12 @@ func LockIssue(ctx *context.Context) {
}
if issue.IsLocked {
ctx.Flash.Error(ctx.Tr("repo.issues.lock_duplicate"))
ctx.Redirect(issue.Link())
ctx.JSONError(ctx.Tr("repo.issues.lock_duplicate"))
return
}
if !form.HasValidReason() {
ctx.Flash.Error(ctx.Tr("repo.issues.lock.unknown_reason"))
ctx.Redirect(issue.Link())
ctx.JSONError(ctx.Tr("repo.issues.lock.unknown_reason"))
return
}
@ -40,7 +38,7 @@ func LockIssue(ctx *context.Context) {
return
}
ctx.Redirect(issue.Link())
ctx.JSONRedirect(issue.Link())
}
// UnlockIssue unlocks a previously locked issue.
@ -51,8 +49,7 @@ func UnlockIssue(ctx *context.Context) {
}
if !issue.IsLocked {
ctx.Flash.Error(ctx.Tr("repo.issues.unlock_error"))
ctx.Redirect(issue.Link())
ctx.JSONError(ctx.Tr("repo.issues.unlock_error"))
return
}
@ -64,5 +61,5 @@ func UnlockIssue(ctx *context.Context) {
return
}
ctx.Redirect(issue.Link())
ctx.JSONRedirect(issue.Link())
}

View File

@ -31,7 +31,7 @@ func IssuePinOrUnpin(ctx *context.Context) {
return
}
ctx.Redirect(issue.Link())
ctx.JSONRedirect(issue.Link())
}
// IssueUnpin unpins a Issue

View File

@ -7,6 +7,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@ -94,9 +95,9 @@ func NewDiffPatchPost(ctx *context.Context) {
Content: strings.ReplaceAll(form.Content, "\r", ""),
})
if err != nil {
if models.IsErrBranchAlreadyExists(err) {
if git_model.IsErrBranchAlreadyExists(err) {
// User has specified a branch that already exists
branchErr := err.(models.ErrBranchAlreadyExists)
branchErr := err.(git_model.ErrBranchAlreadyExists)
ctx.Data["Err_NewBranchName"] = true
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
return

View File

@ -1493,7 +1493,7 @@ func UpdatePullRequestTarget(ctx *context.Context) {
"error": err.Error(),
"user_error": errorMessage,
})
} else if models.IsErrBranchesEqual(err) {
} else if git_model.IsErrBranchesEqual(err) {
errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
ctx.Flash.Error(errorMessage)

View File

@ -286,7 +286,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
}
// FIXME: since we only need to recheck files protected rules, we could improve this
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName)
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
if err != nil {
ctx.ServerError("FindAllMatchedBranches", err)
return

View File

@ -420,7 +420,13 @@ func PackageSettingsPost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("packages.settings.delete.success"))
}
ctx.Redirect(ctx.Package.Owner.HomeLink() + "/-/packages")
redirectURL := ctx.Package.Owner.HomeLink() + "/-/packages"
// redirect to the package if there are still versions available
if has, _ := packages_model.ExistVersion(ctx, &packages_model.PackageSearchOptions{PackageID: ctx.Package.Descriptor.Package.ID}); has {
redirectURL = ctx.Package.Descriptor.PackageWebLink()
}
ctx.Redirect(redirectURL)
return
}
}

View File

@ -1207,6 +1207,7 @@ func registerRoutes(m *web.Route) {
Get(actions.View).
Post(web.Bind(actions.ViewRequest{}), actions.ViewPost)
m.Post("/rerun", reqRepoActionsWriter, actions.RerunOne)
m.Get("/logs", actions.Logs)
})
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
m.Post("/approve", reqRepoActionsWriter, actions.Approve)

View File

@ -56,12 +56,20 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
return nil
}); err != nil {
log.Warn("Cannot stop task %v: %v", task.ID, err)
// go on
} else if remove, err := actions.TransferLogs(ctx, task.LogFilename); err != nil {
log.Warn("Cannot transfer logs of task %v: %v", task.ID, err)
} else {
remove()
continue
}
remove, err := actions.TransferLogs(ctx, task.LogFilename)
if err != nil {
log.Warn("Cannot transfer logs of task %v: %v", task.ID, err)
continue
}
task.LogInStorage = true
if err := actions_model.UpdateTask(ctx, task, "log_in_storage"); err != nil {
log.Warn("Cannot update task %v: %v", task.ID, err)
continue
}
remove()
}
CreateCommitStatus(ctx, jobs...)

View File

@ -50,7 +50,7 @@ func ToEmailSearch(email *user_model.SearchEmailResult) *api.Email {
}
// ToBranch convert a git.Commit and git.Branch to an api.Branch
func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
func ToBranch(ctx context.Context, repo *repo_model.Repository, branchName string, c *git.Commit, bp *git_model.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) {
if bp == nil {
var hasPerm bool
var canPush bool
@ -65,11 +65,11 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c
if err != nil {
return nil, err
}
canPush = issues_model.CanMaintainerWriteToBranch(perms, b.Name, user)
canPush = issues_model.CanMaintainerWriteToBranch(perms, branchName, user)
}
return &api.Branch{
Name: b.Name,
Name: branchName,
Commit: ToPayloadCommit(ctx, repo, c),
Protected: false,
RequiredApprovals: 0,
@ -81,7 +81,7 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c
}
branch := &api.Branch{
Name: b.Name,
Name: branchName,
Commit: ToPayloadCommit(ctx, repo, c),
Protected: true,
RequiredApprovals: bp.RequiredApprovals,

View File

@ -642,7 +642,7 @@ func (g *RepositoryDumper) Finish() error {
// DumpRepository dump repository according MigrateOptions to a local directory
func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error {
doer, err := user_model.GetAdminUser()
doer, err := user_model.GetAdminUser(ctx)
if err != nil {
return err
}
@ -705,7 +705,7 @@ func updateOptionsUnits(opts *base.MigrateOptions, units []string) error {
// RestoreRepository restore a repository from the disk directory
func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, units []string, validation bool) error {
doer, err := user_model.GetAdminUser()
doer, err := user_model.GetAdminUser(ctx)
if err != nil {
return err
}

View File

@ -170,7 +170,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
return err
}
if branchesEqual {
return models.ErrBranchesEqual{
return git_model.ErrBranchesEqual{
HeadBranchName: pr.HeadBranch,
BaseBranchName: targetBranch,
}
@ -338,7 +338,7 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
for _, pr := range prs {
divergence, err := GetDiverging(ctx, pr)
if err != nil {
if models.IsErrBranchDoesNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
if git_model.IsErrBranchNotExist(err) && !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
log.Warn("Cannot test PR %s/%d: head_branch %s no longer exists", pr.BaseRepo.Name, pr.IssueID, pr.HeadBranch)
} else {
log.Error("GetDiverging: %v", err)

View File

@ -11,7 +11,7 @@ import (
"path/filepath"
"strings"
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
@ -181,7 +181,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest)
Run(prCtx.RunOpts()); err != nil {
cancel()
if !git.IsBranchExist(ctx, pr.HeadRepo.RepoPath(), pr.HeadBranch) {
return nil, nil, models.ErrBranchDoesNotExist{
return nil, nil, git_model.ErrBranchNotExist{
BranchName: pr.HeadBranch,
}
}

View File

@ -7,7 +7,6 @@ import (
"context"
"fmt"
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
@ -168,7 +167,7 @@ func GetDiverging(ctx context.Context, pr *issues_model.PullRequest) (*git.Diver
log.Trace("GetDiverging[%-v]: compare commits", pr)
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
if err != nil {
if !models.IsErrBranchDoesNotExist(err) {
if !git_model.IsErrBranchNotExist(err) {
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
}
return nil, err

View File

@ -12,6 +12,7 @@ import (
"strings"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
@ -146,7 +147,15 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r
}
}
}
branches, _, _ := gitRepo.GetBranchNames(0, 0)
branches, _ := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{
RepoID: repo.ID,
ListOptions: db.ListOptions{
ListAll: true,
},
IsDeletedBranch: util.OptionalBoolFalse,
})
found := false
hasDefault := false
hasMaster := false

View File

@ -10,13 +10,21 @@ import (
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
files_service "code.gitea.io/gitea/services/repository/files"
"xorm.io/builder"
)
// CreateNewBranch creates a new repository branch
@ -27,7 +35,7 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode
}
if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) {
return models.ErrBranchDoesNotExist{
return git_model.ErrBranchNotExist{
BranchName: oldBranchName,
}
}
@ -40,16 +48,165 @@ func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_mode
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err
}
return fmt.Errorf("Push: %w", err)
return fmt.Errorf("push: %w", err)
}
return nil
}
// GetBranches returns branches from the repository, skipping skip initial branches and
// returning at most limit branches, or all branches if limit is 0.
func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit int) ([]*git.Branch, int, error) {
return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit)
// Branch contains the branch information
type Branch struct {
DBBranch *git_model.Branch
IsProtected bool
IsIncluded bool
CommitsAhead int
CommitsBehind int
LatestPullRequest *issues_model.PullRequest
MergeMovedOn bool
}
// LoadBranches loads branches from the repository limited by page & pageSize.
func LoadBranches(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, isDeletedBranch util.OptionalBool, page, pageSize int) (*Branch, []*Branch, int64, error) {
defaultDBBranch, err := git_model.GetBranch(ctx, repo.ID, repo.DefaultBranch)
if err != nil {
return nil, nil, 0, err
}
branchOpts := git_model.FindBranchOptions{
RepoID: repo.ID,
IsDeletedBranch: isDeletedBranch,
ListOptions: db.ListOptions{
Page: page,
PageSize: pageSize,
},
}
totalNumOfBranches, err := git_model.CountBranches(ctx, branchOpts)
if err != nil {
return nil, nil, 0, err
}
branchOpts.ExcludeBranchNames = []string{repo.DefaultBranch}
dbBranches, err := git_model.FindBranches(ctx, branchOpts)
if err != nil {
return nil, nil, 0, err
}
if err := dbBranches.LoadDeletedBy(ctx); err != nil {
return nil, nil, 0, err
}
if err := dbBranches.LoadPusher(ctx); err != nil {
return nil, nil, 0, err
}
rules, err := git_model.FindRepoProtectedBranchRules(ctx, repo.ID)
if err != nil {
return nil, nil, 0, err
}
repoIDToRepo := map[int64]*repo_model.Repository{}
repoIDToRepo[repo.ID] = repo
repoIDToGitRepo := map[int64]*git.Repository{}
repoIDToGitRepo[repo.ID] = gitRepo
branches := make([]*Branch, 0, len(dbBranches))
for i := range dbBranches {
branch, err := loadOneBranch(ctx, repo, dbBranches[i], &rules, repoIDToRepo, repoIDToGitRepo)
if err != nil {
return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
}
branches = append(branches, branch)
}
// Always add the default branch
log.Debug("loadOneBranch: load default: '%s'", defaultDBBranch.Name)
defaultBranch, err := loadOneBranch(ctx, repo, defaultDBBranch, &rules, repoIDToRepo, repoIDToGitRepo)
if err != nil {
return nil, nil, 0, fmt.Errorf("loadOneBranch: %v", err)
}
return defaultBranch, branches, totalNumOfBranches, nil
}
func loadOneBranch(ctx context.Context, repo *repo_model.Repository, dbBranch *git_model.Branch, protectedBranches *git_model.ProtectedBranchRules,
repoIDToRepo map[int64]*repo_model.Repository,
repoIDToGitRepo map[int64]*git.Repository,
) (*Branch, error) {
log.Trace("loadOneBranch: '%s'", dbBranch.Name)
branchName := dbBranch.Name
p := protectedBranches.GetFirstMatched(branchName)
isProtected := p != nil
divergence := &git.DivergeObject{
Ahead: -1,
Behind: -1,
}
// it's not default branch
if repo.DefaultBranch != dbBranch.Name && !dbBranch.IsDeleted {
var err error
divergence, err = files_service.CountDivergingCommits(ctx, repo, git.BranchPrefix+branchName)
if err != nil {
log.Error("CountDivergingCommits: %v", err)
}
}
pr, err := issues_model.GetLatestPullRequestByHeadInfo(repo.ID, branchName)
if err != nil {
return nil, fmt.Errorf("GetLatestPullRequestByHeadInfo: %v", err)
}
headCommit := dbBranch.CommitID
mergeMovedOn := false
if pr != nil {
pr.HeadRepo = repo
if err := pr.LoadIssue(ctx); err != nil {
return nil, fmt.Errorf("LoadIssue: %v", err)
}
if repo, ok := repoIDToRepo[pr.BaseRepoID]; ok {
pr.BaseRepo = repo
} else if err := pr.LoadBaseRepo(ctx); err != nil {
return nil, fmt.Errorf("LoadBaseRepo: %v", err)
} else {
repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
}
pr.Issue.Repo = pr.BaseRepo
if pr.HasMerged {
baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
if !ok {
baseGitRepo, err = git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil {
return nil, fmt.Errorf("OpenRepository: %v", err)
}
defer baseGitRepo.Close()
repoIDToGitRepo[pr.BaseRepoID] = baseGitRepo
}
pullCommit, err := baseGitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil && !git.IsErrNotExist(err) {
return nil, fmt.Errorf("GetBranchCommitID: %v", err)
}
if err == nil && headCommit != pullCommit {
// the head has moved on from the merge - we shouldn't delete
mergeMovedOn = true
}
}
}
isIncluded := divergence.Ahead == 0 && repo.DefaultBranch != branchName
return &Branch{
DBBranch: dbBranch,
IsProtected: isProtected,
IsIncluded: isIncluded,
CommitsAhead: divergence.Ahead,
CommitsBehind: divergence.Behind,
LatestPullRequest: pr,
MergeMovedOn: mergeMovedOn,
}, nil
}
func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) {
@ -62,17 +219,17 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
branchRefName := strings.TrimPrefix(refName, git.BranchPrefix)
switch {
case branchRefName == name:
return models.ErrBranchAlreadyExists{
return git_model.ErrBranchAlreadyExists{
BranchName: name,
}
// If branchRefName like a/b but we want to create a branch named a then we have a conflict
case strings.HasPrefix(branchRefName, name+"/"):
return models.ErrBranchNameConflict{
return git_model.ErrBranchNameConflict{
BranchName: branchRefName,
}
// Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict
case strings.HasPrefix(name, branchRefName+"/"):
return models.ErrBranchNameConflict{
return git_model.ErrBranchNameConflict{
BranchName: branchRefName,
}
case refName == git.TagPrefix+name:
@ -101,7 +258,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err
}
return fmt.Errorf("Push: %w", err)
return fmt.Errorf("push: %w", err)
}
return nil
@ -169,13 +326,28 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return git_model.ErrBranchIsProtected
}
rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
if err != nil {
return fmt.Errorf("GetBranch: %vc", err)
}
if rawBranch.IsDeleted {
return nil
}
commit, err := gitRepo.GetBranchCommit(branchName)
if err != nil {
return err
}
if err := gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
Force: true,
if err := db.WithTx(ctx, func(ctx context.Context) error {
if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil {
return err
}
return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
Force: true,
})
}); err != nil {
return err
}
@ -196,3 +368,45 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return nil
}
type BranchSyncOptions struct {
RepoID int64
}
// branchSyncQueue represents a queue to handle branch sync jobs.
var branchSyncQueue *queue.WorkerPoolQueue[*BranchSyncOptions]
func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions {
for _, opts := range items {
_, err := repo_module.SyncRepoBranches(graceful.GetManager().ShutdownContext(), opts.RepoID, 0)
if err != nil {
log.Error("syncRepoBranches [%d] failed: %v", opts.RepoID, err)
}
}
return nil
}
func addRepoToBranchSyncQueue(repoID, doerID int64) error {
return branchSyncQueue.Push(&BranchSyncOptions{
RepoID: repoID,
})
}
func initBranchSyncQueue(ctx context.Context) error {
branchSyncQueue = queue.CreateUniqueQueue(ctx, "branch_sync", handlerBranchSync)
if branchSyncQueue == nil {
return errors.New("unable to create branch_sync queue")
}
go graceful.GetManager().RunWithCancel(branchSyncQueue)
return nil
}
func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error {
if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error {
return addRepoToBranchSyncQueue(repo.ID, doerID)
}); err != nil {
return fmt.Errorf("run sync all branches failed: %v", err)
}
return nil
}

View File

@ -58,7 +58,7 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode
if opts.NewBranch != opts.OldBranch {
existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
if existingBranch != nil {
return models.ErrBranchAlreadyExists{
return git_model.ErrBranchAlreadyExists{
BranchName: opts.NewBranch,
}
}

View File

@ -197,7 +197,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
if opts.NewBranch != opts.OldBranch {
existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
if existingBranch != nil {
return nil, models.ErrBranchAlreadyExists{
return nil, git_model.ErrBranchAlreadyExists{
BranchName: opts.NewBranch,
}
}

View File

@ -157,7 +157,15 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
if err = repo_module.CreateDelegateHooks(repoPath); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err)
}
return nil
gitRepo, err := git.OpenRepository(txCtx, repo.RepoPath())
if err != nil {
return fmt.Errorf("OpenRepository: %w", err)
}
defer gitRepo.Close()
_, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID)
return err
})
needsRollbackInPanic = false
if err != nil {

View File

@ -93,7 +93,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
defer gitRepo.Close()
if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
log.Error("Failed to update size for repository: %v", err)
return fmt.Errorf("Failed to update size for repository: %v", err)
}
addTags := make([]string, 0, len(optsList))
@ -259,8 +259,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
notification.NotifyPushCommits(ctx, pusher, repo, opts, commits)
if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil {
log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err)
if err = git_model.UpdateBranch(ctx, repo.ID, branch, newCommit.ID.String(), newCommit.CommitMessage, opts.PusherID, newCommit.Committer.When); err != nil {
return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err)
}
// Cache for big repository
@ -273,8 +273,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
// close all related pulls
log.Error("close related pull request failed: %v", err)
}
if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, opts.OldCommitID, pusher.ID); err != nil {
log.Warn("AddDeletedBranch: %v", err)
if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branch, pusher.ID); err != nil {
return fmt.Errorf("AddDeletedBranch %s:%s failed: %v", repo.FullName(), branch, err)
}
}

View File

@ -17,6 +17,7 @@ import (
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
@ -100,7 +101,10 @@ func Init() error {
}
system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath)
system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath())
return initPushQueue()
if err := initPushQueue(); err != nil {
return err
}
return initBranchSyncQueue(graceful.GetManager().ShutdownContext())
}
// UpdateRepository updates a repository

View File

@ -1,6 +1,12 @@
<div class="ui right floated secondary filter menu">
<!-- Sort -->
<div class="ui dropdown type jump item">
<div class="ui secondary filter menu gt-ac gt-mx-0">
<form class="ui form ignore-dirty gt-f1">
<div class="ui fluid action input">
{{template "shared/searchinput" dict "locale" .locale "Value" .Keyword "AutoFocus" true}}
<button class="ui primary button">{{.locale.Tr "explore.search"}}</button>
</div>
</form>
<!-- Sort -->
<div class="ui dropdown type jump item gt-mr-0">
<span class="text">
{{.locale.Tr "repo.issues.filter_sort"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
@ -15,9 +21,3 @@
</div>
</div>
</div>
<form class="ui form ignore-dirty">
<div class="ui fluid action input">
{{template "shared/searchinput" dict "locale" .locale "Value" .Keyword "AutoFocus" true}}
<button class="ui primary button">{{.locale.Tr "explore.search"}}</button>
</div>
</form>

View File

@ -4,7 +4,7 @@
{{.locale.Tr "admin.config.server_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.app_name"}}</dt>
<dd>{{AppName}}</dd>
<dt>{{.locale.Tr "admin.config.app_ver"}}</dt>
@ -20,19 +20,19 @@
<dt>{{.locale.Tr "admin.config.disable_router_log"}}</dt>
<dd>{{if .DisableRouterLog}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<div class="ui divider"></div>
<div class="divider"></div>
<dt>{{.locale.Tr "admin.config.run_user"}}</dt>
<dd>{{.RunUser}}</dd>
<dt>{{.locale.Tr "admin.config.run_mode"}}</dt>
<dd>{{.RunMode}}</dd>
<div class="ui divider"></div>
<div class="divider"></div>
<dt>{{.locale.Tr "admin.config.git_version"}}</dt>
<dd>{{.GitVersion}}</dd>
<div class="ui divider"></div>
<div class="divider"></div>
<dt>{{.locale.Tr "admin.config.repo_root_path"}}</dt>
<dd>{{.RepoRootPath}}</dd>
@ -53,7 +53,7 @@
{{.locale.Tr "admin.config.ssh_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.ssh_enabled"}}</dt>
<dd>{{if not .SSH.Disabled}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{if not .SSH.Disabled}}
@ -88,7 +88,7 @@
{{.locale.Tr "admin.config.lfs_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.lfs_enabled"}}</dt>
<dd>{{if .LFS.StartServer}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{if .LFS.StartServer}}
@ -104,7 +104,7 @@
{{.locale.Tr "admin.config.db_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.db_type"}}</dt>
<dd>{{.DbCfg.Type}}</dd>
{{if not (eq .DbCfg.Type "sqlite3")}}
@ -132,7 +132,7 @@
{{.locale.Tr "admin.config.service_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.register_email_confirm"}}</dt>
<dd>{{if .Service.RegisterEmailConfirm}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.locale.Tr "admin.config.disable_register"}}</dt>
@ -174,7 +174,7 @@
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>
<dt>{{.locale.Tr "admin.config.default_enable_dependencies"}}</dt>
<dd>{{if .Service.DefaultEnableDependencies}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<div class="ui divider"></div>
<div class="divider"></div>
<dt>{{.locale.Tr "admin.config.active_code_lives"}}</dt>
<dd>{{.Service.ActiveCodeLives}} {{.locale.Tr "tool.raw_minutes"}}</dd>
<dt>{{.locale.Tr "admin.config.reset_password_code_lives"}}</dt>
@ -186,7 +186,7 @@
{{.locale.Tr "admin.config.webhook_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.queue_length"}}</dt>
<dd>{{.Webhook.QueueLength}}</dd>
<dt>{{.locale.Tr "admin.config.deliver_timeout"}}</dt>
@ -200,7 +200,7 @@
{{.locale.Tr "admin.config.mailer_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.mailer_enabled"}}</dt>
<dd>{{if .MailerEnabled}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{if .MailerEnabled}}
@ -230,7 +230,7 @@
{{end}}
<dt>{{.locale.Tr "admin.config.mailer_user"}}</dt>
<dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd>
<div class="ui divider"></div>
<div class="divider"></div>
<dt class="gt-py-2">{{.locale.Tr "admin.config.send_test_mail"}}</dt>
<dd>
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post">
@ -249,7 +249,7 @@
{{.locale.Tr "admin.config.cache_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.cache_adapter"}}</dt>
<dd>{{.CacheAdapter}}</dd>
{{if eq .CacheAdapter "memory"}}
@ -269,7 +269,7 @@
{{.locale.Tr "admin.config.session_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.session_provider"}}</dt>
<dd>{{.SessionConfig.Provider}}</dd>
<dt>{{.locale.Tr "admin.config.provider_config"}}</dt>
@ -289,14 +289,14 @@
{{.locale.Tr "admin.config.picture_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.disable_gravatar"}}</dt>
<dd>
<div class="ui toggle checkbox">
<input type="checkbox" name="picture.disable_gravatar" version="{{.SystemSettings.GetVersion "picture.disable_gravatar"}}"{{if .SystemSettings.GetBool "picture.disable_gravatar"}} checked{{end}} title="{{.locale.Tr "admin.config.disable_gravatar"}}">
</div>
</dd>
<div class="ui divider"></div>
<div class="divider"></div>
<dt>{{.locale.Tr "admin.config.enable_federated_avatar"}}</dt>
<dd>
<div class="ui toggle checkbox">
@ -310,7 +310,7 @@
{{.locale.Tr "admin.config.git_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.git_disable_diff_highlight"}}</dt>
<dd>{{if .Git.DisableDiffHighlight}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.locale.Tr "admin.config.git_max_diff_lines"}}</dt>
@ -322,7 +322,7 @@
<dt>{{.locale.Tr "admin.config.git_gc_args"}}</dt>
<dd><code>{{.Git.GCArgs}}</code></dd>
<div class="ui divider"></div>
<div class="divider"></div>
<dt>{{.locale.Tr "admin.config.git_migrate_timeout"}}</dt>
<dd>{{.Git.Timeout.Migrate}} {{.locale.Tr "tool.raw_seconds"}}</dd>
@ -341,7 +341,7 @@
{{.locale.Tr "admin.config.log_config"}}
</h4>
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dl class="admin-dl-horizontal">
{{if .Loggers.xorm.IsEnabled}}
<dt>{{$.locale.Tr "admin.config.xorm_log_sql"}}</dt>
<dd>{{if $.LogSQL}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>

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