Compare commits

...

11 Commits

Author SHA1 Message Date
JakobDev
254a82842a
Add API for changing Avatars (#25369)
This adds an API for uploading and Deleting Avatars for of Users, Repos
and Organisations. I'm not sure, if this should also be added to the
Admin API.

Resolves #25344

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
2023-06-29 23:22:55 +00:00
Ed Silkworth
9fd63aaad1
read-only checkboxes don't appear and don't entirely act the way one might expect (#25573)
This pull request fades read-only checkboxes and checkmark, and it makes
the checkboxes act more read-only/disabled by not changing the
border-color when clicked.

Examples using light mode:
 
| Before | After |
| - | - |
| ![Kapture 2023-06-28 at 00 20
45](https://github.com/go-gitea/gitea/assets/63764270/0899fd5c-18a9-4290-9ba9-d3cf71033cf8)
| ![Kapture 2023-06-28 at 00 23
12](https://github.com/go-gitea/gitea/assets/63764270/0db9be14-e16c-42ed-8fb1-999928fd1d25)
|
| ![Kapture 2023-06-28 at 00 25
22](https://github.com/go-gitea/gitea/assets/63764270/65c6c380-b928-4e6c-b403-3655d3565896)
| ![Kapture 2023-06-28 at 00 27
28](https://github.com/go-gitea/gitea/assets/63764270/d8c2a019-e07c-43a1-a7fa-93c0d4e01900)
|
| | read-only checkboxes and checkmark are faded<br>and the checkboxes
act more read-only/disabled |

Fixes/Closes/Resolves #25076

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-30 00:16:53 +02:00
KN4CK3R
cf2356062f
Redirect to package after version deletion (#25594)
Related #25559

Current behaviour:
1. Deletion of a package version
2. Redirect to the owners package list

New behaviour:
1. Deletion of a package version
2.1. If there are more versions available, redirect to the package again
2.2. If there are no versions available, redirect to the owners package
list
2023-06-29 17:01:14 +00:00
isla w
e882398c5a
Update emoji set to Unicode 15 (#25595)
Update emoji set to Unicode 15 which was added upstream here:
cb5c514d47

<img width="854" alt="Screenshot 2023-06-29 at 11 02 56 AM"
src="https://github.com/go-gitea/gitea/assets/1669571/7bfb663d-0804-4d23-a62d-f585a6783ca6">

---------

Co-authored-by: silverwind <me@silverwind.io>
2023-06-29 16:29:48 +00:00
silverwind
fdf71460f9
Fix lint-swagger action (#25593)
- Add detection for swagger changes and run `lint-swagger` on it
- Remove `lint-swagger` from `lint-frontend`
- Remove `lint-md` from `lint-frontend`
2023-06-29 10:37:41 -04:00
silverwind
64f2d70262
Replace fomantic divider module with our own (#25539)
Should look exactly like before for normal dividers. "Horizontal" ones
look better because they no longer use image backgrounds.

<img width="917" alt="Screenshot 2023-06-27 at 19 07 56"
src="https://github.com/go-gitea/gitea/assets/115237/d97d8dec-6859-44a8-85ba-e4549b4dd9df">

<img width="914" alt="Screenshot 2023-06-27 at 19 05 58"
src="https://github.com/go-gitea/gitea/assets/115237/8bf98544-2d82-4ebf-ac68-d6dc237bd6b2">

<img width="1246" alt="Screenshot 2023-06-27 at 19 00 42"
src="https://github.com/go-gitea/gitea/assets/115237/36a6bb21-6029-4f53-8bee-535f55c66fed">

<img width="344" alt="Screenshot 2023-06-27 at 18 58 15"
src="https://github.com/go-gitea/gitea/assets/115237/a9e70aee-8e6b-4ea1-9e93-19c9f96aec6e">
<img width="823" alt="Screenshot 2023-06-27 at 18 56 22"
src="https://github.com/go-gitea/gitea/assets/115237/e7a497cd-f262-4683-8872-23c3c8cce32f">

<img width="330" alt="Screenshot 2023-06-27 at 19 21 11"
src="https://github.com/go-gitea/gitea/assets/115237/42f24149-a655-4c7e-bd26-8ab52db6446b">
2023-06-29 20:24:22 +08:00
Zettat123
72b3af74be
Add documentation about supported workflow trigger events (#25582)
Right now Gitea doesn't support all [Events that trigger
workflows](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows).
This PR lists the supported events to help users write workflow files.
2023-06-29 19:45:53 +08:00
Lunny Xiao
6e19484f4d
Sync branches into databases (#22743)
Related #14180
Related #25233 
Related #22639
Close #19786
Related #12763 

This PR will change all the branches retrieve method from reading git
data to read database to reduce git read operations.

- [x] Sync git branches information into database when push git data
- [x] Create a new table `Branch`, merge some columns of `DeletedBranch`
into `Branch` table and drop the table `DeletedBranch`.
- [x] Read `Branch` table when visit `code` -> `branch` page
- [x] Read `Branch` table when list branch names in `code` page dropdown
- [x] Read `Branch` table when list git ref compare page
- [x] Provide a button in admin page to manually sync all branches.
- [x] Sync branches if repository is not empty but database branches are
empty when visiting pages with branches list
- [x] Use `commit_time desc` as the default FindBranch order by to keep
consistent as before and deleted branches will be always at the end.

---------

Co-authored-by: Jason Song <i@wolfogre.com>
2023-06-29 10:03:20 +00:00
HesterG
5a871932f0
Fix milestones deletion (#25583)
Close #25557 
Fix regression from #25315

`data-id` is still needed for deleting milestone.
2023-06-29 10:17:18 +02:00
silverwind
c76b221cca
Reduce table padding globally (#25568)
Fomantic's tables have too much padding. Reduce it so we have more
information density in them. Especially the admin tables need this
because they are bursting already because of column count.

## Admin repolist before and after

<img width="909" alt="Screenshot 2023-06-28 at 20 27 55"
src="https://github.com/go-gitea/gitea/assets/115237/954c925c-8db5-47ce-ae51-a2168b857014">
<img width="897" alt="Screenshot 2023-06-28 at 20 36 03"
src="https://github.com/go-gitea/gitea/assets/115237/0bddc09a-9117-48b3-a17e-3d34c58d8d3d">

## Other tables

<img width="1230" alt="Screenshot 2023-06-28 at 20 36 22"
src="https://github.com/go-gitea/gitea/assets/115237/38f555b6-a7ce-416a-9f1f-706eaf18863b">
<img width="1236" alt="Screenshot 2023-06-28 at 20 26 37"
src="https://github.com/go-gitea/gitea/assets/115237/82b2878e-358c-4dc2-a6b4-c66e43cd2dfb">
<img width="1231" alt="Screenshot 2023-06-28 at 20 59 30"
src="https://github.com/go-gitea/gitea/assets/115237/c6a92e55-a3a3-4c80-9a0d-50aebb49886c">

Files table is unaffected because it has custom padding already.

---------

Co-authored-by: Giteabot <teabot@gitea.io>
2023-06-29 04:40:03 +00:00
HesterG
c6f1fb1c6d
Use fetch form action for lock/unlock/pin/unpin on sidebar (#25380)
Before:

<img width="364" alt="Screen Shot 2023-06-20 at 11 59 11"
src="https://github.com/go-gitea/gitea/assets/17645053/ad284b7e-8d21-43be-b178-bbcfd37cb5bd">

Might trigger many posts when keep clicking the buttons above.

<img width="448" alt="Screen Shot 2023-06-20 at 11 52 28"
src="https://github.com/go-gitea/gitea/assets/17645053/a60aa6ac-af74-45e4-b13a-512b436b81b0">
<img width="678" alt="Screen Shot 2023-06-20 at 11 52 37"
src="https://github.com/go-gitea/gitea/assets/17645053/d6662700-3643-4cc7-a2ec-64e1c0f5fbdb">

After (PR sidebar, Same for issue):


https://github.com/go-gitea/gitea/assets/17645053/9df3ad1f-e29c-439b-8bde-e6b917d63cc6

For delete, it is using `base/modal_actions_confirm` subtemplate, and we
might need another general solution for this (maybe add another
attribute to the subtemplate or something)

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-06-29 04:16:04 +00:00
147 changed files with 2435 additions and 1233 deletions

View File

@ -15,6 +15,8 @@ on:
value: ${{ jobs.detect.outputs.templates }} value: ${{ jobs.detect.outputs.templates }}
docker: docker:
value: ${{ jobs.detect.outputs.docker }} value: ${{ jobs.detect.outputs.docker }}
swagger:
value: ${{ jobs.detect.outputs.swagger }}
jobs: jobs:
detect: detect:
@ -27,6 +29,7 @@ jobs:
actions: ${{ steps.changes.outputs.actions }} actions: ${{ steps.changes.outputs.actions }}
templates: ${{ steps.changes.outputs.templates }} templates: ${{ steps.changes.outputs.templates }}
docker: ${{ steps.changes.outputs.docker }} docker: ${{ steps.changes.outputs.docker }}
swagger: ${{ steps.changes.outputs.swagger }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: dorny/paths-filter@v2 - uses: dorny/paths-filter@v2
@ -36,6 +39,7 @@ jobs:
backend: backend:
- "**/*.go" - "**/*.go"
- "templates/**/*.tmpl" - "templates/**/*.tmpl"
- "assets/emoji.json"
- "go.mod" - "go.mod"
- "go.sum" - "go.sum"
- "Makefile" - "Makefile"
@ -43,6 +47,7 @@ jobs:
frontend: frontend:
- "**/*.js" - "**/*.js"
- "web_src/**" - "web_src/**"
- "assets/emoji.json"
- "package.json" - "package.json"
- "package-lock.json" - "package-lock.json"
- "Makefile" - "Makefile"
@ -63,3 +68,6 @@ jobs:
- "Dockerfile.rootless" - "Dockerfile.rootless"
- "docker/**" - "docker/**"
- "Makefile" - "Makefile"
swagger:
- "templates/swagger/v1_json.tmpl"

View File

@ -39,6 +39,18 @@ jobs:
- run: make deps-py - run: make deps-py
- run: make lint-templates - 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: lint-go-windows:
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
needs: files-changed needs: files-changed

View File

@ -360,10 +360,10 @@ lint: lint-frontend lint-backend
lint-fix: lint-frontend-fix lint-backend-fix lint-fix: lint-frontend-fix lint-backend-fix
.PHONY: lint-frontend .PHONY: lint-frontend
lint-frontend: lint-js lint-css lint-md lint-swagger lint-frontend: lint-js lint-css
.PHONY: lint-frontend-fix .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 .PHONY: lint-backend
lint-backend: lint-go lint-go-vet lint-editorconfig lint-backend: lint-go lint-go-vet lint-editorconfig

2
assets/emoji.json generated

File diff suppressed because one or more lines are too long

View File

@ -25,7 +25,7 @@ import (
const ( const (
gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json" 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") var flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out")

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. 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. 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. 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都是完全开源的所以任何人都可以创建一个新的/更好的实现。 然而无论您如何决定Gitea 和act runner都是完全开源的所以任何人都可以创建一个新的/更好的实现。
我们支持您的选择,无论您如何决定。 我们支持您的选择,无论您如何决定。
如果您选择分支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` |

View File

@ -318,90 +318,6 @@ func (err ErrFilePathProtected) Unwrap() error {
return util.ErrPermissionDenied 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. // ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it.
type ErrDisallowedToMerge struct { type ErrDisallowedToMerge struct {
Reason string 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" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -18,24 +19,37 @@ import (
func TestAddDeletedBranch(t *testing.T) { func TestAddDeletedBranch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 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.True(t, firstBranch.IsDeleted)
assert.NoError(t, git_model.AddDeletedBranch(db.DefaultContext, repo.ID, "test", "5655464564554545466464656", int64(1))) 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) { func TestGetDeletedBranches(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 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.NoError(t, err)
assert.Len(t, branches, 2) assert.Len(t, branches, 2)
} }
func TestGetDeletedBranch(t *testing.T) { func TestGetDeletedBranch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) 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)) assert.NotNil(t, getDeletedBranch(t, firstBranch))
} }
@ -43,18 +57,18 @@ func TestGetDeletedBranch(t *testing.T) {
func TestDeletedBranchLoadUser(t *testing.T) { func TestDeletedBranchLoadUser(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 1}) firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1})
secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.DeletedBranch{ID: 2}) secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 2})
branch := getDeletedBranch(t, firstBranch) branch := getDeletedBranch(t, firstBranch)
assert.Nil(t, branch.DeletedBy) assert.Nil(t, branch.DeletedBy)
branch.LoadUser(db.DefaultContext) branch.LoadDeletedBy(db.DefaultContext)
assert.NotNil(t, branch.DeletedBy) assert.NotNil(t, branch.DeletedBy)
assert.Equal(t, "user1", branch.DeletedBy.Name) assert.Equal(t, "user1", branch.DeletedBy.Name)
branch = getDeletedBranch(t, secondBranch) branch = getDeletedBranch(t, secondBranch)
assert.Nil(t, branch.DeletedBy) assert.Nil(t, branch.DeletedBy)
branch.LoadUser(db.DefaultContext) branch.LoadDeletedBy(db.DefaultContext)
assert.NotNil(t, branch.DeletedBy) assert.NotNil(t, branch.DeletedBy)
assert.Equal(t, "Ghost", branch.DeletedBy.Name) assert.Equal(t, "Ghost", branch.DeletedBy.Name)
} }
@ -63,22 +77,22 @@ func TestRemoveDeletedBranch(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 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) err := git_model.RemoveDeletedBranchByID(db.DefaultContext, repo.ID, 1)
assert.NoError(t, err) assert.NoError(t, err)
unittest.AssertNotExistsBean(t, firstBranch) 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}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID) deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo.ID, branch.ID)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, branch.ID, deletedBranch.ID) assert.Equal(t, branch.ID, deletedBranch.ID)
assert.Equal(t, branch.Name, deletedBranch.Name) 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) assert.Equal(t, branch.DeletedByID, deletedBranch.DeletedByID)
return deletedBranch return deletedBranch
@ -146,8 +160,8 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) {
deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1) deletedBranch, err := git_model.GetDeletedBranchByID(db.DefaultContext, repo2.ID, 1)
// Expect no error, and the returned branch is nil. // Expect error, and the returned branch is nil.
assert.NoError(t, err) assert.Error(t, err)
assert.Nil(t, deletedBranch) assert.Nil(t, deletedBranch)
// Now get the deletedBranch with ID of 1 on repo with ID 1. // 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" "sort"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob" "github.com/gobwas/glob"
) )
@ -47,19 +47,32 @@ func FindRepoProtectedBranchRules(ctx context.Context, repoID int64) (ProtectedB
} }
// FindAllMatchedBranches find all matched branches // FindAllMatchedBranches find all matched branches
func FindAllMatchedBranches(ctx context.Context, gitRepo *git.Repository, ruleName string) ([]string, error) { func FindAllMatchedBranches(ctx context.Context, repoID int64, ruleName string) ([]string, error) {
// FIXME: how many should we get? results := make([]string, 0, 10)
branches, _, err := gitRepo.GetBranchNames(0, 9999999) for page := 1; ; page++ {
if err != nil { brancheNames, err := FindBranchNames(ctx, FindBranchOptions{
return nil, err ListOptions: db.ListOptions{
} PageSize: 100,
rule := glob.MustCompile(ruleName) Page: page,
results := make([]string, 0, len(branches)) },
for _, branch := range branches { RepoID: repoID,
if rule.Match(branch) { IsDeletedBranch: util.OptionalBoolFalse,
results = append(results, branch) })
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 return results, nil
} }

View File

@ -509,6 +509,8 @@ var migrations = []Migration{
NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun), NewMigration("Add TriggerEvent to action_run table", v1_21.AddTriggerEventToActionRun),
// v263 -> v264 // v263 -> v264
NewMigration("Add git_size and lfs_size columns to repository table", v1_21.AddGitSizeAndLFSSizeToRepositoryTable), 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 // GetCurrentDBVersion returns the current db version

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}, &repo_model.Collaboration{RepoID: repoID},
&issues_model.Comment{RefRepoID: repoID}, &issues_model.Comment{RefRepoID: repoID},
&git_model.CommitStatus{RepoID: repoID}, &git_model.CommitStatus{RepoID: repoID},
&git_model.DeletedBranch{RepoID: repoID}, &git_model.Branch{RepoID: repoID},
&git_model.LFSLock{RepoID: repoID}, &git_model.LFSLock{RepoID: repoID},
&repo_model.LanguageStat{RepoID: repoID}, &repo_model.LanguageStat{RepoID: repoID},
&issues_model.Milestone{RepoID: repoID}, &issues_model.Milestone{RepoID: repoID},

View File

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

View File

@ -667,13 +667,38 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
} }
ctx.Data["Tags"] = tags 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 { if err != nil {
ctx.ServerError("GetBranches", err) ctx.ServerError("GetBranches", err)
return return
} }
ctx.Data["Branches"] = brs // always put default branch on the top
ctx.Data["BranchesCount"] = len(brs) ctx.Data["Branches"] = append(branchOpts.ExcludeBranchNames, brs...)
ctx.Data["BranchesCount"] = branchesTotal
// If not branch selected, try default one. // If not branch selected, try default one.
// If default branch doesn't exist, fall back to some other branch. // 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 { if len(ctx.Params("*")) == 0 {
refName = ctx.Repo.Repository.DefaultBranch refName = ctx.Repo.Repository.DefaultBranch
if !ctx.Repo.GitRepo.IsBranchExist(refName) { 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 { if err == nil && len(brs) != 0 {
refName = brs[0] refName = brs[0].Name
} else if len(brs) == 0 { } else if len(brs) == 0 {
log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path) log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path)
ctx.Repo.Repository.MarkAsBrokenEmpty() ctx.Repo.Repository.MarkAsBrokenEmpty()

View File

@ -230,6 +230,7 @@ var GemojiData = Gemoji{
{"\U0001f382", "birthday cake", []string{"birthday"}, "6.0", false}, {"\U0001f382", "birthday cake", []string{"birthday"}, "6.0", false},
{"\U0001f9ac", "bison", []string{"bison"}, "13.0", false}, {"\U0001f9ac", "bison", []string{"bison"}, "13.0", false},
{"\U0001fae6", "biting lip", []string{"biting_lip"}, "14.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}, {"\U0001f408\u200d\u2b1b", "black cat", []string{"black_cat"}, "13.0", false},
{"\u26ab", "black circle", []string{"black_circle"}, "4.1", false}, {"\u26ab", "black circle", []string{"black_circle"}, "4.1", false},
{"\U0001f3f4", "black flag", []string{"black_flag"}, "7.0", 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}, {"\U0001f42c", "dolphin", []string{"dolphin", "flipper"}, "6.0", false},
{"\U0001f1e9\U0001f1f2", "flag: Dominica", []string{"dominica"}, "6.0", false}, {"\U0001f1e9\U0001f1f2", "flag: Dominica", []string{"dominica"}, "6.0", false},
{"\U0001f1e9\U0001f1f4", "flag: Dominican Republic", []string{"dominican_republic"}, "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}, {"\U0001f6aa", "door", []string{"door"}, "6.0", false},
{"\U0001fae5", "dotted line face", []string{"dotted_line_face"}, "14.0", false}, {"\U0001fae5", "dotted line face", []string{"dotted_line_face"}, "14.0", false},
{"\U0001f369", "doughnut", []string{"doughnut"}, "6.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}, {"\U0001f4be", "floppy disk", []string{"floppy_disk"}, "6.0", false},
{"\U0001f3b4", "flower playing cards", []string{"flower_playing_cards"}, "6.0", false}, {"\U0001f3b4", "flower playing cards", []string{"flower_playing_cards"}, "6.0", false},
{"\U0001f633", "flushed face", []string{"flushed"}, "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}, {"\U0001fab0", "fly", []string{"fly"}, "13.0", false},
{"\U0001f94f", "flying disc", []string{"flying_disc"}, "11.0", false}, {"\U0001f94f", "flying disc", []string{"flying_disc"}, "11.0", false},
{"\U0001f6f8", "flying saucer", []string{"flying_saucer"}, "11.0", false}, {"\U0001f6f8", "flying saucer", []string{"flying_saucer"}, "11.0", false},
{"\U0001f32b\ufe0f", "fog", []string{"fog"}, "7.0", false}, {"\U0001f32b\ufe0f", "fog", []string{"fog"}, "7.0", false},
{"\U0001f301", "foggy", []string{"foggy"}, "6.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}, {"\U0001fad5", "fondue", []string{"fondue"}, "13.0", false},
{"\U0001f9b6", "foot", []string{"foot"}, "11.0", true}, {"\U0001f9b6", "foot", []string{"foot"}, "11.0", true},
{"\U0001f9b6\U0001f3ff", "foot: Dark Skin Tone", []string{"foot_Dark_Skin_Tone"}, "12.0", false}, {"\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}, {"\U0001f1ec\U0001f1ee", "flag: Gibraltar", []string{"gibraltar"}, "6.0", false},
{"\U0001f381", "wrapped gift", []string{"gift"}, "6.0", false}, {"\U0001f381", "wrapped gift", []string{"gift"}, "6.0", false},
{"\U0001f49d", "heart with ribbon", []string{"gift_heart"}, "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}, {"\U0001f992", "giraffe", []string{"giraffe"}, "11.0", false},
{"\U0001f467", "girl", []string{"girl"}, "6.0", true}, {"\U0001f467", "girl", []string{"girl"}, "6.0", true},
{"\U0001f467\U0001f3ff", "girl: Dark Skin Tone", []string{"girl_Dark_Skin_Tone"}, "12.0", false}, {"\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\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\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}, {"\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}, {"\U0001f98d", "gorilla", []string{"gorilla"}, "9.0", false},
{"\U0001f347", "grapes", []string{"grapes"}, "6.0", false}, {"\U0001f347", "grapes", []string{"grapes"}, "6.0", false},
{"\U0001f1ec\U0001f1f7", "flag: Greece", []string{"greece"}, "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\U0001f1f1", "flag: Greenland", []string{"greenland"}, "6.0", false},
{"\U0001f1ec\U0001f1e9", "flag: Grenada", []string{"grenada"}, "6.0", false}, {"\U0001f1ec\U0001f1e9", "flag: Grenada", []string{"grenada"}, "6.0", false},
{"\u2755", "white exclamation mark", []string{"grey_exclamation"}, "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}, {"\u2754", "white question mark", []string{"grey_question"}, "6.0", false},
{"\U0001f62c", "grimacing face", []string{"grimacing"}, "6.1", false}, {"\U0001f62c", "grimacing face", []string{"grimacing"}, "6.1", false},
{"\U0001f601", "beaming face with smiling eyes", []string{"grin"}, "6.0", 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}, {"\U0001f3b8", "guitar", []string{"guitar"}, "6.0", false},
{"\U0001f52b", "water pistol", []string{"gun"}, "6.0", false}, {"\U0001f52b", "water pistol", []string{"gun"}, "6.0", false},
{"\U0001f1ec\U0001f1fe", "flag: Guyana", []string{"guyana"}, "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", "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\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}, {"\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}, {"\U0001f1ed\U0001f1fa", "flag: Hungary", []string{"hungary"}, "6.0", false},
{"\U0001f62f", "hushed face", []string{"hushed"}, "6.1", false}, {"\U0001f62f", "hushed face", []string{"hushed"}, "6.1", false},
{"\U0001f6d6", "hut", []string{"hut"}, "13.0", false}, {"\U0001f6d6", "hut", []string{"hut"}, "13.0", false},
{"\U0001fabb", "hyacinth", []string{"hyacinth"}, "15.0", false},
{"\U0001f368", "ice cream", []string{"ice_cream"}, "6.0", false}, {"\U0001f368", "ice cream", []string{"ice_cream"}, "6.0", false},
{"\U0001f9ca", "ice", []string{"ice_cube"}, "12.0", false}, {"\U0001f9ca", "ice", []string{"ice_cube"}, "12.0", false},
{"\U0001f3d2", "ice hockey", []string{"ice_hockey"}, "8.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}, {"\U0001f479", "ogre", []string{"japanese_ogre"}, "6.0", false},
{"\U0001fad9", "jar", []string{"jar"}, "14.0", false}, {"\U0001fad9", "jar", []string{"jar"}, "14.0", false},
{"\U0001f456", "jeans", []string{"jeans"}, "6.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}, {"\U0001f1ef\U0001f1ea", "flag: Jersey", []string{"jersey"}, "6.0", false},
{"\U0001f9e9", "puzzle piece", []string{"jigsaw"}, "11.0", false}, {"\U0001f9e9", "puzzle piece", []string{"jigsaw"}, "11.0", false},
{"\U0001f1ef\U0001f1f4", "flag: Jordan", []string{"jordan"}, "6.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}, {"\U0001f511", "key", []string{"key"}, "6.0", false},
{"\u2328\ufe0f", "keyboard", []string{"keyboard"}, "", false}, {"\u2328\ufe0f", "keyboard", []string{"keyboard"}, "", false},
{"\U0001f51f", "keycap: 10", []string{"keycap_ten"}, "6.0", 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}, {"\U0001f6f4", "kick scooter", []string{"kick_scooter"}, "9.0", false},
{"\U0001f458", "kimono", []string{"kimono"}, "6.0", false}, {"\U0001f458", "kimono", []string{"kimono"}, "6.0", false},
{"\U0001f1f0\U0001f1ee", "flag: Kiribati", []string{"kiribati"}, "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\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\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}, {"\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", "leg", []string{"leg"}, "11.0", true},
{"\U0001f9b5\U0001f3ff", "leg: Dark Skin Tone", []string{"leg_Dark_Skin_Tone"}, "12.0", false}, {"\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}, {"\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}, {"\u264e", "Libra", []string{"libra"}, "", false},
{"\U0001f1f1\U0001f1fe", "flag: Libya", []string{"libya"}, "6.0", false}, {"\U0001f1f1\U0001f1fe", "flag: Libya", []string{"libya"}, "6.0", false},
{"\U0001f1f1\U0001f1ee", "flag: Liechtenstein", []string{"liechtenstein"}, "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}, {"\U0001f688", "light rail", []string{"light_rail"}, "6.0", false},
{"\U0001f517", "link", []string{"link"}, "6.0", false}, {"\U0001f517", "link", []string{"link"}, "6.0", false},
{"\U0001f981", "lion", []string{"lion"}, "8.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}, {"\U0001f570\ufe0f", "mantelpiece clock", []string{"mantelpiece_clock"}, "7.0", false},
{"\U0001f9bd", "manual wheelchair", []string{"manual_wheelchair"}, "12.0", false}, {"\U0001f9bd", "manual wheelchair", []string{"manual_wheelchair"}, "12.0", false},
{"\U0001f341", "maple leaf", []string{"maple_leaf"}, "6.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}, {"\U0001f1f2\U0001f1ed", "flag: Marshall Islands", []string{"marshall_islands"}, "6.0", false},
{"\U0001f94b", "martial arts uniform", []string{"martial_arts_uniform"}, "9.0", false}, {"\U0001f94b", "martial arts uniform", []string{"martial_arts_uniform"}, "9.0", false},
{"\U0001f1f2\U0001f1f6", "flag: Martinique", []string{"martinique"}, "6.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}, {"\U0001f1f2\U0001f1f8", "flag: Montserrat", []string{"montserrat"}, "6.0", false},
{"\U0001f314", "waxing gibbous moon", []string{"moon", "waxing_gibbous_moon"}, "6.0", false}, {"\U0001f314", "waxing gibbous moon", []string{"moon", "waxing_gibbous_moon"}, "6.0", false},
{"\U0001f96e", "moon cake", []string{"moon_cake"}, "11.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}, {"\U0001f1f2\U0001f1e6", "flag: Morocco", []string{"morocco"}, "6.0", false},
{"\U0001f393", "graduation cap", []string{"mortar_board"}, "6.0", false}, {"\U0001f393", "graduation cap", []string{"mortar_board"}, "6.0", false},
{"\U0001f54c", "mosque", []string{"mosque"}, "8.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}, {"\U0001f6f3\ufe0f", "passenger ship", []string{"passenger_ship"}, "7.0", false},
{"\U0001f6c2", "passport control", []string{"passport_control"}, "6.0", false}, {"\U0001f6c2", "passport control", []string{"passport_control"}, "6.0", false},
{"\u23f8\ufe0f", "pause button", []string{"pause_button"}, "7.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}, {"\u262e\ufe0f", "peace symbol", []string{"peace_symbol"}, "", false},
{"\U0001f351", "peach", []string{"peach"}, "6.0", false}, {"\U0001f351", "peach", []string{"peach"}, "6.0", false},
{"\U0001f99a", "peacock", []string{"peacock"}, "11.0", false}, {"\U0001f99a", "peacock", []string{"peacock"}, "11.0", false},
@ -2085,7 +2106,12 @@ var GemojiData = Gemoji{
{"\u270f\ufe0f", "pencil", []string{"pencil2"}, "", false}, {"\u270f\ufe0f", "pencil", []string{"pencil2"}, "", false},
{"\U0001f427", "penguin", []string{"penguin"}, "6.0", false}, {"\U0001f427", "penguin", []string{"penguin"}, "6.0", false},
{"\U0001f614", "pensive face", []string{"pensive"}, "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}, {"\U0001fac2", "people hugging", []string{"people_hugging"}, "13.0", false},
{"\U0001f3ad", "performing arts", []string{"performing_arts"}, "6.0", false}, {"\U0001f3ad", "performing arts", []string{"performing_arts"}, "6.0", false},
{"\U0001f623", "persevering face", []string{"persevere"}, "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}, {"\U0001f90f\U0001f3fd", "pinching hand: Medium Skin Tone", []string{"pinching_hand_Medium_Skin_Tone"}, "12.0", false},
{"\U0001f34d", "pineapple", []string{"pineapple"}, "6.0", false}, {"\U0001f34d", "pineapple", []string{"pineapple"}, "6.0", false},
{"\U0001f3d3", "ping pong", []string{"ping_pong"}, "8.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}, {"\U0001f3f4\u200d\u2620\ufe0f", "pirate flag", []string{"pirate_flag"}, "11.0", false},
{"\u2653", "Pisces", []string{"pisces"}, "", false}, {"\u2653", "Pisces", []string{"pisces"}, "", false},
{"\U0001f1f5\U0001f1f3", "flag: Pitcairn Islands", []string{"pitcairn_islands"}, "6.0", 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}, {"\U0001f4fb", "radio", []string{"radio"}, "6.0", false},
{"\U0001f518", "radio button", []string{"radio_button"}, "6.0", false}, {"\U0001f518", "radio button", []string{"radio_button"}, "6.0", false},
{"\u2622\ufe0f", "radioactive", []string{"radioactive"}, "", 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}, {"\U0001f683", "railway car", []string{"railway_car"}, "6.0", false},
{"\U0001f6e4\ufe0f", "railway track", []string{"railway_track"}, "7.0", false}, {"\U0001f6e4\ufe0f", "railway track", []string{"railway_track"}, "7.0", false},
{"\U0001f308", "rainbow", []string{"rainbow"}, "6.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\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\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}, {"\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}, {"\U0001f48d", "ring", []string{"ring"}, "6.0", false},
{"\U0001f6df", "ring buoy", []string{"ring_buoy"}, "14.0", false}, {"\U0001f6df", "ring buoy", []string{"ring_buoy"}, "14.0", false},
{"\U0001fa90", "ringed planet", []string{"ringed_planet"}, "12.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}, {"7\ufe0f\u20e3", "keycap: 7", []string{"seven"}, "", false},
{"\U0001faa1", "sewing needle", []string{"sewing_needle"}, "13.0", false}, {"\U0001faa1", "sewing needle", []string{"sewing_needle"}, "13.0", false},
{"\U0001f1f8\U0001f1e8", "flag: Seychelles", []string{"seychelles"}, "6.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}, {"\U0001f958", "shallow pan of food", []string{"shallow_pan_of_food"}, "", false},
{"\u2618\ufe0f", "shamrock", []string{"shamrock"}, "4.1", false}, {"\u2618\ufe0f", "shamrock", []string{"shamrock"}, "4.1", false},
{"\U0001f988", "shark", []string{"shark"}, "9.0", 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}, {"\U0001f32c\ufe0f", "wind face", []string{"wind_face"}, "7.0", false},
{"\U0001fa9f", "window", []string{"window"}, "13.0", false}, {"\U0001fa9f", "window", []string{"window"}, "13.0", false},
{"\U0001f377", "wine glass", []string{"wine_glass"}, "6.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}, {"\U0001f609", "winking face", []string{"wink"}, "6.0", false},
{"\U0001f6dc", "wireless", []string{"wireless"}, "15.0", false},
{"\U0001f43a", "wolf", []string{"wolf"}, "6.0", false}, {"\U0001f43a", "wolf", []string{"wolf"}, "6.0", false},
{"\U0001f469", "woman", []string{"woman"}, "6.0", true}, {"\U0001f469", "woman", []string{"woman"}, "6.0", true},
{"\U0001f469\U0001f3ff", "woman: Dark Skin Tone", []string{"woman_Dark_Skin_Tone"}, "12.0", false}, {"\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", "zombie", []string{"zombie"}, "11.0", false},
{"\U0001f9df\u200d\u2642\ufe0f", "man zombie", []string{"zombie_man"}, "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}, {"\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

@ -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

@ -351,6 +351,12 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re
if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { if err = gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil {
return fmt.Errorf("setDefaultBranch: %w", err) 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 { 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 { if !opts.Releases {
// note: this will greatly improve release (tag) sync // note: this will greatly improve release (tag) sync
// for pull-mirrors with many tags // 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -380,3 +380,9 @@ type NewIssuePinsAllowed struct {
Issues bool `json:"issues"` Issues bool `json:"issues"`
PullRequests bool `json:"pull_requests"` 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 // unique: true
NewName string `json:"new_username" binding:"Required"` 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

@ -2660,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 = Delete all repositories missing their Git files
dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started. dashboard.delete_missing_repos.started = Delete all repositories missing their Git files task started.
dashboard.delete_generated_repository_avatars = Delete generated repository avatars 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.update_mirrors = Update Mirrors
dashboard.repo_health_check = Health check all repositories dashboard.repo_health_check = Health check all repositories
dashboard.check_repo_stats = Check all repository statistics dashboard.check_repo_stats = Check all repository statistics
@ -2713,6 +2714,7 @@ dashboard.gc_lfs = Garbage collect LFS meta objects
dashboard.stop_zombie_tasks = Stop zombie tasks dashboard.stop_zombie_tasks = Stop zombie tasks
dashboard.stop_endless_tasks = Stop endless tasks dashboard.stop_endless_tasks = Stop endless tasks
dashboard.cancel_abandoned_jobs = Cancel abandoned jobs dashboard.cancel_abandoned_jobs = Cancel abandoned jobs
dashboard.sync_branch.started = Branches Sync started
users.user_manage_panel = User Account Management users.user_manage_panel = User Account Management
users.new_account = Create User Account users.new_account = Create User Account

View File

@ -899,6 +899,11 @@ func Routes() *web.Route {
Patch(bind(api.EditHookOption{}), user.EditHook). Patch(bind(api.EditHookOption{}), user.EditHook).
Delete(user.DeleteHook) Delete(user.DeleteHook)
}, reqWebhooksEnabled()) }, reqWebhooksEnabled())
m.Group("/avatar", func() {
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
m.Delete("", user.DeleteAvatar)
}, reqToken())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Repositories (requires repo scope, org scope) // Repositories (requires repo scope, org scope)
@ -1134,6 +1139,10 @@ func Routes() *web.Route {
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages) m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
m.Get("/activities/feeds", repo.ListRepoActivityFeeds) m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed) 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()) }, repoAssignment())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
@ -1314,6 +1323,10 @@ func Routes() *web.Route {
Patch(bind(api.EditHookOption{}), org.EditHook). Patch(bind(api.EditHookOption{}), org.EditHook).
Delete(org.DeleteHook) Delete(org.DeleteHook)
}, reqToken(), reqOrgOwnership(), reqWebhooksEnabled()) }, 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) m.Get("/activities/feeds", org.ListOrgActivityFeeds)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
m.Group("/teams/{teamid}", func() { m.Group("/teams/{teamid}", func() {

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" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
@ -76,7 +78,7 @@ func GetBranch(ctx *context.APIContext) {
return 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return return
@ -118,6 +120,37 @@ func DeleteBranch(ctx *context.APIContext) {
branchName := ctx.Params("*") 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 { if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
switch { switch {
case git.IsErrBranchNotExist(err): 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) err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
if err != nil { if err != nil {
if models.IsErrBranchDoesNotExist(err) { if git_model.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist") ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
} }
if models.IsErrTagAlreadyExists(err) { if models.IsErrTagAlreadyExists(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.") 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.") 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.") ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
} else { } else {
ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err) ctx.Error(http.StatusInternalServerError, "CreateNewBranchFromCommit", err)
@ -236,7 +269,7 @@ func CreateBranch(ctx *context.APIContext) {
return 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return return
@ -275,20 +308,38 @@ func ListBranches(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/BranchList" // "$ref": "#/responses/BranchList"
var totalNumOfBranches int var totalNumOfBranches int64
var apiBranches []*api.Branch var apiBranches []*api.Branch
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil { 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) rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err) ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
return return
} }
skip, _ := listOptions.GetStartEnd() branches, err := git_model.FindBranches(ctx, branchOpts)
branches, total, err := ctx.Repo.GitRepo.GetBranches(skip, listOptions.PageSize)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetBranches", err) ctx.Error(http.StatusInternalServerError, "GetBranches", err)
return return
@ -296,11 +347,11 @@ func ListBranches(ctx *context.APIContext) {
apiBranches = make([]*api.Branch, 0, len(branches)) apiBranches = make([]*api.Branch, 0, len(branches))
for i := range branches { for i := range branches {
c, err := branches[i].GetCommit() c, err := ctx.Repo.GitRepo.GetBranchCommit(branches[i].Name)
if err != nil { if err != nil {
// Skip if this branch doesn't exist anymore. // Skip if this branch doesn't exist anymore.
if git.IsErrNotExist(err) { if git.IsErrNotExist(err) {
total-- totalNumOfBranches--
continue continue
} }
ctx.Error(http.StatusInternalServerError, "GetCommit", err) ctx.Error(http.StatusInternalServerError, "GetCommit", err)
@ -308,19 +359,17 @@ func ListBranches(ctx *context.APIContext) {
} }
branchProtection := rules.GetFirstMatched(branches[i].Name) 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err) ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
return return
} }
apiBranches = append(apiBranches, apiBranch) apiBranches = append(apiBranches, apiBranch)
} }
totalNumOfBranches = total
} }
ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize) ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
ctx.SetTotalCountHeader(int64(totalNumOfBranches)) ctx.SetTotalCountHeader(totalNumOfBranches)
ctx.JSON(http.StatusOK, apiBranches) 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 // 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
return return
@ -851,7 +900,7 @@ func EditBranchProtection(ctx *context.APIContext) {
} }
// FIXME: since we only need to recheck files protected rules, we could improve this // 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err) ctx.Error(http.StatusInternalServerError, "FindAllMatchedBranches", err)
return return

View File

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

View File

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

View File

@ -181,4 +181,10 @@ type swaggerParameterBodies struct {
// in:body // in:body
CreatePushMirrorOption api.CreatePushMirrorOption 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" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker" "code.gitea.io/gitea/modules/updatechecker"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/cron" "code.gitea.io/gitea/services/cron"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
repo_service "code.gitea.io/gitea/services/repository"
) )
const ( const (
@ -133,12 +136,22 @@ func DashboardPost(ctx *context.Context) {
// Run operation. // Run operation.
if form.Op != "" { if form.Op != "" {
task := cron.GetTask(form.Op) switch form.Op {
if task != nil { case "sync_repo_branches":
go task.RunWithUser(ctx.Doer, nil) go func() {
ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op))) if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), ctx.Doer.ID); err != nil {
} else { log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err)
ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op)) }
}()
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" { if form.From == "monitor" {

View File

@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@ -28,32 +27,16 @@ import (
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
release_service "code.gitea.io/gitea/services/release" release_service "code.gitea.io/gitea/services/release"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files"
) )
const ( const (
tplBranch base.TplName = "repo/branch/list" 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 // Branches render repository branch page
func Branches(ctx *context.Context) { func Branches(ctx *context.Context) {
ctx.Data["Title"] = "Branches" ctx.Data["Title"] = "Branches"
ctx.Data["IsRepoToolbarBranches"] = true ctx.Data["IsRepoToolbarBranches"] = true
ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls() ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls()
ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode) ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode)
ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
@ -68,15 +51,15 @@ func Branches(ctx *context.Context) {
} }
pageSize := setting.Git.BranchesRangeSize pageSize := setting.Git.BranchesRangeSize
skip := (page - 1) * pageSize defaultBranch, branches, branchesCount, err := repo_service.LoadBranches(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, util.OptionalBoolNone, page, pageSize)
log.Debug("Branches: skip: %d limit: %d", skip, pageSize) if err != nil {
defaultBranchBranch, branches, branchesCount := loadBranches(ctx, skip, pageSize) ctx.ServerError("LoadBranches", err)
if ctx.Written() {
return return
} }
ctx.Data["Branches"] = branches ctx.Data["Branches"] = branches
ctx.Data["DefaultBranchBranch"] = defaultBranchBranch ctx.Data["DefaultBranchBranch"] = defaultBranch
pager := context.NewPagination(branchesCount, pageSize, page, 5) pager := context.NewPagination(int(branchesCount), pageSize, page, 5)
pager.SetDefaultParams(ctx) pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
@ -130,7 +113,7 @@ func RestoreBranchPost(ctx *context.Context) {
if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{ if err := git.Push(ctx, ctx.Repo.Repository.RepoPath(), git.PushOptions{
Remote: ctx.Repo.Repository.RepoPath(), 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), Env: repo_module.PushingEnvironment(ctx.Doer, ctx.Repo.Repository),
}); err != nil { }); err != nil {
if strings.Contains(err.Error(), "already exists") { if strings.Contains(err.Error(), "already exists") {
@ -148,7 +131,7 @@ func RestoreBranchPost(ctx *context.Context) {
&repo_module.PushUpdateOptions{ &repo_module.PushUpdateOptions{
RefFullName: git.RefNameFromBranch(deletedBranch.Name), RefFullName: git.RefNameFromBranch(deletedBranch.Name),
OldCommitID: git.EmptySHA, OldCommitID: git.EmptySHA,
NewCommitID: deletedBranch.Commit, NewCommitID: deletedBranch.CommitID,
PusherID: ctx.Doer.ID, PusherID: ctx.Doer.ID,
PusherName: ctx.Doer.Name, PusherName: ctx.Doer.Name,
RepoUserName: ctx.Repo.Owner.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 // CreateBranch creates new branch in repository
func CreateBranch(ctx *context.Context) { func CreateBranch(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewBranchForm) form := web.GetForm(ctx).(*forms.NewBranchForm)
@ -380,13 +189,13 @@ func CreateBranch(ctx *context.Context) {
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return 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.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return return
} }
if models.IsErrBranchNameConflict(err) { if git_model.IsErrBranchNameConflict(err) {
e := err.(models.ErrBranchNameConflict) e := err.(git_model.ErrBranchNameConflict)
ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName)) ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()) ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
return return

View File

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

View File

@ -16,6 +16,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access" 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() 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -734,7 +741,13 @@ func CompareDiff(ctx *context.Context) {
return 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 { if err != nil {
ctx.ServerError("GetBranches", err) ctx.ServerError("GetBranches", err)
return return

View File

@ -327,10 +327,10 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
} else { } else {
ctx.Error(http.StatusInternalServerError, err.Error()) 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 // For when a user specifies a new branch that already exists
ctx.Data["Err_NewBranchName"] = true 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) ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplEditFile, &form)
} else { } else {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.Error(http.StatusInternalServerError, err.Error())
@ -529,9 +529,9 @@ func DeleteFilePost(ctx *context.Context) {
} else { } else {
ctx.Error(http.StatusInternalServerError, err.Error()) 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 // 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) ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplDeleteFile, &form)
} else { } else {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.Error(http.StatusInternalServerError, err.Error())
@ -731,10 +731,10 @@ func UploadFilePost(ctx *context.Context) {
} else if git.IsErrBranchNotExist(err) { } else if git.IsErrBranchNotExist(err) {
branchErr := err.(git.ErrBranchNotExist) branchErr := err.(git.ErrBranchNotExist)
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name), tplUploadFile, &form) 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 // For when a user specifies a new branch that already exists
ctx.Data["Err_NewBranchName"] = true 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) ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form)
} else if git.IsErrPushOutOfDate(err) { } 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) 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 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 { if err != nil {
ctx.ServerError("GetBranches", err) ctx.ServerError("GetBranches", err)
return nil return nil

View File

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

View File

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

View File

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

View File

@ -1493,7 +1493,7 @@ func UpdatePullRequestTarget(ctx *context.Context) {
"error": err.Error(), "error": err.Error(),
"user_error": errorMessage, "user_error": errorMessage,
}) })
} else if models.IsErrBranchesEqual(err) { } else if git_model.IsErrBranchesEqual(err) {
errorMessage := ctx.Tr("repo.pulls.nothing_to_compare") errorMessage := ctx.Tr("repo.pulls.nothing_to_compare")
ctx.Flash.Error(errorMessage) 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 // 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 { if err != nil {
ctx.ServerError("FindAllMatchedBranches", err) ctx.ServerError("FindAllMatchedBranches", err)
return return

View File

@ -420,7 +420,13 @@ func PackageSettingsPost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("packages.settings.delete.success")) 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 return
} }
} }

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 // 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 { if bp == nil {
var hasPerm bool var hasPerm bool
var canPush bool var canPush bool
@ -65,11 +65,11 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c
if err != nil { if err != nil {
return nil, err return nil, err
} }
canPush = issues_model.CanMaintainerWriteToBranch(perms, b.Name, user) canPush = issues_model.CanMaintainerWriteToBranch(perms, branchName, user)
} }
return &api.Branch{ return &api.Branch{
Name: b.Name, Name: branchName,
Commit: ToPayloadCommit(ctx, repo, c), Commit: ToPayloadCommit(ctx, repo, c),
Protected: false, Protected: false,
RequiredApprovals: 0, RequiredApprovals: 0,
@ -81,7 +81,7 @@ func ToBranch(ctx context.Context, repo *repo_model.Repository, b *git.Branch, c
} }
branch := &api.Branch{ branch := &api.Branch{
Name: b.Name, Name: branchName,
Commit: ToPayloadCommit(ctx, repo, c), Commit: ToPayloadCommit(ctx, repo, c),
Protected: true, Protected: true,
RequiredApprovals: bp.RequiredApprovals, RequiredApprovals: bp.RequiredApprovals,

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "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 found := false
hasDefault := false hasDefault := false
hasMaster := false hasMaster := false

View File

@ -10,13 +10,21 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository" 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 // 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) { if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) {
return models.ErrBranchDoesNotExist{ return git_model.ErrBranchNotExist{
BranchName: oldBranchName, 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) { if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err return err
} }
return fmt.Errorf("Push: %w", err) return fmt.Errorf("push: %w", err)
} }
return nil return nil
} }
// GetBranches returns branches from the repository, skipping skip initial branches and // Branch contains the branch information
// returning at most limit branches, or all branches if limit is 0. type Branch struct {
func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit int) ([]*git.Branch, int, error) { DBBranch *git_model.Branch
return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit) 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) { 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) branchRefName := strings.TrimPrefix(refName, git.BranchPrefix)
switch { switch {
case branchRefName == name: case branchRefName == name:
return models.ErrBranchAlreadyExists{ return git_model.ErrBranchAlreadyExists{
BranchName: name, BranchName: name,
} }
// If branchRefName like a/b but we want to create a branch named a then we have a conflict // If branchRefName like a/b but we want to create a branch named a then we have a conflict
case strings.HasPrefix(branchRefName, name+"/"): case strings.HasPrefix(branchRefName, name+"/"):
return models.ErrBranchNameConflict{ return git_model.ErrBranchNameConflict{
BranchName: branchRefName, BranchName: branchRefName,
} }
// Conversely if branchRefName like a but we want to create a branch named a/b then we also have a conflict // 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+"/"): case strings.HasPrefix(name, branchRefName+"/"):
return models.ErrBranchNameConflict{ return git_model.ErrBranchNameConflict{
BranchName: branchRefName, BranchName: branchRefName,
} }
case refName == git.TagPrefix+name: 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) { if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err return err
} }
return fmt.Errorf("Push: %w", err) return fmt.Errorf("push: %w", err)
} }
return nil return nil
@ -169,13 +326,28 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return git_model.ErrBranchIsProtected 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) commit, err := gitRepo.GetBranchCommit(branchName)
if err != nil { if err != nil {
return err return err
} }
if err := gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{ if err := db.WithTx(ctx, func(ctx context.Context) error {
Force: true, 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 { }); err != nil {
return err return err
} }
@ -196,3 +368,45 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return nil 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 { if opts.NewBranch != opts.OldBranch {
existingBranch, err := gitRepo.GetBranch(opts.NewBranch) existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
if existingBranch != nil { if existingBranch != nil {
return models.ErrBranchAlreadyExists{ return git_model.ErrBranchAlreadyExists{
BranchName: opts.NewBranch, 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 { if opts.NewBranch != opts.OldBranch {
existingBranch, err := gitRepo.GetBranch(opts.NewBranch) existingBranch, err := gitRepo.GetBranch(opts.NewBranch)
if existingBranch != nil { if existingBranch != nil {
return nil, models.ErrBranchAlreadyExists{ return nil, git_model.ErrBranchAlreadyExists{
BranchName: opts.NewBranch, 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 { if err = repo_module.CreateDelegateHooks(repoPath); err != nil {
return fmt.Errorf("createDelegateHooks: %w", err) 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 needsRollbackInPanic = false
if err != nil { if err != nil {

View File

@ -93,7 +93,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
defer gitRepo.Close() defer gitRepo.Close()
if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { 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)) addTags := make([]string, 0, len(optsList))
@ -259,8 +259,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
notification.NotifyPushCommits(ctx, pusher, repo, opts, commits) notification.NotifyPushCommits(ctx, pusher, repo, opts, commits)
if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil { if err = git_model.UpdateBranch(ctx, repo.ID, branch, newCommit.ID.String(), newCommit.CommitMessage, opts.PusherID, newCommit.Committer.When); err != nil {
log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err) return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err)
} }
// Cache for big repository // Cache for big repository
@ -273,8 +273,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
// close all related pulls // close all related pulls
log.Error("close related pull request failed: %v", err) 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" system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" 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/log"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository" 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 repository uploads", setting.Repository.Upload.TempPath)
system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath()) 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 // UpdateRepository updates a repository

View File

@ -20,19 +20,19 @@
<dt>{{.locale.Tr "admin.config.disable_router_log"}}</dt> <dt>{{.locale.Tr "admin.config.disable_router_log"}}</dt>
<dd>{{if .DisableRouterLog}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <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> <dt>{{.locale.Tr "admin.config.run_user"}}</dt>
<dd>{{.RunUser}}</dd> <dd>{{.RunUser}}</dd>
<dt>{{.locale.Tr "admin.config.run_mode"}}</dt> <dt>{{.locale.Tr "admin.config.run_mode"}}</dt>
<dd>{{.RunMode}}</dd> <dd>{{.RunMode}}</dd>
<div class="ui divider"></div> <div class="divider"></div>
<dt>{{.locale.Tr "admin.config.git_version"}}</dt> <dt>{{.locale.Tr "admin.config.git_version"}}</dt>
<dd>{{.GitVersion}}</dd> <dd>{{.GitVersion}}</dd>
<div class="ui divider"></div> <div class="divider"></div>
<dt>{{.locale.Tr "admin.config.repo_root_path"}}</dt> <dt>{{.locale.Tr "admin.config.repo_root_path"}}</dt>
<dd>{{.RepoRootPath}}</dd> <dd>{{.RepoRootPath}}</dd>
@ -174,7 +174,7 @@
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd> <dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>
<dt>{{.locale.Tr "admin.config.default_enable_dependencies"}}</dt> <dt>{{.locale.Tr "admin.config.default_enable_dependencies"}}</dt>
<dd>{{if .Service.DefaultEnableDependencies}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <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> <dt>{{.locale.Tr "admin.config.active_code_lives"}}</dt>
<dd>{{.Service.ActiveCodeLives}} {{.locale.Tr "tool.raw_minutes"}}</dd> <dd>{{.Service.ActiveCodeLives}} {{.locale.Tr "tool.raw_minutes"}}</dd>
<dt>{{.locale.Tr "admin.config.reset_password_code_lives"}}</dt> <dt>{{.locale.Tr "admin.config.reset_password_code_lives"}}</dt>
@ -230,7 +230,7 @@
{{end}} {{end}}
<dt>{{.locale.Tr "admin.config.mailer_user"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_user"}}</dt>
<dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd> <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> <dt class="gt-py-2">{{.locale.Tr "admin.config.send_test_mail"}}</dt>
<dd> <dd>
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post"> <form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post">
@ -296,7 +296,7 @@
<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"}}"> <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> </div>
</dd> </dd>
<div class="ui divider"></div> <div class="divider"></div>
<dt>{{.locale.Tr "admin.config.enable_federated_avatar"}}</dt> <dt>{{.locale.Tr "admin.config.enable_federated_avatar"}}</dt>
<dd> <dd>
<div class="ui toggle checkbox"> <div class="ui toggle checkbox">
@ -322,7 +322,7 @@
<dt>{{.locale.Tr "admin.config.git_gc_args"}}</dt> <dt>{{.locale.Tr "admin.config.git_gc_args"}}</dt>
<dd><code>{{.Git.GCArgs}}</code></dd> <dd><code>{{.Git.GCArgs}}</code></dd>
<div class="ui divider"></div> <div class="divider"></div>
<dt>{{.locale.Tr "admin.config.git_migrate_timeout"}}</dt> <dt>{{.locale.Tr "admin.config.git_migrate_timeout"}}</dt>
<dd>{{.Git.Timeout.Migrate}} {{.locale.Tr "tool.raw_seconds"}}</dd> <dd>{{.Git.Timeout.Migrate}} {{.locale.Tr "tool.raw_seconds"}}</dd>

View File

@ -61,6 +61,10 @@
<td>{{.locale.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td> <td>{{.locale.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td>
<td class="text right"><button type="submit" class="ui green button" name="op" value="delete_generated_repository_avatars">{{svg "octicon-play"}} {{.locale.Tr "admin.dashboard.operation_run"}}</button></td> <td class="text right"><button type="submit" class="ui green button" name="op" value="delete_generated_repository_avatars">{{svg "octicon-play"}} {{.locale.Tr "admin.dashboard.operation_run"}}</button></td>
</tr> </tr>
<tr>
<td>{{.locale.Tr "admin.dashboard.sync_repo_branches"}}</td>
<td class="text right"><button type="submit" class="ui green button" name="op" value="sync_repo_branches">{{svg "octicon-play"}} {{.locale.Tr "admin.dashboard.operation_run"}}</button></td>
</tr>
</tbody> </tbody>
</table> </table>
</form> </form>
@ -75,7 +79,7 @@
<dd><relative-time format="duration" datetime="{{.SysStatus.StartTime}}">{{.SysStatus.StartTime}}</relative-time></dd> <dd><relative-time format="duration" datetime="{{.SysStatus.StartTime}}">{{.SysStatus.StartTime}}</relative-time></dd>
<dt>{{.locale.Tr "admin.dashboard.current_goroutine"}}</dt> <dt>{{.locale.Tr "admin.dashboard.current_goroutine"}}</dt>
<dd>{{.SysStatus.NumGoroutine}}</dd> <dd>{{.SysStatus.NumGoroutine}}</dd>
<div class="ui divider"></div> <div class="divider"></div>
<dt>{{.locale.Tr "admin.dashboard.current_memory_usage"}}</dt> <dt>{{.locale.Tr "admin.dashboard.current_memory_usage"}}</dt>
<dd>{{.SysStatus.MemAllocated}}</dd> <dd>{{.SysStatus.MemAllocated}}</dd>
<dt>{{.locale.Tr "admin.dashboard.total_memory_allocated"}}</dt> <dt>{{.locale.Tr "admin.dashboard.total_memory_allocated"}}</dt>
@ -88,7 +92,7 @@
<dd>{{.SysStatus.MemMallocs}}</dd> <dd>{{.SysStatus.MemMallocs}}</dd>
<dt>{{.locale.Tr "admin.dashboard.memory_free_times"}}</dt> <dt>{{.locale.Tr "admin.dashboard.memory_free_times"}}</dt>
<dd>{{.SysStatus.MemFrees}}</dd> <dd>{{.SysStatus.MemFrees}}</dd>
<div class="ui divider"></div> <div class="divider"></div>
<dt>{{.locale.Tr "admin.dashboard.current_heap_usage"}}</dt> <dt>{{.locale.Tr "admin.dashboard.current_heap_usage"}}</dt>
<dd>{{.SysStatus.HeapAlloc}}</dd> <dd>{{.SysStatus.HeapAlloc}}</dd>
<dt>{{.locale.Tr "admin.dashboard.heap_memory_obtained"}}</dt> <dt>{{.locale.Tr "admin.dashboard.heap_memory_obtained"}}</dt>
@ -101,7 +105,7 @@
<dd>{{.SysStatus.HeapReleased}}</dd> <dd>{{.SysStatus.HeapReleased}}</dd>
<dt>{{.locale.Tr "admin.dashboard.heap_objects"}}</dt> <dt>{{.locale.Tr "admin.dashboard.heap_objects"}}</dt>
<dd>{{.SysStatus.HeapObjects}}</dd> <dd>{{.SysStatus.HeapObjects}}</dd>
<div class="ui divider"></div> <div class="divider"></div>
<dt>{{.locale.Tr "admin.dashboard.bootstrap_stack_usage"}}</dt> <dt>{{.locale.Tr "admin.dashboard.bootstrap_stack_usage"}}</dt>
<dd>{{.SysStatus.StackInuse}}</dd> <dd>{{.SysStatus.StackInuse}}</dd>
<dt>{{.locale.Tr "admin.dashboard.stack_memory_obtained"}}</dt> <dt>{{.locale.Tr "admin.dashboard.stack_memory_obtained"}}</dt>
@ -120,7 +124,7 @@
<dd>{{.SysStatus.GCSys}}</dd> <dd>{{.SysStatus.GCSys}}</dd>
<dt>{{.locale.Tr "admin.dashboard.other_system_allocation_obtained"}}</dt> <dt>{{.locale.Tr "admin.dashboard.other_system_allocation_obtained"}}</dt>
<dd>{{.SysStatus.OtherSys}}</dd> <dd>{{.SysStatus.OtherSys}}</dd>
<div class="ui divider"></div> <div class="divider"></div>
<dt>{{.locale.Tr "admin.dashboard.next_gc_recycle"}}</dt> <dt>{{.locale.Tr "admin.dashboard.next_gc_recycle"}}</dt>
<dd>{{.SysStatus.NextGC}}</dd> <dd>{{.SysStatus.NextGC}}</dd>
<dt>{{.locale.Tr "admin.dashboard.last_gc_time"}}</dt> <dt>{{.locale.Tr "admin.dashboard.last_gc_time"}}</dt>

View File

@ -16,7 +16,7 @@
</form> </form>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{printf "%d Goroutines" .GoroutineCount}}{{/* Goroutine is non-translatable*/}} {{printf "%d Goroutines" .GoroutineCount}}{{/* Goroutine is non-translatable*/}}

View File

@ -79,7 +79,7 @@
<input id="location" name="location" value="{{.User.Location}}" maxlength="50"> <input id="location" name="location" value="{{.User.Location}}" maxlength="50">
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline field {{if .Err_MaxRepoCreation}}error{{end}}"> <div class="inline field {{if .Err_MaxRepoCreation}}error{{end}}">
<label for="max_repo_creation">{{.locale.Tr "admin.users.max_repo_creation"}}</label> <label for="max_repo_creation">{{.locale.Tr "admin.users.max_repo_creation"}}</label>
@ -87,7 +87,7 @@
<p class="help">{{.locale.Tr "admin.users.max_repo_creation_desc"}}</p> <p class="help">{{.locale.Tr "admin.users.max_repo_creation_desc"}}</p>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
@ -135,7 +135,7 @@
{{end}} {{end}}
{{if .TwoFactorEnabled}} {{if .TwoFactorEnabled}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.locale.Tr "admin.users.reset_2fa"}}</strong></label> <label><strong>{{.locale.Tr "admin.users.reset_2fa"}}</strong></label>
@ -144,7 +144,7 @@
</div> </div>
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="field"> <div class="field">
<button class="ui green button">{{.locale.Tr "admin.users.update_profile"}}</button> <button class="ui green button">{{.locale.Tr "admin.users.update_profile"}}</button>

View File

@ -16,19 +16,19 @@
<span class="text">{{.locale.Tr "admin.users.list_status_filter.menu_text"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}</span> <span class="text">{{.locale.Tr "admin.users.list_status_filter.menu_text"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}</span>
<div class="menu"> <div class="menu">
<a class="item j-reset-status-filter">{{.locale.Tr "admin.users.list_status_filter.reset"}}</a> <a class="item j-reset-status-filter">{{.locale.Tr "admin.users.list_status_filter.reset"}}</a>
<div class="ui divider"></div> <div class="divider"></div>
<label class="item"><input type="radio" name="status_filter[is_admin]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_admin"}}</label> <label class="item"><input type="radio" name="status_filter[is_admin]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_admin"}}</label>
<label class="item"><input type="radio" name="status_filter[is_admin]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_admin"}}</label> <label class="item"><input type="radio" name="status_filter[is_admin]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_admin"}}</label>
<div class="ui divider"></div> <div class="divider"></div>
<label class="item"><input type="radio" name="status_filter[is_active]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_active"}}</label> <label class="item"><input type="radio" name="status_filter[is_active]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_active"}}</label>
<label class="item"><input type="radio" name="status_filter[is_active]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_active"}}</label> <label class="item"><input type="radio" name="status_filter[is_active]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_active"}}</label>
<div class="ui divider"></div> <div class="divider"></div>
<label class="item"><input type="radio" name="status_filter[is_restricted]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_restricted"}}</label> <label class="item"><input type="radio" name="status_filter[is_restricted]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_restricted"}}</label>
<label class="item"><input type="radio" name="status_filter[is_restricted]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_restricted"}}</label> <label class="item"><input type="radio" name="status_filter[is_restricted]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_restricted"}}</label>
<div class="ui divider"></div> <div class="divider"></div>
<label class="item"><input type="radio" name="status_filter[is_prohibit_login]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_prohibit_login"}}</label> <label class="item"><input type="radio" name="status_filter[is_prohibit_login]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_prohibit_login"}}</label>
<label class="item"><input type="radio" name="status_filter[is_prohibit_login]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_prohibit_login"}}</label> <label class="item"><input type="radio" name="status_filter[is_prohibit_login]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_prohibit_login"}}</label>
<div class="ui divider"></div> <div class="divider"></div>
<label class="item"><input type="radio" name="status_filter[is_2fa_enabled]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_2fa_enabled"}}</label> <label class="item"><input type="radio" name="status_filter[is_2fa_enabled]" value="1"> {{.locale.Tr "admin.users.list_status_filter.is_2fa_enabled"}}</label>
<label class="item"><input type="radio" name="status_filter[is_2fa_enabled]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_2fa_enabled"}}</label> <label class="item"><input type="radio" name="status_filter[is_2fa_enabled]" value="0"> {{.locale.Tr "admin.users.list_status_filter.not_2fa_enabled"}}</label>
</div> </div>

View File

@ -3,7 +3,7 @@
{{template "explore/navbar" .}} {{template "explore/navbar" .}}
<div class="ui container"> <div class="ui container">
{{template "code/searchform" .}} {{template "code/searchform" .}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui user list"> <div class="ui user list">
{{if .CodeIndexerUnavailable}} {{if .CodeIndexerUnavailable}}
<div class="ui error message"> <div class="ui error message">

View File

@ -39,4 +39,4 @@
<span data-tooltip-content="{{.locale.Tr "explore.relevant_repositories_tooltip"}}">{{.locale.Tr "explore.relevant_repositories" ((printf "%s%s" $.Link "?only_show_relevant=0")|Escape) | Safe}}</span> <span data-tooltip-content="{{.locale.Tr "explore.relevant_repositories_tooltip"}}">{{.locale.Tr "explore.relevant_repositories" ((printf "%s%s" $.Link "?only_show_relevant=0")|Escape) | Safe}}</span>
</div> </div>
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>

View File

@ -21,4 +21,4 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>

View File

@ -321,7 +321,7 @@
</div> </div>
</details> </details>
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline field"> <div class="inline field">
<label></label> <label></label>
<button class="ui primary button">{{.locale.Tr "install.install_btn_confirm"}}</button> <button class="ui primary button">{{.locale.Tr "install.install_btn_confirm"}}</button>

View File

@ -51,7 +51,7 @@
<a class="ui green button" href="{{AppSubUrl}}/repo/migrate?org={{.Org.ID}}&mirror=1">{{.locale.Tr "new_migrate"}}</a> <a class="ui green button" href="{{AppSubUrl}}/repo/migrate?org={{.Org.ID}}&mirror=1">{{.locale.Tr "new_migrate"}}</a>
{{end}} {{end}}
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
<h4 class="ui top attached header gt-df"> <h4 class="ui top attached header gt-df">
<strong class="gt-f1">{{.locale.Tr "org.members"}}</strong> <strong class="gt-f1">{{.locale.Tr "org.members"}}</strong>

View File

@ -10,7 +10,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
{{template "repo/issue/labels/label_new" .}} {{template "repo/issue/labels/label_new" .}}
{{template "repo/issue/labels/label_list" .}} {{template "repo/issue/labels/label_list" .}}
</div> </div>

View File

@ -31,7 +31,7 @@
<input id="location" name="location" value="{{.Org.Location}}" maxlength="50"> <input id="location" name="location" value="{{.Org.Location}}" maxlength="50">
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div class="field" id="visibility_box"> <div class="field" id="visibility_box">
<label for="visibility">{{.locale.Tr "org.settings.visibility"}}</label> <label for="visibility">{{.locale.Tr "org.settings.visibility"}}</label>
<div class="field"> <div class="field">
@ -65,7 +65,7 @@
</div> </div>
{{if .SignedUser.IsAdmin}} {{if .SignedUser.IsAdmin}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline field {{if .Err_MaxRepoCreation}}error{{end}}"> <div class="inline field {{if .Err_MaxRepoCreation}}error{{end}}">
<label for="max_repo_creation">{{.locale.Tr "admin.users.max_repo_creation"}}</label> <label for="max_repo_creation">{{.locale.Tr "admin.users.max_repo_creation"}}</label>
@ -79,7 +79,7 @@
</div> </div>
</form> </form>
<div class="ui divider"></div> <div class="divider"></div>
<form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data"> <form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}

View File

@ -69,7 +69,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div class="team-units required grouped field {{if eq .Team.AccessMode 3}}gt-hidden{{end}}"> <div class="team-units required grouped field {{if eq .Team.AccessMode 3}}gt-hidden{{end}}">
<label>{{.locale.Tr "org.team_unit_desc"}}</label> <label>{{.locale.Tr "org.team_unit_desc"}}</label>

View File

@ -7,7 +7,7 @@
<div class="text right"> <div class="text right">
<a class="ui green button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus"}} {{.locale.Tr "org.create_new_team"}}</a> <a class="ui green button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus"}} {{.locale.Tr "org.create_new_team"}}</a>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
<div class="ui two column stackable grid"> <div class="ui two column stackable grid">

View File

@ -23,7 +23,7 @@
<input type="checkbox" name="match_full_name" {{if .CleanupRule.MatchFullName}}checked{{end}}> <input type="checkbox" name="match_full_name" {{if .CleanupRule.MatchFullName}}checked{{end}}>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<p>{{.locale.Tr "packages.owner.settings.cleanuprules.keep.title"}}</p> <p>{{.locale.Tr "packages.owner.settings.cleanuprules.keep.title"}}</p>
<div class="field {{if .Err_KeepCount}}error{{end}}"> <div class="field {{if .Err_KeepCount}}error{{end}}">
<label>{{.locale.Tr "packages.owner.settings.cleanuprules.keep.count"}}:</label> <label>{{.locale.Tr "packages.owner.settings.cleanuprules.keep.count"}}:</label>
@ -42,7 +42,7 @@
<input name="keep_pattern" type="text" value="{{.CleanupRule.KeepPattern}}"> <input name="keep_pattern" type="text" value="{{.CleanupRule.KeepPattern}}">
<p>{{.locale.Tr "packages.owner.settings.cleanuprules.keep.pattern.container" | Safe}}</p> <p>{{.locale.Tr "packages.owner.settings.cleanuprules.keep.pattern.container" | Safe}}</p>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<p>{{.locale.Tr "packages.owner.settings.cleanuprules.remove.title"}}</p> <p>{{.locale.Tr "packages.owner.settings.cleanuprules.remove.title"}}</p>
<div class="field {{if .Err_RemoveDays}}error{{end}}"> <div class="field {{if .Err_RemoveDays}}error{{end}}">
<label>{{.locale.Tr "packages.owner.settings.cleanuprules.remove.days"}}:</label> <label>{{.locale.Tr "packages.owner.settings.cleanuprules.remove.days"}}:</label>

View File

@ -73,7 +73,7 @@
{{end}} {{end}}
</div> </div>
{{if not (eq .PackageDescriptor.Package.Type "container")}} {{if not (eq .PackageDescriptor.Package.Type "container")}}
<div class="ui divider"></div> <div class="divider"></div>
<strong>{{.locale.Tr "packages.assets"}} ({{len .PackageDescriptor.Files}})</strong> <strong>{{.locale.Tr "packages.assets"}} ({{len .PackageDescriptor.Files}})</strong>
<div class="ui relaxed list"> <div class="ui relaxed list">
{{range .PackageDescriptor.Files}} {{range .PackageDescriptor.Files}}
@ -84,21 +84,19 @@
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
{{if .LatestVersions}} <div class="divider"></div>
<div class="ui divider"></div> <strong>{{.locale.Tr "packages.versions"}} ({{.TotalVersionCount}})</strong>
<strong>{{.locale.Tr "packages.versions"}} ({{.TotalVersionCount}})</strong> <a class="ui right" href="{{$.PackageDescriptor.PackageWebLink}}/versions">{{.locale.Tr "packages.versions.view_all"}}</a>
<a class="ui right" href="{{$.PackageDescriptor.PackageWebLink}}/versions">{{.locale.Tr "packages.versions.view_all"}}</a> <div class="ui relaxed list">
<div class="ui relaxed list"> {{range .LatestVersions}}
{{range .LatestVersions}} <div class="item gt-df">
<div class="item gt-df"> <a class="gt-f1" href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .LowerVersion}}">{{.Version}}</a>
<a class="gt-f1" href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .LowerVersion}}">{{.Version}}</a> <span class="text small">{{DateTime "short" .CreatedUnix}}</span>
<span class="text small">{{DateTime "short" .CreatedUnix}}</span>
</div>
{{end}}
</div> </div>
{{end}} {{end}}
</div>
{{if or .CanWritePackages .HasRepositoryAccess}} {{if or .CanWritePackages .HasRepositoryAccess}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui relaxed list"> <div class="ui relaxed list">
{{if .HasRepositoryAccess}} {{if .HasRepositoryAccess}}
<div class="item">{{svg "octicon-issue-opened" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{.locale.Tr "repo.issues"}}</a></div> <div class="item">{{svg "octicon-issue-opened" 16 "gt-mr-3"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{.locale.Tr "repo.issues"}}</a></div>

View File

@ -6,7 +6,7 @@
<a class="ui small green button" href="{{$.Link}}/new">{{.locale.Tr "repo.projects.new"}}</a> <a class="ui small green button" href="{{$.Link}}/new">{{.locale.Tr "repo.projects.new"}}</a>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{template "base/alert" .}} {{template "base/alert" .}}

View File

@ -56,7 +56,7 @@
</div> </div>
</div> </div>
<div class="ui container"> <div class="ui container">
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui left"> <div class="ui left">
<a class="ui cancel button" href="{{$.CancelLink}}"> <a class="ui cancel button" href="{{$.CancelLink}}">
{{.locale.Tr "repo.milestones.cancel"}} {{.locale.Tr "repo.milestones.cancel"}}

View File

@ -37,7 +37,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui two column stackable grid"> <div class="ui two column stackable grid">
<div class="column"> <div class="column">
<h2 class="project-title">{{$.Project.Title}}</h2> <h2 class="project-title">{{$.Project.Title}}</h2>
@ -69,7 +69,7 @@
</div> </div>
{{end}} {{end}}
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
</div> </div>
<div class="ui container fluid padded" id="project-board"> <div class="ui container fluid padded" id="project-board">
@ -175,7 +175,7 @@
</div> </div>
{{end}} {{end}}
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui cards board" data-url="{{$.Link}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}"> <div class="ui cards board" data-url="{{$.Link}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}">

View File

@ -23,7 +23,7 @@
</div> </div>
</div> </div>
</h2> </h2>
<div class="ui divider"></div> <div class="divider"></div>
{{if (or (.Permission.CanRead $.UnitTypeIssues) (.Permission.CanRead $.UnitTypePullRequests))}} {{if (or (.Permission.CanRead $.UnitTypeIssues) (.Permission.CanRead $.UnitTypePullRequests))}}
<h4 class="ui top attached header">{{.locale.Tr "repo.activity.overview"}}</h4> <h4 class="ui top attached header">{{.locale.Tr "repo.activity.overview"}}</h4>
@ -115,8 +115,8 @@
{{end}} {{end}}
{{if gt .Activity.PublishedReleaseCount 0}} {{if gt .Activity.PublishedReleaseCount 0}}
<h4 class="ui horizontal divider header" id="published-releases"> <h4 class="divider divider-text gt-normal-case" id="published-releases">
<span class="text">{{svg "octicon-tag"}}</span> {{svg "octicon-tag" 16 "gt-mr-3"}}
{{.locale.Tr "repo.activity.title.releases_published_by" {{.locale.Tr "repo.activity.title.releases_published_by"
(.locale.TrN .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n" .Activity.PublishedReleaseCount) (.locale.TrN .Activity.PublishedReleaseCount "repo.activity.title.releases_1" "repo.activity.title.releases_n" .Activity.PublishedReleaseCount)
(.locale.TrN .Activity.PublishedReleaseAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.PublishedReleaseAuthorCount) (.locale.TrN .Activity.PublishedReleaseAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.PublishedReleaseAuthorCount)
@ -137,8 +137,8 @@
{{end}} {{end}}
{{if gt .Activity.MergedPRCount 0}} {{if gt .Activity.MergedPRCount 0}}
<h4 class="ui horizontal divider header" id="merged-pull-requests"> <h4 class="divider divider-text gt-normal-case" id="merged-pull-requests">
<span class="text">{{svg "octicon-git-pull-request"}}</span> {{svg "octicon-git-pull-request" 16 "gt-mr-3"}}
{{.locale.Tr "repo.activity.title.prs_merged_by" {{.locale.Tr "repo.activity.title.prs_merged_by"
(.locale.TrN .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.MergedPRCount) (.locale.TrN .Activity.MergedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.MergedPRCount)
(.locale.TrN .Activity.MergedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.MergedPRAuthorCount) (.locale.TrN .Activity.MergedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.MergedPRAuthorCount)
@ -156,8 +156,8 @@
{{end}} {{end}}
{{if gt .Activity.OpenedPRCount 0}} {{if gt .Activity.OpenedPRCount 0}}
<h4 class="ui horizontal divider header" id="proposed-pull-requests"> <h4 class="divider divider-text gt-normal-case" id="proposed-pull-requests">
<span class="text">{{svg "octicon-git-branch"}}</span> {{svg "octicon-git-branch" 16 "gt-mr-3"}}
{{.locale.Tr "repo.activity.title.prs_opened_by" {{.locale.Tr "repo.activity.title.prs_opened_by"
(.locale.TrN .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.OpenedPRCount) (.locale.TrN .Activity.OpenedPRCount "repo.activity.title.prs_1" "repo.activity.title.prs_n" .Activity.OpenedPRCount)
(.locale.TrN .Activity.OpenedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedPRAuthorCount) (.locale.TrN .Activity.OpenedPRAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedPRAuthorCount)
@ -175,8 +175,8 @@
{{end}} {{end}}
{{if gt .Activity.ClosedIssueCount 0}} {{if gt .Activity.ClosedIssueCount 0}}
<h4 class="ui horizontal divider header" id="closed-issues"> <h4 class="divider divider-text gt-normal-case" id="closed-issues">
<span class="text">{{svg "octicon-issue-closed"}}</span> {{svg "octicon-issue-closed" 16 "gt-mr-3"}}
{{.locale.Tr "repo.activity.title.issues_closed_from" {{.locale.Tr "repo.activity.title.issues_closed_from"
(.locale.TrN .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.ClosedIssueCount) (.locale.TrN .Activity.ClosedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.ClosedIssueCount)
(.locale.TrN .Activity.ClosedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.ClosedIssueAuthorCount) (.locale.TrN .Activity.ClosedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.ClosedIssueAuthorCount)
@ -194,8 +194,8 @@
{{end}} {{end}}
{{if gt .Activity.OpenedIssueCount 0}} {{if gt .Activity.OpenedIssueCount 0}}
<h4 class="ui horizontal divider header" id="new-issues"> <h4 class="divider divider-text gt-normal-case" id="new-issues">
<span class="text">{{svg "octicon-issue-opened"}}</span> {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
{{.locale.Tr "repo.activity.title.issues_created_by" {{.locale.Tr "repo.activity.title.issues_created_by"
(.locale.TrN .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.OpenedIssueCount) (.locale.TrN .Activity.OpenedIssueCount "repo.activity.title.issues_1" "repo.activity.title.issues_n" .Activity.OpenedIssueCount)
(.locale.TrN .Activity.OpenedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedIssueAuthorCount) (.locale.TrN .Activity.OpenedIssueAuthorCount "repo.activity.title.user_1" "repo.activity.title.user_n" .Activity.OpenedIssueAuthorCount)
@ -213,13 +213,10 @@
{{end}} {{end}}
{{if gt .Activity.UnresolvedIssueCount 0}} {{if gt .Activity.UnresolvedIssueCount 0}}
<h4 class="ui horizontal divider header" id="unresolved-conversations"> <h4 class="divider divider-text gt-normal-case" id="unresolved-conversations" data-tooltip-content="{{.locale.Tr "repo.activity.unresolved_conv_desc"}}">
<span class="text">{{svg "octicon-comment-discussion"}}</span> {{svg "octicon-comment-discussion" 16 "gt-mr-3"}}
{{.locale.TrN .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n" .Activity.UnresolvedIssueCount}} {{.locale.TrN .Activity.UnresolvedIssueCount "repo.activity.title.unresolved_conv_1" "repo.activity.title.unresolved_conv_n" .Activity.UnresolvedIssueCount}}
</h4> </h4>
<div class="text center desc">
{{.locale.Tr "repo.activity.unresolved_conv_desc"}}
</div>
<div class="list"> <div class="list">
{{range .Activity.UnresolvedIssues}} {{range .Activity.UnresolvedIssues}}
<p class="desc"> <p class="desc">

View File

@ -22,29 +22,29 @@
{{if .DefaultBranchBranch.IsProtected}} {{if .DefaultBranchBranch.IsProtected}}
{{svg "octicon-shield-lock"}} {{svg "octicon-shield-lock"}}
{{end}} {{end}}
<a href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranch}}">{{.DefaultBranch}}</a> <a href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a>
<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.Commit.ID.String}}">{{ShortSha .DefaultBranchBranch.Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.Commit.CommitMessage .RepoLink .Repository.ComposeMetas}}</span> · {{.locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.Commit.Committer.When .locale}}</p> <p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.DBBranch.CommitMessage .RepoLink .Repository.ComposeMetas}}</span> · {{.locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.DBBranch.CommitTime.AsTime .locale}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "Context" $.Context "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
</td> </td>
<td class="right aligned overflow-visible"> <td class="right aligned overflow-visible">
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}} {{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
<button class="btn interact-bg show-create-branch-modal gt-p-3" <button class="btn interact-bg show-create-branch-modal gt-p-3"
data-modal="#create-branch-modal" data-modal="#create-branch-modal"
data-branch-from="{{$.DefaultBranch}}" data-branch-from="{{$.DefaultBranchBranch}}"
data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranch}}" data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}"
data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" ($.DefaultBranch)}}" data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" ($.DefaultBranchBranch.DBBranch.Name)}}"
> >
{{svg "octicon-git-branch"}} {{svg "octicon-git-branch"}}
</button> </button>
{{end}} {{end}}
{{if .EnableFeed}} {{if .EnableFeed}}
<a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranch}}">{{svg "octicon-rss"}}</a> <a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-rss"}}</a>
{{end}} {{end}}
{{if not $.DisableDownloadSourceArchives}} {{if not $.DisableDownloadSourceArchives}}
<div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranch)}}"> <div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranchBranch.DBBranch.Name)}}">
{{svg "octicon-download"}} {{svg "octicon-download"}}
<div class="menu"> <div class="menu">
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a> <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a> <a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a>
</div> </div>
</div> </div>
{{end}} {{end}}
@ -52,8 +52,8 @@
<button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal" <button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
data-is-default-branch="true" data-is-default-branch="true"
data-modal="#rename-branch-modal" data-modal="#rename-branch-modal"
data-old-branch-name="{{$.DefaultBranch}}" data-old-branch-name="{{$.DefaultBranchBranch}}"
data-tooltip-content="{{$.locale.Tr "repo.branch.rename" ($.DefaultBranch)}}" data-tooltip-content="{{$.locale.Tr "repo.branch.rename" ($.DefaultBranchBranch.DBBranch.Name)}}"
> >
{{svg "octicon-pencil"}} {{svg "octicon-pencil"}}
</button> </button>
@ -65,7 +65,7 @@
</div> </div>
{{end}} {{end}}
{{if gt (len .Branches) 1}} {{if .Branches}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.locale.Tr "repo.branches"}} {{.locale.Tr "repo.branches"}}
</h4> </h4>
@ -73,112 +73,110 @@
<table class="ui very basic striped fixed table single line"> <table class="ui very basic striped fixed table single line">
<tbody> <tbody>
{{range .Branches}} {{range .Branches}}
{{if ne .Name $.DefaultBranch}} <tr>
<tr> <td class="eight wide">
<td class="six wide"> {{if .DBBranch.IsDeleted}}
{{if .IsDeleted}} <s><a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a></s>
<s><a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a></s> <p class="info">{{$.locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{TimeSinceUnix .DBBranch.DeletedUnix $.locale}}</p>
<p class="info">{{$.locale.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSinceUnix .DeletedBranch.DeletedUnix $.locale}}</p> {{else}}
{{else}} {{if .IsProtected}}
{{if .IsProtected}} {{svg "octicon-shield-lock"}}
{{svg "octicon-shield-lock"}}
{{end}}
<a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a>
<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .Commit.ID.String}}">{{ShortSha .Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .Commit.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.locale.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.locale}}</p>
{{end}} {{end}}
</td> <a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
<td class="three wide ui"> <p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DBBranch.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.locale.Tr "org.repo_updated"}} {{TimeSince .DBBranch.CommitTime.AsTime $.locale}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "Context" $.Context "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
{{if and (not .IsDeleted) $.DefaultBranchBranch}} {{end}}
<div class="commit-divergence"> </td>
<div class="bar-group"> <td class="two wide ui">
<div class="count count-behind">{{.CommitsBehind}}</div> {{if and (not .DBBranch.IsDeleted) $.DefaultBranchBranch}}
{{/* old code bears 0/0.0 = NaN output, so it might output invalid "width: NaNpx", it just works and doesn't caues any problem. */}} <div class="commit-divergence">
<div class="bar bar-behind" style="width: {{Eval 100 "*" .CommitsBehind "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div> <div class="bar-group">
</div> <div class="count count-behind">{{.CommitsBehind}}</div>
<div class="bar-group"> {{/* old code bears 0/0.0 = NaN output, so it might output invalid "width: NaNpx", it just works and doesn't caues any problem. */}}
<div class="count count-ahead">{{.CommitsAhead}}</div> <div class="bar bar-behind" style="width: {{Eval 100 "*" .CommitsBehind "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
<div class="bar bar-ahead" style="width: {{Eval 100 "*" .CommitsAhead "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div> </div>
<div class="bar-group">
<div class="count count-ahead">{{.CommitsAhead}}</div>
<div class="bar bar-ahead" style="width: {{Eval 100 "*" .CommitsAhead "/" "(" .CommitsBehind "+" .CommitsAhead "+" 0.0 ")"}}%"></div>
</div>
</div>
{{end}}
</td>
<td class="two wide right aligned">
{{if not .LatestPullRequest}}
{{if .IsIncluded}}
<span class="ui orange large label" data-tooltip-content="{{$.locale.Tr "repo.branch.included_desc"}}">
{{svg "octicon-git-pull-request"}} {{$.locale.Tr "repo.branch.included"}}
</span>
{{else if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch.DBBranch.Name}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .DBBranch.Name}}">
<button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{end}}
{{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
{{if and (not .DBBranch.IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranchBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | PathEscapeSegments}}">
<button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{end}}
{{else}}
<a href="{{.LatestPullRequest.Issue.Link}}" class="gt-vm ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
{{if .LatestPullRequest.HasMerged}}
<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label purple large label gt-vm">{{svg "octicon-git-merge" 16 "gt-mr-2"}}{{$.locale.Tr "repo.pulls.merged"}}</a>
{{else if .LatestPullRequest.Issue.IsClosed}}
<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label red large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.closed_title"}}</a>
{{else}}
<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label green large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.open_title"}}</a>
{{end}}
{{end}}
</td>
<td class="three wide right aligned overflow-visible">
{{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted)}}
<button class="btn interact-bg gt-p-3 show-modal show-create-branch-modal"
data-branch-from="{{.DBBranch.Name}}"
data-branch-from-urlcomponent="{{PathEscapeSegments .DBBranch.Name}}"
data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" .DBBranch.Name}}"
data-modal="#create-branch-modal" data-name="{{.DBBranch.Name}}"
>
{{svg "octicon-git-branch"}}
</button>
{{end}}
{{if $.EnableFeed}}
<a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DBBranch.Name}}">{{svg "octicon-rss"}}</a>
{{end}}
{{if and (not .DBBranch.IsDeleted) (not $.DisableDownloadSourceArchives)}}
<div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.DBBranch.Name)}}">
{{svg "octicon-download"}}
<div class="menu">
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .DBBranch.Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .DBBranch.Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a>
</div> </div>
</div> </div>
{{end}} {{end}}
</td> {{if and $.IsWriter (not $.Repository.IsArchived) (not .DBBranch.IsDeleted) (not $.IsMirror)}}
<td class="three wide right aligned"> <button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
{{if not .LatestPullRequest}} data-is-default-branch="false"
{{if .IsIncluded}} data-old-branch-name="{{.DBBranch.Name}}"
<span class="ui orange large label" data-tooltip-content="{{$.locale.Tr "repo.branch.included_desc"}}"> data-modal="#rename-branch-modal"
{{svg "octicon-git-pull-request"}} {{$.locale.Tr "repo.branch.included"}} data-tooltip-content="{{$.locale.Tr "repo.branch.rename" (.DBBranch.Name)}}"
>
{{svg "octicon-pencil"}}
</button>
{{end}}
{{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
{{if .DBBranch.IsDeleted}}
<button class="btn interact-bg gt-p-3 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DBBranch.ID}}&name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.restore" (.DBBranch.Name)}}">
<span class="text blue">
{{svg "octicon-reply"}}
</span> </span>
{{else if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} </button>
<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .Name}}">
<button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{end}}
{{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
{{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | PathEscapeSegments}}">
<button id="new-pull-request" class="ui compact basic button gt-mr-0">{{if $.CanPull}}{{$.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{$.locale.Tr "action.compare_branch"}}{{end}}</button>
</a>
{{end}}
{{else}} {{else}}
<a href="{{.LatestPullRequest.Issue.Link}}" class="gt-vm ref-issue">{{if not .LatestPullRequest.IsSameRepo}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a> <button class="btn interact-bg gt-p-3 delete-button delete-branch-button" data-url="{{$.Link}}/delete?name={{.DBBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.delete" (.DBBranch.Name)}}" data-name="{{.DBBranch.Name}}">
{{if .LatestPullRequest.HasMerged}} {{svg "octicon-trash"}}
<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label purple large label gt-vm">{{svg "octicon-git-merge" 16 "gt-mr-2"}}{{$.locale.Tr "repo.pulls.merged"}}</a>
{{else if .LatestPullRequest.Issue.IsClosed}}
<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label red large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.closed_title"}}</a>
{{else}}
<a href="{{.LatestPullRequest.Issue.Link}}" class="ui text-label green large label gt-vm">{{svg "octicon-git-pull-request" 16 "gt-mr-2"}}{{$.locale.Tr "repo.issues.open_title"}}</a>
{{end}}
{{end}}
</td>
<td class="three wide right aligned overflow-visible">
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
<button class="btn interact-bg gt-p-3 show-modal show-create-branch-modal"
data-branch-from="{{.Name}}"
data-branch-from-urlcomponent="{{PathEscapeSegments .Name}}"
data-tooltip-content="{{$.locale.Tr "repo.branch.new_branch_from" .Name}}"
data-modal="#create-branch-modal" data-name="{{.Name}}"
>
{{svg "octicon-git-branch"}}
</button> </button>
{{end}} {{end}}
{{if $.EnableFeed}} {{end}}
<a role="button" class="btn interact-bg gt-p-3" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .Name}}">{{svg "octicon-rss"}}</a> </td>
{{end}} </tr>
{{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}}
<div class="ui dropdown btn interact-bg gt-p-3" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.Name)}}">
{{svg "octicon-download"}}
<div class="menu">
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a>
</div>
</div>
{{end}}
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted) (not $.IsMirror)}}
<button class="btn interact-bg gt-p-3 show-modal show-rename-branch-modal"
data-is-default-branch="false"
data-old-branch-name="{{.Name}}"
data-modal="#rename-branch-modal"
data-tooltip-content="{{$.locale.Tr "repo.branch.rename" (.Name)}}"
>
{{svg "octicon-pencil"}}
</button>
{{end}}
{{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
{{if .IsDeleted}}
<button class="btn interact-bg gt-p-3 link-action restore-branch-button" data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID}}&name={{.DeletedBranch.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.restore" (.Name)}}">
<span class="text blue">
{{svg "octicon-reply"}}
</span>
</button>
{{else}}
<button class="btn interact-bg gt-p-3 delete-button delete-branch-button" data-url="{{$.Link}}/delete?name={{.Name}}&page={{$.Page.Paginater.Current}}" data-tooltip-content="{{$.locale.Tr "repo.branch.delete" (.Name)}}" data-name="{{.Name}}">
{{svg "octicon-trash"}}
</button>
{{end}}
{{end}}
</td>
</tr>
{{end}}
{{end}} {{end}}
</tbody> </tbody>
</table> </table>

View File

@ -124,7 +124,7 @@
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline field"> <div class="inline field">
<label>.gitignore</label> <label>.gitignore</label>

View File

@ -28,7 +28,7 @@
{{template "repo/upload" .}} {{template "repo/upload" .}}
</div> </div>
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>
{{$showSelfTooltip := (and $.IsSigned ($.Issue.IsPoster $.SignedUser.ID))}} {{$showSelfTooltip := (and $.IsSigned ($.Issue.IsPoster $.SignedUser.ID))}}
{{if $showSelfTooltip}} {{if $showSelfTooltip}}
<span class="gt-dib" data-tooltip-content="{{$.locale.Tr "repo.diff.review.self_approve"}}"> <span class="gt-dib" data-tooltip-content="{{$.locale.Tr "repo.diff.review.self_approve"}}">

View File

@ -44,7 +44,7 @@
</div> </div>
{{if not .Repository.IsArchived}} {{if not .Repository.IsArchived}}
<div class="ui divider gt-my-0"></div> <div class="divider gt-my-0"></div>
<div class="item"> <div class="item">
<h3>{{.locale.Tr "repo.create_new_repo_command"}}</h3> <h3>{{.locale.Tr "repo.create_new_repo_command"}}</h3>
@ -58,7 +58,7 @@ git remote add origin <span class="js-clone-url">{{$.CloneButtonOriginLink.HTTPS
git push -u origin {{.Repository.DefaultBranch}}</code></pre> git push -u origin {{.Repository.DefaultBranch}}</code></pre>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div class="item"> <div class="item">
<h3>{{.locale.Tr "repo.push_exist_repo"}}</h3> <h3>{{.locale.Tr "repo.push_exist_repo"}}</h3>

View File

@ -132,7 +132,7 @@
{{end}} {{end}}
</div> </div>
{{if $.CanSignedUserFork}} {{if $.CanSignedUserFork}}
<div class="ui divider"></div> <div class="divider"></div>
<a href="{{AppSubUrl}}/repo/fork/{{.ID}}"> <a href="{{AppSubUrl}}/repo/fork/{{.ID}}">
{{$.locale.Tr "repo.fork_to_different_account"}} {{$.locale.Tr "repo.fork_to_different_account"}}
</a> </a>

View File

@ -50,5 +50,5 @@
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}

View File

@ -6,7 +6,7 @@
<div class="navbar"> <div class="navbar">
{{template "repo/issue/navbar" .}} {{template "repo/issue/navbar" .}}
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
{{range .IssueTemplates}} {{range .IssueTemplates}}
<div class="ui attached segment"> <div class="ui attached segment">
<div class="ui two column grid"> <div class="ui two column grid">

View File

@ -22,10 +22,10 @@
<div class="ui attached segment"> <div class="ui attached segment">
{{if and (not $.PageIsOrgSettingsLabels) (or $.CanWriteIssues $.CanWritePulls) (eq .NumLabels 0) (not $.Repository.IsArchived)}} {{if and (not $.PageIsOrgSettingsLabels) (or $.CanWriteIssues $.CanWritePulls) (eq .NumLabels 0) (not $.Repository.IsArchived)}}
{{template "repo/issue/labels/label_load_template" .}} {{template "repo/issue/labels/label_load_template" .}}
<div class="ui divider"></div> <div class="divider"></div>
{{else if and ($.PageIsOrgSettingsLabels) (eq .NumLabels 0)}} {{else if and ($.PageIsOrgSettingsLabels) (eq .NumLabels 0)}}
{{template "repo/issue/labels/label_load_template" .}} {{template "repo/issue/labels/label_load_template" .}}
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
<ul class="issue-label-list"> <ul class="issue-label-list">

View File

@ -18,18 +18,18 @@
{{range .Labels}} {{range .Labels}}
{{$exclusiveScope := .ExclusiveScope}} {{$exclusiveScope := .ExclusiveScope}}
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}} {{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{$previousExclusiveScope = $exclusiveScope}} {{$previousExclusiveScope = $exclusiveScope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel $.Context .}} <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel $.Context .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}</a> {{if .Description}}<br><small class="desc">{{.Description | RenderEmoji $.Context}}</small>{{end}}</a>
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>
{{$previousExclusiveScope := "_no_scope"}} {{$previousExclusiveScope := "_no_scope"}}
{{range .OrgLabels}} {{range .OrgLabels}}
{{$exclusiveScope := .ExclusiveScope}} {{$exclusiveScope := .ExclusiveScope}}
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}} {{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{$previousExclusiveScope = $exclusiveScope}} {{$previousExclusiveScope = $exclusiveScope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel $.Context .}} <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$exclusiveScope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel $.Context .}}

View File

@ -102,14 +102,14 @@
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_label"}}"> <input type="text" placeholder="{{.locale.Tr "repo.issues.filter_label"}}">
</div> </div>
<span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span> <span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
<div class="ui divider"></div> <div class="divider"></div>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=0&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_select_no_label"}}</a> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels=0&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_select_no_label"}}</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a> <a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a>
{{$previousExclusiveScope := "_no_scope"}} {{$previousExclusiveScope := "_no_scope"}}
{{range .Labels}} {{range .Labels}}
{{$exclusiveScope := .ExclusiveScope}} {{$exclusiveScope := .ExclusiveScope}}
{{if and (ne $previousExclusiveScope $exclusiveScope)}} {{if and (ne $previousExclusiveScope $exclusiveScope)}}
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{$previousExclusiveScope = $exclusiveScope}} {{$previousExclusiveScope = $exclusiveScope}}
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel $.Context .}}</a> <a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel $.Context .}}</a>
@ -306,7 +306,7 @@
{{range .Labels}} {{range .Labels}}
{{$exclusiveScope := .ExclusiveScope}} {{$exclusiveScope := .ExclusiveScope}}
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}} {{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{$previousExclusiveScope = $exclusiveScope}} {{$previousExclusiveScope = $exclusiveScope}}
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels"> <div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">

View File

@ -48,7 +48,7 @@
<div class="gt-mr-3">{{.locale.Tr "repo.milestones.completeness" .Milestone.Completeness | Safe}}</div> <div class="gt-mr-3">{{.locale.Tr "repo.milestones.completeness" .Milestone.Completeness | Safe}}</div>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div id="issue-filters" class="issue-list-toolbar"> <div id="issue-filters" class="issue-list-toolbar">
<div class="issue-list-toolbar-left"> <div class="issue-list-toolbar-left">

View File

@ -10,7 +10,7 @@
</div> </div>
{{end}} {{end}}
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<h2 class="ui dividing header"> <h2 class="ui dividing header">
{{if .PageIsEditMilestone}} {{if .PageIsEditMilestone}}
{{.locale.Tr "repo.milestones.edit"}} {{.locale.Tr "repo.milestones.edit"}}
@ -41,7 +41,7 @@
</div> </div>
</div> </div>
<div class="ui container"> <div class="ui container">
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui right"> <div class="ui right">
{{if .PageIsEditMilestone}} {{if .PageIsEditMilestone}}
<a class="ui primary basic button" href="{{.RepoLink}}/milestones"> <a class="ui primary basic button" href="{{.RepoLink}}/milestones">

View File

@ -114,7 +114,7 @@
{{else}} {{else}}
<a class="link-action flex-text-inline" href data-url="{{$.Link}}/{{.ID}}/close">{{svg "octicon-x" 14}}{{$.locale.Tr "repo.milestones.close"}}</a> <a class="link-action flex-text-inline" href data-url="{{$.Link}}/{{.ID}}/close">{{svg "octicon-x" 14}}{{$.locale.Tr "repo.milestones.close"}}</a>
{{end}} {{end}}
<a class="delete-button flex-text-inline" href="#" data-url="{{$.RepoLink}}/milestones/delete">{{svg "octicon-trash" 14}}{{$.locale.Tr "repo.issues.label_delete"}}</a> <a class="delete-button flex-text-inline" href="#" data-url="{{$.RepoLink}}/milestones/delete" data-id="{{.ID}}">{{svg "octicon-trash" 14}}{{$.locale.Tr "repo.issues.label_delete"}}</a>
</div> </div>
{{end}} {{end}}
</div> </div>

View File

@ -55,7 +55,7 @@
{{template "repo/issue/labels/labels_selector_field" .}} {{template "repo/issue/labels/labels_selector_field" .}}
{{template "repo/issue/labels/labels_sidebar" dict "root" $}} {{template "repo/issue/labels/labels_sidebar" dict "root" $}}
<div class="ui divider"></div> <div class="divider"></div>
<input id="milestone_id" name="milestone_id" type="hidden" value="{{.milestone_id}}"> <input id="milestone_id" name="milestone_id" type="hidden" value="{{.milestone_id}}">
<div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-milestone dropdown"> <div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-milestone dropdown">
@ -82,7 +82,7 @@
</div> </div>
{{if .IsProjectsEnabled}} {{if .IsProjectsEnabled}}
<div class="ui divider"></div> <div class="divider"></div>
<input id="project_id" name="project_id" type="hidden" value="{{.project_id}}"> <input id="project_id" name="project_id" type="hidden" value="{{.project_id}}">
<div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-project dropdown"> <div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-project dropdown">
@ -141,7 +141,7 @@
</div> </div>
</div> </div>
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>
<input id="assignee_ids" name="assignee_ids" type="hidden" value="{{.assignee_ids}}"> <input id="assignee_ids" name="assignee_ids" type="hidden" value="{{.assignee_ids}}">
<div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-assignees dropdown"> <div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-assignees dropdown">
<span class="text"> <span class="text">
@ -177,7 +177,7 @@
{{end}} {{end}}
</div> </div>
{{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}} {{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label data-tooltip-content="{{.locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"><strong>{{.locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label> <label data-tooltip-content="{{.locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"><strong>{{.locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label>

View File

@ -1,6 +1,6 @@
<div class="dropzone-attachments"> <div class="dropzone-attachments">
{{if .Attachments}} {{if .Attachments}}
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{$hasThumbnails := false}} {{$hasThumbnails := false}}
{{- range .Attachments -}} {{- range .Attachments -}}
@ -25,7 +25,7 @@
{{end -}} {{end -}}
{{if $hasThumbnails}} {{if $hasThumbnails}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui small thumbnails"> <div class="ui small thumbnails">
{{- range .Attachments -}} {{- range .Attachments -}}
{{if FilenameIsImage .Name}} {{if FilenameIsImage .Name}}

View File

@ -179,7 +179,7 @@
{{end}} {{end}}
{{template "repo/issue/view_content/update_branch_by_merge" $}} {{template "repo/issue/view_content/update_branch_by_merge" $}}
{{if .Issue.PullRequest.IsEmpty}} {{if .Issue.PullRequest.IsEmpty}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="item"> <div class="item">
{{svg "octicon-alert"}} {{svg "octicon-alert"}}
@ -196,7 +196,7 @@
{{$createdPRMergeStr := TimeSinceUnix .PendingPullRequestMerge.CreatedUnix $.locale}} {{$createdPRMergeStr := TimeSinceUnix .PendingPullRequestMerge.CreatedUnix $.locale}}
{{$hasPendingPullRequestMergeTip = $.locale.Tr "repo.pulls.auto_merge_has_pending_schedule" .PendingPullRequestMerge.Doer.Name $createdPRMergeStr}} {{$hasPendingPullRequestMergeTip = $.locale.Tr "repo.pulls.auto_merge_has_pending_schedule" .PendingPullRequestMerge.Doer.Name $createdPRMergeStr}}
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>
<script type="module"> <script type="module">
const issueUrl = window.location.origin + {{$.Issue.Link}}; const issueUrl = window.location.origin + {{$.Issue.Link}};
const defaultMergeTitle = {{.DefaultMergeMessage}}; const defaultMergeTitle = {{.DefaultMergeMessage}};
@ -276,7 +276,7 @@
<div id="pull-request-merge-form"></div> <div id="pull-request-merge-form"></div>
{{else}} {{else}}
{{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}} {{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="item text red"> <div class="item text red">
{{svg "octicon-x"}} {{svg "octicon-x"}}
{{$.locale.Tr "repo.pulls.no_merge_desc"}} {{$.locale.Tr "repo.pulls.no_merge_desc"}}
@ -288,7 +288,7 @@
{{end}} {{/* end if the repo was set to use any merge style */}} {{end}} {{/* end if the repo was set to use any merge style */}}
{{else}} {{else}}
{{/* user is not allowed to merge */}} {{/* user is not allowed to merge */}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="item"> <div class="item">
{{svg "octicon-info"}} {{svg "octicon-info"}}
{{$.locale.Tr "repo.pulls.no_merge_access"}} {{$.locale.Tr "repo.pulls.no_merge_access"}}
@ -358,7 +358,7 @@
* Then the Manually Merged form will be shown to repo admin users * Then the Manually Merged form will be shown to repo admin users
*/}} */}}
{{if and $.StillCanManualMerge (not $showGeneralMergeForm)}} {{if and $.StillCanManualMerge (not $showGeneralMergeForm)}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui form"> <div class="ui form">
<form action="{{.Link}}/merge" method="post"> <form action="{{.Link}}/merge" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}

View File

@ -1,4 +1,4 @@
<div class="ui divider"></div> <div class="divider"></div>
<div class="instruct-toggle"> {{$.locale.Tr "repo.pulls.merge_instruction_hint" | Safe}} </div> <div class="instruct-toggle"> {{$.locale.Tr "repo.pulls.merge_instruction_hint" | Safe}} </div>
<div class="instruct-content gt-mt-3 gt-hidden"> <div class="instruct-content gt-mt-3 gt-hidden">
<div><h3 class="gt-di">{{$.locale.Tr "step1"}} </h3>{{$.locale.Tr "repo.pulls.merge_instruction_step1_desc"}}</div> <div><h3 class="gt-di">{{$.locale.Tr "step1"}} </h3>{{$.locale.Tr "repo.pulls.merge_instruction_step1_desc"}}</div>

View File

@ -29,7 +29,7 @@
{{end}} {{end}}
{{end}} {{end}}
{{if .TeamReviewers}} {{if .TeamReviewers}}
<div class="ui divider"></div> <div class="divider"></div>
{{range .TeamReviewers}} {{range .TeamReviewers}}
{{if .Team}} {{if .Team}}
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{$.locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}> <a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{$.locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
@ -118,13 +118,13 @@
</a> </a>
</div> </div>
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>
{{end}} {{end}}
{{template "repo/issue/labels/labels_selector_field" .}} {{template "repo/issue/labels/labels_selector_field" .}}
{{template "repo/issue/labels/labels_sidebar" dict "root" $}} {{template "repo/issue/labels/labels_sidebar" dict "root" $}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-milestone dropdown"> <div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-milestone dropdown">
<a class="text gt-df gt-ac muted"> <a class="text gt-df gt-ac muted">
@ -150,7 +150,7 @@
</div> </div>
{{if .IsProjectsEnabled}} {{if .IsProjectsEnabled}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-project dropdown"> <div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-project dropdown">
<a class="text gt-df gt-ac muted"> <a class="text gt-df gt-ac muted">
@ -203,7 +203,7 @@
</div> </div>
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>
<input id="assignee_id" name="assignee_id" type="hidden" value="{{.assignee_id}}"> <input id="assignee_id" name="assignee_id" type="hidden" value="{{.assignee_id}}">
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-assignees-modify dropdown"> <div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-assignees-modify dropdown">
@ -251,7 +251,7 @@
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
{{if .Participants}} {{if .Participants}}
<span class="text"><strong>{{.locale.Tr "repo.issues.num_participants" .NumParticipants}}</strong></span> <span class="text"><strong>{{.locale.Tr "repo.issues.num_participants" .NumParticipants}}</strong></span>
@ -265,7 +265,7 @@
{{end}} {{end}}
{{if and $.IssueWatch (not .Repository.IsArchived)}} {{if and $.IssueWatch (not .Repository.IsArchived)}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui watching"> <div class="ui watching">
<span class="text"><strong>{{.locale.Tr "notification.notifications"}}</strong></span> <span class="text"><strong>{{.locale.Tr "notification.notifications"}}</strong></span>
@ -288,7 +288,7 @@
{{end}} {{end}}
{{if .Repository.IsTimetrackerEnabled $.Context}} {{if .Repository.IsTimetrackerEnabled $.Context}}
{{if and .CanUseTimetracker (not .Repository.IsArchived)}} {{if and .CanUseTimetracker (not .Repository.IsArchived)}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui timetrack"> <div class="ui timetrack">
<span class="text"><strong>{{.locale.Tr "repo.issues.tracker"}}</strong></span> <span class="text"><strong>{{.locale.Tr "repo.issues.tracker"}}</strong></span>
<div class="gt-mt-3"> <div class="gt-mt-3">
@ -328,7 +328,7 @@
</div> </div>
{{end}} {{end}}
{{if gt (len .WorkingUsers) 0}} {{if gt (len .WorkingUsers) 0}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui comments"> <div class="ui comments">
<span class="text"><strong>{{.locale.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Time) | Safe}}</strong></span> <span class="text"><strong>{{.locale.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Time) | Safe}}</strong></span>
<div> <div>
@ -350,7 +350,7 @@
{{end}} {{end}}
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>
<span class="text"><strong>{{.locale.Tr "repo.issues.due_date"}}</strong></span> <span class="text"><strong>{{.locale.Tr "repo.issues.due_date"}}</strong></span>
<div class="ui form" id="deadline-loader"> <div class="ui form" id="deadline-loader">
<div class="ui negative message gt-hidden" id="deadline-err-invalid-date"> <div class="ui negative message gt-hidden" id="deadline-err-invalid-date">
@ -394,7 +394,7 @@
</div> </div>
{{if .Repository.IsDependenciesEnabled $.Context}} {{if .Repository.IsDependenciesEnabled $.Context}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui depending"> <div class="ui depending">
{{if (and (not .BlockedByDependencies) (not .BlockedByDependenciesNotPermitted) (not .BlockingDependencies) (not .BlockingDependenciesNotPermitted))}} {{if (and (not .BlockedByDependencies) (not .BlockedByDependenciesNotPermitted) (not .BlockingDependencies) (not .BlockingDependenciesNotPermitted))}}
@ -543,7 +543,7 @@
{{end}} {{end}}
{{end}} {{end}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="ui equal width compact grid"> <div class="ui equal width compact grid">
{{$issueReferenceLink := printf "%s#%d" .Issue.Repo.FullName .Issue.Index}} {{$issueReferenceLink := printf "%s#%d" .Issue.Repo.FullName .Issue.Index}}
<div class="row gt-ac" data-tooltip-content="{{$issueReferenceLink}}"> <div class="row gt-ac" data-tooltip-content="{{$issueReferenceLink}}">
@ -553,10 +553,10 @@
</div> </div>
{{if and .IsRepoAdmin (not .Repository.IsArchived)}} {{if and .IsRepoAdmin (not .Repository.IsArchived)}}
<div class="ui divider"></div> <div class="divider"></div>
{{if or .PinEnabled .Issue.IsPinned}} {{if or .PinEnabled .Issue.IsPinned}}
<form class="gt-mt-2" method="post" {{if $.NewPinAllowed}}action="{{.Issue.Link}}/pin"{{else}}data-tooltip-content="{{.locale.Tr "repo.issues.max_pinned"}}"{{end}}> <form class="gt-mt-2 form-fetch-action single-button-form" method="post" {{if $.NewPinAllowed}}action="{{.Issue.Link}}/pin"{{else}}data-tooltip-content="{{.locale.Tr "repo.issues.max_pinned"}}"{{end}}>
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<button class="fluid ui button {{if not $.NewPinAllowed}}disabled{{end}}"> <button class="fluid ui button {{if not $.NewPinAllowed}}disabled{{end}}">
{{if not .Issue.IsPinned}} {{if not .Issue.IsPinned}}
@ -599,7 +599,7 @@
{{end}} {{end}}
</div> </div>
<form class="ui form" action="{{.Issue.Link}}{{if .Issue.IsLocked}}/unlock{{else}}/lock{{end}}" <form class="ui form form-fetch-action" action="{{.Issue.Link}}{{if .Issue.IsLocked}}/unlock{{else}}/lock{{end}}"
method="post"> method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
@ -673,7 +673,7 @@
{{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}} {{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}}
{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}} {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox" id="allow-edits-from-maintainers" <div class="ui checkbox" id="allow-edits-from-maintainers"
data-url="{{.Issue.Link}}" data-url="{{.Issue.Link}}"

View File

@ -1,5 +1,5 @@
{{if and (gt $.Issue.PullRequest.CommitsBehind 0) (not $.Issue.IsClosed) (not $.Issue.PullRequest.IsChecking) (not $.IsPullFilesConflicted) (not $.IsPullRequestBroken)}} {{if and (gt $.Issue.PullRequest.CommitsBehind 0) (not $.Issue.IsClosed) (not $.Issue.PullRequest.IsChecking) (not $.IsPullFilesConflicted) (not $.IsPullRequestBroken)}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="item item-section"> <div class="item item-section">
<div class="item-section-left flex-text-inline"> <div class="item-section-left flex-text-inline">
{{svg "octicon-alert"}} {{svg "octicon-alert"}}

View File

@ -55,7 +55,7 @@
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline required field {{if .Err_Owner}}error{{end}}"> <div class="inline required field {{if .Err_Owner}}error{{end}}">
<label>{{.locale.Tr "repo.owner"}}</label> <label>{{.locale.Tr "repo.owner"}}</label>

View File

@ -29,7 +29,7 @@
{{template "repo/migrate/options" .}} {{template "repo/migrate/options" .}}
<div class="ui divider"></div> <div class="divider"></div>
<div class="inline required field {{if .Err_Owner}}error{{end}}"> <div class="inline required field {{if .Err_Owner}}error{{end}}">
<label>{{.locale.Tr "repo.owner"}}</label> <label>{{.locale.Tr "repo.owner"}}</label>

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