Merge remote-tracking branch 'upstream/main' into limit-repo-size

This commit is contained in:
DmitryFrolovTri
2023-08-23 10:10:22 +00:00
710 changed files with 13439 additions and 12772 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
}
ctxname := fmt.Sprintf("%s / %s (%s)", runName, job.Name, event)
state := toCommitStatus(job.Status)
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{}); err == nil {
if statuses, _, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptions{ListAll: true}); err == nil {
for _, v := range statuses {
if v.Context == ctxname {
if v.State == state {
+9 -2
View File
@@ -143,14 +143,21 @@ func notify(ctx context.Context, input *notifyInput) error {
}
var detectedWorkflows []*actions_module.DetectedWorkflow
workflows, err := actions_module.DetectWorkflows(commit, input.Event, input.Payload)
workflows, err := actions_module.DetectWorkflows(gitRepo, commit, input.Event, input.Payload)
if err != nil {
return fmt.Errorf("DetectWorkflows: %w", err)
}
if len(workflows) == 0 {
log.Trace("repo %s with commit %s couldn't find workflows", input.Repo.RepoPath(), commit.ID)
} else {
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
for _, wf := range workflows {
if actionsConfig.IsWorkflowDisabled(wf.EntryName) {
log.Trace("repo %s has disable workflows %s", input.Repo.RepoPath(), wf.EntryName)
continue
}
if wf.TriggerEvent != actions_module.GithubEventPullRequestTarget {
detectedWorkflows = append(detectedWorkflows, wf)
}
@@ -164,7 +171,7 @@ func notify(ctx context.Context, input *notifyInput) error {
if err != nil {
return fmt.Errorf("gitRepo.GetCommit: %w", err)
}
baseWorkflows, err := actions_module.DetectWorkflows(baseCommit, input.Event, input.Payload)
baseWorkflows, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload)
if err != nil {
return fmt.Errorf("DetectWorkflows: %w", err)
}
+1 -10
View File
@@ -336,16 +336,7 @@ func InitSigningKey() error {
// loadSymmetricKey checks if the configured secret is valid.
// If it is not valid, it will return an error.
func loadSymmetricKey() (any, error) {
key := make([]byte, 32)
n, err := base64.RawURLEncoding.Decode(key, []byte(setting.OAuth2.JWTSecretBase64))
if err != nil {
return nil, err
}
if n != 32 {
return nil, fmt.Errorf("JWT secret must be 32 bytes long")
}
return key, nil
return util.Base64FixedDecode(base64.RawURLEncoding, []byte(setting.OAuth2.JWTSecretBase64), 32)
}
// loadOrCreateAsymmetricKey checks if the configured private key exists.
+1 -1
View File
@@ -56,7 +56,7 @@ func (p *AuthSourceProvider) DisplayName() string {
func (p *AuthSourceProvider) IconHTML() template.HTML {
if p.iconURL != "" {
img := fmt.Sprintf(`<img class="gt-mr-3" width="20" height="20" src="%s" alt="%s">`,
img := fmt.Sprintf(`<img class="gt-object-contain gt-mr-3" width="20" height="20" src="%s" alt="%s">`,
html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()),
)
return template.HTML(img)
+1
View File
@@ -208,6 +208,7 @@ func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_m
Exclusive: label.Exclusive,
Color: strings.TrimLeft(label.Color, "#"),
Description: label.Description,
IsArchived: label.IsArchived(),
}
// calculate URL
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package convert
import (
secret_model "code.gitea.io/gitea/models/secret"
api "code.gitea.io/gitea/modules/structs"
)
// ToSecret converts Secret to API format
func ToSecret(secret *secret_model.Secret) *api.Secret {
result := &api.Secret{
Name: secret.Name,
}
return result
}
+1 -1
View File
@@ -51,7 +51,7 @@ func toUser(ctx context.Context, user *user_model.User, signed, authed bool) *ap
ID: user.ID,
UserName: user.Name,
FullName: user.FullName,
Email: user.GetEmail(),
Email: user.GetPlaceholderEmail(),
AvatarURL: user.AvatarLink(ctx),
Created: user.CreatedUnix.AsTime(),
Restricted: user.IsRestricted,
+1 -1
View File
@@ -152,7 +152,7 @@ func registerCleanupPackages() {
OlderThan: 24 * time.Hour,
}, func(ctx context.Context, _ *user_model.User, config Config) error {
realConfig := config.(*OlderThanConfig)
return packages_cleanup_service.Cleanup(ctx, realConfig.OlderThan)
return packages_cleanup_service.CleanupTask(ctx, realConfig.OlderThan)
})
}
+12
View File
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker"
repo_service "code.gitea.io/gitea/services/repository"
@@ -213,6 +214,16 @@ func registerGCLFS() {
})
}
func registerRebuildIssueIndexer() {
RegisterTaskFatal("rebuild_issue_indexer", &BaseConfig{
Enabled: false,
RunAtStart: false,
Schedule: "@annually",
}, func(ctx context.Context, _ *user_model.User, config Config) error {
return issue_indexer.PopulateIssueIndexer(ctx, false)
})
}
func initExtendedTasks() {
registerDeleteInactiveUsers()
registerDeleteRepositoryArchives()
@@ -227,4 +238,5 @@ func initExtendedTasks() {
registerUpdateGiteaChecker()
registerDeleteOldSystemNotices()
registerGCLFS()
registerRebuildIssueIndexer()
}
+2
View File
@@ -361,6 +361,7 @@ func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) b
type NewTelegramHookForm struct {
BotToken string `binding:"Required"`
ChatID string `binding:"Required"`
ThreadID string
WebhookForm
}
@@ -576,6 +577,7 @@ type CreateLabelForm struct {
ID int64
Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"`
Exclusive bool `form:"exclusive"`
IsArchived bool `form:"is_archived"`
Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"`
Color string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"`
}
+20 -3
View File
@@ -13,7 +13,6 @@ import (
"html/template"
"io"
"net/url"
"os"
"sort"
"strings"
"time"
@@ -427,6 +426,23 @@ func (diffFile *DiffFile) ShouldBeHidden() bool {
return diffFile.IsGenerated || diffFile.IsViewed
}
func (diffFile *DiffFile) ModeTranslationKey(mode string) string {
switch mode {
case "040000":
return "git.filemode.directory"
case "100644":
return "git.filemode.normal_file"
case "100755":
return "git.filemode.executable_file"
case "120000":
return "git.filemode.symbolic_link"
case "160000":
return "git.filemode.submodule"
default:
return mode
}
}
func getCommitFileLineCount(commit *git.Commit, filePath string) int {
blob, err := commit.GetBlobByPath(filePath)
if err != nil {
@@ -1135,14 +1151,15 @@ func GetDiff(gitRepo *git.Repository, opts *DiffOptions, files ...string) (*Diff
}()
go func() {
stderr := &bytes.Buffer{}
cmdDiff.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath))
if err := cmdDiff.Run(&git.RunOpts{
Timeout: time.Duration(setting.Git.Timeout.Default) * time.Second,
Dir: repoPath,
Stderr: os.Stderr,
Stdout: writer,
Stderr: stderr,
}); err != nil {
log.Error("error during RunWithContext: %w", err)
log.Error("error during GetDiff(git diff dir: %s): %v, stderr: %s", repoPath, err, stderr.String())
}
_ = writer.Close()
+7 -7
View File
@@ -33,7 +33,7 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe
if !found {
// This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here
if _, _, err := ToggleAssignee(ctx, issue, doer, assignee.ID); err != nil {
if _, _, err := ToggleAssigneeWithNotify(ctx, issue, doer, assignee.ID); err != nil {
return err
}
}
@@ -42,8 +42,8 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe
return nil
}
// ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it.
func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) {
// ToggleAssigneeWithNoNotify changes a user between assigned and not assigned for this issue, and make issue comment for it.
func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) {
removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID)
if err != nil {
return false, nil, err
@@ -62,9 +62,9 @@ func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_m
// ReviewRequest add or remove a review request from a user for this PR, and make comment for it.
func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) {
if isAdd {
comment, err = issues_model.AddReviewRequest(issue, reviewer, doer)
comment, err = issues_model.AddReviewRequest(ctx, issue, reviewer, doer)
} else {
comment, err = issues_model.RemoveReviewRequest(issue, reviewer, doer)
comment, err = issues_model.RemoveReviewRequest(ctx, issue, reviewer, doer)
}
if err != nil {
@@ -229,9 +229,9 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team,
// TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it.
func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) {
if isAdd {
comment, err = issues_model.AddTeamReviewRequest(issue, reviewer, doer)
comment, err = issues_model.AddTeamReviewRequest(ctx, issue, reviewer, doer)
} else {
comment, err = issues_model.RemoveTeamReviewRequest(issue, reviewer, doer)
comment, err = issues_model.RemoveTeamReviewRequest(ctx, issue, reviewer, doer)
}
if err != nil {
+2 -22
View File
@@ -15,26 +15,6 @@ import (
"code.gitea.io/gitea/modules/timeutil"
)
// CreateComment creates comment of issue or commit.
func CreateComment(ctx context.Context, opts *issues_model.CreateCommentOptions) (comment *issues_model.Comment, err error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
defer committer.Close()
comment, err = issues_model.CreateComment(ctx, opts)
if err != nil {
return nil, err
}
if err = committer.Commit(); err != nil {
return nil, err
}
return comment, nil
}
// CreateRefComment creates a commit reference comment to issue.
func CreateRefComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content, commitSHA string) error {
if len(commitSHA) == 0 {
@@ -53,7 +33,7 @@ func CreateRefComment(ctx context.Context, doer *user_model.User, repo *repo_mod
return nil
}
_, err = CreateComment(ctx, &issues_model.CreateCommentOptions{
_, err = issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
Type: issues_model.CommentTypeCommitRef,
Doer: doer,
Repo: repo,
@@ -66,7 +46,7 @@ func CreateRefComment(ctx context.Context, doer *user_model.User, repo *repo_mod
// CreateIssueComment creates a plain issue comment.
func CreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content string, attachments []string) (*issues_model.Comment, error) {
comment, err := CreateComment(ctx, &issues_model.CreateCommentOptions{
comment, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
Type: issues_model.CommentTypeComment,
Doer: doer,
Repo: repo,
+14 -14
View File
@@ -27,7 +27,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo
}
for _, assigneeID := range assigneeIDs {
if err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID); err != nil {
if _, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, true); err != nil {
return err
}
}
@@ -128,7 +128,7 @@ func UpdateAssignees(ctx context.Context, issue *issues_model.Issue, oneAssignee
// has access to the repo.
for _, assignee := range allNewAssignees {
// Extra method to prevent double adding (which would result in removing)
err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID)
_, err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID, true)
if err != nil {
return err
}
@@ -173,36 +173,36 @@ func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Reposi
// AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue.
// Also checks for access of assigned user
func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (err error) {
func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64, notify bool) (comment *issues_model.Comment, err error) {
assignee, err := user_model.GetUserByID(ctx, assigneeID)
if err != nil {
return err
return nil, err
}
// Check if the user is already assigned
isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assignee)
if err != nil {
return err
return nil, err
}
if isAssigned {
// nothing to to
return nil
return nil, nil
}
valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull)
if err != nil {
return err
return nil, err
}
if !valid {
return repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name}
return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name}
}
_, _, err = ToggleAssignee(ctx, issue, doer, assigneeID)
if err != nil {
return err
if notify {
_, comment, err = ToggleAssigneeWithNotify(ctx, issue, doer, assigneeID)
return comment, err
}
return nil
_, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID)
return comment, err
}
// GetRefEndNamesAndURLs retrieves the ref end names (e.g. refs/heads/branch-name -> branch-name)
@@ -248,7 +248,7 @@ func deleteIssue(ctx context.Context, issue *issues_model.Issue) error {
issue.MilestoneID, err)
}
if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID); err != nil {
if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID, issue.Index); err != nil {
return err
}
+22 -4
View File
@@ -20,9 +20,17 @@ import (
debian_service "code.gitea.io/gitea/services/packages/debian"
)
// Cleanup removes expired package data
func Cleanup(taskCtx context.Context, olderThan time.Duration) error {
ctx, committer, err := db.TxContext(taskCtx)
// Task method to execute cleanup rules and cleanup expired package data
func CleanupTask(ctx context.Context, olderThan time.Duration) error {
if err := ExecuteCleanupRules(ctx); err != nil {
return err
}
return CleanupExpiredData(ctx, olderThan)
}
func ExecuteCleanupRules(outerCtx context.Context) error {
ctx, committer, err := db.TxContext(outerCtx)
if err != nil {
return err
}
@@ -30,7 +38,7 @@ func Cleanup(taskCtx context.Context, olderThan time.Duration) error {
err = packages_model.IterateEnabledCleanupRules(ctx, func(ctx context.Context, pcr *packages_model.PackageCleanupRule) error {
select {
case <-taskCtx.Done():
case <-outerCtx.Done():
return db.ErrCancelledf("While processing package cleanup rules")
default:
}
@@ -122,6 +130,16 @@ func Cleanup(taskCtx context.Context, olderThan time.Duration) error {
return err
}
return committer.Commit()
}
func CleanupExpiredData(outerCtx context.Context, olderThan time.Duration) error {
ctx, committer, err := db.TxContext(outerCtx)
if err != nil {
return err
}
defer committer.Close()
if err := container_service.Cleanup(ctx, olderThan); err != nil {
return err
}
+1 -1
View File
@@ -212,7 +212,7 @@ func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packa
}
addSeparator = true
fmt.Fprint(w, pfd.Properties.GetByName(debian_module.PropertyControl))
fmt.Fprintf(w, "%s\n", strings.TrimSpace(pfd.Properties.GetByName(debian_module.PropertyControl)))
fmt.Fprintf(w, "Filename: pool/%s/%s/%s\n", distribution, component, pfd.File.Name)
fmt.Fprintf(w, "Size: %d\n", pfd.Blob.Size)
+1 -2
View File
@@ -11,7 +11,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
issue_service "code.gitea.io/gitea/services/issue"
)
// getCommitIDsFromRepo get commit IDs from repo in between oldCommitID and newCommitID
@@ -90,7 +89,7 @@ func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *iss
ops.Content = string(dataJSON)
comment, err = issue_service.CreateComment(ctx, ops)
comment, err = issues_model.CreateComment(ctx, ops)
return comment, err
}
+1 -1
View File
@@ -143,7 +143,7 @@ func GetPullRequestCommitStatusState(ctx context.Context, pr *issues_model.PullR
return "", errors.Wrap(err, "LoadBaseRepo")
}
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{})
commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true})
if err != nil {
return "", errors.Wrap(err, "GetLatestCommitStatus")
}
+7 -2
View File
@@ -62,14 +62,19 @@ func TestPatch(pr *issues_model.PullRequest) error {
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("TestPatch: %s", pr))
defer finished()
// Clone base repo.
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
if err != nil {
log.Error("createTemporaryRepoForPR %-v: %v", pr, err)
if !git_model.IsErrBranchNotExist(err) {
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
}
return err
}
defer cancel()
return testPatch(ctx, prCtx, pr)
}
func testPatch(ctx context.Context, prCtx *prContext, pr *issues_model.PullRequest) error {
gitRepo, err := git.OpenRepository(ctx, prCtx.tmpBasePath)
if err != nil {
return fmt.Errorf("OpenRepository: %w", err)
+154 -56
View File
@@ -10,6 +10,7 @@ import (
"os"
"regexp"
"strings"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
@@ -17,13 +18,14 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/container"
gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/process"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
@@ -35,73 +37,70 @@ import (
var pullWorkingPool = sync.NewExclusivePool()
// NewPullRequest creates new pull request with labels for repository.
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error {
if err := TestPatch(pr); err != nil {
func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error {
prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr)
if err != nil {
if !git_model.IsErrBranchNotExist(err) {
log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err)
}
return err
}
defer cancel()
if err := testPatch(ctx, prCtx, pr); err != nil {
return err
}
divergence, err := GetDiverging(ctx, pr)
divergence, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch)
if err != nil {
return err
}
pr.CommitsAhead = divergence.Ahead
pr.CommitsBehind = divergence.Behind
if err := issues_model.NewPullRequest(ctx, repo, pull, labelIDs, uuids, pr); err != nil {
return err
}
for _, assigneeID := range assigneeIDs {
if err := issue_service.AddAssigneeIfNotAssigned(ctx, pull, pull.Poster, assigneeID); err != nil {
return err
}
}
pr.Issue = pull
pull.PullRequest = pr
// Now - even if the request context has been cancelled as the PR has been created
// in the db and there is no way to cancel that transaction we have to proceed - therefore
// create new context and work from there
prCtx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("NewPullRequest: %s:%d", repo.FullName(), pr.Index))
defer finished()
if pr.Flow == issues_model.PullRequestFlowGithub {
err = PushToBaseRepo(prCtx, pr)
} else {
err = UpdateRef(prCtx, pr)
}
if err != nil {
return err
}
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, pull, pull.Poster, pull.Content)
if err != nil {
return err
}
notification.NotifyNewPullRequest(prCtx, pr, mentions)
if len(pull.Labels) > 0 {
notification.NotifyIssueChangeLabels(prCtx, pull.Poster, pull, pull.Labels, nil)
}
if pull.Milestone != nil {
notification.NotifyIssueChangeMilestone(prCtx, pull.Poster, pull, 0)
}
assigneeCommentMap := make(map[int64]*issues_model.Comment)
// add first push codes comment
baseGitRepo, err := git.OpenRepository(prCtx, pr.BaseRepo.RepoPath())
baseGitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath())
if err != nil {
return err
}
defer baseGitRepo.Close()
compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
if err != nil {
return err
}
if err := db.WithTx(ctx, func(ctx context.Context) error {
if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil {
return err
}
for _, assigneeID := range assigneeIDs {
comment, err := issue_service.AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, false)
if err != nil {
return err
}
assigneeCommentMap[assigneeID] = comment
}
pr.Issue = issue
issue.PullRequest = pr
if pr.Flow == issues_model.PullRequestFlowGithub {
err = PushToBaseRepo(ctx, pr)
} else {
err = UpdateRef(ctx, pr)
}
if err != nil {
return err
}
compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(),
git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false)
if err != nil {
return err
}
if len(compareInfo.Commits) == 0 {
return nil
}
if len(compareInfo.Commits) > 0 {
data := issues_model.PushActionContent{IsForcePush: false}
data.CommitIDs = make([]string, 0, len(compareInfo.Commits))
for i := len(compareInfo.Commits) - 1; i >= 0; i-- {
@@ -115,21 +114,52 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issu
ops := &issues_model.CreateCommentOptions{
Type: issues_model.CommentTypePullRequestPush,
Doer: pull.Poster,
Doer: issue.Poster,
Repo: repo,
Issue: pr.Issue,
IsForcePush: false,
Content: string(dataJSON),
}
_, _ = issue_service.CreateComment(ctx, ops)
if _, err = issues_model.CreateComment(ctx, ops); err != nil {
return err
}
if !pr.IsWorkInProgress() {
if err := issues_model.PullRequestCodeOwnersReview(ctx, pull, pr); err != nil {
if err := issues_model.PullRequestCodeOwnersReview(ctx, issue, pr); err != nil {
return err
}
}
return nil
}); err != nil {
// cleanup: this will only remove the reference, the real commit will be clean up when next GC
if err1 := baseGitRepo.RemoveReference(pr.GetGitRefName()); err1 != nil {
log.Error("RemoveReference: %v", err1)
}
return err
}
baseGitRepo.Close() // close immediately to avoid notifications will open the repository again
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content)
if err != nil {
return err
}
notification.NotifyNewPullRequest(ctx, pr, mentions)
if len(issue.Labels) > 0 {
notification.NotifyIssueChangeLabels(ctx, issue.Poster, issue, issue.Labels, nil)
}
if issue.Milestone != nil {
notification.NotifyIssueChangeMilestone(ctx, issue.Poster, issue, 0)
}
if len(assigneeIDs) > 0 {
for _, assigneeID := range assigneeIDs {
assignee, err := user_model.GetUserByID(ctx, assigneeID)
if err != nil {
return ErrDependenciesLeft
}
notification.NotifyIssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assigneeID])
}
}
return nil
@@ -228,7 +258,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
OldRef: oldBranch,
NewRef: targetBranch,
}
if _, err = issue_service.CreateComment(ctx, options); err != nil {
if _, err = issues_model.CreateComment(ctx, options); err != nil {
return fmt.Errorf("CreateChangeTargetBranchComment: %w", err)
}
@@ -801,7 +831,7 @@ func getAllCommitStatus(gitRepo *git.Repository, pr *issues_model.PullRequest) (
return nil, nil, shaErr
}
statuses, _, err = git_model.GetLatestCommitStatus(db.DefaultContext, pr.BaseRepo.ID, sha, db.ListOptions{})
statuses, _, err = git_model.GetLatestCommitStatus(db.DefaultContext, pr.BaseRepo.ID, sha, db.ListOptions{ListAll: true})
lastStatus = git_model.CalcCommitStatus(statuses)
return statuses, lastStatus, err
}
@@ -856,3 +886,71 @@ func IsHeadEqualWithBranch(ctx context.Context, pr *issues_model.PullRequest, br
}
return baseCommit.HasPreviousCommit(headCommit.ID)
}
type CommitInfo struct {
Summary string `json:"summary"`
CommitterOrAuthorName string `json:"committer_or_author_name"`
ID string `json:"id"`
ShortSha string `json:"short_sha"`
Time string `json:"time"`
}
// GetPullCommits returns all commits on given pull request and the last review commit sha
func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]CommitInfo, string, error) {
pull := issue.PullRequest
baseGitRepo := ctx.Repo.GitRepo
if err := pull.LoadBaseRepo(ctx); err != nil {
return nil, "", err
}
baseBranch := pull.BaseBranch
if pull.HasMerged {
baseBranch = pull.MergeBase
}
prInfo, err := baseGitRepo.GetCompareInfo(pull.BaseRepo.RepoPath(), baseBranch, pull.GetGitRefName(), true, false)
if err != nil {
return nil, "", err
}
commits := make([]CommitInfo, 0, len(prInfo.Commits))
for _, commit := range prInfo.Commits {
var committerOrAuthorName string
var commitTime time.Time
if commit.Committer != nil {
committerOrAuthorName = commit.Committer.Name
commitTime = commit.Committer.When
} else {
committerOrAuthorName = commit.Author.Name
commitTime = commit.Author.When
}
commits = append(commits, CommitInfo{
Summary: commit.Summary(),
CommitterOrAuthorName: committerOrAuthorName,
ID: commit.ID.String(),
ShortSha: base.ShortSha(commit.ID.String()),
Time: commitTime.Format(time.RFC3339),
})
}
var lastReviewCommitID string
if ctx.IsSigned {
// get last review of current user and store information in context (if available)
lastreview, err := issues_model.FindLatestReviews(ctx, issues_model.FindReviewOptions{
IssueID: issue.ID,
ReviewerID: ctx.Doer.ID,
Type: issues_model.ReviewTypeUnknown,
})
if err != nil && !issues_model.IsErrReviewNotExist(err) {
return nil, "", err
}
if len(lastreview) > 0 {
lastReviewCommitID = lastreview[0].CommitID
}
}
return commits, lastReviewCommitID, nil
}
+3 -4
View File
@@ -20,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
issue_service "code.gitea.io/gitea/services/issue"
)
var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`)
@@ -248,7 +247,7 @@ func createCodeComment(ctx context.Context, doer *user_model.User, repo *repo_mo
return nil, err
}
}
return issue_service.CreateComment(ctx, &issues_model.CreateCommentOptions{
return issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
Type: issues_model.CommentTypeCode,
Doer: doer,
Repo: repo,
@@ -340,7 +339,7 @@ func DismissApprovalReviews(ctx context.Context, doer *user_model.User, pull *is
return err
}
comment, err := issue_service.CreateComment(ctx, &issues_model.CreateCommentOptions{
comment, err := issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
Doer: doer,
Content: "New commits pushed, approval review dismissed automatically according to repository settings",
Type: issues_model.CommentTypeDismissReview,
@@ -411,7 +410,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string,
return nil, err
}
comment, err = issue_service.CreateComment(ctx, &issues_model.CreateCommentOptions{
comment, err = issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{
Doer: doer,
Content: message,
Type: issues_model.CommentTypeDismissReview,
+3 -1
View File
@@ -263,7 +263,9 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
}
if repo.IsEmpty {
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
if isEmpty, err := gitRepo.IsEmpty(); err == nil && !isEmpty {
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: repo.ID, IsEmpty: false, DefaultBranch: opts.NewBranch}, "is_empty", "default_branch")
}
}
return filesResponse, nil
+3 -2
View File
@@ -28,8 +28,9 @@ import (
// WebSearchRepository represents a repository returned by web search
type WebSearchRepository struct {
Repository *structs.Repository `json:"repository"`
LatestCommitStatus *git.CommitStatus `json:"latest_commit_status"`
Repository *structs.Repository `json:"repository"`
LatestCommitStatus *git.CommitStatus `json:"latest_commit_status"`
LocaleLatestCommitStatus string `json:"locale_latest_commit_status"`
}
// WebSearchResults results of a successful web search
+24
View File
@@ -126,3 +126,27 @@ func CreateMigrateTask(doer, u *user_model.User, opts base.MigrateOptions) (*adm
return task, nil
}
// RetryMigrateTask retry a migrate task
func RetryMigrateTask(repoID int64) error {
migratingTask, err := admin_model.GetMigratingTask(repoID)
if err != nil {
log.Error("GetMigratingTask: %v", err)
return err
}
if migratingTask.Status == structs.TaskStatusQueued || migratingTask.Status == structs.TaskStatusRunning {
return nil
}
// TODO Need to removing the storage/database garbage brought by the failed task
// Reset task status and messages
migratingTask.Status = structs.TaskStatusQueued
migratingTask.Message = ""
if err = migratingTask.UpdateCols("status", "message"); err != nil {
log.Error("task.UpdateCols failed: %v", err)
return err
}
return taskQueue.Push(migratingTask)
}
+64
View File
@@ -4,10 +4,13 @@
package user
import (
"fmt"
"path/filepath"
"strings"
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
@@ -94,6 +97,67 @@ func TestCreateUser(t *testing.T) {
assert.NoError(t, DeleteUser(db.DefaultContext, user, false))
}
func TestRenameUser(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 21})
t.Run("Non-Local", func(t *testing.T) {
u := &user_model.User{
Type: user_model.UserTypeIndividual,
LoginType: auth.OAuth2,
}
assert.ErrorIs(t, RenameUser(db.DefaultContext, u, "user_rename"), user_model.ErrUserIsNotLocal{})
})
t.Run("Same username", func(t *testing.T) {
assert.ErrorIs(t, RenameUser(db.DefaultContext, user, user.Name), user_model.ErrUsernameNotChanged{UID: user.ID, Name: user.Name})
})
t.Run("Non usable username", func(t *testing.T) {
usernames := []string{"--diff", "aa.png", ".well-known", "search", "aaa.atom"}
for _, username := range usernames {
t.Run(username, func(t *testing.T) {
assert.Error(t, user_model.IsUsableUsername(username))
assert.Error(t, RenameUser(db.DefaultContext, user, username))
})
}
})
t.Run("Only capitalization", func(t *testing.T) {
caps := strings.ToUpper(user.Name)
unittest.AssertNotExistsBean(t, &user_model.User{ID: user.ID, Name: caps})
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: user.Name})
assert.NoError(t, RenameUser(db.DefaultContext, user, caps))
unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID, Name: caps})
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: caps})
})
t.Run("Already exists", func(t *testing.T) {
existUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
assert.ErrorIs(t, RenameUser(db.DefaultContext, user, existUser.Name), user_model.ErrUserAlreadyExist{Name: existUser.Name})
assert.ErrorIs(t, RenameUser(db.DefaultContext, user, existUser.LowerName), user_model.ErrUserAlreadyExist{Name: existUser.LowerName})
newUsername := fmt.Sprintf("uSEr%d", existUser.ID)
assert.ErrorIs(t, RenameUser(db.DefaultContext, user, newUsername), user_model.ErrUserAlreadyExist{Name: newUsername})
})
t.Run("Normal", func(t *testing.T) {
oldUsername := user.Name
newUsername := "User_Rename"
assert.NoError(t, RenameUser(db.DefaultContext, user, newUsername))
unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID, Name: newUsername, LowerName: strings.ToLower(newUsername)})
redirectUID, err := user_model.LookupUserRedirect(oldUsername)
assert.NoError(t, err)
assert.EqualValues(t, user.ID, redirectUID)
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: user.Name})
})
}
func TestCreateUser_Issue5882(t *testing.T) {
// Init settings
_ = setting.Admin
+5
View File
@@ -131,6 +131,10 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm
case api.HookIssueReviewed:
text = fmt.Sprintf("[%s] Pull request reviewed: %s", repoLink, titleLink)
attachmentText = p.Review.Content
case api.HookIssueReviewRequested:
text = fmt.Sprintf("[%s] Pull request review requested: %s", repoLink, titleLink)
case api.HookIssueReviewRequestRemoved:
text = fmt.Sprintf("[%s] Pull request review request removed: %s", repoLink, titleLink)
}
if withSender {
text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName))
@@ -256,5 +260,6 @@ func ToHook(repoLink string, w *webhook_model.Webhook) (*api.Hook, error) {
AuthorizationHeader: authorizationHeader,
Updated: w.UpdatedUnix.AsTime(),
Created: w.CreatedUnix.AsTime(),
BranchFilter: w.BranchFilter,
}, nil
}
+1
View File
@@ -28,6 +28,7 @@ type (
TelegramMeta struct {
BotToken string `json:"bot_token"`
ChatID string `json:"chat_id"`
ThreadID string `json:"thread_id"`
}
)
+6 -1
View File
@@ -5,6 +5,7 @@ package webhook
import (
"context"
"errors"
"fmt"
"strings"
@@ -111,7 +112,11 @@ func handler(items ...int64) []int64 {
for _, taskID := range items {
task, err := webhook_model.GetHookTaskByID(ctx, taskID)
if err != nil {
log.Error("GetHookTaskByID[%d] failed: %v", taskID, err)
if errors.Is(err, util.ErrNotExist) {
log.Warn("GetHookTaskByID[%d] warn: %v", taskID, err)
} else {
log.Error("GetHookTaskByID[%d] failed: %v", taskID, err)
}
continue
}
+10 -6
View File
@@ -22,15 +22,16 @@ import (
// - "/wiki/100%25+Free"
// - "/wiki/2000-01-02+meeting.-"
// - If a segment has a suffix "DashMarker(.-)", it means that there is no dash-space conversion for this segment.
// - If a WebPath is a "*.md" pattern, then use it directly as GitPath, to make users can access the raw file.
// - If a WebPath is a "*.md" pattern, then use the unescaped value directly as GitPath, to make users can access the raw file.
// * Git Path (only space doesn't need to be escaped):
// - "/.wiki.git/Home-Page.md"
// - "/.wiki.git/100%25 Free.md"
// - "/.wiki.git/2000-01-02 meeting.-.md"
// TODO: support subdirectory in the future
//
// Although this package now has the ablity to support subdirectory, but the route package doesn't:
// Although this package now has the ability to support subdirectory, but the route package doesn't:
// * Double-escaping problem: the URL "/wiki/abc%2Fdef" becomes "/wiki/abc/def" by ctx.Params, which is incorrect
// * This problem should have been 99% fixed, but it needs more tests.
// * The old wiki code's behavior is always using %2F, instead of subdirectory, so there are a lot of legacy "%2F" files in user wikis.
type WebPath string
@@ -91,7 +92,8 @@ func WebPathSegments(s WebPath) []string {
func WebPathToGitPath(s WebPath) string {
if strings.HasSuffix(string(s), ".md") {
return string(s)
ret, _ := url.PathUnescape(string(s))
return util.PathJoinRelX(ret)
}
a := strings.Split(string(s), "/")
@@ -124,7 +126,10 @@ func GitPathToWebPath(s string) (wp WebPath, err error) {
func WebPathToUserTitle(s WebPath) (dir, display string) {
dir = path.Dir(string(s))
display = path.Base(string(s))
display = strings.TrimSuffix(display, ".md")
if strings.HasSuffix(display, ".md") {
display = strings.TrimSuffix(display, ".md")
display, _ = url.PathUnescape(display)
}
display, _ = unescapeSegment(display)
return dir, display
}
@@ -141,8 +146,7 @@ func WebPathFromRequest(s string) WebPath {
}
func UserTitleToWebPath(base, title string) WebPath {
// TODO: ctx.Params does un-escaping, so the URL "/wiki/abc%2Fdef" becomes "wiki path = `abc/def`", which is incorrect.
// And the old wiki code's behavior is always using %2F, instead of subdirectory.
// TODO: no support for subdirectory, because the old wiki code's behavior is always using %2F, instead of subdirectory.
// So we do not add the support for writing slashes in title at the moment.
title = strings.TrimSpace(title)
title = util.PathJoinRelX(base, escapeSegToWeb(title, false))
+15 -1
View File
@@ -59,6 +59,7 @@ func TestWebPathToDisplayName(t *testing.T) {
{"name with / slash", "name-with %2F slash"},
{"name with % percent", "name-with %25 percent"},
{"2000-01-02 meeting", "2000-01-02+meeting.-.md"},
{"a b", "a%20b.md"},
} {
_, displayName := WebPathToUserTitle(test.WebPath)
assert.EqualValues(t, test.Expected, displayName)
@@ -73,7 +74,8 @@ func TestWebPathToGitPath(t *testing.T) {
for _, test := range []test{
{"wiki-name.md", "wiki%20name"},
{"wiki-name.md", "wiki+name"},
{"wiki%20name.md", "wiki%20name.md"},
{"wiki name.md", "wiki%20name.md"},
{"wiki%20name.md", "wiki%2520name.md"},
{"2000-01-02-meeting.md", "2000-01-02+meeting"},
{"2000-01-02 meeting.-.md", "2000-01-02%20meeting.-"},
} {
@@ -307,3 +309,15 @@ func TestPrepareWikiFileName_FirstPage(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, "Home.md", newWikiPath)
}
func TestWebPathConversion(t *testing.T) {
assert.Equal(t, "path/wiki", WebPathToURLPath(WebPath("path/wiki")))
assert.Equal(t, "wiki", WebPathToURLPath(WebPath("wiki")))
assert.Equal(t, "", WebPathToURLPath(WebPath("")))
}
func TestWebPathFromRequest(t *testing.T) {
assert.Equal(t, WebPath("a%2Fb"), WebPathFromRequest("a/b"))
assert.Equal(t, WebPath("a"), WebPathFromRequest("a"))
assert.Equal(t, WebPath("b"), WebPathFromRequest("a/../b"))
}