Compare commits
29 Commits
d03cf68d82
...
06b397f79b
Author | SHA1 | Date |
---|---|---|
silverwind | 06b397f79b | |
yp05327 | f7d2f695a4 | |
wxiaoguang | 3fdb2d4ad8 | |
Zettat123 | f09e68ec33 | |
Lunny Xiao | a303c973e0 | |
Lunny Xiao | f5f921c095 | |
silverwind | d9b37d085a | |
GiteaBot | f1b0729078 | |
Kemal Zebari | 880e0b7c82 | |
wxiaoguang | 67c1a07285 | |
Lunny Xiao | ebf0c96940 | |
Lunny Xiao | 6ad77125ca | |
wxiaoguang | 9c08637eae | |
wxiaoguang | 7c613f100e | |
6543 | 8e8ca6c653 | |
wxiaoguang | eda10cc2bb | |
wxiaoguang | ce8b11ae13 | |
Kemal Zebari | 22c7b3a744 | |
wxiaoguang | 982b20d259 | |
wxiaoguang | 5c236bd4c0 | |
yp05327 | ecd1d96f49 | |
Neal Caffery | bb0e4ce581 | |
wxiaoguang | c7bb3aa034 | |
wxiaoguang | 0f3e717a1a | |
Kemal Zebari | 9f0ef3621a | |
yp05327 | a50026e2f3 | |
wxiaoguang | 53b55223d1 | |
silverwind | c4e875402b | |
silverwind | b30b7df9f4 |
|
@ -61,3 +61,4 @@ kerwin612 <kerwin612@qq.com> (@kerwin612)
|
|||
Gary Wang <git@blumia.net> (@BLumia)
|
||||
Tim-Niclas Oelschläger <zokki.softwareschmiede@gmail.com> (@zokkis)
|
||||
Yu Liu <1240335630@qq.com> (@HEREYUA)
|
||||
Kemal Zebari <kemalzebra@gmail.com> (@kemzeb)
|
||||
|
|
10
cmd/hook.go
|
@ -220,10 +220,7 @@ Gitea or set your environment appropriately.`, "")
|
|||
}
|
||||
}
|
||||
|
||||
supportProcReceive := false
|
||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
||||
supportProcReceive = true
|
||||
}
|
||||
supportProcReceive := git.DefaultFeatures().SupportProcReceive
|
||||
|
||||
for scanner.Scan() {
|
||||
// TODO: support news feeds for wiki
|
||||
|
@ -341,6 +338,7 @@ Gitea or set your environment appropriately.`, "")
|
|||
isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki))
|
||||
repoName := os.Getenv(repo_module.EnvRepoName)
|
||||
pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64)
|
||||
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
|
||||
pusherName := os.Getenv(repo_module.EnvPusherName)
|
||||
|
||||
hookOptions := private.HookOptions{
|
||||
|
@ -350,6 +348,8 @@ Gitea or set your environment appropriately.`, "")
|
|||
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
|
||||
GitQuarantinePath: os.Getenv(private.GitQuarantinePath),
|
||||
GitPushOptions: pushOptions(),
|
||||
PullRequestID: prID,
|
||||
PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)),
|
||||
}
|
||||
oldCommitIDs := make([]string, hookBatchSize)
|
||||
newCommitIDs := make([]string, hookBatchSize)
|
||||
|
@ -497,7 +497,7 @@ Gitea or set your environment appropriately.`, "")
|
|||
return nil
|
||||
}
|
||||
|
||||
if git.CheckGitVersionAtLeast("2.29") != nil {
|
||||
if !git.DefaultFeatures().SupportProcReceive {
|
||||
return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.")
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ var CmdMigrateStorage = &cli.Command{
|
|||
Name: "type",
|
||||
Aliases: []string{"t"},
|
||||
Value: "",
|
||||
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log'",
|
||||
Usage: "Type of stored files to copy. Allowed types: 'attachments', 'lfs', 'avatars', 'repo-avatars', 'repo-archivers', 'packages', 'actions-log', 'actions-artifacts",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "storage",
|
||||
|
@ -160,6 +160,13 @@ func migrateActionsLog(ctx context.Context, dstStorage storage.ObjectStorage) er
|
|||
})
|
||||
}
|
||||
|
||||
func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStorage) error {
|
||||
return db.Iterate(ctx, nil, func(ctx context.Context, artifact *actions_model.ActionArtifact) error {
|
||||
_, err := storage.Copy(dstStorage, artifact.ArtifactPath, storage.ActionsArtifacts, artifact.ArtifactPath)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func runMigrateStorage(ctx *cli.Context) error {
|
||||
stdCtx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
@ -223,13 +230,14 @@ func runMigrateStorage(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
migratedMethods := map[string]func(context.Context, storage.ObjectStorage) error{
|
||||
"attachments": migrateAttachments,
|
||||
"lfs": migrateLFS,
|
||||
"avatars": migrateAvatars,
|
||||
"repo-avatars": migrateRepoAvatars,
|
||||
"repo-archivers": migrateRepoArchivers,
|
||||
"packages": migratePackages,
|
||||
"actions-log": migrateActionsLog,
|
||||
"attachments": migrateAttachments,
|
||||
"lfs": migrateLFS,
|
||||
"avatars": migrateAvatars,
|
||||
"repo-avatars": migrateRepoAvatars,
|
||||
"repo-archivers": migrateRepoArchivers,
|
||||
"packages": migratePackages,
|
||||
"actions-log": migrateActionsLog,
|
||||
"actions-artifacts": migrateActionsArtifacts,
|
||||
}
|
||||
|
||||
tp := strings.ToLower(ctx.String("type"))
|
||||
|
|
|
@ -178,7 +178,7 @@ func runServ(c *cli.Context) error {
|
|||
}
|
||||
|
||||
if len(words) < 2 {
|
||||
if git.CheckGitVersionAtLeast("2.29") == nil {
|
||||
if git.DefaultFeatures().SupportProcReceive {
|
||||
// for AGit Flow
|
||||
if cmd == "ssh_info" {
|
||||
fmt.Print(`{"type":"gitea","version":1}`)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Gitea - Docker
|
||||
|
||||
Dockerfile is found in root of repository.
|
||||
Dockerfile is found in the root of the repository.
|
||||
|
||||
Docker image can be found on [docker hub](https://hub.docker.com/r/gitea/gitea)
|
||||
Docker image can be found on [docker hub](https://hub.docker.com/r/gitea/gitea).
|
||||
|
||||
Documentation on using docker image can be found on [Gitea Docs site](https://docs.gitea.com/installation/install-with-docker-rootless)
|
||||
Documentation on using docker image can be found on [Gitea Docs site](https://docs.gitea.com/installation/install-with-docker-rootless).
|
||||
|
|
|
@ -57,6 +57,7 @@ type Engine interface {
|
|||
SumInt(bean any, columnName string) (res int64, err error)
|
||||
Sync(...any) error
|
||||
Select(string) *xorm.Session
|
||||
SetExpr(string, any) *xorm.Session
|
||||
NotIn(string, ...any) *xorm.Session
|
||||
OrderBy(any, ...any) *xorm.Session
|
||||
Exist(...any) (bool, error)
|
||||
|
|
|
@ -5,11 +5,11 @@ package issues
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// LoadProject load the project the issue was assigned to
|
||||
|
@ -90,58 +90,73 @@ func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (m
|
|||
return issuesMap, nil
|
||||
}
|
||||
|
||||
// ChangeProjectAssign changes the project associated with an issue
|
||||
func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
// IssueAssignOrRemoveProject changes the project associated with an issue
|
||||
// If newProjectID is 0, the issue is removed from the project
|
||||
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
oldProjectID := issue.projectID(ctx)
|
||||
|
||||
if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
|
||||
oldProjectID := issue.projectID(ctx)
|
||||
|
||||
if err := issue.LoadRepo(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only check if we add a new project and not remove it.
|
||||
if newProjectID > 0 {
|
||||
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
|
||||
if err != nil {
|
||||
if err := issue.LoadRepo(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
|
||||
return fmt.Errorf("issue's repository is not the same as project's repository")
|
||||
|
||||
// Only check if we add a new project and not remove it.
|
||||
if newProjectID > 0 {
|
||||
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !newProject.CanBeAccessedByOwnerRepo(issue.Repo.OwnerID, issue.Repo) {
|
||||
return util.NewPermissionDeniedErrorf("issue %d can't be accessed by project %d", issue.ID, newProject.ID)
|
||||
}
|
||||
if newColumnID == 0 {
|
||||
newDefaultColumn, err := newProject.GetDefaultBoard(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newColumnID = newDefaultColumn.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if oldProjectID > 0 || newProjectID > 0 {
|
||||
if _, err := CreateComment(ctx, &CreateCommentOptions{
|
||||
Type: CommentTypeProject,
|
||||
Doer: doer,
|
||||
Repo: issue.Repo,
|
||||
Issue: issue,
|
||||
OldProjectID: oldProjectID,
|
||||
ProjectID: newProjectID,
|
||||
}); err != nil {
|
||||
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return db.Insert(ctx, &project_model.ProjectIssue{
|
||||
IssueID: issue.ID,
|
||||
ProjectID: newProjectID,
|
||||
if oldProjectID > 0 || newProjectID > 0 {
|
||||
if _, err := CreateComment(ctx, &CreateCommentOptions{
|
||||
Type: CommentTypeProject,
|
||||
Doer: doer,
|
||||
Repo: issue.Repo,
|
||||
Issue: issue,
|
||||
OldProjectID: oldProjectID,
|
||||
ProjectID: newProjectID,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if newProjectID == 0 {
|
||||
return nil
|
||||
}
|
||||
if newColumnID == 0 {
|
||||
panic("newColumnID must not be zero") // shouldn't happen
|
||||
}
|
||||
|
||||
res := struct {
|
||||
MaxSorting int64
|
||||
IssueCount int64
|
||||
}{}
|
||||
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as issue_count").Table("project_issue").
|
||||
Where("project_id=?", newProjectID).
|
||||
And("project_board_id=?", newColumnID).
|
||||
Get(&res); err != nil {
|
||||
return err
|
||||
}
|
||||
newSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0)
|
||||
return db.Insert(ctx, &project_model.ProjectIssue{
|
||||
IssueID: issue.ID,
|
||||
ProjectID: newProjectID,
|
||||
ProjectBoardID: newColumnID,
|
||||
Sorting: newSorting,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -429,62 +429,6 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
|
|||
return nil
|
||||
}
|
||||
|
||||
// UpdateIssueByAPI updates all allowed fields of given issue.
|
||||
// If the issue status is changed a statusChangeComment is returned
|
||||
// similarly if the title is changed the titleChanged bool is set to true
|
||||
func UpdateIssueByAPI(ctx context.Context, issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if err := issue.LoadRepo(ctx); err != nil {
|
||||
return nil, false, fmt.Errorf("loadRepo: %w", err)
|
||||
}
|
||||
|
||||
// Reload the issue
|
||||
currentIssue, err := GetIssueByID(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(
|
||||
"name", "content", "milestone_id", "priority",
|
||||
"deadline_unix", "updated_unix", "is_locked").
|
||||
Update(issue); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
titleChanged = currentIssue.Title != issue.Title
|
||||
if titleChanged {
|
||||
opts := &CreateCommentOptions{
|
||||
Type: CommentTypeChangeTitle,
|
||||
Doer: doer,
|
||||
Repo: issue.Repo,
|
||||
Issue: issue,
|
||||
OldTitle: currentIssue.Title,
|
||||
NewTitle: issue.Title,
|
||||
}
|
||||
_, err := CreateComment(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("createComment: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if currentIssue.IsClosed != issue.IsClosed {
|
||||
statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := issue.AddCrossReferences(ctx, doer, true); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return statusChangeComment, titleChanged, committer.Commit()
|
||||
}
|
||||
|
||||
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
|
||||
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
|
||||
// if the deadline hasn't changed do nothing
|
||||
|
|
|
@ -5,12 +5,14 @@ package project
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
@ -82,6 +84,17 @@ func (b *Board) NumIssues(ctx context.Context) int {
|
|||
return int(c)
|
||||
}
|
||||
|
||||
func (b *Board) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
|
||||
issues := make([]*ProjectIssue, 0, 5)
|
||||
if err := db.GetEngine(ctx).Where("project_id=?", b.ProjectID).
|
||||
And("project_board_id=?", b.ID).
|
||||
OrderBy("sorting, id").
|
||||
Find(&issues); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(Board))
|
||||
}
|
||||
|
@ -150,12 +163,27 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error {
|
|||
return db.Insert(ctx, boards)
|
||||
}
|
||||
|
||||
// maxProjectColumns max columns allowed in a project, this should not bigger than 127
|
||||
// because sorting is int8 in database
|
||||
const maxProjectColumns = 20
|
||||
|
||||
// NewBoard adds a new project board to a given project
|
||||
func NewBoard(ctx context.Context, board *Board) error {
|
||||
if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
|
||||
return fmt.Errorf("bad color code: %s", board.Color)
|
||||
}
|
||||
|
||||
res := struct {
|
||||
MaxSorting int64
|
||||
ColumnCount int64
|
||||
}{}
|
||||
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as column_count").Table("project_board").
|
||||
Where("project_id=?", board.ProjectID).Get(&res); err != nil {
|
||||
return err
|
||||
}
|
||||
if res.ColumnCount >= maxProjectColumns {
|
||||
return fmt.Errorf("NewBoard: maximum number of columns reached")
|
||||
}
|
||||
board.Sorting = int8(util.Iif(res.ColumnCount > 0, res.MaxSorting+1, 0))
|
||||
_, err := db.GetEngine(ctx).Insert(board)
|
||||
return err
|
||||
}
|
||||
|
@ -189,7 +217,17 @@ func deleteBoardByID(ctx context.Context, boardID int64) error {
|
|||
return fmt.Errorf("deleteBoardByID: cannot delete default board")
|
||||
}
|
||||
|
||||
if err = board.removeIssues(ctx); err != nil {
|
||||
// move all issues to the default column
|
||||
project, err := GetProjectByID(ctx, board.ProjectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defaultColumn, err := project.GetDefaultBoard(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = board.moveIssuesToAnotherColumn(ctx, defaultColumn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -242,21 +280,15 @@ func UpdateBoard(ctx context.Context, board *Board) error {
|
|||
// GetBoards fetches all boards related to a project
|
||||
func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
|
||||
boards := make([]*Board, 0, 5)
|
||||
|
||||
if err := db.GetEngine(ctx).Where("project_id=? AND `default`=?", p.ID, false).OrderBy("sorting").Find(&boards); err != nil {
|
||||
if err := db.GetEngine(ctx).Where("project_id=?", p.ID).OrderBy("sorting, id").Find(&boards); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultB, err := p.getDefaultBoard(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append([]*Board{defaultB}, boards...), nil
|
||||
return boards, nil
|
||||
}
|
||||
|
||||
// getDefaultBoard return default board and ensure only one exists
|
||||
func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
|
||||
// GetDefaultBoard return default board and ensure only one exists
|
||||
func (p *Project) GetDefaultBoard(ctx context.Context) (*Board, error) {
|
||||
var board Board
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("project_id=? AND `default` = ?", p.ID, true).
|
||||
|
@ -316,3 +348,42 @@ func UpdateBoardSorting(ctx context.Context, bs BoardList) error {
|
|||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (BoardList, error) {
|
||||
columns := make([]*Board, 0, 5)
|
||||
if err := db.GetEngine(ctx).
|
||||
Where("project_id =?", projectID).
|
||||
In("id", columnsIDs).
|
||||
OrderBy("sorting").Find(&columns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
// MoveColumnsOnProject sorts columns in a project
|
||||
func MoveColumnsOnProject(ctx context.Context, project *Project, sortedColumnIDs map[int64]int64) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
sess := db.GetEngine(ctx)
|
||||
columnIDs := util.ValuesOfMap(sortedColumnIDs)
|
||||
movedColumns, err := GetColumnsByIDs(ctx, project.ID, columnIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(movedColumns) != len(sortedColumnIDs) {
|
||||
return errors.New("some columns do not exist")
|
||||
}
|
||||
|
||||
for _, column := range movedColumns {
|
||||
if column.ProjectID != project.ID {
|
||||
return fmt.Errorf("column[%d]'s projectID is not equal to project's ID [%d]", column.ProjectID, project.ID)
|
||||
}
|
||||
}
|
||||
|
||||
for sorting, columnID := range sortedColumnIDs {
|
||||
if _, err := sess.Exec("UPDATE `project_board` SET sorting=? WHERE id=?", sorting, columnID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package project
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -19,7 +21,7 @@ func TestGetDefaultBoard(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// check if default board was added
|
||||
board, err := projectWithoutDefault.getDefaultBoard(db.DefaultContext)
|
||||
board, err := projectWithoutDefault.GetDefaultBoard(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(5), board.ProjectID)
|
||||
assert.Equal(t, "Uncategorized", board.Title)
|
||||
|
@ -28,7 +30,7 @@ func TestGetDefaultBoard(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// check if multiple defaults were removed
|
||||
board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext)
|
||||
board, err = projectWithMultipleDefaults.GetDefaultBoard(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(6), board.ProjectID)
|
||||
assert.Equal(t, int64(9), board.ID)
|
||||
|
@ -42,3 +44,84 @@ func TestGetDefaultBoard(t *testing.T) {
|
|||
assert.Equal(t, int64(6), board.ProjectID)
|
||||
assert.False(t, board.Default)
|
||||
}
|
||||
|
||||
func Test_moveIssuesToAnotherColumn(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
column1 := unittest.AssertExistsAndLoadBean(t, &Board{ID: 1, ProjectID: 1})
|
||||
|
||||
issues, err := column1.GetIssues(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, issues, 1)
|
||||
assert.EqualValues(t, 1, issues[0].ID)
|
||||
|
||||
column2 := unittest.AssertExistsAndLoadBean(t, &Board{ID: 2, ProjectID: 1})
|
||||
issues, err = column2.GetIssues(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, issues, 1)
|
||||
assert.EqualValues(t, 3, issues[0].ID)
|
||||
|
||||
err = column1.moveIssuesToAnotherColumn(db.DefaultContext, column2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
issues, err = column1.GetIssues(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, issues, 0)
|
||||
|
||||
issues, err = column2.GetIssues(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, issues, 2)
|
||||
assert.EqualValues(t, 3, issues[0].ID)
|
||||
assert.EqualValues(t, 0, issues[0].Sorting)
|
||||
assert.EqualValues(t, 1, issues[1].ID)
|
||||
assert.EqualValues(t, 1, issues[1].Sorting)
|
||||
}
|
||||
|
||||
func Test_MoveColumnsOnProject(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
project1 := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1})
|
||||
columns, err := project1.GetBoards(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, columns, 3)
|
||||
assert.EqualValues(t, 0, columns[0].Sorting) // even if there is no default sorting, the code should also work
|
||||
assert.EqualValues(t, 0, columns[1].Sorting)
|
||||
assert.EqualValues(t, 0, columns[2].Sorting)
|
||||
|
||||
err = MoveColumnsOnProject(db.DefaultContext, project1, map[int64]int64{
|
||||
0: columns[1].ID,
|
||||
1: columns[2].ID,
|
||||
2: columns[0].ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
columnsAfter, err := project1.GetBoards(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, columnsAfter, 3)
|
||||
assert.EqualValues(t, columns[1].ID, columnsAfter[0].ID)
|
||||
assert.EqualValues(t, columns[2].ID, columnsAfter[1].ID)
|
||||
assert.EqualValues(t, columns[0].ID, columnsAfter[2].ID)
|
||||
}
|
||||
|
||||
func Test_NewBoard(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
project1 := unittest.AssertExistsAndLoadBean(t, &Project{ID: 1})
|
||||
columns, err := project1.GetBoards(db.DefaultContext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, columns, 3)
|
||||
|
||||
for i := 0; i < maxProjectColumns-3; i++ {
|
||||
err := NewBoard(db.DefaultContext, &Board{
|
||||
Title: fmt.Sprintf("board-%d", i+4),
|
||||
ProjectID: project1.ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
err = NewBoard(db.DefaultContext, &Board{
|
||||
Title: "board-21",
|
||||
ProjectID: project1.ID,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), "maximum number of columns reached"))
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ProjectIssue saves relation from issue to a project
|
||||
|
@ -17,7 +18,7 @@ type ProjectIssue struct { //revive:disable-line:exported
|
|||
IssueID int64 `xorm:"INDEX"`
|
||||
ProjectID int64 `xorm:"INDEX"`
|
||||
|
||||
// If 0, then it has not been added to a specific board in the project
|
||||
// ProjectBoardID should not be zero since 1.22. If it's zero, the issue will not be displayed on UI and it might result in errors.
|
||||
ProjectBoardID int64 `xorm:"INDEX"`
|
||||
|
||||
// the sorting order on the board
|
||||
|
@ -79,11 +80,8 @@ func (p *Project) NumOpenIssues(ctx context.Context) int {
|
|||
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
sess := db.GetEngine(ctx)
|
||||
issueIDs := util.ValuesOfMap(sortedIssueIDs)
|
||||
|
||||
issueIDs := make([]int64, 0, len(sortedIssueIDs))
|
||||
for _, issueID := range sortedIssueIDs {
|
||||
issueIDs = append(issueIDs, issueID)
|
||||
}
|
||||
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -102,7 +100,44 @@ func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs
|
|||
})
|
||||
}
|
||||
|
||||
func (b *Board) removeIssues(ctx context.Context) error {
|
||||
_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", b.ID)
|
||||
return err
|
||||
func (b *Board) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Board) error {
|
||||
if b.ProjectID != newColumn.ProjectID {
|
||||
return fmt.Errorf("columns have to be in the same project")
|
||||
}
|
||||
|
||||
if b.ID == newColumn.ID {
|
||||
return nil
|
||||
}
|
||||
|
||||
res := struct {
|
||||
MaxSorting int64
|
||||
IssueCount int64
|
||||
}{}
|
||||
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as issue_count").
|
||||
Table("project_issue").
|
||||
Where("project_id=?", newColumn.ProjectID).
|
||||
And("project_board_id=?", newColumn.ID).
|
||||
Get(&res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
issues, err := b.GetIssues(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(issues) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
nextSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0)
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
for i, issue := range issues {
|
||||
issue.ProjectBoardID = newColumn.ID
|
||||
issue.Sorting = nextSorting + int64(i)
|
||||
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols("project_board_id", "sorting").Update(issue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -161,6 +161,13 @@ func (p *Project) IsRepositoryProject() bool {
|
|||
return p.Type == TypeRepository
|
||||
}
|
||||
|
||||
func (p *Project) CanBeAccessedByOwnerRepo(ownerID int64, repo *repo_model.Repository) bool {
|
||||
if p.Type == TypeRepository {
|
||||
return repo != nil && p.RepoID == repo.ID // if a project belongs to a repository, then its OwnerID is 0 and can be ignored
|
||||
}
|
||||
return p.OwnerID == ownerID && p.RepoID == 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(Project))
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import (
|
|||
"image/png"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/avatar"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
@ -84,13 +84,7 @@ func (repo *Repository) relAvatarLink(ctx context.Context) string {
|
|||
return setting.AppSubURL + "/repo-avatars/" + url.PathEscape(repo.Avatar)
|
||||
}
|
||||
|
||||
// AvatarLink returns a link to the repository's avatar.
|
||||
// AvatarLink returns the full avatar url with http host. TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
||||
func (repo *Repository) AvatarLink(ctx context.Context) string {
|
||||
link := repo.relAvatarLink(ctx)
|
||||
// we only prepend our AppURL to our known (relative, internal) avatar link to get an absolute URL
|
||||
if strings.HasPrefix(link, "/") && !strings.HasPrefix(link, "//") {
|
||||
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL)[1:]
|
||||
}
|
||||
// otherwise, return the link as it is
|
||||
return link
|
||||
return httplib.MakeAbsoluteURL(ctx, repo.relAvatarLink(ctx))
|
||||
}
|
||||
|
|
|
@ -8,14 +8,14 @@ import "code.gitea.io/gitea/models/db"
|
|||
// SearchOrderByMap represents all possible search order
|
||||
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
|
||||
"asc": {
|
||||
"alpha": db.SearchOrderByAlphabetically,
|
||||
"alpha": "owner_name ASC, name ASC",
|
||||
"created": db.SearchOrderByOldest,
|
||||
"updated": db.SearchOrderByLeastUpdated,
|
||||
"size": db.SearchOrderBySize,
|
||||
"id": db.SearchOrderByID,
|
||||
},
|
||||
"desc": {
|
||||
"alpha": db.SearchOrderByAlphabeticallyReverse,
|
||||
"alpha": "owner_name DESC, name DESC",
|
||||
"created": db.SearchOrderByNewest,
|
||||
"updated": db.SearchOrderByRecentUpdated,
|
||||
"size": db.SearchOrderBySizeReverse,
|
||||
|
|
|
@ -9,11 +9,11 @@ import (
|
|||
"fmt"
|
||||
"image/png"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/avatars"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/avatar"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
@ -89,13 +89,9 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
|||
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
||||
}
|
||||
|
||||
// AvatarLink returns the full avatar link with http host
|
||||
// AvatarLink returns the full avatar url with http host. TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
||||
func (u *User) AvatarLink(ctx context.Context) string {
|
||||
link := u.AvatarLinkWithSize(ctx, 0)
|
||||
if !strings.HasPrefix(link, "//") && !strings.Contains(link, "://") {
|
||||
return setting.AppURL + strings.TrimPrefix(link, setting.AppSubURL+"/")
|
||||
}
|
||||
return link
|
||||
return httplib.MakeAbsoluteURL(ctx, u.AvatarLinkWithSize(ctx, 0))
|
||||
}
|
||||
|
||||
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
||||
|
|
|
@ -132,7 +132,7 @@ func (r *BlameReader) Close() error {
|
|||
// CreateBlameReader creates reader for given repository, commit and file
|
||||
func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
|
||||
var ignoreRevsFile *string
|
||||
if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
|
||||
ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit)
|
||||
}
|
||||
|
||||
|
|
|
@ -423,7 +423,7 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
|
|||
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
|
||||
func (c *Commit) GetBranchName() (string, error) {
|
||||
cmd := NewCommand(c.repo.Ctx, "name-rev")
|
||||
if CheckGitVersionAtLeast("2.13.0") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.13.0") {
|
||||
cmd.AddArguments("--exclude", "refs/tags/*")
|
||||
}
|
||||
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
|
||||
|
|
|
@ -22,42 +22,63 @@ import (
|
|||
"github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
// RequiredVersion is the minimum Git version required
|
||||
const RequiredVersion = "2.0.0"
|
||||
const RequiredVersion = "2.0.0" // the minimum Git version required
|
||||
|
||||
type Features struct {
|
||||
gitVersion *version.Version
|
||||
|
||||
UsingGogit bool
|
||||
SupportProcReceive bool // >= 2.29
|
||||
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
||||
SupportedObjectFormats []ObjectFormat // sha1, sha256
|
||||
}
|
||||
|
||||
var (
|
||||
// GitExecutable is the command name of git
|
||||
// Could be updated to an absolute path while initialization
|
||||
GitExecutable = "git"
|
||||
|
||||
// DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx
|
||||
DefaultContext context.Context
|
||||
|
||||
DefaultFeatures struct {
|
||||
GitVersion *version.Version
|
||||
|
||||
SupportProcReceive bool // >= 2.29
|
||||
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
|
||||
}
|
||||
GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
|
||||
DefaultContext context.Context // the default context to run git commands in, must be initialized by git.InitXxx
|
||||
defaultFeatures *Features
|
||||
)
|
||||
|
||||
// loadGitVersion tries to get the current git version and stores it into a global variable
|
||||
func loadGitVersion() error {
|
||||
// doesn't need RWMutex because it's executed by Init()
|
||||
if DefaultFeatures.GitVersion != nil {
|
||||
return nil
|
||||
}
|
||||
func (f *Features) CheckVersionAtLeast(atLeast string) bool {
|
||||
return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0
|
||||
}
|
||||
|
||||
// VersionInfo returns git version information
|
||||
func (f *Features) VersionInfo() string {
|
||||
return f.gitVersion.Original()
|
||||
}
|
||||
|
||||
func DefaultFeatures() *Features {
|
||||
if defaultFeatures == nil {
|
||||
if !setting.IsProd || setting.IsInTesting {
|
||||
log.Warn("git.DefaultFeatures is called before git.InitXxx, initializing with default values")
|
||||
}
|
||||
if err := InitSimple(context.Background()); err != nil {
|
||||
log.Fatal("git.InitSimple failed: %v", err)
|
||||
}
|
||||
}
|
||||
return defaultFeatures
|
||||
}
|
||||
|
||||
func loadGitVersionFeatures() (*Features, error) {
|
||||
stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
|
||||
if runErr != nil {
|
||||
return runErr
|
||||
return nil, runErr
|
||||
}
|
||||
|
||||
ver, err := parseGitVersionLine(strings.TrimSpace(stdout))
|
||||
if err == nil {
|
||||
DefaultFeatures.GitVersion = ver
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return err
|
||||
|
||||
features := &Features{gitVersion: ver, UsingGogit: isGogit}
|
||||
features.SupportProcReceive = features.CheckVersionAtLeast("2.29")
|
||||
features.SupportHashSha256 = features.CheckVersionAtLeast("2.42") && !isGogit
|
||||
features.SupportedObjectFormats = []ObjectFormat{Sha1ObjectFormat}
|
||||
if features.SupportHashSha256 {
|
||||
features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat)
|
||||
}
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func parseGitVersionLine(s string) (*version.Version, error) {
|
||||
|
@ -85,56 +106,24 @@ func SetExecutablePath(path string) error {
|
|||
return fmt.Errorf("git not found: %w", err)
|
||||
}
|
||||
GitExecutable = absPath
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = loadGitVersion(); err != nil {
|
||||
return fmt.Errorf("unable to load git version: %w", err)
|
||||
}
|
||||
|
||||
versionRequired, err := version.NewVersion(RequiredVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if DefaultFeatures.GitVersion.LessThan(versionRequired) {
|
||||
func ensureGitVersion() error {
|
||||
if !DefaultFeatures().CheckVersionAtLeast(RequiredVersion) {
|
||||
moreHint := "get git: https://git-scm.com/download/"
|
||||
if runtime.GOOS == "linux" {
|
||||
// there are a lot of CentOS/RHEL users using old git, so we add a special hint for them
|
||||
if _, err = os.Stat("/etc/redhat-release"); err == nil {
|
||||
if _, err := os.Stat("/etc/redhat-release"); err == nil {
|
||||
// ius.io is the recommended official(git-scm.com) method to install git
|
||||
moreHint = "get git: https://git-scm.com/download/linux and https://ius.io"
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures.GitVersion.Original(), RequiredVersion, moreHint)
|
||||
return fmt.Errorf("installed git version %q is not supported, Gitea requires git version >= %q, %s", DefaultFeatures().gitVersion.Original(), RequiredVersion, moreHint)
|
||||
}
|
||||
|
||||
if err = checkGitVersionCompatibility(DefaultFeatures.GitVersion); err != nil {
|
||||
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures.GitVersion.String(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VersionInfo returns git version information
|
||||
func VersionInfo() string {
|
||||
if DefaultFeatures.GitVersion == nil {
|
||||
return "(git not found)"
|
||||
}
|
||||
format := "%s"
|
||||
args := []any{DefaultFeatures.GitVersion.Original()}
|
||||
// Since git wire protocol has been released from git v2.18
|
||||
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
|
||||
format += ", Wire Protocol %s Enabled"
|
||||
args = append(args, "Version 2") // for focus color
|
||||
}
|
||||
|
||||
return fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func checkInit() error {
|
||||
if setting.Git.HomePath == "" {
|
||||
return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
|
||||
}
|
||||
if DefaultContext != nil {
|
||||
log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
|
||||
if err := checkGitVersionCompatibility(DefaultFeatures().gitVersion); err != nil {
|
||||
return fmt.Errorf("installed git version %s has a known compatibility issue with Gitea: %w, please upgrade (or downgrade) git", DefaultFeatures().gitVersion.String(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -154,8 +143,12 @@ func HomeDir() string {
|
|||
// InitSimple initializes git module with a very simple step, no config changes, no global command arguments.
|
||||
// This method doesn't change anything to filesystem. At the moment, it is only used by some Gitea sub-commands.
|
||||
func InitSimple(ctx context.Context) error {
|
||||
if err := checkInit(); err != nil {
|
||||
return err
|
||||
if setting.Git.HomePath == "" {
|
||||
return errors.New("unable to init Git's HomeDir, incorrect initialization of the setting and git modules")
|
||||
}
|
||||
|
||||
if DefaultContext != nil && (!setting.IsProd || setting.IsInTesting) {
|
||||
log.Warn("git module has been initialized already, duplicate init may work but it's better to fix it")
|
||||
}
|
||||
|
||||
DefaultContext = ctx
|
||||
|
@ -165,7 +158,24 @@ func InitSimple(ctx context.Context) error {
|
|||
defaultCommandExecutionTimeout = time.Duration(setting.Git.Timeout.Default) * time.Second
|
||||
}
|
||||
|
||||
return SetExecutablePath(setting.Git.Path)
|
||||
if err := SetExecutablePath(setting.Git.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
defaultFeatures, err = loadGitVersionFeatures()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ensureGitVersion(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
|
||||
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
|
||||
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitFull initializes git module with version check and change global variables, sync gitconfig.
|
||||
|
@ -175,30 +185,18 @@ func InitFull(ctx context.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// when git works with gnupg (commit signing), there should be a stable home for gnupg commands
|
||||
if _, ok := os.LookupEnv("GNUPGHOME"); !ok {
|
||||
_ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg"))
|
||||
}
|
||||
|
||||
// Since git wire protocol has been released from git v2.18
|
||||
if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil {
|
||||
if setting.Git.EnableAutoGitWireProtocol && DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2")
|
||||
}
|
||||
|
||||
// Explicitly disable credential helper, otherwise Git credentials might leak
|
||||
if CheckGitVersionAtLeast("2.9") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.9") {
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=")
|
||||
}
|
||||
DefaultFeatures.SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil
|
||||
DefaultFeatures.SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil && !isGogit
|
||||
if DefaultFeatures.SupportHashSha256 {
|
||||
SupportedObjectFormats = append(SupportedObjectFormats, Sha256ObjectFormat)
|
||||
} else {
|
||||
log.Warn("sha256 hash support is disabled - requires Git >= 2.42. Gogit is currently unsupported")
|
||||
}
|
||||
|
||||
if setting.LFS.StartServer {
|
||||
if CheckGitVersionAtLeast("2.1.2") != nil {
|
||||
if !DefaultFeatures().CheckVersionAtLeast("2.1.2") {
|
||||
return errors.New("LFS server support requires Git >= 2.1.2")
|
||||
}
|
||||
globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
|
||||
|
@ -238,13 +236,13 @@ func syncGitConfig() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
if CheckGitVersionAtLeast("2.10") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.10") {
|
||||
if err := configSet("receive.advertisePushOptions", "true"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if CheckGitVersionAtLeast("2.18") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||
if err := configSet("core.commitGraph", "true"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -256,7 +254,7 @@ func syncGitConfig() (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if DefaultFeatures.SupportProcReceive {
|
||||
if DefaultFeatures().SupportProcReceive {
|
||||
// set support for AGit flow
|
||||
if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil {
|
||||
return err
|
||||
|
@ -294,7 +292,7 @@ func syncGitConfig() (err error) {
|
|||
}
|
||||
|
||||
// By default partial clones are disabled, enable them from git v2.22
|
||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||
if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") {
|
||||
if err = configSet("uploadpack.allowfilter", "true"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -309,21 +307,6 @@ func syncGitConfig() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// CheckGitVersionAtLeast check git version is at least the constraint version
|
||||
func CheckGitVersionAtLeast(atLeast string) error {
|
||||
if DefaultFeatures.GitVersion == nil {
|
||||
panic("git module is not initialized") // it shouldn't happen
|
||||
}
|
||||
atLeastVersion, err := version.NewVersion(atLeast)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if DefaultFeatures.GitVersion.Compare(atLeastVersion) < 0 {
|
||||
return fmt.Errorf("installed git binary version %s is not at least %s", DefaultFeatures.GitVersion.Original(), atLeast)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkGitVersionCompatibility(gitVer *version.Version) error {
|
||||
badVersions := []struct {
|
||||
Version *version.Version
|
||||
|
|
|
@ -29,6 +29,7 @@ type GrepOptions struct {
|
|||
ContextLineNumber int
|
||||
IsFuzzy bool
|
||||
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
|
||||
PathspecList []string
|
||||
}
|
||||
|
||||
func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) {
|
||||
|
@ -62,6 +63,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
|
|||
cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
|
||||
}
|
||||
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
|
||||
cmd.AddDashesAndList(opts.PathspecList...)
|
||||
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
|
||||
stderr := bytes.Buffer{}
|
||||
err = cmd.Run(&RunOpts{
|
||||
|
|
|
@ -31,6 +31,26 @@ func TestGrepSearch(t *testing.T) {
|
|||
},
|
||||
}, res)
|
||||
|
||||
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob)java-hello/*"}})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*GrepResult{
|
||||
{
|
||||
Filename: "java-hello/main.java",
|
||||
LineNumbers: []int{3},
|
||||
LineCodes: []string{" public static void main(String[] args)"},
|
||||
},
|
||||
}, res)
|
||||
|
||||
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*GrepResult{
|
||||
{
|
||||
Filename: "main.vendor.java",
|
||||
LineNumbers: []int{3},
|
||||
LineCodes: []string{" public static void main(String[] args)"},
|
||||
},
|
||||
}, res)
|
||||
|
||||
res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*GrepResult{
|
||||
|
|
|
@ -120,12 +120,8 @@ var (
|
|||
Sha256ObjectFormat ObjectFormat = Sha256ObjectFormatImpl{}
|
||||
)
|
||||
|
||||
var SupportedObjectFormats = []ObjectFormat{
|
||||
Sha1ObjectFormat,
|
||||
}
|
||||
|
||||
func ObjectFormatFromName(name string) ObjectFormat {
|
||||
for _, objectFormat := range SupportedObjectFormats {
|
||||
for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
|
||||
if name == objectFormat.Name() {
|
||||
return objectFormat
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func (*Sha256Hash) Type() ObjectFormat { return Sha256ObjectFormat }
|
|||
|
||||
func NewIDFromString(hexHash string) (ObjectID, error) {
|
||||
var theObjectFormat ObjectFormat
|
||||
for _, objectFormat := range SupportedObjectFormats {
|
||||
for _, objectFormat := range DefaultFeatures().SupportedObjectFormats {
|
||||
if len(hexHash) == objectFormat.FullLength() {
|
||||
theObjectFormat = objectFormat
|
||||
break
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
// GetRemoteAddress returns remote url of git repository in the repoPath with special remote name
|
||||
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
|
||||
var cmd *Command
|
||||
if CheckGitVersionAtLeast("2.7") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.7") {
|
||||
cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName)
|
||||
} else {
|
||||
cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
|
||||
|
|
|
@ -7,7 +7,6 @@ package git
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
@ -63,32 +62,6 @@ func IsRepoURLAccessible(ctx context.Context, url string) bool {
|
|||
return err == nil
|
||||
}
|
||||
|
||||
// GetObjectFormatOfRepo returns the hash type of repository at a given path
|
||||
func GetObjectFormatOfRepo(ctx context.Context, repoPath string) (ObjectFormat, error) {
|
||||
var stdout, stderr strings.Builder
|
||||
|
||||
err := NewCommand(ctx, "hash-object", "--stdin").Run(&RunOpts{
|
||||
Dir: repoPath,
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Stdin: &strings.Reader{},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stderr.Len() > 0 {
|
||||
return nil, errors.New(stderr.String())
|
||||
}
|
||||
|
||||
h, err := NewIDFromString(strings.TrimRight(stdout.String(), "\n"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return h.Type(), nil
|
||||
}
|
||||
|
||||
// InitRepository initializes a new Git repository.
|
||||
func InitRepository(ctx context.Context, repoPath string, bare bool, objectFormatName string) error {
|
||||
err := os.MkdirAll(repoPath, os.ModePerm)
|
||||
|
@ -101,7 +74,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
|
|||
if !IsValidObjectFormat(objectFormatName) {
|
||||
return fmt.Errorf("invalid object format: %s", objectFormatName)
|
||||
}
|
||||
if DefaultFeatures.SupportHashSha256 {
|
||||
if DefaultFeatures().SupportHashSha256 {
|
||||
cmd.AddOptionValues("--object-format", objectFormatName)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
var isGogit bool
|
|
@ -22,9 +22,7 @@ import (
|
|||
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||
)
|
||||
|
||||
func init() {
|
||||
isGogit = true
|
||||
}
|
||||
const isGogit = true
|
||||
|
||||
// Repository represents a Git repository.
|
||||
type Repository struct {
|
||||
|
|
|
@ -15,9 +15,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
isGogit = false
|
||||
}
|
||||
const isGogit = false
|
||||
|
||||
// Repository represents a Git repository.
|
||||
type Repository struct {
|
||||
|
|
|
@ -438,7 +438,7 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit,
|
|||
}
|
||||
|
||||
func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
|
||||
if CheckGitVersionAtLeast("2.7.0") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.7.0") {
|
||||
stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").
|
||||
AddOptionFormat("--count=%d", limit).
|
||||
AddOptionValues("--contains", commit.ID.String(), BranchPrefix).
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
// WriteCommitGraph write commit graph to speed up repo access
|
||||
// this requires git v2.18 to be installed
|
||||
func WriteCommitGraph(ctx context.Context, repoPath string) error {
|
||||
if CheckGitVersionAtLeast("2.18") == nil {
|
||||
if DefaultFeatures().CheckVersionAtLeast("2.18") {
|
||||
if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil {
|
||||
return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package httplib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
|
@ -11,6 +13,10 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
type RequestContextKeyStruct struct{}
|
||||
|
||||
var RequestContextKey = RequestContextKeyStruct{}
|
||||
|
||||
func urlIsRelative(s string, u *url.URL) bool {
|
||||
// Unfortunately browsers consider a redirect Location with preceding "//", "\\", "/\" and "\/" as meaning redirect to "http(s)://REST_OF_PATH"
|
||||
// Therefore we should ignore these redirect locations to prevent open redirects
|
||||
|
@ -26,7 +32,56 @@ func IsRelativeURL(s string) bool {
|
|||
return err == nil && urlIsRelative(s, u)
|
||||
}
|
||||
|
||||
func IsCurrentGiteaSiteURL(s string) bool {
|
||||
func guessRequestScheme(req *http.Request, def string) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
|
||||
if s := req.Header.Get("X-Forwarded-Proto"); s != "" {
|
||||
return s
|
||||
}
|
||||
if s := req.Header.Get("X-Forwarded-Protocol"); s != "" {
|
||||
return s
|
||||
}
|
||||
if s := req.Header.Get("X-Url-Scheme"); s != "" {
|
||||
return s
|
||||
}
|
||||
if s := req.Header.Get("Front-End-Https"); s != "" {
|
||||
return util.Iif(s == "on", "https", "http")
|
||||
}
|
||||
if s := req.Header.Get("X-Forwarded-Ssl"); s != "" {
|
||||
return util.Iif(s == "on", "https", "http")
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func guessForwardedHost(req *http.Request) string {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
|
||||
return req.Header.Get("X-Forwarded-Host")
|
||||
}
|
||||
|
||||
// GuessCurrentAppURL tries to guess the current full URL by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
|
||||
func GuessCurrentAppURL(ctx context.Context) string {
|
||||
req, ok := ctx.Value(RequestContextKey).(*http.Request)
|
||||
if !ok {
|
||||
return setting.AppURL
|
||||
}
|
||||
if host := guessForwardedHost(req); host != "" {
|
||||
// if it is behind a reverse proxy, use "https" as default scheme in case the site admin forgets to set the correct forwarded-protocol headers
|
||||
return guessRequestScheme(req, "https") + "://" + host + setting.AppSubURL + "/"
|
||||
} else if req.Host != "" {
|
||||
// if it is not behind a reverse proxy, use the scheme from config options, meanwhile use "https" as much as possible
|
||||
defaultScheme := util.Iif(setting.Protocol == "http", "http", "https")
|
||||
return guessRequestScheme(req, defaultScheme) + "://" + req.Host + setting.AppSubURL + "/"
|
||||
}
|
||||
return setting.AppURL
|
||||
}
|
||||
|
||||
func MakeAbsoluteURL(ctx context.Context, s string) string {
|
||||
if IsRelativeURL(s) {
|
||||
return GuessCurrentAppURL(ctx) + strings.TrimPrefix(s, "/")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -45,5 +100,6 @@ func IsCurrentGiteaSiteURL(s string) bool {
|
|||
if u.Path == "" {
|
||||
u.Path = "/"
|
||||
}
|
||||
return strings.HasPrefix(strings.ToLower(u.String()), strings.ToLower(setting.AppURL))
|
||||
urlLower := strings.ToLower(u.String())
|
||||
return strings.HasPrefix(urlLower, strings.ToLower(setting.AppURL)) || strings.HasPrefix(urlLower, strings.ToLower(GuessCurrentAppURL(ctx)))
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package httplib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -37,9 +39,44 @@ func TestIsRelativeURL(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMakeAbsoluteURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Protocol, "http")()
|
||||
defer test.MockVariableValue(&setting.AppURL, "http://the-host/sub/")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||
|
||||
ctx := context.Background()
|
||||
assert.Equal(t, "http://the-host/sub/", MakeAbsoluteURL(ctx, ""))
|
||||
assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
|
||||
assert.Equal(t, "http://the-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||
Host: "user-host",
|
||||
})
|
||||
assert.Equal(t, "http://user-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||
Host: "user-host",
|
||||
Header: map[string][]string{
|
||||
"X-Forwarded-Host": {"forwarded-host"},
|
||||
},
|
||||
})
|
||||
assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||
Host: "user-host",
|
||||
Header: map[string][]string{
|
||||
"X-Forwarded-Host": {"forwarded-host"},
|
||||
"X-Forwarded-Proto": {"https"},
|
||||
},
|
||||
})
|
||||
assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||
}
|
||||
|
||||
func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||
ctx := context.Background()
|
||||
good := []string{
|
||||
"?key=val",
|
||||
"/sub",
|
||||
|
@ -50,7 +87,7 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
|||
"http://localhost:3000/sub/",
|
||||
}
|
||||
for _, s := range good {
|
||||
assert.True(t, IsCurrentGiteaSiteURL(s), "good = %q", s)
|
||||
assert.True(t, IsCurrentGiteaSiteURL(ctx, s), "good = %q", s)
|
||||
}
|
||||
bad := []string{
|
||||
".",
|
||||
|
@ -64,13 +101,23 @@ func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
|||
"http://other/",
|
||||
}
|
||||
for _, s := range bad {
|
||||
assert.False(t, IsCurrentGiteaSiteURL(s), "bad = %q", s)
|
||||
assert.False(t, IsCurrentGiteaSiteURL(ctx, s), "bad = %q", s)
|
||||
}
|
||||
|
||||
setting.AppURL = "http://localhost:3000/"
|
||||
setting.AppSubURL = ""
|
||||
assert.False(t, IsCurrentGiteaSiteURL("//"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL("\\\\"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL("http://localhost"))
|
||||
assert.True(t, IsCurrentGiteaSiteURL("http://localhost:3000?key=val"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL(ctx, "//"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL(ctx, "\\\\"))
|
||||
assert.False(t, IsCurrentGiteaSiteURL(ctx, "http://localhost"))
|
||||
assert.True(t, IsCurrentGiteaSiteURL(ctx, "http://localhost:3000?key=val"))
|
||||
|
||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||
Host: "user-host",
|
||||
Header: map[string][]string{
|
||||
"X-Forwarded-Host": {"forwarded-host"},
|
||||
"X-Forwarded-Proto": {"https"},
|
||||
},
|
||||
})
|
||||
assert.True(t, IsCurrentGiteaSiteURL(ctx, "http://localhost:3000"))
|
||||
assert.True(t, IsCurrentGiteaSiteURL(ctx, "https://forwarded-host"))
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan c
|
|||
go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg)
|
||||
|
||||
// 1. Run batch-check on all objects in the repository
|
||||
if git.CheckGitVersionAtLeast("2.6.0") != nil {
|
||||
if !git.DefaultFeatures().CheckVersionAtLeast("2.6.0") {
|
||||
revListReader, revListWriter := io.Pipe()
|
||||
shasToCheckReader, shasToCheckWriter := io.Pipe()
|
||||
wg.Add(2)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
|
@ -54,7 +55,7 @@ var (
|
|||
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
|
||||
|
||||
// anyHashPattern splits url containing SHA into parts
|
||||
anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~_%.a-zA-Z0-9/]+)?(#[-+~_%.a-zA-Z0-9]+)?`)
|
||||
anyHashPattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~%./\w]+)?(\?[-+~%.\w&=]+)?(#[-+~%.\w]+)?`)
|
||||
|
||||
// comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
|
||||
comparePattern = regexp.MustCompile(`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?`)
|
||||
|
@ -591,7 +592,8 @@ func replaceContentList(node *html.Node, i, j int, newNodes []*html.Node) {
|
|||
|
||||
func mentionProcessor(ctx *RenderContext, node *html.Node) {
|
||||
start := 0
|
||||
for node != nil {
|
||||
nodeStop := node.NextSibling
|
||||
for node != nodeStop {
|
||||
found, loc := references.FindFirstMentionBytes(util.UnsafeStringToBytes(node.Data[start:]))
|
||||
if !found {
|
||||
node = node.NextSibling
|
||||
|
@ -962,57 +964,68 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
}
|
||||
}
|
||||
|
||||
type anyHashPatternResult struct {
|
||||
PosStart int
|
||||
PosEnd int
|
||||
FullURL string
|
||||
CommitID string
|
||||
SubPath string
|
||||
QueryHash string
|
||||
}
|
||||
|
||||
func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) {
|
||||
m := anyHashPattern.FindStringSubmatchIndex(s)
|
||||
if m == nil {
|
||||
return ret, false
|
||||
}
|
||||
|
||||
ret.PosStart, ret.PosEnd = m[0], m[1]
|
||||
ret.FullURL = s[ret.PosStart:ret.PosEnd]
|
||||
if strings.HasSuffix(ret.FullURL, ".") {
|
||||
// if url ends in '.', it's very likely that it is not part of the actual url but used to finish a sentence.
|
||||
ret.PosEnd--
|
||||
ret.FullURL = ret.FullURL[:len(ret.FullURL)-1]
|
||||
for i := 0; i < len(m); i++ {
|
||||
m[i] = min(m[i], ret.PosEnd)
|
||||
}
|
||||
}
|
||||
|
||||
ret.CommitID = s[m[2]:m[3]]
|
||||
if m[5] > 0 {
|
||||
ret.SubPath = s[m[4]:m[5]]
|
||||
}
|
||||
|
||||
lastStart, lastEnd := m[len(m)-2], m[len(m)-1]
|
||||
if lastEnd > 0 {
|
||||
ret.QueryHash = s[lastStart:lastEnd][1:]
|
||||
}
|
||||
return ret, true
|
||||
}
|
||||
|
||||
// fullHashPatternProcessor renders SHA containing URLs
|
||||
func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
if ctx.Metas == nil {
|
||||
return
|
||||
}
|
||||
|
||||
next := node.NextSibling
|
||||
for node != nil && node != next {
|
||||
m := anyHashPattern.FindStringSubmatchIndex(node.Data)
|
||||
if m == nil {
|
||||
return
|
||||
nodeStop := node.NextSibling
|
||||
for node != nodeStop {
|
||||
if node.Type != html.TextNode {
|
||||
node = node.NextSibling
|
||||
continue
|
||||
}
|
||||
|
||||
urlFull := node.Data[m[0]:m[1]]
|
||||
text := base.ShortSha(node.Data[m[2]:m[3]])
|
||||
|
||||
// 3rd capture group matches a optional path
|
||||
subpath := ""
|
||||
if m[5] > 0 {
|
||||
subpath = node.Data[m[4]:m[5]]
|
||||
ret, ok := anyHashPatternExtract(node.Data)
|
||||
if !ok {
|
||||
node = node.NextSibling
|
||||
continue
|
||||
}
|
||||
|
||||
// 4th capture group matches a optional url hash
|
||||
hash := ""
|
||||
if m[7] > 0 {
|
||||
hash = node.Data[m[6]:m[7]][1:]
|
||||
text := base.ShortSha(ret.CommitID)
|
||||
if ret.SubPath != "" {
|
||||
text += ret.SubPath
|
||||
}
|
||||
|
||||
start := m[0]
|
||||
end := m[1]
|
||||
|
||||
// If url ends in '.', it's very likely that it is not part of the
|
||||
// actual url but used to finish a sentence.
|
||||
if strings.HasSuffix(urlFull, ".") {
|
||||
end--
|
||||
urlFull = urlFull[:len(urlFull)-1]
|
||||
if hash != "" {
|
||||
hash = hash[:len(hash)-1]
|
||||
} else if subpath != "" {
|
||||
subpath = subpath[:len(subpath)-1]
|
||||
}
|
||||
if ret.QueryHash != "" {
|
||||
text += " (" + ret.QueryHash + ")"
|
||||
}
|
||||
|
||||
if subpath != "" {
|
||||
text += subpath
|
||||
}
|
||||
|
||||
if hash != "" {
|
||||
text += " (" + hash + ")"
|
||||
}
|
||||
replaceContent(node, start, end, createCodeLink(urlFull, text, "commit"))
|
||||
replaceContent(node, ret.PosStart, ret.PosEnd, createCodeLink(ret.FullURL, text, "commit"))
|
||||
node = node.NextSibling.NextSibling
|
||||
}
|
||||
}
|
||||
|
@ -1021,19 +1034,16 @@ func comparePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
if ctx.Metas == nil {
|
||||
return
|
||||
}
|
||||
|
||||
next := node.NextSibling
|
||||
for node != nil && node != next {
|
||||
m := comparePattern.FindStringSubmatchIndex(node.Data)
|
||||
if m == nil {
|
||||
return
|
||||
nodeStop := node.NextSibling
|
||||
for node != nodeStop {
|
||||
if node.Type != html.TextNode {
|
||||
node = node.NextSibling
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure that every group (m[0]...m[7]) has a match
|
||||
for i := 0; i < 8; i++ {
|
||||
if m[i] == -1 {
|
||||
return
|
||||
}
|
||||
m := comparePattern.FindStringSubmatchIndex(node.Data)
|
||||
if m == nil || slices.Contains(m[:8], -1) { // ensure that every group (m[0]...m[7]) has a match
|
||||
node = node.NextSibling
|
||||
continue
|
||||
}
|
||||
|
||||
urlFull := node.Data[m[0]:m[1]]
|
||||
|
|
|
@ -42,7 +42,7 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
|
|||
CommitID: node.Data[m[6]:m[7]],
|
||||
FilePath: node.Data[m[8]:m[9]],
|
||||
}
|
||||
if !httplib.IsCurrentGiteaSiteURL(opts.FullURL) {
|
||||
if !httplib.IsCurrentGiteaSiteURL(ctx.Ctx, opts.FullURL) {
|
||||
return 0, 0, "", nil
|
||||
}
|
||||
u, err := url.Parse(opts.FilePath)
|
||||
|
@ -60,7 +60,8 @@ func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosSt
|
|||
}
|
||||
|
||||
func codePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
for node != nil {
|
||||
nodeStop := node.NextSibling
|
||||
for node != nodeStop {
|
||||
if node.Type != html.TextNode {
|
||||
node = node.NextSibling
|
||||
continue
|
||||
|
|
|
@ -399,36 +399,61 @@ func TestRegExp_sha1CurrentPattern(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRegExp_anySHA1Pattern(t *testing.T) {
|
||||
testCases := map[string][]string{
|
||||
testCases := map[string]anyHashPatternResult{
|
||||
"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": {
|
||||
"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||
"/test/unit/event.js",
|
||||
"#L2703",
|
||||
CommitID: "a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||
SubPath: "/test/unit/event.js",
|
||||
QueryHash: "L2703",
|
||||
},
|
||||
"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": {
|
||||
"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||
"/test/unit/event.js",
|
||||
"",
|
||||
CommitID: "a644101ed04d0beacea864ce805e0c4f86ba1cd1",
|
||||
SubPath: "/test/unit/event.js",
|
||||
},
|
||||
"https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": {
|
||||
"0705be475092aede1eddae01319ec931fb9c65fc",
|
||||
"",
|
||||
"",
|
||||
CommitID: "0705be475092aede1eddae01319ec931fb9c65fc",
|
||||
},
|
||||
"https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": {
|
||||
"0705be475092aede1eddae01319ec931fb9c65fc",
|
||||
"/src",
|
||||
"",
|
||||
CommitID: "0705be475092aede1eddae01319ec931fb9c65fc",
|
||||
SubPath: "/src",
|
||||
},
|
||||
"https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": {
|
||||
"d8a994ef243349f321568f9e36d5c3f444b99cae",
|
||||
"",
|
||||
"#diff-2",
|
||||
CommitID: "d8a994ef243349f321568f9e36d5c3f444b99cae",
|
||||
QueryHash: "diff-2",
|
||||
},
|
||||
"non-url": {},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678?a=b#L1-L2": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
QueryHash: "L1-L2",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678.": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678/sub.": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
SubPath: "/sub",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678?a=b.": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678?a=b&c=d": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
},
|
||||
"http://a/b/c/d/e/1234567812345678123456781234567812345678123456781234567812345678#hash.": {
|
||||
CommitID: "1234567812345678123456781234567812345678123456781234567812345678",
|
||||
QueryHash: "hash",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
assert.Equal(t, anyHashPattern.FindStringSubmatch(k)[1:], v)
|
||||
ret, ok := anyHashPatternExtract(k)
|
||||
if v.CommitID == "" {
|
||||
assert.False(t, ok)
|
||||
} else {
|
||||
assert.EqualValues(t, strings.TrimSuffix(k, "."), ret.FullURL)
|
||||
assert.EqualValues(t, v.CommitID, ret.CommitID)
|
||||
assert.EqualValues(t, v.SubPath, ret.SubPath)
|
||||
assert.EqualValues(t, v.QueryHash, ret.QueryHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -124,6 +124,11 @@ func TestRender_CrossReferences(t *testing.T) {
|
|||
test(
|
||||
util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
|
||||
`<p><a href="`+util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/some-repo-name#12345</a></p>`)
|
||||
|
||||
inputURL := "https://host/a/b/commit/0123456789012345678901234567890123456789/foo.txt?a=b#L2-L3"
|
||||
test(
|
||||
inputURL,
|
||||
`<p><a href="`+inputURL+`" rel="nofollow"><code>0123456789/foo.txt (L2-L3)</code></a></p>`)
|
||||
}
|
||||
|
||||
func TestMisc_IsSameDomain(t *testing.T) {
|
||||
|
@ -695,7 +700,7 @@ func TestIssue18471(t *testing.T) {
|
|||
}, strings.NewReader(data), &res)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>", res.String())
|
||||
assert.Equal(t, `<a href="http://domain/org/repo/compare/783b039...da951ce" class="compare"><code class="nohighlight">783b039...da951ce</code></a>`, res.String())
|
||||
}
|
||||
|
||||
func TestIsFullURL(t *testing.T) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
|
@ -54,6 +55,7 @@ type HookOptions struct {
|
|||
GitQuarantinePath string
|
||||
GitPushOptions GitPushOptions
|
||||
PullRequestID int64
|
||||
PushTrigger repository.PushTrigger
|
||||
DeployKeyID int64 // if the pusher is a DeployKey, then UserID is the repo's org user.
|
||||
IsWiki bool
|
||||
ActionPerm int
|
||||
|
|
|
@ -5,6 +5,7 @@ package repository
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
|
@ -36,6 +37,15 @@ func SyncRepoBranches(ctx context.Context, repoID, doerID int64) (int64, error)
|
|||
}
|
||||
|
||||
func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, doerID int64) (int64, error) {
|
||||
objFmt, err := gitRepo.GetObjectFormat()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("GetObjectFormat: %w", err)
|
||||
}
|
||||
_, err = db.GetEngine(ctx).ID(repo.ID).Update(&repo_model.Repository{ObjectFormatName: objFmt.Name()})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("UpdateRepository: %w", err)
|
||||
}
|
||||
|
||||
allBranches := container.Set[string]{}
|
||||
{
|
||||
branches, _, err := gitRepo.GetBranchNames(0, 0)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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/models/unittest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSyncRepoBranches(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
_, err := db.GetEngine(db.DefaultContext).ID(1).Update(&repo_model.Repository{ObjectFormatName: "bad-fmt"})
|
||||
assert.NoError(t, db.TruncateBeans(db.DefaultContext, &git_model.Branch{}))
|
||||
assert.NoError(t, err)
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.Equal(t, "bad-fmt", repo.ObjectFormatName)
|
||||
_, err = SyncRepoBranches(db.DefaultContext, 1, 0)
|
||||
assert.NoError(t, err)
|
||||
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
assert.Equal(t, "sha1", repo.ObjectFormatName)
|
||||
branch, err := git_model.GetBranch(db.DefaultContext, 1, "master")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "master", branch.Name)
|
||||
}
|
|
@ -25,11 +25,19 @@ const (
|
|||
EnvKeyID = "GITEA_KEY_ID" // public key ID
|
||||
EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID"
|
||||
EnvPRID = "GITEA_PR_ID"
|
||||
EnvPushTrigger = "GITEA_PUSH_TRIGGER"
|
||||
EnvIsInternal = "GITEA_INTERNAL_PUSH"
|
||||
EnvAppURL = "GITEA_ROOT_URL"
|
||||
EnvActionPerm = "GITEA_ACTION_PERM"
|
||||
)
|
||||
|
||||
type PushTrigger string
|
||||
|
||||
const (
|
||||
PushTriggerPRMergeToBase PushTrigger = "pr-merge-to-base"
|
||||
PushTriggerPRUpdateWithBase PushTrigger = "pr-update-with-base"
|
||||
)
|
||||
|
||||
// InternalPushingEnvironment returns an os environment to switch off hooks on push
|
||||
// It is recommended to avoid using this unless you are pushing within a transaction
|
||||
// or if you absolutely are sure that post-receive and pre-receive will do nothing
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import "github.com/gobwas/glob"
|
||||
|
||||
type GlobMatcher struct {
|
||||
compiledGlob glob.Glob
|
||||
patternString string
|
||||
}
|
||||
|
||||
var _ glob.Glob = (*GlobMatcher)(nil)
|
||||
|
||||
func (g *GlobMatcher) Match(s string) bool {
|
||||
return g.compiledGlob.Match(s)
|
||||
}
|
||||
|
||||
func (g *GlobMatcher) PatternString() string {
|
||||
return g.patternString
|
||||
}
|
||||
|
||||
func GlobMatcherCompile(pattern string, separators ...rune) (*GlobMatcher, error) {
|
||||
g, err := glob.Compile(pattern, separators...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GlobMatcher{
|
||||
compiledGlob: g,
|
||||
patternString: pattern,
|
||||
}, nil
|
||||
}
|
|
@ -10,8 +10,6 @@ import (
|
|||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
// Indexer settings
|
||||
|
@ -30,8 +28,8 @@ var Indexer = struct {
|
|||
RepoConnStr string
|
||||
RepoIndexerName string
|
||||
MaxIndexerFileSize int64
|
||||
IncludePatterns []glob.Glob
|
||||
ExcludePatterns []glob.Glob
|
||||
IncludePatterns []*GlobMatcher
|
||||
ExcludePatterns []*GlobMatcher
|
||||
ExcludeVendored bool
|
||||
}{
|
||||
IssueType: "bleve",
|
||||
|
@ -93,12 +91,12 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
|
|||
}
|
||||
|
||||
// IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing
|
||||
func IndexerGlobFromString(globstr string) []glob.Glob {
|
||||
extarr := make([]glob.Glob, 0, 10)
|
||||
func IndexerGlobFromString(globstr string) []*GlobMatcher {
|
||||
extarr := make([]*GlobMatcher, 0, 10)
|
||||
for _, expr := range strings.Split(strings.ToLower(globstr), ",") {
|
||||
expr = strings.TrimSpace(expr)
|
||||
if expr != "" {
|
||||
if g, err := glob.Compile(expr, '.', '/'); err != nil {
|
||||
if g, err := GlobMatcherCompile(expr, '.', '/'); err != nil {
|
||||
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err)
|
||||
} else {
|
||||
extarr = append(extarr, g)
|
||||
|
|
|
@ -85,7 +85,7 @@ type CreatePullRequestOption struct {
|
|||
// EditPullRequestOption options when modify pull request
|
||||
type EditPullRequestOption struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Body *string `json:"body"`
|
||||
Base string `json:"base"`
|
||||
Assignee string `json:"assignee"`
|
||||
Assignees []string `json:"assignees"`
|
||||
|
|
|
@ -436,6 +436,7 @@ oauth_signin_submit=绑定账号
|
|||
oauth.signin.error=处理授权请求时出错。 如果此错误仍然存在,请联系站点管理员。
|
||||
oauth.signin.error.access_denied=授权请求被拒绝。
|
||||
oauth.signin.error.temporarily_unavailable=授权失败,因为认证服务器暂时不可用。请稍后再试。
|
||||
oauth_callback_unable_auto_reg=自动注册已启用,但OAuth2 提供商 %[1]s 返回缺失的字段:%[2]s,无法自动创建帐户,请创建或链接到一个帐户,或联系站点管理员。
|
||||
openid_connect_submit=连接
|
||||
openid_connect_title=连接到现有的帐户
|
||||
openid_connect_desc=所选的 OpenID URI 未知。在这里关联一个新帐户。
|
||||
|
@ -763,6 +764,8 @@ manage_themes=选择默认主题
|
|||
manage_openid=管理 OpenID 地址
|
||||
email_desc=您的主要电子邮件地址将用于通知、密码恢复,基于网页界面的Git操作(只要它不是设置为隐藏的)。
|
||||
theme_desc=这将是您在整个网站上的默认主题。
|
||||
theme_colorblindness_help=颜色障碍主题支持
|
||||
theme_colorblindness_prompt=Gitea 只能获得一些基本的颜色障碍支持,这些主题只定义了少数颜色。 这项工作仍在进行中,可以通过在主题的 CSS 文件中定义更多颜色来做更多的改进。
|
||||
primary=主要
|
||||
activated=已激活
|
||||
requires_activation=需要激活
|
||||
|
@ -1810,7 +1813,7 @@ pulls.is_empty=此分支上的更改已经在目标分支上。这将是一个
|
|||
pulls.required_status_check_failed=一些必要的检查没有成功
|
||||
pulls.required_status_check_missing=缺少一些必要的检查。
|
||||
pulls.required_status_check_administrator=作为管理员,您仍可合并此合并请求
|
||||
pulls.blocked_by_approvals=此合并请求没有通过审批。已获取审批数%d个,共需要审批数%d个。
|
||||
pulls.blocked_by_approvals=此合并请求还没有足够的批准。已获批准数 %d 个,需获批准数 %d 个。
|
||||
pulls.blocked_by_rejection=此合并请求有官方审核员请求的更改。
|
||||
pulls.blocked_by_official_review_requests=此合并请求需要官方评审。
|
||||
pulls.blocked_by_outdated_branch=此合并请求因过期而被阻止。
|
||||
|
@ -1884,14 +1887,14 @@ pulls.clear_merge_message_hint=清除合并消息只会删除提交消息内容
|
|||
pulls.auto_merge_button_when_succeed=(当检查成功时)
|
||||
pulls.auto_merge_when_succeed=在所有检查成功后自动合并
|
||||
pulls.auto_merge_newly_scheduled=合并请求计划在所有检查成功后合并。
|
||||
pulls.auto_merge_has_pending_schedule=%[1]s 安排此拉取请求在所有检查成功时自动合并 %[2]s。
|
||||
pulls.auto_merge_has_pending_schedule=%[1]s 于 %[2]s 设置此合并请求在所有检查成功时自动合并 。
|
||||
|
||||
pulls.auto_merge_cancel_schedule=取消自动合并
|
||||
pulls.auto_merge_not_scheduled=此拉取请求没有计划自动合并。
|
||||
pulls.auto_merge_canceled_schedule=此拉取请求的自动合并已取消。
|
||||
pulls.auto_merge_not_scheduled=此合并请求没有计划自动合并。
|
||||
pulls.auto_merge_canceled_schedule=此合并请求的自动合并已取消。
|
||||
|
||||
pulls.auto_merge_newly_scheduled_comment=`已安排此拉取请求在所有检查成功后自动合并 %[1]s`
|
||||
pulls.auto_merge_canceled_schedule_comment=`已取消当所有检查成功后自动合并此拉取请求 %[1]s`
|
||||
pulls.auto_merge_newly_scheduled_comment=`已于 %[1]s 设置此拉取请求在所有检查成功后自动合并`
|
||||
pulls.auto_merge_canceled_schedule_comment=`已于 %[1]s 取消了自动合并设置 `
|
||||
|
||||
pulls.delete.title=删除此拉取请求?
|
||||
pulls.delete.text=你真的要删除这个拉取请求吗? (这将永久删除所有内容。如果你打算将内容存档,请考虑关闭它)
|
||||
|
@ -3331,7 +3334,7 @@ reopen_pull_request=`重新开启了合并请求 <a href="%[1]s">%[3]s#%[2]s</a>
|
|||
comment_issue=`评论了工单 <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
comment_pull=`评论了合并请求 <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
merge_pull_request=`合并了合并请求 <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
auto_merge_pull_request=`自动合并了拉取请求 <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
auto_merge_pull_request=`自动合并了合并请求 <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||
transfer_repo=将仓库 <code>%s</code> 转移至 <a href="%s">%s</a>
|
||||
push_tag=推送了标签 <a href="%[2]s">%[3]s</a> 至仓库 <a href="%[1]s">%[4]s</a>
|
||||
delete_tag=从<a href="%[1]s">%[3]s</a> 删除了标签 %[2]s
|
||||
|
@ -3492,6 +3495,7 @@ npm.install=要使用 npm 安装软件包,请运行以下命令:
|
|||
npm.install2=或将其添加到 package.json 文件:
|
||||
npm.dependencies=依赖项
|
||||
npm.dependencies.development=开发依赖
|
||||
npm.dependencies.bundle=已绑定的依赖关系
|
||||
npm.dependencies.peer=Peer 依赖
|
||||
npm.dependencies.optional=可选依赖
|
||||
npm.details.tag=标签
|
||||
|
|
34
package.json
|
@ -5,7 +5,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@citation-js/core": "0.7.11",
|
||||
"@citation-js/plugin-bibtex": "0.7.11",
|
||||
"@citation-js/plugin-bibtex": "0.7.12",
|
||||
"@citation-js/plugin-csl": "0.7.11",
|
||||
"@citation-js/plugin-software-formats": "0.6.1",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
|
@ -14,15 +14,15 @@
|
|||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.9.0",
|
||||
"@silverwind/vue3-calendar-heatmap": "2.0.6",
|
||||
"add-asset-webpack-plugin": "2.0.1",
|
||||
"add-asset-webpack-plugin": "3.0.0",
|
||||
"ansi_up": "6.0.2",
|
||||
"asciinema-player": "3.7.1",
|
||||
"chart.js": "4.4.2",
|
||||
"chartjs-adapter-dayjs-4": "1.0.4",
|
||||
"chartjs-plugin-zoom": "2.0.1",
|
||||
"clippie": "4.0.7",
|
||||
"clippie": "4.1.1",
|
||||
"css-loader": "7.1.1",
|
||||
"dayjs": "1.11.10",
|
||||
"dayjs": "1.11.11",
|
||||
"dropzone": "6.0.0-beta.2",
|
||||
"easymde": "2.18.0",
|
||||
"esbuild-loader": "4.1.0",
|
||||
|
@ -43,7 +43,7 @@
|
|||
"postcss-loader": "8.1.1",
|
||||
"postcss-nesting": "12.1.2",
|
||||
"sortablejs": "1.15.2",
|
||||
"swagger-ui-dist": "5.17.2",
|
||||
"swagger-ui-dist": "5.17.6",
|
||||
"tailwindcss": "3.4.3",
|
||||
"temporal-polyfill": "0.2.4",
|
||||
"throttle-debounce": "5.0.0",
|
||||
|
@ -53,7 +53,7 @@
|
|||
"tributejs": "5.1.3",
|
||||
"uint8-to-base64": "0.2.0",
|
||||
"vanilla-colorful": "0.7.2",
|
||||
"vue": "3.4.25",
|
||||
"vue": "3.4.27",
|
||||
"vue-bar-graph": "2.0.0",
|
||||
"vue-chartjs": "5.3.1",
|
||||
"vue-loader": "17.4.2",
|
||||
|
@ -63,10 +63,10 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "4.3.0",
|
||||
"@playwright/test": "1.43.1",
|
||||
"@playwright/test": "1.44.0",
|
||||
"@stoplight/spectral-cli": "6.11.1",
|
||||
"@stylistic/eslint-plugin-js": "1.7.2",
|
||||
"@stylistic/stylelint-plugin": "2.1.1",
|
||||
"@stylistic/eslint-plugin-js": "2.0.0",
|
||||
"@stylistic/stylelint-plugin": "2.1.2",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-array-func": "4.0.0",
|
||||
|
@ -76,24 +76,24 @@
|
|||
"eslint-plugin-no-jquery": "2.7.0",
|
||||
"eslint-plugin-no-use-extend-native": "0.5.0",
|
||||
"eslint-plugin-regexp": "2.5.0",
|
||||
"eslint-plugin-sonarjs": "0.25.1",
|
||||
"eslint-plugin-sonarjs": "1.0.3",
|
||||
"eslint-plugin-unicorn": "52.0.0",
|
||||
"eslint-plugin-vitest": "0.4.1",
|
||||
"eslint-plugin-vitest-globals": "1.5.0",
|
||||
"eslint-plugin-vue": "9.25.0",
|
||||
"eslint-plugin-vue-scoped-css": "2.8.0",
|
||||
"eslint-plugin-wc": "2.1.0",
|
||||
"happy-dom": "14.7.1",
|
||||
"markdownlint-cli": "0.39.0",
|
||||
"postcss-html": "1.6.0",
|
||||
"stylelint": "16.4.0",
|
||||
"happy-dom": "14.10.1",
|
||||
"markdownlint-cli": "0.40.0",
|
||||
"postcss-html": "1.7.0",
|
||||
"stylelint": "16.5.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.4",
|
||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||
"svgo": "3.2.0",
|
||||
"svgo": "3.3.0",
|
||||
"updates": "16.0.1",
|
||||
"vite-string-plugin": "1.2.0",
|
||||
"vitest": "1.5.2"
|
||||
"vite-string-plugin": "1.3.1",
|
||||
"vitest": "1.6.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="svg gitea-chef" width="16" height="16" aria-hidden="true"><g fill="none" fill-rule="evenodd"><path fill="#435363" d="M18 25.8c-4.3 0-7.7-3.6-7.7-8s3.4-7.9 7.7-7.9c3.5 0 6.4 2.4 7.3 5.7h3c-1-5-5.2-8.7-10.3-8.7-5.9 0-10.6 4.9-10.6 10.9 0 6.1 4.7 11 10.6 11 5.1 0 9.3-3.7 10.3-8.7h-3c-.9 3.3-3.8 5.7-7.3 5.7"/><path fill="#435363" d="M12.8 23.2c1.3 1.4 3.1 2.3 5.2 2.3v-3.2c-1.2 0-2.3-.5-3.1-1.3z"/><path fill="#F38B00" d="M10.6 17.8c0 1.1.3 2.2.6 3.1l2.9-1.3c-.3-.5-.4-1.1-.4-1.8 0-2.4 1.9-4.4 4.3-4.4v-3.2c-4.1 0-7.4 3.4-7.4 7.6"/><path fill="#435363" d="m20.6 10.7-1.1 3c.9.4 1.7 1.1 2.2 1.9H25c-.7-2.2-2.3-4-4.4-4.9"/><path fill="#F38B00" d="m19.5 22 1.1 2.9c2.1-.8 3.7-2.6 4.4-4.8h-3.3c-.5.8-1.3 1.5-2.2 1.9"/><path fill="#435363" d="M4.4 22.1c-.1-.2-.1-.3-.1-.5-.1-.2-.1-.3-.2-.5V21c0-.1 0-.3-.1-.4v-.5c-.1-.1-.1-.2-.1-.3-.1-.6-.1-1.3-.1-2H.9c0 .8 0 1.5.1 2.2 0 .2.1.4.1.6v.1c0 .2.1.4.1.5s0 .2.1.3v.3c.1.1.1.2.1.4 0 0 .1.1.1.2 0 .2 0 .3.1.4v.2c.2.7.5 1.3.7 2L5 23.8c-.2-.6-.4-1.1-.6-1.7"/><path fill="#F38B00" d="M18 32.6c-3.9 0-7.5-1.7-10.1-4.4l-2 2.2c3.1 3.2 7.3 5.2 12.1 5.2 8.7 0 15.8-6.8 16.9-15.5H32c-1.1 7-7 12.5-14 12.5M18 3.1c3.1 0 6.1 1.1 8.4 2.9l1.8-2.4C25.3 1.4 21.8.1 18 .1 10.7.1 4.5 4.8 2.1 11.4l2.7 1.1C6.8 7 12 3.1 18 3.1"/><path fill="#435363" d="M32 15.6h2.9c-.3-2.6-1.2-5-2.5-7.2L30 10c1 1.7 1.7 3.6 2 5.6"/><path fill="#F38B00" d="M28.7 15.6h2.9c-.8-5.1-4.1-9.3-8.6-11.1l-1.1 2.8c3.5 1.3 6 4.5 6.8 8.3"/><path fill="#435363" d="M18 6.5v-3c-5.9 0-10.9 3.8-12.9 9.1l2.7 1.1C9.4 9.5 13.3 6.5 18 6.5"/><path fill="#F38B00" d="M7 17.8H4.1c0 6.1 3.6 11.2 8.7 13.4l1.1-2.8C9.9 26.7 7 22.6 7 17.8"/><path fill="#435363" d="M18 29.2v3c6.9 0 12.6-5.3 13.6-12.1h-2.9c-1 5.2-5.4 9.1-10.7 9.1"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="svg gitea-chef" width="16" height="16" aria-hidden="true"><g fill="none" fill-rule="evenodd"><path fill="#435363" d="M18 25.8c-4.3 0-7.7-3.6-7.7-8s3.4-7.9 7.7-7.9c3.5 0 6.4 2.4 7.3 5.7h3c-1-5-5.2-8.7-10.3-8.7-5.9 0-10.6 4.9-10.6 10.9 0 6.1 4.7 11 10.6 11 5.1 0 9.3-3.7 10.3-8.7h-3c-.9 3.3-3.8 5.7-7.3 5.7"/><path fill="#435363" d="M12.8 23.2c1.3 1.4 3.1 2.3 5.2 2.3v-3.2c-1.2 0-2.3-.5-3.1-1.3z"/><path fill="#f38b00" d="M10.6 17.8c0 1.1.3 2.2.6 3.1l2.9-1.3c-.3-.5-.4-1.1-.4-1.8 0-2.4 1.9-4.4 4.3-4.4v-3.2c-4.1 0-7.4 3.4-7.4 7.6"/><path fill="#435363" d="m20.6 10.7-1.1 3c.9.4 1.7 1.1 2.2 1.9H25c-.7-2.2-2.3-4-4.4-4.9"/><path fill="#f38b00" d="m19.5 22 1.1 2.9c2.1-.8 3.7-2.6 4.4-4.8h-3.3c-.5.8-1.3 1.5-2.2 1.9"/><path fill="#435363" d="M4.4 22.1c-.1-.2-.1-.3-.1-.5-.1-.2-.1-.3-.2-.5V21c0-.1 0-.3-.1-.4v-.5c-.1-.1-.1-.2-.1-.3-.1-.6-.1-1.3-.1-2H.9c0 .8 0 1.5.1 2.2 0 .2.1.4.1.6v.1c0 .2.1.4.1.5s0 .2.1.3v.3c.1.1.1.2.1.4 0 0 .1.1.1.2 0 .2 0 .3.1.4v.2c.2.7.5 1.3.7 2L5 23.8c-.2-.6-.4-1.1-.6-1.7"/><path fill="#f38b00" d="M18 32.6c-3.9 0-7.5-1.7-10.1-4.4l-2 2.2c3.1 3.2 7.3 5.2 12.1 5.2 8.7 0 15.8-6.8 16.9-15.5H32c-1.1 7-7 12.5-14 12.5M18 3.1c3.1 0 6.1 1.1 8.4 2.9l1.8-2.4C25.3 1.4 21.8.1 18 .1 10.7.1 4.5 4.8 2.1 11.4l2.7 1.1C6.8 7 12 3.1 18 3.1"/><path fill="#435363" d="M32 15.6h2.9c-.3-2.6-1.2-5-2.5-7.2L30 10c1 1.7 1.7 3.6 2 5.6"/><path fill="#f38b00" d="M28.7 15.6h2.9c-.8-5.1-4.1-9.3-8.6-11.1l-1.1 2.8c3.5 1.3 6 4.5 6.8 8.3"/><path fill="#435363" d="M18 6.5v-3c-5.9 0-10.9 3.8-12.9 9.1l2.7 1.1C9.4 9.5 13.3 6.5 18 6.5"/><path fill="#f38b00" d="M7 17.8H4.1c0 6.1 3.6 11.2 8.7 13.4l1.1-2.8C9.9 26.7 7 22.6 7 17.8"/><path fill="#435363" d="M18 29.2v3c6.9 0 12.6-5.3 13.6-12.1h-2.9c-1 5.2-5.4 9.1-10.7 9.1"/></g></svg>
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 260" class="svg gitea-debian" width="16" height="16" aria-hidden="true"><g fill="#D70751"><path d="M124.525 137.053c-4.125.058.78 2.125 6.165 2.954a55 55 0 0 0 4.04-3.479c-3.354.821-6.765.838-10.205.525m22.14-5.52c2.457-3.389 4.246-7.102 4.878-10.939-.551 2.736-2.035 5.099-3.435 7.592-7.711 4.854-.726-2.883-.004-5.824-8.29 10.436-1.138 6.257-1.439 9.171m8.174-21.265c.497-7.428-1.462-5.08-2.121-2.245.766.4 1.377 5.237 2.121 2.245M108.883 8.736c2.201.395 4.757.698 4.398 1.224 2.407-.528 2.954-1.015-4.398-1.224M113.281 9.96l-1.556.32 1.448-.127z"/><path d="M181.93 113.085c.247 6.671-1.95 9.907-3.932 15.637l-3.564 1.781c-2.919 5.666.282 3.598-1.807 8.105-4.556 4.049-13.823 12.67-16.789 13.457-2.163-.047 1.469-2.554 1.943-3.537-6.097 4.188-4.894 6.285-14.217 8.83l-.273-.607c-23.001 10.818-54.947-10.622-54.526-39.876-.246 1.857-.698 1.393-1.208 2.144-1.186-15.052 6.952-30.17 20.675-36.343 13.427-6.646 29.163-3.918 38.78 5.044-5.282-6.92-15.795-14.254-28.255-13.568-12.208.193-23.625 7.95-27.436 16.369-6.253 3.938-6.979 15.177-9.704 17.233-3.665 26.943 6.896 38.583 24.762 52.275 2.812 1.896.792 2.184 1.173 3.627-5.936-2.779-11.372-6.976-15.841-12.114 2.372 3.473 4.931 6.847 8.239 9.499-5.596-1.897-13.074-13.563-15.256-14.038 9.647 17.274 39.142 30.295 54.587 23.836-7.146.263-16.226.146-24.256-2.822-3.371-1.734-7.958-5.331-7.14-6.003 21.079 7.875 42.854 5.965 61.09-8.655 4.641-3.614 9.709-9.761 11.173-9.846-2.206 3.317.377 1.596-1.318 4.523 4.625-7.456-2.008-3.035 4.779-12.877l2.507 3.453c-.931-6.188 7.687-13.704 6.813-23.492 1.975-2.994 2.206 3.22.107 10.107 2.912-7.64.767-8.867 1.516-15.171.81 2.118 1.867 4.37 2.412 6.606-1.895-7.382 1.948-12.433 2.898-16.724-.937-.415-2.928 3.264-3.383-5.457.065-3.788 1.054-1.985 1.435-2.917-.744-.427-2.694-3.33-3.88-8.9.86-1.308 2.3 3.393 3.47 3.586-.753-4.429-2.049-7.805-2.103-11.202-3.421-7.149-1.211.953-3.985-3.069-3.641-11.357 3.021-2.637 3.47-7.796 5.52 7.995 8.667 20.387 10.11 25.519-1.103-6.258-2.883-12.32-5.058-18.185 1.677.705-2.699-12.875 2.18-3.882-5.21-19.172-22.302-37.087-38.025-45.493 1.924 1.76 4.354 3.971 3.481 4.317-7.819-4.656-6.444-5.018-7.565-6.985-6.369-2.591-6.788.208-11.007.004-12.005-6.368-14.318-5.69-25.368-9.681l.502 2.349c-7.953-2.649-9.265 1.005-17.862.009-.523-.409 2.753-1.479 5.452-1.871-7.69 1.015-7.329-1.515-14.854.279 1.855-1.301 3.815-2.162 5.793-3.269-6.271.381-14.971 3.649-12.286.677-10.235 4.569-28.403 10.976-38.597 20.535l-.321-2.142c-4.672 5.608-20.371 16.748-21.622 24.011l-1.249.291c-2.431 4.116-4.004 8.781-5.932 13.016-3.18 5.417-4.661 2.085-4.208 2.934-6.253 12.679-9.359 23.332-12.043 32.069 1.912 2.858.046 17.206.769 28.688-3.141 56.709 39.8 111.77 86.737 124.48 6.88 2.459 17.11 2.364 25.813 2.618-10.268-2.937-11.595-1.556-21.595-5.044-7.215-3.398-8.797-7.277-13.907-11.711l2.022 3.573c-10.021-3.547-5.829-4.39-13.982-6.972l2.16-2.82c-3.249-.246-8.604-5.475-10.069-8.371l-3.553.14c-4.27-5.269-6.545-9.063-6.379-12.005l-1.148 2.047c-1.301-2.235-15.709-19.759-8.234-15.679-1.389-1.271-3.235-2.067-5.237-5.703l1.522-1.739c-3.597-4.627-6.621-10.562-6.391-12.536 1.919 2.592 3.25 3.075 4.568 3.52-9.083-22.539-9.593-1.242-16.474-22.942l1.456-.116c-1.116-1.682-1.793-3.506-2.69-5.298l.633-6.313c-6.541-7.562-1.829-32.151-.887-45.637.655-5.485 5.459-11.322 9.114-20.477l-2.227-.384c4.256-7.423 24.301-29.814 33.583-28.662 4.499-5.649-.892-.02-1.772-1.443 9.878-10.223 12.984-7.222 19.65-9.061 7.19-4.268-6.17 1.664-2.761-1.628 12.427-3.174 8.808-7.216 25.021-8.828 1.71.973-3.969 1.503-5.395 2.766 10.354-5.066 32.769-3.914 47.326 2.811 16.895 7.896 35.873 31.232 36.622 53.189l.852.229c-.431 8.729 1.336 18.822-1.727 28.094l2.1-4.385"/><path d="m79.5 142.715-.578 2.893c2.71 3.683 4.861 7.673 8.323 10.552-2.49-4.863-4.341-6.872-7.745-13.445m6.409-.251c-1.435-1.587-2.284-3.497-3.235-5.4.909 3.345 2.771 6.219 4.504 9.143zm113.411-24.65-.605 1.52c-1.111 7.892-3.511 15.701-7.189 22.941a72.1 72.1 0 0 0 7.79-24.461M109.698 6.757c2.789-1.022 6.855-.56 9.814-1.233-3.855.324-7.693.517-11.484 1.005zM11.781 58.824c.642 5.951-4.477 8.26 1.134 4.337 3.007-6.773-1.175-1.87-1.134-4.337M5.188 86.362c1.292-3.967 1.526-6.349 2.02-8.645-3.571 4.566-1.643 5.539-2.02 8.645"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 260" class="svg gitea-debian" width="16" height="16" aria-hidden="true"><g fill="#d70751"><path d="M124.525 137.053c-4.125.058.78 2.125 6.165 2.954a55 55 0 0 0 4.04-3.479c-3.354.821-6.765.838-10.205.525m22.14-5.52c2.457-3.389 4.246-7.102 4.878-10.939-.551 2.736-2.035 5.099-3.435 7.592-7.711 4.854-.726-2.883-.004-5.824-8.29 10.436-1.138 6.257-1.439 9.171m8.174-21.265c.497-7.428-1.462-5.08-2.121-2.245.766.4 1.377 5.237 2.121 2.245M108.883 8.736c2.201.395 4.757.698 4.398 1.224 2.407-.528 2.954-1.015-4.398-1.224M113.281 9.96l-1.556.32 1.448-.127z"/><path d="M181.93 113.085c.247 6.671-1.95 9.907-3.932 15.637l-3.564 1.781c-2.919 5.666.282 3.598-1.807 8.105-4.556 4.049-13.823 12.67-16.789 13.457-2.163-.047 1.469-2.554 1.943-3.537-6.097 4.188-4.894 6.285-14.217 8.83l-.273-.607c-23.001 10.818-54.947-10.622-54.526-39.876-.246 1.857-.698 1.393-1.208 2.144-1.186-15.052 6.952-30.17 20.675-36.343 13.427-6.646 29.163-3.918 38.78 5.044-5.282-6.92-15.795-14.254-28.255-13.568-12.208.193-23.625 7.95-27.436 16.369-6.253 3.938-6.979 15.177-9.704 17.233-3.665 26.943 6.896 38.583 24.762 52.275 2.812 1.896.792 2.184 1.173 3.627-5.936-2.779-11.372-6.976-15.841-12.114 2.372 3.473 4.931 6.847 8.239 9.499-5.596-1.897-13.074-13.563-15.256-14.038 9.647 17.274 39.142 30.295 54.587 23.836-7.146.263-16.226.146-24.256-2.822-3.371-1.734-7.958-5.331-7.14-6.003 21.079 7.875 42.854 5.965 61.09-8.655 4.641-3.614 9.709-9.761 11.173-9.846-2.206 3.317.377 1.596-1.318 4.523 4.625-7.456-2.008-3.035 4.779-12.877l2.507 3.453c-.931-6.188 7.687-13.704 6.813-23.492 1.975-2.994 2.206 3.22.107 10.107 2.912-7.64.767-8.867 1.516-15.171.81 2.118 1.867 4.37 2.412 6.606-1.895-7.382 1.948-12.433 2.898-16.724-.937-.415-2.928 3.264-3.383-5.457.065-3.788 1.054-1.985 1.435-2.917-.744-.427-2.694-3.33-3.88-8.9.86-1.308 2.3 3.393 3.47 3.586-.753-4.429-2.049-7.805-2.103-11.202-3.421-7.149-1.211.953-3.985-3.069-3.641-11.357 3.021-2.637 3.47-7.796 5.52 7.995 8.667 20.387 10.11 25.519-1.103-6.258-2.883-12.32-5.058-18.185 1.677.705-2.699-12.875 2.18-3.882-5.21-19.172-22.302-37.087-38.025-45.493 1.924 1.76 4.354 3.971 3.481 4.317-7.819-4.656-6.444-5.018-7.565-6.985-6.369-2.591-6.788.208-11.007.004-12.005-6.368-14.318-5.69-25.368-9.681l.502 2.349c-7.953-2.649-9.265 1.005-17.862.009-.523-.409 2.753-1.479 5.452-1.871-7.69 1.015-7.329-1.515-14.854.279 1.855-1.301 3.815-2.162 5.793-3.269-6.271.381-14.971 3.649-12.286.677-10.235 4.569-28.403 10.976-38.597 20.535l-.321-2.142c-4.672 5.608-20.371 16.748-21.622 24.011l-1.249.291c-2.431 4.116-4.004 8.781-5.932 13.016-3.18 5.417-4.661 2.085-4.208 2.934-6.253 12.679-9.359 23.332-12.043 32.069 1.912 2.858.046 17.206.769 28.688-3.141 56.709 39.8 111.77 86.737 124.48 6.88 2.459 17.11 2.364 25.813 2.618-10.268-2.937-11.595-1.556-21.595-5.044-7.215-3.398-8.797-7.277-13.907-11.711l2.022 3.573c-10.021-3.547-5.829-4.39-13.982-6.972l2.16-2.82c-3.249-.246-8.604-5.475-10.069-8.371l-3.553.14c-4.27-5.269-6.545-9.063-6.379-12.005l-1.148 2.047c-1.301-2.235-15.709-19.759-8.234-15.679-1.389-1.271-3.235-2.067-5.237-5.703l1.522-1.739c-3.597-4.627-6.621-10.562-6.391-12.536 1.919 2.592 3.25 3.075 4.568 3.52-9.083-22.539-9.593-1.242-16.474-22.942l1.456-.116c-1.116-1.682-1.793-3.506-2.69-5.298l.633-6.313c-6.541-7.562-1.829-32.151-.887-45.637.655-5.485 5.459-11.322 9.114-20.477l-2.227-.384c4.256-7.423 24.301-29.814 33.583-28.662 4.499-5.649-.892-.02-1.772-1.443 9.878-10.223 12.984-7.222 19.65-9.061 7.19-4.268-6.17 1.664-2.761-1.628 12.427-3.174 8.808-7.216 25.021-8.828 1.71.973-3.969 1.503-5.395 2.766 10.354-5.066 32.769-3.914 47.326 2.811 16.895 7.896 35.873 31.232 36.622 53.189l.852.229c-.431 8.729 1.336 18.822-1.727 28.094l2.1-4.385"/><path d="m79.5 142.715-.578 2.893c2.71 3.683 4.861 7.673 8.323 10.552-2.49-4.863-4.341-6.872-7.745-13.445m6.409-.251c-1.435-1.587-2.284-3.497-3.235-5.4.909 3.345 2.771 6.219 4.504 9.143zm113.411-24.65-.605 1.52c-1.111 7.892-3.511 15.701-7.189 22.941a72.1 72.1 0 0 0 7.79-24.461M109.698 6.757c2.789-1.022 6.855-.56 9.814-1.233-3.855.324-7.693.517-11.484 1.005zM11.781 58.824c.642 5.951-4.477 8.26 1.134 4.337 3.007-6.773-1.175-1.87-1.134-4.337M5.188 86.362c1.292-3.967 1.526-6.349 2.02-8.645-3.571 4.566-1.643 5.539-2.02 8.645"/></g></svg>
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 316 329" class="svg gitea-gitbucket" width="16" height="16" aria-hidden="true"><path d="M123 21.1c-44.8 2.8-84 12.8-97.1 24.6-5 4.5-5 7.1 0 11.6C41.7 71.5 96.6 82.9 150 83h10.6l5.4-5.6c11.8-12.1 21.3-12.4 32.6-1.2l5.2 5 13.3-1.7c33.8-4.2 61.5-12.7 71.8-22 5.3-4.8 5.3-7.2 0-12-10.1-9.1-39.1-18.1-70.4-21.9-28.3-3.4-65.6-4.4-95.5-2.5M23.2 80.6c.4 1.6 7 42.9 14.8 91.9 7.9 49 14.7 89.5 15.2 90.2 1.7 2.1 25.8 11.4 41.6 15.9 13 3.7 35.1 8.4 40 8.4.6 0 1.2-.6 1.2-1.3 0-.6-17.4-18.5-38.6-39.7C57.9 206.6 55 203.2 55 196s3-10.7 38.3-45.9c30.1-30 34.8-34.3 36.6-33.5 1.1.5 8.7 7.4 17 15.2l15.1 14.4v5.9c0 7.3 2.4 12.4 7.7 16.7l3.8 3v61.8l-3.8 3.4c-10.2 8.9-10.2 22.9-.1 30.7 3.1 2.3 4.9 2.8 10.8 3.1 8.2.4 11.5-1.1 16.2-7.3 2.2-3 2.9-5.1 3.2-10 .4-6.5-.2-8.3-5.3-15.4l-2.5-3.4v-26.6c0-26.7.3-31.1 2.3-31.1.5 0 5.4 4.4 10.9 9.7 9.6 9.5 9.9 10 10.8 15.7 1.7 10.3 8.9 16.6 19 16.6 7.6 0 13.5-3.9 17.4-11.7 3.2-6.4 1.6-14.3-4.3-20.6-4.1-4.4-7.3-5.7-14.9-5.7h-6.8l-12.7-12.1c-10.7-10.1-12.7-12.6-13.2-15.7-1.2-7.2-1.6-8.2-4.7-11.7-3.9-4.5-7.7-6.5-12.2-6.5-1.9 0-4.5-.4-5.8-.9s-9.9-8.2-19.3-17l-17-16-7.1-.1c-10.6 0-36-2.7-52.4-5.5-22.8-4-38.5-8.6-57.9-17.2-1.1-.5-1.3 0-.9 2.3M278.5 83.6c-8.6 3.6-28 8.8-42.5 11.4-6.9 1.2-12.9 2.6-13.5 3.1-.6.6 9.3 11.2 27.5 29.4 15.6 15.6 28.5 28.3 28.7 28.1s1.9-15.8 3.8-34.7 3.7-35.6 4-37.2c.6-3.4-.2-3.4-8-.1M255.2 259.3c-7.8 7.8-14.2 14.6-14.2 15s.7.7 1.6.7c2.2 0 23-8.9 24.2-10.3.9-1.1 3.5-18.7 2.9-19.3-.2-.2-6.7 6.1-14.5 13.9M56 283.5c0 3.4 4 9.5 8.4 12.9 6.1 4.6 19.7 10.4 31.7 13.5 16.9 4.3 32.1 6.2 53.4 6.8l19 .5-7-7.1c-6.8-6.9-7.1-7.1-12-7.1-18.9 0-55.1-7.9-80.6-17.6C62.5 283 57 281 56.6 281c-.3 0-.6 1.1-.6 2.5M262 283.4c-5.3 2.8-25 9.7-36 12.6l-11.4 2.9-7.8 7.8c-4.2 4.2-7.6 7.9-7.4 8.1.9.8 24.4-4.1 33.4-6.9 16.4-5.3 26.7-11.4 30.8-18.5 2.4-4 3.1-7.4 1.7-7.4-.5.1-1.9.7-3.3 1.4"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 316 329" class="svg gitea-gitbucket" width="16" height="16" aria-hidden="true"><path d="M123 21.1c-44.8 2.8-84 12.8-97.1 24.6-5 4.5-5 7.1 0 11.6C41.7 71.5 96.6 82.9 150 83h10.6l5.4-5.6c11.8-12.1 21.3-12.4 32.6-1.2l5.2 5 13.3-1.7c33.8-4.2 61.5-12.7 71.8-22 5.3-4.8 5.3-7.2 0-12-10.1-9.1-39.1-18.1-70.4-21.9-28.3-3.4-65.6-4.4-95.5-2.5M23.2 80.6c.4 1.6 7 42.9 14.8 91.9 7.9 49 14.7 89.5 15.2 90.2 1.7 2.1 25.8 11.4 41.6 15.9 13 3.7 35.1 8.4 40 8.4.6 0 1.2-.6 1.2-1.3 0-.6-17.4-18.5-38.6-39.7C57.9 206.6 55 203.2 55 196s3-10.7 38.3-45.9c30.1-30 34.8-34.3 36.6-33.5 1.1.5 8.7 7.4 17 15.2l15.1 14.4v5.9c0 7.3 2.4 12.4 7.7 16.7l3.8 3v61.8l-3.8 3.4c-10.2 8.9-10.2 22.9-.1 30.7 3.1 2.3 4.9 2.8 10.8 3.1 8.2.4 11.5-1.1 16.2-7.3 2.2-3 2.9-5.1 3.2-10 .4-6.5-.2-8.3-5.3-15.4l-2.5-3.4v-26.6c0-26.7.3-31.1 2.3-31.1.5 0 5.4 4.4 10.9 9.7 9.6 9.5 9.9 10 10.8 15.7 1.7 10.3 8.9 16.6 19 16.6 7.6 0 13.5-3.9 17.4-11.7 3.2-6.4 1.6-14.3-4.3-20.6-4.1-4.4-7.3-5.7-14.9-5.7h-6.8l-12.7-12.1c-10.7-10.1-12.7-12.6-13.2-15.7-1.2-7.2-1.6-8.2-4.7-11.7-3.9-4.5-7.7-6.5-12.2-6.5-1.9 0-4.5-.4-5.8-.9s-9.9-8.2-19.3-17l-17-16-7.1-.1c-10.6 0-36-2.7-52.4-5.5-22.8-4-38.5-8.6-57.9-17.2-1.1-.5-1.3 0-.9 2.3M278.5 83.6c-8.6 3.6-28 8.8-42.5 11.4-6.9 1.2-12.9 2.6-13.5 3.1-.6.6 9.3 11.2 27.5 29.4 15.6 15.6 28.5 28.3 28.7 28.1s1.9-15.8 3.8-34.7 3.7-35.6 4-37.2c.6-3.4-.2-3.4-8-.1M255.2 259.3c-7.8 7.8-14.2 14.6-14.2 15s.7.7 1.6.7c2.2 0 23-8.9 24.2-10.3.9-1.1 3.5-18.7 2.9-19.3-.2-.2-6.7 6.1-14.5 13.9M56 283.5c0 3.4 4 9.5 8.4 12.9 6.1 4.6 19.7 10.4 31.7 13.5 16.9 4.3 32.1 6.2 53.4 6.8l19 .5-7-7.1c-6.8-6.9-7.1-7.1-12-7.1-18.9 0-55.1-7.9-80.6-17.6C62.5 283 57 281 56.6 281c-.3 0-.6 1.1-.6 2.5M262 283.4c-5.3 2.8-25 9.7-36 12.6l-11.4 2.9-7.8 7.8c-4.2 4.2-7.6 7.9-7.4 8.1.9.8 24.4-4.1 33.4-6.9 16.4-5.3 26.7-11.4 30.8-18.5 2.4-4 3.1-7.4 1.7-7.4-.5.1-1.9.7-3.3 1.4"/></svg>
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="svg gitea-gitlab" width="16" height="16" aria-hidden="true"><path fill="#E24329" d="m31.462 12.779-.045-.115-4.35-11.35a1.14 1.14 0 0 0-.447-.541 1.16 1.16 0 0 0-1.343.071c-.187.15-.322.356-.386.587l-2.94 9.001h-11.9l-2.941-9a1.14 1.14 0 0 0-1.045-.84 1.15 1.15 0 0 0-1.13.72L.579 12.68l-.045.113a8.09 8.09 0 0 0 2.68 9.34l.016.012.038.03 6.635 4.967 3.28 2.484 1.994 1.51a1.35 1.35 0 0 0 1.627 0l1.994-1.51 3.282-2.484 6.673-4.997.018-.013a8.09 8.09 0 0 0 2.69-9.352Z"/><path fill="#FC6D26" d="m31.462 12.779-.045-.115a14.75 14.75 0 0 0-5.856 2.634l-9.553 7.24L22.1 27.14l6.673-4.997.019-.013a8.09 8.09 0 0 0 2.67-9.352Z"/><path fill="#FCA326" d="m9.908 27.14 3.275 2.485 1.994 1.51a1.35 1.35 0 0 0 1.627 0l1.994-1.51 3.282-2.484s-2.835-2.14-6.092-4.603z"/><path fill="#FC6D26" d="M6.435 15.305A14.7 14.7 0 0 0 .58 12.672l-.045.113a8.09 8.09 0 0 0 2.68 9.347l.016.012.038.03 6.635 4.967 6.105-4.603-9.573-7.233Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="svg gitea-gitlab" width="16" height="16" aria-hidden="true"><path fill="#e24329" d="m31.462 12.779-.045-.115-4.35-11.35a1.14 1.14 0 0 0-.447-.541 1.16 1.16 0 0 0-1.343.071c-.187.15-.322.356-.386.587l-2.94 9.001h-11.9l-2.941-9a1.14 1.14 0 0 0-1.045-.84 1.15 1.15 0 0 0-1.13.72L.579 12.68l-.045.113a8.09 8.09 0 0 0 2.68 9.34l.016.012.038.03 6.635 4.967 3.28 2.484 1.994 1.51a1.35 1.35 0 0 0 1.627 0l1.994-1.51 3.282-2.484 6.673-4.997.018-.013a8.09 8.09 0 0 0 2.69-9.352Z"/><path fill="#fc6d26" d="m31.462 12.779-.045-.115a14.75 14.75 0 0 0-5.856 2.634l-9.553 7.24L22.1 27.14l6.673-4.997.019-.013a8.09 8.09 0 0 0 2.67-9.352Z"/><path fill="#fca326" d="m9.908 27.14 3.275 2.485 1.994 1.51a1.35 1.35 0 0 0 1.627 0l1.994-1.51 3.282-2.484s-2.835-2.14-6.092-4.603z"/><path fill="#fc6d26" d="M6.435 15.305A14.7 14.7 0 0 0 .58 12.672l-.045.113a8.09 8.09 0 0 0 2.68 9.347l.016.012.038.03 6.635 4.967 6.105-4.603-9.573-7.233Z"/></svg>
|
Before Width: | Height: | Size: 988 B After Width: | Height: | Size: 988 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="gitea-google__svg gitea-google__gitea-google svg gitea-google" viewBox="0 0 24 24" width="16" height="16"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53"/><path fill="none" d="M1 1h22v22H1z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="gitea-google__svg gitea-google__gitea-google svg gitea-google" viewBox="0 0 24 24" width="16" height="16"><path fill="#4285f4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09"/><path fill="#34a853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23"/><path fill="#fbbc05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22z"/><path fill="#ea4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53"/><path fill="none" d="M1 1h22v22H1z"/></svg>
|
Before Width: | Height: | Size: 821 B After Width: | Height: | Size: 821 B |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48" class="svg gitea-microsoftonline" width="16" height="16" aria-hidden="true"><path fill="url(#gitea-microsoftonline__a)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__b)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__c)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__d)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__e)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__f)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__g)" d="M4.004 30.998"/><path fill="url(#gitea-microsoftonline__h)" d="M4.004 30.998"/><defs><radialGradient id="gitea-microsoftonline__a" cx="0" cy="0" r="1" gradientTransform="rotate(110.528 5.021 11.358)scale(33.3657 58.1966)" gradientUnits="userSpaceOnUse"><stop offset=".064" stop-color="#AE7FE2"/><stop offset="1" stop-color="#0078D4"/></radialGradient><radialGradient id="gitea-microsoftonline__c" cx="0" cy="0" r="1" gradientTransform="matrix(30.7198 -4.51832 2.98465 20.29248 10.43 36.351)" gradientUnits="userSpaceOnUse"><stop offset=".134" stop-color="#D59DFF"/><stop offset="1" stop-color="#5E438F"/></radialGradient><radialGradient id="gitea-microsoftonline__e" cx="0" cy="0" r="1" gradientTransform="matrix(-24.1583 -6.12555 10.3118 -40.66824 41.055 26.504)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50E6FF"/><stop offset="1" stop-color="#436DCD"/></radialGradient><radialGradient id="gitea-microsoftonline__g" cx="0" cy="0" r="1" gradientTransform="matrix(-24.1583 -6.12555 10.3118 -40.66824 41.055 26.504)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50E6FF"/><stop offset="1" stop-color="#436DCD"/></radialGradient><linearGradient id="gitea-microsoftonline__b" x1="17.512" x2="12.751" y1="37.868" y2="29.635" gradientUnits="userSpaceOnUse"><stop stop-color="#114A8B"/><stop offset="1" stop-color="#0078D4" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__d" x1="40.357" x2="35.255" y1="25.377" y2="32.692" gradientUnits="userSpaceOnUse"><stop stop-color="#493474"/><stop offset="1" stop-color="#8C66BA" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__f" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2D3F80"/><stop offset="1" stop-color="#436DCD" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__h" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2D3F80"/><stop offset="1" stop-color="#436DCD" stop-opacity="0"/></linearGradient></defs></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48" class="svg gitea-microsoftonline" width="16" height="16" aria-hidden="true"><path fill="url(#gitea-microsoftonline__a)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__b)" d="m20.084 3.026-.224.136a8 8 0 0 0-1.009.722l.648-.456H25L26 11l-5 5-5 3.475v4.008a8 8 0 0 0 3.857 6.844l5.264 3.186L14 40h-2.145l-3.998-2.42A8 8 0 0 1 4 30.737V17.26a8 8 0 0 1 3.86-6.846l12-7.258q.111-.068.224-.131Z"/><path fill="url(#gitea-microsoftonline__c)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__d)" d="M32 19v4.48a8 8 0 0 1-3.857 6.844l-12 7.264a8 8 0 0 1-8.008.16l11.722 7.096a8 8 0 0 0 8.286 0l12-7.264A8 8 0 0 0 44 30.736V27.5L43 26z"/><path fill="url(#gitea-microsoftonline__e)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__f)" d="m40.14 10.415-12-7.258a8 8 0 0 0-8.042-.139l-.238.144A8 8 0 0 0 16 10.008v9.483l3.86-2.334a8 8 0 0 1 8.28 0l12 7.258A8 8 0 0 1 43.997 31q.004-.132.004-.263V17.26a8 8 0 0 0-3.86-6.845Z"/><path fill="url(#gitea-microsoftonline__g)" d="M4.004 30.998"/><path fill="url(#gitea-microsoftonline__h)" d="M4.004 30.998"/><defs><radialGradient id="gitea-microsoftonline__a" cx="0" cy="0" r="1" gradientTransform="rotate(110.528 5.021 11.358)scale(33.3657 58.1966)" gradientUnits="userSpaceOnUse"><stop offset=".064" stop-color="#ae7fe2"/><stop offset="1" stop-color="#0078d4"/></radialGradient><radialGradient id="gitea-microsoftonline__c" cx="0" cy="0" r="1" gradientTransform="rotate(-8.367 253.693 -53.118)scale(31.0503 20.5108)" gradientUnits="userSpaceOnUse"><stop offset=".134" stop-color="#d59dff"/><stop offset="1" stop-color="#5e438f"/></radialGradient><radialGradient id="gitea-microsoftonline__e" cx="0" cy="0" r="1" gradientTransform="rotate(194.228 22.182 10.69)scale(24.9228 41.9552)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50e6ff"/><stop offset="1" stop-color="#436dcd"/></radialGradient><radialGradient id="gitea-microsoftonline__g" cx="0" cy="0" r="1" gradientTransform="rotate(194.228 22.182 10.69)scale(24.9228 41.9552)" gradientUnits="userSpaceOnUse"><stop offset=".058" stop-color="#50e6ff"/><stop offset="1" stop-color="#436dcd"/></radialGradient><linearGradient id="gitea-microsoftonline__b" x1="17.512" x2="12.751" y1="37.868" y2="29.635" gradientUnits="userSpaceOnUse"><stop stop-color="#114a8b"/><stop offset="1" stop-color="#0078d4" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__d" x1="40.357" x2="35.255" y1="25.377" y2="32.692" gradientUnits="userSpaceOnUse"><stop stop-color="#493474"/><stop offset="1" stop-color="#8c66ba" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__f" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2d3f80"/><stop offset="1" stop-color="#436dcd" stop-opacity="0"/></linearGradient><linearGradient id="gitea-microsoftonline__h" x1="16.976" x2="24.487" y1="3.057" y2="3.057" gradientUnits="userSpaceOnUse"><stop stop-color="#2d3f80"/><stop offset="1" stop-color="#436dcd" stop-opacity="0"/></linearGradient></defs></svg>
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.5 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 7" class="svg gitea-npm" width="16" height="16" aria-hidden="true"><path fill="#CB3837" d="M0 0h18v6H9v1H5V6H0zm1 5h2V2h1v3h1V1H1zm5-4v5h2V5h2V1zm2 1h1v2H8zm3-1v4h2V2h1v3h1V2h1v3h1V1z"/><path fill="#fff" d="M1 5h2V2h1v3h1V1H1zM6 1v5h2V5h2V1zm3 3H8V2h1zM11 1v4h2V2h1v3h1V2h1v3h1V1z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 7" class="svg gitea-npm" width="16" height="16" aria-hidden="true"><path fill="#cb3837" d="M0 0h18v6H9v1H5V6H0zm1 5h2V2h1v3h1V1H1zm5-4v5h2V5h2V1zm2 1h1v2H8zm3-1v4h2V2h1v3h1V2h1v3h1V1z"/><path fill="#fff" d="M1 5h2V2h1v3h1V1H1zM6 1v5h2V5h2V1zm3 3H8V2h1zM11 1v4h2V2h1v3h1V2h1v3h1V1z"/></svg>
|
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 345 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 700 700" class="svg gitea-onedev" width="16" height="16" aria-hidden="true"><path d="M315.5 99.6c-29.5 4-55.8 12-81.2 24.8L223 130l-5.2-4c-14.9-11.3-37.6-14.9-55.8-9-19.1 6.3-35.1 22.2-41.1 41-2.7 8.3-3.6 22.9-1.9 31.2 1.5 8 5 16.5 9.1 22.5 3.1 4.7 3.1 4.8 1.4 7.8C106 260 95.1 294.4 92 337.7c-1.1 15.7-.1 40.2 2.1 53l1.1 6.5-4.9 4.4c-2.8 2.3-7.5 7.6-10.6 11.6-19.4 25.5-24.7 57.9-14.4 88.3 9.2 26.9 31.2 48.8 58.4 58.1 20.6 6.9 40.6 7 61.1.1l6.7-2.2 10.5 7.1c45.6 31 92 45.5 146 45.5 33 0 61.6-5.2 91-16.4 67.6-25.8 122.9-81.1 148.4-148.4l2.7-7.2 7.7-3.8c9.1-4.5 21.1-15.7 25.9-24.3 21.1-37.5-1-84.3-43.2-91.7-19.9-3.5-39.3 2.7-53.9 17.2-7.1 7.1-11.7 14.5-15.3 24.7-3.4 9.4-3.8 25.8-.9 35.3 2.8 9.5 8.5 19.3 15.3 26.4 7.2 7.6 7.2 6 0 20.5-8.9 18.1-20.3 34.1-35.2 49.5-34.6 35.7-78.2 56.3-128.3 60.3-42.8 3.4-89.3-8.9-125-33-1.1-.8-1-1.7.8-5.2 12.1-23.6 13.5-53.7 3.9-78-8.7-21.8-27.5-41.6-48.6-51.2-9-4.1-22.7-7.4-34-8.3l-9.1-.7-.8-9.6c-3.5-46.9 13.5-99.8 45.5-141.7 6.5-8.6 24.3-26.7 33.6-34.2 43.8-35.6 101.3-52.8 158.1-47.2 39.9 3.9 79 19.1 110.6 43 16.9 12.8 37.5 34.9 48.6 52l4.3 6.7-3.3 5.2c-2.9 4.7-3.3 6.3-3.6 13.4-.3 7.3-.1 8.6 2.5 13.6 3.2 6.1 10.2 12 16.3 13.9 22.8 6.8 43-16.9 32.6-38.2-3.1-6.4-9.3-12.2-14.7-13.8-2.5-.8-4.1-2.1-5.2-4.3-.9-1.7-3.2-5.8-5.1-9.2l-3.5-6 3.6-5c17.7-24.4 15.8-57.5-4.4-79.4-8-8.6-15.5-13.6-25.9-17.2-19.8-6.8-38.9-4.2-56.5 7.8l-7.8 5.3-15.3-7.4c-27.9-13.4-55-21.3-84-24.4-13.3-1.5-48.1-1.2-60.3.5"/><path d="M271.8 271.1c-13.9 2.1-30.5 17.3-40.5 37.4-18.3 36.4-13.4 81.5 9.8 91.5 15.2 6.5 34.5-2.7 48.6-23.2 5.5-8 9.7-15.7 9-16.5-.3-.2-2 .3-3.8 1.2-2.4 1.3-5.1 1.6-10.5 1.3-6.1-.3-7.9-.8-11.6-3.4-8.9-6.2-12.4-19.1-7.9-29 2.4-5.2 9-10.8 14.7-12.4 9.1-2.6 20 1.4 25.2 9.2l2.7 4.2.3-12.4c.4-18.9-3.4-31.6-12.4-40.5-6.3-6.3-14.2-8.8-23.6-7.4M420.5 271c-11.6 1.9-20.2 11.3-24.9 27-2.1 6.9-3.1 20-2.2 27.4l.8 5.7 2.1-3.2c10.2-15 31.6-14 39.9 2 6 11.5 1.5 25.1-10.4 31.2-5 2.5-15 2.6-20 .1l-3.6-1.9 1.4 3.3c6.1 14.5 20 30.1 32.3 36.1 5.7 2.8 14.4 4 20.4 2.9 5.2-1 12.1-6.1 16.1-11.9 18.1-26.4 8.1-79-20-105.8-10.8-10.2-21.6-14.6-31.9-12.9M322.5 431.9c-16.1 1.6-23.5 6.1-23.5 14.3 0 11.4 13 21.1 34 25.4 10.2 2 31.2 1.5 40.5-1 13.5-3.7 23.8-10.3 27.6-17.7 4.9-9.7-.2-17.1-13.8-20-6.1-1.2-54.2-2-64.8-1"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 700" class="svg gitea-onedev" width="16" height="16" aria-hidden="true"><path d="M315.5 99.6c-29.5 4-55.8 12-81.2 24.8L223 130l-5.2-4c-14.9-11.3-37.6-14.9-55.8-9-19.1 6.3-35.1 22.2-41.1 41-2.7 8.3-3.6 22.9-1.9 31.2 1.5 8 5 16.5 9.1 22.5 3.1 4.7 3.1 4.8 1.4 7.8C106 260 95.1 294.4 92 337.7c-1.1 15.7-.1 40.2 2.1 53l1.1 6.5-4.9 4.4c-2.8 2.3-7.5 7.6-10.6 11.6-19.4 25.5-24.7 57.9-14.4 88.3 9.2 26.9 31.2 48.8 58.4 58.1 20.6 6.9 40.6 7 61.1.1l6.7-2.2 10.5 7.1c45.6 31 92 45.5 146 45.5 33 0 61.6-5.2 91-16.4 67.6-25.8 122.9-81.1 148.4-148.4l2.7-7.2 7.7-3.8c9.1-4.5 21.1-15.7 25.9-24.3 21.1-37.5-1-84.3-43.2-91.7-19.9-3.5-39.3 2.7-53.9 17.2-7.1 7.1-11.7 14.5-15.3 24.7-3.4 9.4-3.8 25.8-.9 35.3 2.8 9.5 8.5 19.3 15.3 26.4 7.2 7.6 7.2 6 0 20.5-8.9 18.1-20.3 34.1-35.2 49.5-34.6 35.7-78.2 56.3-128.3 60.3-42.8 3.4-89.3-8.9-125-33-1.1-.8-1-1.7.8-5.2 12.1-23.6 13.5-53.7 3.9-78-8.7-21.8-27.5-41.6-48.6-51.2-9-4.1-22.7-7.4-34-8.3l-9.1-.7-.8-9.6c-3.5-46.9 13.5-99.8 45.5-141.7 6.5-8.6 24.3-26.7 33.6-34.2 43.8-35.6 101.3-52.8 158.1-47.2 39.9 3.9 79 19.1 110.6 43 16.9 12.8 37.5 34.9 48.6 52l4.3 6.7-3.3 5.2c-2.9 4.7-3.3 6.3-3.6 13.4-.3 7.3-.1 8.6 2.5 13.6 3.2 6.1 10.2 12 16.3 13.9 22.8 6.8 43-16.9 32.6-38.2-3.1-6.4-9.3-12.2-14.7-13.8-2.5-.8-4.1-2.1-5.2-4.3-.9-1.7-3.2-5.8-5.1-9.2l-3.5-6 3.6-5c17.7-24.4 15.8-57.5-4.4-79.4-8-8.6-15.5-13.6-25.9-17.2-19.8-6.8-38.9-4.2-56.5 7.8l-7.8 5.3-15.3-7.4c-27.9-13.4-55-21.3-84-24.4-13.3-1.5-48.1-1.2-60.3.5"/><path d="M271.8 271.1c-13.9 2.1-30.5 17.3-40.5 37.4-18.3 36.4-13.4 81.5 9.8 91.5 15.2 6.5 34.5-2.7 48.6-23.2 5.5-8 9.7-15.7 9-16.5-.3-.2-2 .3-3.8 1.2-2.4 1.3-5.1 1.6-10.5 1.3-6.1-.3-7.9-.8-11.6-3.4-8.9-6.2-12.4-19.1-7.9-29 2.4-5.2 9-10.8 14.7-12.4 9.1-2.6 20 1.4 25.2 9.2l2.7 4.2.3-12.4c.4-18.9-3.4-31.6-12.4-40.5-6.3-6.3-14.2-8.8-23.6-7.4M420.5 271c-11.6 1.9-20.2 11.3-24.9 27-2.1 6.9-3.1 20-2.2 27.4l.8 5.7 2.1-3.2c10.2-15 31.6-14 39.9 2 6 11.5 1.5 25.1-10.4 31.2-5 2.5-15 2.6-20 .1l-3.6-1.9 1.4 3.3c6.1 14.5 20 30.1 32.3 36.1 5.7 2.8 14.4 4 20.4 2.9 5.2-1 12.1-6.1 16.1-11.9 18.1-26.4 8.1-79-20-105.8-10.8-10.2-21.6-14.6-31.9-12.9M322.5 431.9c-16.1 1.6-23.5 6.1-23.5 14.3 0 11.4 13 21.1 34 25.4 10.2 2 31.2 1.5 40.5-1 13.5-3.7 23.8-10.3 27.6-17.7 4.9-9.7-.2-17.1-13.8-20-6.1-1.2-54.2-2-64.8-1"/></svg>
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 2400 2400" class="svg gitea-openid" width="16" height="16" aria-hidden="true"><path fill="#ff7c00" d="m1270 218.3-173.1 84.3-.7 981.8c-.3 540 .2 981.4 1.1 981l174.5-81.8 172.3-80.8v-984.4c0-541.7-.2-984.7-.4-984.4z"/><path fill="#aaa" d="M981.9 785.5c-425.3 63.2-766.5 264.1-889 523a491.5 491.5 0 0 0-43.6 146c-4.2 29.2-4.7 95-1.2 124 19 152.6 115.2 299.9 273.2 418.8 147.7 111 350.5 196.5 568.6 239.7 59 11.6 179 29 200.5 29 2.3 0 3-23.2 3-109.1v-109.2l-5.1-1-37.9-6a1182 1182 0 0 1-305.4-90.6c-122.2-55.7-225.1-137.7-284.6-226.4-107.5-160.5-81.3-344.3 70-491.3 57-55.5 115.4-95.2 199.5-136.1a1112.6 1112.6 0 0 1 269.4-89.2l29.7-6c3.7-1.2 4-8.6 4-111.5V779.5l-6.3.2a823 823 0 0 0-44.8 5.8m525 104c0 103 .2 110.4 4.1 111.6l29.5 6a1221.6 1221.6 0 0 1 207.7 61.3A1088 1088 0 0 1 1862 1123c4.6 3.7 1.4 5.8-88 56-51.1 28.5-93 52.7-93 53.4 0 1.9 671.6 146.8 673.2 145.2 1.2-1.2-45.5-496-47-497.6-.2-.2-38.5 21-85 47.2l-89.6 50.2c-4.2 2-8.8.2-27.9-10.7-130.8-75-289.6-132.2-460.8-166.1a1871 1871 0 0 0-132.9-21.1c-4 0-4.2 6.7-4.2 110z"/><path fill="#cbaa7c" d="M1094.5 2156.9c0 60.6.3 85.5.5 55 .5-30.2.5-79.9 0-110.3-.2-30.2-.5-5.3-.5 55.3"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2400 2400" class="svg gitea-openid" width="16" height="16" aria-hidden="true"><path fill="#ff7c00" d="m1270 218.3-173.1 84.3-.7 981.8c-.3 540 .2 981.4 1.1 981l174.5-81.8 172.3-80.8v-984.4c0-541.7-.2-984.7-.4-984.4z"/><path fill="#aaa" d="M981.9 785.5c-425.3 63.2-766.5 264.1-889 523a491.5 491.5 0 0 0-43.6 146c-4.2 29.2-4.7 95-1.2 124 19 152.6 115.2 299.9 273.2 418.8 147.7 111 350.5 196.5 568.6 239.7 59 11.6 179 29 200.5 29 2.3 0 3-23.2 3-109.1v-109.2l-5.1-1-37.9-6a1182 1182 0 0 1-305.4-90.6c-122.2-55.7-225.1-137.7-284.6-226.4-107.5-160.5-81.3-344.3 70-491.3 57-55.5 115.4-95.2 199.5-136.1a1112.6 1112.6 0 0 1 269.4-89.2l29.7-6c3.7-1.2 4-8.6 4-111.5V779.5l-6.3.2a823 823 0 0 0-44.8 5.8m525 104c0 103 .2 110.4 4.1 111.6l29.5 6a1221.6 1221.6 0 0 1 207.7 61.3A1088 1088 0 0 1 1862 1123c4.6 3.7 1.4 5.8-88 56-51.1 28.5-93 52.7-93 53.4 0 1.9 671.6 146.8 673.2 145.2 1.2-1.2-45.5-496-47-497.6-.2-.2-38.5 21-85 47.2l-89.6 50.2c-4.2 2-8.8.2-27.9-10.7-130.8-75-289.6-132.2-460.8-166.1a1871 1871 0 0 0-132.9-21.1c-4 0-4.2 6.7-4.2 110z"/><path fill="#cbaa7c" d="M1094.5 2156.9c0 60.6.3 85.5.5 55 .5-30.2.5-79.9 0-110.3-.2-30.2-.5-5.3-.5 55.3"/></svg>
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 59.5 59.5" class="svg gitea-swift" width="16" height="16" aria-hidden="true"><path fill="#F05138" d="M59.387 16.45a83 83 0 0 0-.027-1.792c-.034-1.301-.111-2.614-.343-3.9-.234-1.308-.618-2.523-1.222-3.71a12.46 12.46 0 0 0-5.452-5.452C51.156.992 49.94.609 48.635.374c-1.287-.232-2.6-.308-3.902-.343a86 86 0 0 0-1.792-.027Q41.876-.001 40.813 0H18.578q-1.064-.001-2.127.004c-.598.004-1.196.01-1.793.027q-.488.012-.978.036c-.978.047-1.959.133-2.924.307-.98.176-1.908.436-2.811.81A12.5 12.5 0 0 0 3.89 3.89a12.5 12.5 0 0 0-2.294 3.158C.992 8.235.61 9.45.374 10.758c-.231 1.286-.308 2.599-.343 3.9a86 86 0 0 0-.027 1.792Q-.002 17.515 0 18.578v22.234q-.001 1.064.004 2.129c.004.597.01 1.194.027 1.79.035 1.302.112 2.615.343 3.902.235 1.306.618 2.522 1.222 3.71a12.457 12.457 0 0 0 5.453 5.453c1.186.603 2.401.986 3.707 1.22 1.287.232 2.6.309 3.902.344q.896.023 1.793.026 1.063.006 2.127.004h22.235q1.065.002 2.128-.004.897-.003 1.792-.026c1.302-.035 2.615-.112 3.902-.344 1.306-.234 2.521-.617 3.708-1.221a12.46 12.46 0 0 0 5.452-5.453c.604-1.187.988-2.403 1.223-3.71.23-1.286.308-2.599.342-3.9.017-.597.023-1.194.027-1.791q.006-1.065.004-2.129V18.578q.001-1.065-.004-2.128"/><path fill="#fff" d="m47.061 36.661-.004-.005c.066-.223.133-.446.19-.675 2.466-9.82-3.55-21.432-13.731-27.545 4.461 6.048 6.434 13.373 4.681 19.78-.156.572-.344 1.12-.552 1.653-.225-.148-.51-.316-.89-.526 0 0-10.128-6.253-21.104-17.312-.288-.29 5.853 8.776 12.822 16.14-3.283-1.843-12.434-8.5-18.227-13.802.712 1.186 1.559 2.33 2.49 3.43 4.837 6.135 11.145 13.704 18.703 19.517-5.31 3.25-12.814 3.502-20.285.003a30.7 30.7 0 0 1-5.193-3.098c3.162 5.058 8.033 9.423 13.96 11.97 7.07 3.039 14.1 2.833 19.337.05l-.004.007.079-.047q.323-.172.637-.358c2.516-1.306 7.485-2.63 10.152 2.559.653 1.27 2.041-5.46-3.062-11.739z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 59.5 59.5" class="svg gitea-swift" width="16" height="16" aria-hidden="true"><path fill="#f05138" d="M59.387 16.45a83 83 0 0 0-.027-1.792c-.034-1.301-.111-2.614-.343-3.9-.234-1.308-.618-2.523-1.222-3.71a12.46 12.46 0 0 0-5.452-5.452C51.156.992 49.94.609 48.635.374c-1.287-.232-2.6-.308-3.902-.343a86 86 0 0 0-1.792-.027Q41.876-.001 40.813 0H18.578q-1.064-.001-2.127.004c-.598.004-1.196.01-1.793.027q-.488.012-.978.036c-.978.047-1.959.133-2.924.307-.98.176-1.908.436-2.811.81A12.5 12.5 0 0 0 3.89 3.89a12.5 12.5 0 0 0-2.294 3.158C.992 8.235.61 9.45.374 10.758c-.231 1.286-.308 2.599-.343 3.9a86 86 0 0 0-.027 1.792Q-.002 17.515 0 18.578v22.234q-.001 1.064.004 2.129c.004.597.01 1.194.027 1.79.035 1.302.112 2.615.343 3.902.235 1.306.618 2.522 1.222 3.71a12.457 12.457 0 0 0 5.453 5.453c1.186.603 2.401.986 3.707 1.22 1.287.232 2.6.309 3.902.344q.896.023 1.793.026 1.063.006 2.127.004h22.235q1.065.002 2.128-.004.897-.003 1.792-.026c1.302-.035 2.615-.112 3.902-.344 1.306-.234 2.521-.617 3.708-1.221a12.46 12.46 0 0 0 5.452-5.453c.604-1.187.988-2.403 1.223-3.71.23-1.286.308-2.599.342-3.9.017-.597.023-1.194.027-1.791q.006-1.065.004-2.129V18.578q.001-1.065-.004-2.128"/><path fill="#fff" d="m47.061 36.661-.004-.005c.066-.223.133-.446.19-.675 2.466-9.82-3.55-21.432-13.731-27.545 4.461 6.048 6.434 13.373 4.681 19.78-.156.572-.344 1.12-.552 1.653-.225-.148-.51-.316-.89-.526 0 0-10.128-6.253-21.104-17.312-.288-.29 5.853 8.776 12.822 16.14-3.283-1.843-12.434-8.5-18.227-13.802.712 1.186 1.559 2.33 2.49 3.43 4.837 6.135 11.145 13.704 18.703 19.517-5.31 3.25-12.814 3.502-20.285.003a30.7 30.7 0 0 1-5.193-3.098c3.162 5.058 8.033 9.423 13.96 11.97 7.07 3.039 14.1 2.833 19.337.05l-.004.007.079-.047q.323-.172.637-.358c2.516-1.306 7.485-2.63 10.152 2.559.653 1.27 2.041-5.46-3.062-11.739z"/></svg>
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" viewBox="0 0 255 263" class="svg gitea-vagrant" width="16" height="16" aria-hidden="true"><path fill="#1159CC" d="M254.22 20.234 196.03 53.47l-1.64 20.618-44.19 99.772-26.27 17.34 3.18 71.6 49.53-28.55 77.58-189.946zM92.45 56.933V34.051l-.238-.136-38.483 19.102 1.642 23.034L103.4 180.6l26.02-14.71-2.31-28.09z"/><path fill="#127EFF" d="m219.56 0-57.75 33.814h-.04v23.119L127.11 137.8v27.02l-23.12 13.41L57.788 74.146V53.81L92.45 33.848 34.668 0 .006 20.234v24.783L78.022 234.49l49.088 28.31v-71.16l23.09-13.41-.27-.17 46.51-103.914V53.81l57.78-33.576z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" viewBox="0 0 255 263" class="svg gitea-vagrant" width="16" height="16" aria-hidden="true"><path fill="#1159cc" d="M254.22 20.234 196.03 53.47l-1.64 20.618-44.19 99.772-26.27 17.34 3.18 71.6 49.53-28.55 77.58-189.946zM92.45 56.933V34.051l-.238-.136-38.483 19.102 1.642 23.034L103.4 180.6l26.02-14.71-2.31-28.09z"/><path fill="#127eff" d="m219.56 0-57.75 33.814h-.04v23.119L127.11 137.8v27.02l-23.12 13.41L57.788 74.146V53.81L92.45 33.848 34.668 0 .006 20.234v24.783L78.022 234.49l49.088 28.31v-71.16l23.09-13.41-.27-.17 46.51-103.914V53.81l57.78-33.576z"/></svg>
|
Before Width: | Height: | Size: 632 B After Width: | Height: | Size: 632 B |
|
@ -71,6 +71,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -184,8 +185,8 @@ type artifactRoutes struct {
|
|||
fs storage.ObjectStorage
|
||||
}
|
||||
|
||||
func (ar artifactRoutes) buildArtifactURL(runID int64, artifactHash, suffix string) string {
|
||||
uploadURL := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(ar.prefix, "/") +
|
||||
func (ar artifactRoutes) buildArtifactURL(ctx *ArtifactContext, runID int64, artifactHash, suffix string) string {
|
||||
uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(ar.prefix, "/") +
|
||||
strings.ReplaceAll(artifactRouteBase, "{run_id}", strconv.FormatInt(runID, 10)) +
|
||||
"/" + artifactHash + "/" + suffix
|
||||
return uploadURL
|
||||
|
@ -224,7 +225,7 @@ func (ar artifactRoutes) getUploadArtifactURL(ctx *ArtifactContext) {
|
|||
// use md5(artifact_name) to create upload url
|
||||
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(req.Name)))
|
||||
resp := getUploadArtifactResponse{
|
||||
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "upload"+retentionQuery),
|
||||
FileContainerResourceURL: ar.buildArtifactURL(ctx, runID, artifactHash, "upload"+retentionQuery),
|
||||
}
|
||||
log.Debug("[artifact] get upload url: %s", resp.FileContainerResourceURL)
|
||||
ctx.JSON(http.StatusOK, resp)
|
||||
|
@ -365,7 +366,7 @@ func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) {
|
|||
artifactHash := fmt.Sprintf("%x", md5.Sum([]byte(art.ArtifactName)))
|
||||
item := listArtifactsResponseItem{
|
||||
Name: art.ArtifactName,
|
||||
FileContainerResourceURL: ar.buildArtifactURL(runID, artifactHash, "download_url"),
|
||||
FileContainerResourceURL: ar.buildArtifactURL(ctx, runID, artifactHash, "download_url"),
|
||||
}
|
||||
items = append(items, item)
|
||||
values[art.ArtifactName] = true
|
||||
|
@ -437,7 +438,7 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
|
|||
}
|
||||
}
|
||||
if downloadURL == "" {
|
||||
downloadURL = ar.buildArtifactURL(runID, strconv.FormatInt(artifact.ID, 10), "download")
|
||||
downloadURL = ar.buildArtifactURL(ctx, runID, strconv.FormatInt(artifact.ID, 10), "download")
|
||||
}
|
||||
item := downloadArtifactResponseItem{
|
||||
Path: util.PathJoinRel(itemPath, artifact.ArtifactPath),
|
||||
|
|
|
@ -92,6 +92,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
|
@ -160,9 +161,9 @@ func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, tas
|
|||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
func (r artifactV4Routes) buildArtifactURL(endp, artifactName string, taskID int64) string {
|
||||
func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID int64) string {
|
||||
expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST")
|
||||
uploadURL := strings.TrimSuffix(setting.AppURL, "/") + strings.TrimSuffix(r.prefix, "/") +
|
||||
uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(r.prefix, "/") +
|
||||
"/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID)
|
||||
return uploadURL
|
||||
}
|
||||
|
@ -278,7 +279,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
|
|||
|
||||
respData := CreateArtifactResponse{
|
||||
Ok: true,
|
||||
SignedUploadUrl: r.buildArtifactURL("UploadArtifact", artifactName, ctx.ActionTask.ID),
|
||||
SignedUploadUrl: r.buildArtifactURL(ctx, "UploadArtifact", artifactName, ctx.ActionTask.ID),
|
||||
}
|
||||
r.sendProtbufBody(ctx, &respData)
|
||||
}
|
||||
|
@ -454,7 +455,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
|
|||
}
|
||||
}
|
||||
if respData.SignedUrl == "" {
|
||||
respData.SignedUrl = r.buildArtifactURL("DownloadArtifact", artifactName, ctx.ActionTask.ID)
|
||||
respData.SignedUrl = r.buildArtifactURL(ctx, "DownloadArtifact", artifactName, ctx.ActionTask.ID)
|
||||
}
|
||||
r.sendProtbufBody(ctx, &respData)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
container_model "code.gitea.io/gitea/models/packages/container"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
|
@ -115,7 +116,7 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
|
|||
}
|
||||
|
||||
func apiUnauthorizedError(ctx *context.Context) {
|
||||
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+setting.AppURL+`v2/token",service="container_registry",scope="*"`)
|
||||
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+httplib.GuessCurrentAppURL(ctx)+`v2/token",service="container_registry",scope="*"`)
|
||||
apiErrorDefined(ctx, errUnauthorized)
|
||||
}
|
||||
|
||||
|
|
|
@ -140,9 +140,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
|
|||
ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
|
||||
ctx.Resp.Header().Set("Content-Type", contentTypeXML)
|
||||
|
||||
if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil {
|
||||
log.Error("write bytes failed: %v", err)
|
||||
}
|
||||
_, _ = ctx.Resp.Write(xmlMetadataWithHeader)
|
||||
}
|
||||
|
||||
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
|
||||
|
|
|
@ -29,7 +29,6 @@ import (
|
|||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
)
|
||||
|
||||
// SearchIssues searches for issues across the repositories that the user has access to
|
||||
|
@ -803,12 +802,19 @@ func EditIssue(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
oldTitle := issue.Title
|
||||
if len(form.Title) > 0 {
|
||||
issue.Title = form.Title
|
||||
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if form.Body != nil {
|
||||
issue.Content = *form.Body
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if form.Ref != nil {
|
||||
err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
|
||||
|
@ -880,24 +886,14 @@ func EditIssue(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
}
|
||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
||||
}
|
||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
||||
if err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
|
||||
return
|
||||
}
|
||||
|
||||
if titleChanged {
|
||||
notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle)
|
||||
}
|
||||
|
||||
if statusChangeComment != nil {
|
||||
notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed)
|
||||
}
|
||||
|
||||
// Refetch from database to assign some automatic values
|
||||
|
|
|
@ -602,12 +602,19 @@ func EditPullRequest(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
oldTitle := issue.Title
|
||||
if len(form.Title) > 0 {
|
||||
issue.Title = form.Title
|
||||
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(form.Body) > 0 {
|
||||
issue.Content = form.Body
|
||||
if form.Body != nil {
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Update or remove deadline if set
|
||||
|
@ -686,24 +693,14 @@ func EditPullRequest(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
|
||||
return
|
||||
}
|
||||
issue.IsClosed = api.StateClosed == api.StateType(*form.State)
|
||||
}
|
||||
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
|
||||
if err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
||||
if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
|
||||
if issues_model.IsErrDependenciesLeft(err) {
|
||||
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
|
||||
return
|
||||
}
|
||||
|
||||
if titleChanged {
|
||||
notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle)
|
||||
}
|
||||
|
||||
if statusChangeComment != nil {
|
||||
notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed)
|
||||
}
|
||||
|
||||
// change pull target branch
|
||||
|
@ -884,7 +881,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
// start with merging by checking
|
||||
if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil {
|
||||
if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil {
|
||||
if errors.Is(err, pull_service.ErrIsClosed) {
|
||||
ctx.NotFound()
|
||||
} else if errors.Is(err, pull_service.ErrUserNotAllowedToMerge) {
|
||||
|
@ -893,7 +890,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusMethodNotAllowed, "PR already merged", "")
|
||||
} else if errors.Is(err, pull_service.ErrIsWorkInProgress) {
|
||||
ctx.Error(http.StatusMethodNotAllowed, "PR is a work in progress", "Work in progress PRs cannot be merged")
|
||||
} else if errors.Is(err, pull_service.ErrNotMergableState) {
|
||||
} else if errors.Is(err, pull_service.ErrNotMergeableState) {
|
||||
ctx.Error(http.StatusMethodNotAllowed, "PR not in mergeable state", "Please try again later")
|
||||
} else if models.IsErrDisallowedToMerge(err) {
|
||||
ctx.Error(http.StatusMethodNotAllowed, "PR is not ready to be merged", err)
|
||||
|
|
|
@ -6,10 +6,8 @@ package user
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
|
@ -44,7 +42,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
|||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.UnitAccessMode(unit_model.TypeCode) >= perm.AccessModeRead {
|
||||
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.HasAnyUnitAccess() {
|
||||
apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
go_context "context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
|
@ -34,6 +36,7 @@ func ProtocolMiddlewares() (handlers []any) {
|
|||
}
|
||||
}()
|
||||
req = req.WithContext(middleware.WithContextData(req.Context()))
|
||||
req = req.WithContext(go_context.WithValue(req.Context(), httplib.RequestContextKey, req))
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -17,7 +17,7 @@ func FetchRedirectDelegate(resp http.ResponseWriter, req *http.Request) {
|
|||
// The typical page is "issue comment" page. The backend responds "/owner/repo/issues/1#comment-2",
|
||||
// then frontend needs this delegate to redirect to the new location with hash correctly.
|
||||
redirect := req.PostFormValue("redirect")
|
||||
if !httplib.IsCurrentGiteaSiteURL(redirect) {
|
||||
if !httplib.IsCurrentGiteaSiteURL(req.Context(), redirect) {
|
||||
resp.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/system"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/routing"
|
||||
actions_router "code.gitea.io/gitea/routers/api/actions"
|
||||
|
@ -112,7 +113,10 @@ func InitWebInstallPage(ctx context.Context) {
|
|||
// InitWebInstalled is for global installed configuration.
|
||||
func InitWebInstalled(ctx context.Context) {
|
||||
mustInitCtx(ctx, git.InitFull)
|
||||
log.Info("Git version: %s (home: %s)", git.VersionInfo(), git.HomeDir())
|
||||
log.Info("Git version: %s (home: %s)", git.DefaultFeatures().VersionInfo(), git.HomeDir())
|
||||
if !git.DefaultFeatures().SupportHashSha256 {
|
||||
log.Warn("sha256 hash support is disabled - requires Git >= 2.42." + util.Iif(git.DefaultFeatures().UsingGogit, " Gogit is currently unsupported.", ""))
|
||||
}
|
||||
|
||||
// Setup i18n
|
||||
translation.InitLocales(ctx)
|
||||
|
|
|
@ -4,20 +4,25 @@
|
|||
package private
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
timeutil "code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
|
@ -158,6 +163,14 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||
}
|
||||
}
|
||||
|
||||
// handle pull request merging, a pull request action should push at least 1 commit
|
||||
if opts.PushTrigger == repo_module.PushTriggerPRMergeToBase {
|
||||
handlePullRequestMerging(ctx, opts, ownerName, repoName, updates)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isPrivate := opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate)
|
||||
isTemplate := opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate)
|
||||
// Handle Push Options
|
||||
|
@ -172,7 +185,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||
wasEmpty = repo.IsEmpty
|
||||
}
|
||||
|
||||
pusher, err := user_model.GetUserByID(ctx, opts.UserID)
|
||||
pusher, err := loadContextCacheUser(ctx, opts.UserID)
|
||||
if err != nil {
|
||||
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
||||
|
@ -307,3 +320,52 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||
RepoWasEmpty: wasEmpty,
|
||||
})
|
||||
}
|
||||
|
||||
func loadContextCacheUser(ctx context.Context, id int64) (*user_model.User, error) {
|
||||
return cache.GetWithContextCache(ctx, "hook_post_receive_user", id, func() (*user_model.User, error) {
|
||||
return user_model.GetUserByID(ctx, id)
|
||||
})
|
||||
}
|
||||
|
||||
// handlePullRequestMerging handle pull request merging, a pull request action should push at least 1 commit
|
||||
func handlePullRequestMerging(ctx *gitea_context.PrivateContext, opts *private.HookOptions, ownerName, repoName string, updates []*repo_module.PushUpdateOptions) {
|
||||
if len(updates) == 0 {
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
||||
Err: fmt.Sprintf("Pushing a merged PR (pr:%d) no commits pushed ", opts.PullRequestID),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
pr, err := issues_model.GetPullRequestByID(ctx, opts.PullRequestID)
|
||||
if err != nil {
|
||||
log.Error("GetPullRequestByID[%d]: %v", opts.PullRequestID, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "GetPullRequestByID failed"})
|
||||
return
|
||||
}
|
||||
|
||||
pusher, err := loadContextCacheUser(ctx, opts.UserID)
|
||||
if err != nil {
|
||||
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Load pusher user failed"})
|
||||
return
|
||||
}
|
||||
|
||||
pr.MergedCommitID = updates[len(updates)-1].NewCommitID
|
||||
pr.MergedUnix = timeutil.TimeStampNow()
|
||||
pr.Merger = pusher
|
||||
pr.MergerID = pusher.ID
|
||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
// Removing an auto merge pull and ignore if not exist
|
||||
if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
|
||||
return fmt.Errorf("DeleteScheduledAutoMerge[%d]: %v", opts.PullRequestID, err)
|
||||
}
|
||||
if _, err := pr.SetMerged(ctx); err != nil {
|
||||
return fmt.Errorf("SetMerged failed: %s/%s Error: %v", ownerName, repoName, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Failed to update PR to merged: %v", err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Failed to update PR to merged"})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package private
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/private"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHandlePullRequestMerging(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
pr, err := issues_model.GetUnmergedPullRequest(db.DefaultContext, 1, 1, "branch2", "master", issues_model.PullRequestFlowGithub)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, pr.LoadBaseRepo(db.DefaultContext))
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
err = pull_model.ScheduleAutoMerge(db.DefaultContext, user1, pr.ID, repo_model.MergeStyleSquash, "squash merge a pr")
|
||||
assert.NoError(t, err)
|
||||
|
||||
autoMerge := unittest.AssertExistsAndLoadBean(t, &pull_model.AutoMerge{PullID: pr.ID})
|
||||
|
||||
ctx, resp := contexttest.MockPrivateContext(t, "/")
|
||||
handlePullRequestMerging(ctx, &private.HookOptions{
|
||||
PullRequestID: pr.ID,
|
||||
UserID: 2,
|
||||
}, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, []*repo_module.PushUpdateOptions{
|
||||
{NewCommitID: "01234567"},
|
||||
})
|
||||
assert.Equal(t, 0, len(resp.Body.String()))
|
||||
pr, err = issues_model.GetPullRequestByID(db.DefaultContext, pr.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, pr.HasMerged)
|
||||
assert.EqualValues(t, "01234567", pr.MergedCommitID)
|
||||
|
||||
unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{ID: autoMerge.ID})
|
||||
}
|
|
@ -122,7 +122,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
|||
preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName)
|
||||
case refFullName.IsTag():
|
||||
preReceiveTag(ourCtx, refFullName)
|
||||
case git.DefaultFeatures.SupportProcReceive && refFullName.IsFor():
|
||||
case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor():
|
||||
preReceiveFor(ourCtx, refFullName)
|
||||
default:
|
||||
ourCtx.AssertCanWriteCode()
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
// HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present
|
||||
func HookProcReceive(ctx *gitea_context.PrivateContext) {
|
||||
opts := web.GetForm(ctx).(*private.HookOptions)
|
||||
if !git.DefaultFeatures.SupportProcReceive {
|
||||
if !git.DefaultFeatures().SupportProcReceive {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -297,7 +297,7 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||
}
|
||||
} else {
|
||||
// Because of the special ref "refs/for" we will need to delay write permission check
|
||||
if git.DefaultFeatures.SupportProcReceive && unitType == unit.TypeCode {
|
||||
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
|
||||
mode = perm.AccessModeRead
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ func Config(ctx *context.Context) {
|
|||
ctx.Data["OfflineMode"] = setting.OfflineMode
|
||||
ctx.Data["RunUser"] = setting.RunUser
|
||||
ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode)
|
||||
ctx.Data["GitVersion"] = git.VersionInfo()
|
||||
ctx.Data["GitVersion"] = git.DefaultFeatures().VersionInfo()
|
||||
|
||||
ctx.Data["AppDataPath"] = setting.AppDataPath
|
||||
ctx.Data["RepoRootPath"] = setting.RepoRootPath
|
||||
|
|
|
@ -368,7 +368,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
|||
return setting.AppSubURL + "/"
|
||||
}
|
||||
|
||||
if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" && httplib.IsCurrentGiteaSiteURL(redirectTo) {
|
||||
if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" && httplib.IsCurrentGiteaSiteURL(ctx, redirectTo) {
|
||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||
if obeyRedirect {
|
||||
ctx.RedirectToCurrentSite(redirectTo)
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
func SSHInfo(rw http.ResponseWriter, req *http.Request) {
|
||||
if !git.DefaultFeatures.SupportProcReceive {
|
||||
if !git.DefaultFeatures().SupportProcReceive {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -390,74 +389,6 @@ func ViewProject(ctx *context.Context) {
|
|||
ctx.HTML(http.StatusOK, tplProjectsView)
|
||||
}
|
||||
|
||||
func getActionIssues(ctx *context.Context) issues_model.IssueList {
|
||||
commaSeparatedIssueIDs := ctx.FormString("issue_ids")
|
||||
if len(commaSeparatedIssueIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
issueIDs := make([]int64, 0, 10)
|
||||
for _, stringIssueID := range strings.Split(commaSeparatedIssueIDs, ",") {
|
||||
issueID, err := strconv.ParseInt(stringIssueID, 10, 64)
|
||||
if err != nil {
|
||||
ctx.ServerError("ParseInt", err)
|
||||
return nil
|
||||
}
|
||||
issueIDs = append(issueIDs, issueID)
|
||||
}
|
||||
issues, err := issues_model.GetIssuesByIDs(ctx, issueIDs)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetIssuesByIDs", err)
|
||||
return nil
|
||||
}
|
||||
// Check access rights for all issues
|
||||
issueUnitEnabled := ctx.Repo.CanRead(unit.TypeIssues)
|
||||
prUnitEnabled := ctx.Repo.CanRead(unit.TypePullRequests)
|
||||
for _, issue := range issues {
|
||||
if issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound("some issue's RepoID is incorrect", errors.New("some issue's RepoID is incorrect"))
|
||||
return nil
|
||||
}
|
||||
if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled {
|
||||
ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil)
|
||||
return nil
|
||||
}
|
||||
if err = issue.LoadAttributes(ctx); err != nil {
|
||||
ctx.ServerError("LoadAttributes", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
// UpdateIssueProject change an issue's project
|
||||
func UpdateIssueProject(ctx *context.Context) {
|
||||
issues := getActionIssues(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := issues.LoadProjects(ctx); err != nil {
|
||||
ctx.ServerError("LoadProjects", err)
|
||||
return
|
||||
}
|
||||
|
||||
projectID := ctx.FormInt64("id")
|
||||
for _, issue := range issues {
|
||||
if issue.Project != nil {
|
||||
if issue.Project.ID == projectID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
|
||||
ctx.ServerError("ChangeProjectAssign", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSONOK()
|
||||
}
|
||||
|
||||
// DeleteProjectBoard allows for the deletion of a project board
|
||||
func DeleteProjectBoard(ctx *context.Context) {
|
||||
if ctx.Doer == nil {
|
||||
|
|
|
@ -183,7 +183,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
|||
|
||||
if repoExist {
|
||||
// Because of special ref "refs/for" .. , need delay write permission check
|
||||
if git.DefaultFeatures.SupportProcReceive {
|
||||
if git.DefaultFeatures().SupportProcReceive {
|
||||
accessMode = perm.AccessModeRead
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -383,17 +384,21 @@ func UpdateIssueProject(ctx *context.Context) {
|
|||
ctx.ServerError("LoadProjects", err)
|
||||
return
|
||||
}
|
||||
if _, err := issues.LoadRepositories(ctx); err != nil {
|
||||
ctx.ServerError("LoadProjects", err)
|
||||
return
|
||||
}
|
||||
|
||||
projectID := ctx.FormInt64("id")
|
||||
for _, issue := range issues {
|
||||
if issue.Project != nil {
|
||||
if issue.Project.ID == projectID {
|
||||
if issue.Project != nil && issue.Project.ID == projectID {
|
||||
continue
|
||||
}
|
||||
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, projectID, 0); err != nil {
|
||||
if errors.Is(err, util.ErrPermissionDenied) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := issues_model.ChangeProjectAssign(ctx, issue, ctx.Doer, projectID); err != nil {
|
||||
ctx.ServerError("ChangeProjectAssign", err)
|
||||
ctx.ServerError("IssueAssignOrRemoveProject", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1007,7 +1007,7 @@ func MergePullRequest(ctx *context.Context) {
|
|||
}
|
||||
|
||||
// start with merging by checking
|
||||
if err := pull_service.CheckPullMergable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil {
|
||||
if err := pull_service.CheckPullMergeable(ctx, ctx.Doer, &ctx.Repo.Permission, pr, mergeCheckType, form.ForceMerge); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, pull_service.ErrIsClosed):
|
||||
if issue.IsPull {
|
||||
|
@ -1021,7 +1021,7 @@ func MergePullRequest(ctx *context.Context) {
|
|||
ctx.JSONError(ctx.Tr("repo.pulls.has_merged"))
|
||||
case errors.Is(err, pull_service.ErrIsWorkInProgress):
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_wip"))
|
||||
case errors.Is(err, pull_service.ErrNotMergableState):
|
||||
case errors.Is(err, pull_service.ErrNotMergeableState):
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
|
||||
case models.IsErrDisallowedToMerge(err):
|
||||
ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
|
||||
|
@ -1329,14 +1329,12 @@ func CompareAndPullRequestPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if projectID > 0 {
|
||||
if !ctx.Repo.CanWrite(unit.TypeProjects) {
|
||||
ctx.Error(http.StatusBadRequest, "user hasn't the permission to write to projects")
|
||||
return
|
||||
}
|
||||
if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID); err != nil {
|
||||
ctx.ServerError("ChangeProjectAssign", err)
|
||||
return
|
||||
if projectID > 0 && ctx.Repo.CanWrite(unit.TypeProjects) {
|
||||
if err := issues_model.IssueAssignOrRemoveProject(ctx, pullIssue, ctx.Doer, projectID, 0); err != nil {
|
||||
if !errors.Is(err, util.ErrPermissionDenied) {
|
||||
ctx.ServerError("IssueAssignOrRemoveProject", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -180,7 +180,7 @@ func Create(ctx *context.Context) {
|
|||
|
||||
ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo()
|
||||
ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit()
|
||||
ctx.Data["SupportedObjectFormats"] = git.SupportedObjectFormats
|
||||
ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats
|
||||
ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat
|
||||
|
||||
ctx.HTML(http.StatusOK, tplCreate)
|
||||
|
|
|
@ -17,6 +17,16 @@ import (
|
|||
|
||||
const tplSearch base.TplName = "repo/search"
|
||||
|
||||
func indexSettingToGitGrepPathspecList() (list []string) {
|
||||
for _, expr := range setting.Indexer.IncludePatterns {
|
||||
list = append(list, ":(glob)"+expr.PatternString())
|
||||
}
|
||||
for _, expr := range setting.Indexer.ExcludePatterns {
|
||||
list = append(list, ":(glob,exclude)"+expr.PatternString())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Search render repository search page
|
||||
func Search(ctx *context.Context) {
|
||||
language := ctx.FormTrim("l")
|
||||
|
@ -65,8 +75,14 @@ func Search(ctx *context.Context) {
|
|||
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
|
||||
}
|
||||
} else {
|
||||
res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{ContextLineNumber: 3, IsFuzzy: isFuzzy})
|
||||
res, err := git.GrepSearch(ctx, ctx.Repo.GitRepo, keyword, git.GrepOptions{
|
||||
ContextLineNumber: 1,
|
||||
IsFuzzy: isFuzzy,
|
||||
RefName: git.RefNameFromBranch(ctx.Repo.BranchName).String(), // BranchName should be default branch or the first existing branch
|
||||
PathspecList: indexSettingToGitGrepPathspecList(),
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: if no branch exists, it reports: exit status 128, fatal: this operation must be run in a work tree.
|
||||
ctx.ServerError("GrepSearch", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIndexSettingToGitGrepPathspecList(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Indexer.IncludePatterns, setting.IndexerGlobFromString("a"))()
|
||||
defer test.MockVariableValue(&setting.Indexer.ExcludePatterns, setting.IndexerGlobFromString("b"))()
|
||||
assert.Equal(t, []string{":(glob)a", ":(glob,exclude)b"}, indexSettingToGitGrepPathspecList())
|
||||
}
|
|
@ -789,6 +789,7 @@ func SettingsPost(ctx *context.Context) {
|
|||
ctx.Repo.GitRepo = nil
|
||||
}
|
||||
|
||||
oldFullname := repo.FullName()
|
||||
if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil {
|
||||
if repo_model.IsErrRepoAlreadyExist(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
|
||||
|
@ -803,8 +804,13 @@ func SettingsPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner)
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName()))
|
||||
if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer {
|
||||
log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner)
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName()))
|
||||
} else {
|
||||
log.Trace("Repository transferred: %s -> %s", oldFullname, ctx.Repo.Repository.FullName())
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.transfer_succeed"))
|
||||
}
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
|
||||
case "cancel_transfer":
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package project
|
||||
|
||||
import (
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// MoveColumns moves or keeps columns in a project and sorts them inside that project
|
||||
func MoveColumns(ctx *context.Context) {
|
||||
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
|
||||
return
|
||||
}
|
||||
if !project.CanBeAccessedByOwnerRepo(ctx.ContextUser.ID, ctx.Repo.Repository) {
|
||||
ctx.NotFound("CanBeAccessedByOwnerRepo", nil)
|
||||
return
|
||||
}
|
||||
|
||||
type movedColumnsForm struct {
|
||||
Columns []struct {
|
||||
ColumnID int64 `json:"columnID"`
|
||||
Sorting int64 `json:"sorting"`
|
||||
} `json:"columns"`
|
||||
}
|
||||
|
||||
form := &movedColumnsForm{}
|
||||
if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
|
||||
ctx.ServerError("DecodeMovedColumnsForm", err)
|
||||
return
|
||||
}
|
||||
|
||||
sortedColumnIDs := make(map[int64]int64)
|
||||
for _, column := range form.Columns {
|
||||
sortedColumnIDs[column.Sorting] = column.ColumnID
|
||||
}
|
||||
|
||||
if err = project_model.MoveColumnsOnProject(ctx, project, sortedColumnIDs); err != nil {
|
||||
ctx.ServerError("MoveColumnsOnProject", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSONOK()
|
||||
}
|
|
@ -37,6 +37,7 @@ import (
|
|||
"code.gitea.io/gitea/routers/web/repo"
|
||||
"code.gitea.io/gitea/routers/web/repo/actions"
|
||||
repo_setting "code.gitea.io/gitea/routers/web/repo/setting"
|
||||
"code.gitea.io/gitea/routers/web/shared/project"
|
||||
"code.gitea.io/gitea/routers/web/user"
|
||||
user_setting "code.gitea.io/gitea/routers/web/user/setting"
|
||||
"code.gitea.io/gitea/routers/web/user/setting/security"
|
||||
|
@ -999,6 +1000,7 @@ func registerRoutes(m *web.Route) {
|
|||
m.Post("/new", web.Bind(forms.CreateProjectForm{}), org.NewProjectPost)
|
||||
m.Group("/{id}", func() {
|
||||
m.Post("", web.Bind(forms.EditProjectBoardForm{}), org.AddBoardToProjectPost)
|
||||
m.Post("/move", project.MoveColumns)
|
||||
m.Post("/delete", org.DeleteProject)
|
||||
|
||||
m.Get("/edit", org.RenderEditProject)
|
||||
|
@ -1354,6 +1356,7 @@ func registerRoutes(m *web.Route) {
|
|||
m.Post("/new", web.Bind(forms.CreateProjectForm{}), repo.NewProjectPost)
|
||||
m.Group("/{id}", func() {
|
||||
m.Post("", web.Bind(forms.EditProjectBoardForm{}), repo.AddBoardToProjectPost)
|
||||
m.Post("/move", project.MoveColumns)
|
||||
m.Post("/delete", repo.DeleteProject)
|
||||
|
||||
m.Get("/edit", repo.RenderEditProject)
|
||||
|
|
|
@ -229,12 +229,12 @@ func handlePull(pullID int64, sha string) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := pull_service.CheckPullMergable(ctx, doer, &perm, pr, pull_service.MergeCheckTypeGeneral, false); err != nil {
|
||||
if err := pull_service.CheckPullMergeable(ctx, doer, &perm, pr, pull_service.MergeCheckTypeGeneral, false); err != nil {
|
||||
if errors.Is(pull_service.ErrUserNotAllowedToMerge, err) {
|
||||
log.Info("%-v was scheduled to automerge by an unauthorized user", pr)
|
||||
return
|
||||
}
|
||||
log.Error("%-v CheckPullMergable: %v", pr, err)
|
||||
log.Error("%-v CheckPullMergeable: %v", pr, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -234,9 +234,7 @@ func (b *Base) plainTextInternal(skip, status int, bs []byte) {
|
|||
b.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||
b.Resp.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
b.Resp.WriteHeader(status)
|
||||
if _, err := b.Resp.Write(bs); err != nil {
|
||||
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
|
||||
}
|
||||
_, _ = b.Resp.Write(bs)
|
||||
}
|
||||
|
||||
// PlainTextBytes renders bytes as plain text
|
||||
|
@ -256,7 +254,7 @@ func (b *Base) Redirect(location string, status ...int) {
|
|||
code = status[0]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(location, "http://") || strings.HasPrefix(location, "https://") || strings.HasPrefix(location, "//") {
|
||||
if !httplib.IsRelativeURL(location) {
|
||||
// Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path
|
||||
// 1. the first request to "/my-path" contains cookie
|
||||
// 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking)
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
@ -51,7 +52,7 @@ func (ctx *Context) RedirectToCurrentSite(location ...string) {
|
|||
continue
|
||||
}
|
||||
|
||||
if !httplib.IsCurrentGiteaSiteURL(loc) {
|
||||
if !httplib.IsCurrentGiteaSiteURL(ctx, loc) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -77,7 +78,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
|
|||
}
|
||||
|
||||
err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
|
||||
if err == nil {
|
||||
if err == nil || errors.Is(err, syscall.EPIPE) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,19 @@ func MockAPIContext(t *testing.T, reqPath string) (*context.APIContext, *httptes
|
|||
return ctx, resp
|
||||
}
|
||||
|
||||
func MockPrivateContext(t *testing.T, reqPath string) (*context.PrivateContext, *httptest.ResponseRecorder) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := mockRequest(t, reqPath)
|
||||
base, baseCleanUp := context.NewBaseContext(resp, req)
|
||||
base.Data = middleware.GetContextData(req.Context())
|
||||
base.Locale = &translation.MockLocale{}
|
||||
ctx := &context.PrivateContext{Base: base}
|
||||
_ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later
|
||||
chiCtx := chi.NewRouteContext()
|
||||
ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
|
||||
return ctx, resp
|
||||
}
|
||||
|
||||
// LoadRepo load a repo into a test context.
|
||||
func LoadRepo(t *testing.T, ctx gocontext.Context, repoID int64) {
|
||||
var doer *user_model.User
|
||||
|
|
|
@ -1143,7 +1143,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
|||
// so if we are using at least this version of git we don't have to tell ParsePatch to do
|
||||
// the skipping for us
|
||||
parsePatchSkipToFile := opts.SkipTo
|
||||
if opts.SkipTo != "" && git.CheckGitVersionAtLeast("2.31") == nil {
|
||||
if opts.SkipTo != "" && git.DefaultFeatures().CheckVersionAtLeast("2.31") {
|
||||
cmdDiff.AddOptionFormat("--skip-to=%s", opts.SkipTo)
|
||||
parsePatchSkipToFile = ""
|
||||
}
|
||||
|
|
|
@ -152,3 +152,19 @@ func (r *indexerNotifier) IssueChangeLabels(ctx context.Context, doer *user_mode
|
|||
func (r *indexerNotifier) IssueClearLabels(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) {
|
||||
issue_indexer.UpdateIssueIndexer(ctx, issue.ID)
|
||||
}
|
||||
|
||||
func (r *indexerNotifier) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
log.Error("LoadIssue: %v", err)
|
||||
return
|
||||
}
|
||||
issue_indexer.UpdateIssueIndexer(ctx, pr.Issue.ID)
|
||||
}
|
||||
|
||||
func (r *indexerNotifier) AutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) {
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
log.Error("LoadIssue: %v", err)
|
||||
return
|
||||
}
|
||||
issue_indexer.UpdateIssueIndexer(ctx, pr.Issue.ID)
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
|
|||
}
|
||||
}
|
||||
if projectID > 0 {
|
||||
if err := issues_model.ChangeProjectAssign(ctx, issue, issue.Poster, projectID); err != nil {
|
||||
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, issue.Poster, projectID, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ var (
|
|||
ErrHasMerged = errors.New("has already been merged")
|
||||
ErrIsWorkInProgress = errors.New("work in progress PRs cannot be merged")
|
||||
ErrIsChecking = errors.New("cannot merge while conflict checking is in progress")
|
||||
ErrNotMergableState = errors.New("not in mergeable state")
|
||||
ErrNotMergeableState = errors.New("not in mergeable state")
|
||||
ErrDependenciesLeft = errors.New("is blocked by an open dependency")
|
||||
)
|
||||
|
||||
|
@ -66,8 +66,8 @@ const (
|
|||
MergeCheckTypeAuto // Auto Merge (Scheduled Merge) After Checks Succeed
|
||||
)
|
||||
|
||||
// CheckPullMergable check if the pull mergeable based on all conditions (branch protection, merge options, ...)
|
||||
func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminSkipProtectionCheck bool) error {
|
||||
// CheckPullMergeable check if the pull mergeable based on all conditions (branch protection, merge options, ...)
|
||||
func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *issues_model.PullRequest, mergeCheckType MergeCheckType, adminSkipProtectionCheck bool) error {
|
||||
return db.WithTx(stdCtx, func(ctx context.Context) error {
|
||||
if pr.HasMerged {
|
||||
return ErrHasMerged
|
||||
|
@ -97,7 +97,7 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *acce
|
|||
}
|
||||
|
||||
if !pr.CanAutoMerge() && !pr.IsEmpty() {
|
||||
return ErrNotMergableState
|
||||
return ErrNotMergeableState
|
||||
}
|
||||
|
||||
if pr.IsChecking() {
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
@ -162,12 +161,6 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||
pullWorkingPool.CheckIn(fmt.Sprint(pr.ID))
|
||||
defer pullWorkingPool.CheckOut(fmt.Sprint(pr.ID))
|
||||
|
||||
// Removing an auto merge pull and ignore if not exist
|
||||
// FIXME: is this the correct point to do this? Shouldn't this be after IsMergeStyleAllowed?
|
||||
if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
|
||||
|
@ -184,17 +177,15 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "")
|
||||
}()
|
||||
|
||||
pr.MergedCommitID, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message)
|
||||
_, err = doMergeAndPush(ctx, pr, doer, mergeStyle, expectedHeadCommitID, message, repo_module.PushTriggerPRMergeToBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr.MergedUnix = timeutil.TimeStampNow()
|
||||
pr.Merger = doer
|
||||
pr.MergerID = doer.ID
|
||||
|
||||
if _, err := pr.SetMerged(ctx); err != nil {
|
||||
log.Error("SetMerged %-v: %v", pr, err)
|
||||
// reload pull request because it has been updated by post receive hook
|
||||
pr, err = issues_model.GetPullRequestByID(ctx, pr.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
|
@ -245,7 +236,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||
}
|
||||
|
||||
// doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository
|
||||
func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string) (string, error) {
|
||||
func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) {
|
||||
// Clone base repo.
|
||||
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
|
||||
if err != nil {
|
||||
|
@ -318,11 +309,13 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use
|
|||
pr.BaseRepo.Name,
|
||||
pr.ID,
|
||||
)
|
||||
|
||||
mergeCtx.env = append(mergeCtx.env, repo_module.EnvPushTrigger+"="+string(pushTrigger))
|
||||
pushCmd := git.NewCommand(ctx, "push", "origin").AddDynamicArguments(baseBranch + ":" + git.BranchPrefix + pr.BaseBranch)
|
||||
|
||||
// Push back to upstream.
|
||||
// TODO: this cause an api call to "/api/internal/hook/post-receive/...",
|
||||
// that prevents us from doint the whole merge in one db transaction
|
||||
// This cause an api call to "/api/internal/hook/post-receive/...",
|
||||
// If it's merge, all db transaction and operations should be there but not here to prevent deadlock.
|
||||
if err := pushCmd.Run(mergeCtx.RunOpts()); err != nil {
|
||||
if strings.Contains(mergeCtx.errbuf.String(), "non-fast-forward") {
|
||||
return "", &git.ErrPushOutOfDate{
|
||||
|
|
|
@ -383,7 +383,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
|
|||
cmdApply.AddArguments("--ignore-whitespace")
|
||||
}
|
||||
is3way := false
|
||||
if git.CheckGitVersionAtLeast("2.32.0") == nil {
|
||||
if git.DefaultFeatures().CheckVersionAtLeast("2.32.0") {
|
||||
cmdApply.AddArguments("--3way")
|
||||
is3way = true
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest)
|
|||
baseBranch := "base"
|
||||
|
||||
fetchArgs := git.TrustedCmdArgs{"--no-tags"}
|
||||
if git.CheckGitVersionAtLeast("2.25.0") == nil {
|
||||
if git.DefaultFeatures().CheckVersionAtLeast("2.25.0") {
|
||||
// Writing the commit graph can be slow and is not needed here
|
||||
fetchArgs = append(fetchArgs, "--no-write-commit-graph")
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/repository"
|
||||
)
|
||||
|
||||
// Update updates pull request with base branch.
|
||||
|
@ -72,7 +73,7 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
|
|||
BaseBranch: pr.HeadBranch,
|
||||
}
|
||||
|
||||
_, err = doMergeAndPush(ctx, reversePR, doer, repo_model.MergeStyleMerge, "", message)
|
||||
_, err = doMergeAndPush(ctx, reversePR, doer, repo_model.MergeStyleMerge, "", message, repository.PushTriggerPRUpdateWithBase)
|
||||
|
||||
defer func() {
|
||||
go AddTestPullRequestTask(doer, reversePR.HeadRepo.ID, reversePR.HeadBranch, false, "", "")
|
||||
|
|
|
@ -195,6 +195,10 @@ func adoptRepository(ctx context.Context, repoPath string, repo *repo_model.Repo
|
|||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, 0); err != nil {
|
||||
return fmt.Errorf("SyncRepoBranches: %w", err)
|
||||
}
|
||||
|
||||
if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
|
||||
return fmt.Errorf("SyncReleasesWithTags: %w", err)
|
||||
}
|
||||
|
|