Forgejo/services/issue/issue.go

312 lines
9.1 KiB
Go
Raw Normal View History

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package issue
import (
"context"
"fmt"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/storage"
)
// NewIssue creates new issue with labels for repository.
func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, assigneeIDs []int64) error {
[MODERATION] user blocking - Add the ability to block a user via their profile page. - This will unstar their repositories and visa versa. - Blocked users cannot create issues or pull requests on your the doer's repositories (mind that this is not the case for organizations). - Blocked users cannot comment on the doer's opened issues or pull requests. - Blocked users cannot add reactions to doer's comments. - Blocked users cannot cause a notification trough mentioning the doer. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/540 (cherry picked from commit 687d852480388897db4d7b0cb397cf7135ab97b1) (cherry picked from commit 0c32a4fde531018f74e01d9db6520895fcfa10cc) (cherry picked from commit 1791130e3cb8470b9b39742e0004d5e4c7d1e64d) (cherry picked from commit 37858b7e8fb6ba6c6ea0ac2562285b3b144efa19) (cherry picked from commit a3e2bfd7e9eab82cc2c17061f6bb4e386a108c46) (cherry picked from commit 7009b9fe87696b6182fab65ae82bf5a25cd39971) Conflicts: https://codeberg.org/forgejo/forgejo/pulls/1014 routers/web/user/profile.go templates/user/profile.tmpl (cherry picked from commit b2aec3479177e725cfc7cbbb9d94753226928d1c) (cherry picked from commit e2f1b73752f6bd3f830297d8f4ac438837471226) [MODERATION] organization blocking a user (#802) - Resolves #476 - Follow up for: #540 - Ensure that the doer and blocked person cannot follow each other. - Ensure that the block person cannot watch doer's repositories. - Add unblock button to the blocked user list. - Add blocked since information to the blocked user list. - Add extra testing to moderation code. - Blocked user will unwatch doer's owned repository upon blocking. - Add flash messages to let the user know the block/unblock action was successful. - Add "You haven't blocked any users" message. - Add organization blocking a user. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/802 (cherry picked from commit 0505a1042197bd9136b58bc70ec7400a23471585) (cherry picked from commit 37b4e6ef9b85e97d651cf350c9f3ea272ee8d76a) (cherry picked from commit c17c121f2cf1f00e2a8d6fd6847705df47d0771e) [MODERATION] organization blocking a user (#802) (squash) Changes to adapt to: 6bbccdd177 Improve AJAX link and modal confirm dialog (#25210) Refs: https://codeberg.org/forgejo/forgejo/pulls/882/files#issuecomment-945962 Refs: https://codeberg.org/forgejo/forgejo/pulls/882#issue-330561 (cherry picked from commit 523635f83cb2a1a4386769b79326088c5c4bbec7) (cherry picked from commit 4743eaa6a0be0ef47de5b17c211dfe8bad1b7af9) (cherry picked from commit eff5b43d2e843d5d537756d4fa58a8a010b6b527) Conflicts: https://codeberg.org/forgejo/forgejo/pulls/1014 routers/web/user/profile.go (cherry picked from commit 9d359be5ed11237088ccf6328571939af814984e) (cherry picked from commit b1f3069a22a03734cffbfcd503ce004ba47561b7) [MODERATION] add user blocking API - Follow up for: #540, #802 - Add API routes for user blocking from user and organization perspective. - The new routes have integration testing. - The new model functions have unit tests. - Actually quite boring to write and to read this pull request. (cherry picked from commit f3afaf15c7e34038363c9ce8e1ef957ec1e22b06) (cherry picked from commit 6d754db3e5faff93a58fab2867737f81f40f6599) (cherry picked from commit 2a89ddc0acffa9aea0f02b721934ef9e2b496a88) (cherry picked from commit 4a147bff7e963ab9dffcfaefa5c2c01c59b4c732) Conflicts: routers/api/v1/api.go templates/swagger/v1_json.tmpl (cherry picked from commit bb8c33918569f65f25b014f0d7fe6ac20f9036fc) (cherry picked from commit 5a11569a011b7d0a14391e2b5c07d0af825d7b0e) (cherry picked from commit 2373c801ee6b84c368b498b16e6ad18650b38f42) [MODERATION] restore redirect on unblock ctx.RedirectToFirst(ctx.FormString("redirect_to"), ctx.ContextUser.HomeLink()) was replaced by ctx.JSONOK() in 128d77a3a Following up fixes for "Fix inconsistent user profile layout across tabs" (#25739) thus changing the behavior (nicely spotted by the tests). This restores it. (cherry picked from commit 597c243707c3c86e7256faf1e6ba727224554de3) (cherry picked from commit cfa539e590127b4b953b010fba3dea21c82a1714) [MODERATION] Add test case (squash) - Add an test case, to test an property of the function. (cherry picked from commit 70dadb1916bfef8ba8cbc4e9b042cc8740f45e28) [MODERATION] Block adding collaborators - Ensure that the doer and blocked user cannot add each other as collaborators to repositories. - The Web UI gets an detailed message of the specific situation, the API gets an generic Forbidden code. - Unit tests has been added. - Integration testing for Web and API has been added. - This commit doesn't introduce removing each other as collaborators on the block action, due to the complexity of database calls that needs to be figured out. That deserves its own commit and test code. (cherry picked from commit 747be949a1b3cd06f6586512f1af4630e55d7ad4) [MODERATION] move locale_en-US.ini strings to avoid conflicts Conflicts: web_src/css/org.css web_src/css/user.css https://codeberg.org/forgejo/forgejo/pulls/1180 (cherry picked from commit e53f955c888ebaafc863a6e463da87f70f5605da) Conflicts: services/issue/comments.go https://codeberg.org/forgejo/forgejo/pulls/1212
2023-03-12 13:28:18 +01:00
// Check if the user is not blocked by the repo's owner.
if user_model.IsBlocked(ctx, repo.OwnerID, issue.PosterID) {
return user_model.ErrBlockedByUser
}
if err := issues_model.NewIssue(repo, issue, labelIDs, uuids); err != nil {
return err
}
2019-10-28 17:45:43 +01:00
for _, assigneeID := range assigneeIDs {
if err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID); err != nil {
2019-10-28 17:45:43 +01:00
return err
}
}
mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content)
if err != nil {
return err
}
notification.NotifyNewIssue(ctx, issue, 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)
}
return nil
}
// ChangeTitle changes the title of this issue, as the given user.
func ChangeTitle(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, title string) error {
oldTitle := issue.Title
issue.Title = title
if err := issues_model.ChangeIssueTitle(ctx, issue, doer, oldTitle); err != nil {
return err
}
if issue.IsPull && issues_model.HasWorkInProgressPrefix(oldTitle) && !issues_model.HasWorkInProgressPrefix(title) {
if err := issues_model.PullRequestCodeOwnersReview(ctx, issue, issue.PullRequest); err != nil {
return err
}
}
notification.NotifyIssueChangeTitle(ctx, doer, issue, oldTitle)
return nil
}
// ChangeIssueRef changes the branch of this issue, as the given user.
func ChangeIssueRef(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, ref string) error {
oldRef := issue.Ref
issue.Ref = ref
if err := issues_model.ChangeIssueRef(issue, doer, oldRef); err != nil {
return err
}
notification.NotifyIssueChangeRef(ctx, doer, issue, oldRef)
return nil
}
// UpdateAssignees is a helper function to add or delete one or multiple issue assignee(s)
// Deleting is done the GitHub way (quote from their api documentation):
// https://developer.github.com/v3/issues/#edit-an-issue
// "assignees" (array): Logins for Users to assign to this issue.
// Pass one or more user logins to replace the set of assignees on this Issue.
// Send an empty array ([]) to clear all assignees from the Issue.
func UpdateAssignees(ctx context.Context, issue *issues_model.Issue, oneAssignee string, multipleAssignees []string, doer *user_model.User) (err error) {
var allNewAssignees []*user_model.User
// Keep the old assignee thingy for compatibility reasons
if oneAssignee != "" {
// Prevent double adding assignees
var isDouble bool
for _, assignee := range multipleAssignees {
if assignee == oneAssignee {
isDouble = true
break
}
}
if !isDouble {
multipleAssignees = append(multipleAssignees, oneAssignee)
}
}
// Loop through all assignees to add them
for _, assigneeName := range multipleAssignees {
assignee, err := user_model.GetUserByName(ctx, assigneeName)
if err != nil {
return err
}
allNewAssignees = append(allNewAssignees, assignee)
}
// Delete all old assignees not passed
if err = DeleteNotPassedAssignee(ctx, issue, doer, allNewAssignees); err != nil {
return err
}
// Add all new assignees
// Update the assignee. The function will check if the user exists, is already
// assigned (which he shouldn't as we deleted all assignees before) and
// 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)
if err != nil {
return err
}
}
return err
}
// DeleteIssue deletes an issue
func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Repository, issue *issues_model.Issue) error {
// load issue before deleting it
if err := issue.LoadAttributes(ctx); err != nil {
return err
}
if err := issue.LoadPullRequest(ctx); err != nil {
return err
}
// delete entries in database
if err := deleteIssue(ctx, issue); err != nil {
return err
}
// delete pull request related git data
if issue.IsPull && gitRepo != nil {
if err := gitRepo.RemoveReference(fmt.Sprintf("%s%d/head", git.PullPrefix, issue.PullRequest.Index)); err != nil {
return err
}
}
// If the Issue is pinned, we should unpin it before deletion to avoid problems with other pinned Issues
if issue.IsPinned() {
if err := issue.Unpin(ctx, doer); err != nil {
return err
}
}
notification.NotifyDeleteIssue(ctx, doer, issue)
return nil
}
// 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) {
assignee, err := user_model.GetUserByID(ctx, assigneeID)
if err != nil {
return err
}
// Check if the user is already assigned
isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assignee)
if err != nil {
return err
}
if isAssigned {
// nothing to to
return nil
}
valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull)
if err != nil {
return err
}
if !valid {
return repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name}
}
_, _, err = ToggleAssignee(ctx, issue, doer, assigneeID)
if err != nil {
return err
}
return nil
}
// GetRefEndNamesAndURLs retrieves the ref end names (e.g. refs/heads/branch-name -> branch-name)
// and their respective URLs.
func GetRefEndNamesAndURLs(issues []*issues_model.Issue, repoLink string) (map[int64]string, map[int64]string) {
issueRefEndNames := make(map[int64]string, len(issues))
issueRefURLs := make(map[int64]string, len(issues))
for _, issue := range issues {
if issue.Ref != "" {
Use the type RefName for all the needed places and fix pull mirror sync bugs (#24634) This PR replaces all string refName as a type `git.RefName` to make the code more maintainable. Fix #15367 Replaces #23070 It also fixed a bug that tags are not sync because `git remote --prune origin` will not remove local tags if remote removed. We in fact should use `git fetch --prune --tags origin` but not `git remote update origin` to do the sync. Some answer from ChatGPT as ref. > If the git fetch --prune --tags command is not working as expected, there could be a few reasons why. Here are a few things to check: > >Make sure that you have the latest version of Git installed on your system. You can check the version by running git --version in your terminal. If you have an outdated version, try updating Git and see if that resolves the issue. > >Check that your Git repository is properly configured to track the remote repository's tags. You can check this by running git config --get-all remote.origin.fetch and verifying that it includes +refs/tags/*:refs/tags/*. If it does not, you can add it by running git config --add remote.origin.fetch "+refs/tags/*:refs/tags/*". > >Verify that the tags you are trying to prune actually exist on the remote repository. You can do this by running git ls-remote --tags origin to list all the tags on the remote repository. > >Check if any local tags have been created that match the names of tags on the remote repository. If so, these local tags may be preventing the git fetch --prune --tags command from working properly. You can delete local tags using the git tag -d command. --------- Co-authored-by: delvh <dev.lh@web.de>
2023-05-26 03:04:48 +02:00
issueRefEndNames[issue.ID] = git.RefName(issue.Ref).ShortName()
Prepend refs/heads/ to issue template refs (#20461) Fix #20456 At some point during the 1.17 cycle abbreviated refishs to issue branches started breaking. This is likely due serious inconsistencies in our management of refs throughout Gitea - which is a bug needing to be addressed in a different PR. (Likely more than one) We should try to use non-abbreviated `fullref`s as much as possible. That is where a user has inputted a abbreviated `refish` we should add `refs/heads/` if it is `branch` etc. I know people keep writing and merging PRs that remove prefixes from stored content but it is just wrong and it keeps causing problems like this. We should only remove the prefix at the time of presentation as the prefix is the only way of knowing umambiguously and permanently if the `ref` is referring to a `branch`, `tag` or `commit` / `SHA`. We need to make it so that every ref has the appropriate prefix, and probably also need to come up with some definitely unambiguous way of storing `SHA`s if they're used in a `ref` or `refish` field. We must not store a potentially ambiguous `refish` as a `ref`. (Especially when referring a `tag` - there is no reason why users cannot create a `branch` with the same short name as a `tag` and vice versa and any attempt to prevent this will fail. You can even create a `branch` and a `tag` that matches the `SHA` pattern.) To that end in order to fix this bug, when parsing issue templates check the provided `Ref` (here a `refish` because almost all users do not know or understand the subtly), if it does not start with `refs/` add the `BranchPrefix` to it. This allows people to make their templates refer to a `tag` but not to a `SHA` directly. (I don't think that is particularly unreasonable but if people disagree I can make the `refish` be checked to see if it matches the `SHA` pattern.) Next we need to handle the issue links that are already written. The links here are created with `git.RefURL` Here we see there is a bug introduced in #17551 whereby the provided `ref` argument can be double-escaped so we remove the incorrect external escape. (The escape added in #17551 is in the right place - unfortunately I missed that the calling function was doing the wrong thing.) Then within `RefURL()` we check if an unprefixed `ref` (therefore potentially a `refish`) matches the `SHA` pattern before assuming that is actually a `commit` - otherwise is assumed to be a `branch`. This will handle most of the problem cases excepting the very unusual cases where someone has deliberately written a `branch` to look like a `SHA1`. But please if something is called a `ref` or interpreted as a `ref` make it a full-ref before storing or using it. By all means if something is a `branch` assume the prefix is removed but always add it back in if you are using it as a `ref`. Stop storing abbreviated `branch` names and `tag` names - which are `refish` as a `ref`. It will keep on causing problems like this. Fix #20456 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-11-22 13:58:49 +01:00
issueRefURLs[issue.ID] = git.RefURL(repoLink, issue.Ref)
}
}
return issueRefEndNames, issueRefURLs
}
// deleteIssue deletes the issue
func deleteIssue(ctx context.Context, issue *issues_model.Issue) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()
e := db.GetEngine(ctx)
if _, err := e.ID(issue.ID).NoAutoCondition().Delete(issue); err != nil {
return err
}
// update the total issue numbers
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, false); err != nil {
return err
}
// if the issue is closed, update the closed issue numbers
if issue.IsClosed {
if err := repo_model.UpdateRepoIssueNumbers(ctx, issue.RepoID, issue.IsPull, true); err != nil {
return err
}
}
if err := issues_model.UpdateMilestoneCounters(ctx, issue.MilestoneID); err != nil {
return fmt.Errorf("error updating counters for milestone id %d: %w",
issue.MilestoneID, err)
}
if err := activities_model.DeleteIssueActions(ctx, issue.RepoID, issue.ID); err != nil {
return err
}
// find attachments related to this issue and remove them
if err := issue.LoadAttributes(ctx); err != nil {
return err
}
for i := range issue.Attachments {
system_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", issue.Attachments[i].RelativePath())
}
// delete all database data still assigned to this issue
if err := issues_model.DeleteInIssue(ctx, issue.ID,
&issues_model.ContentHistory{},
&issues_model.Comment{},
&issues_model.IssueLabel{},
&issues_model.IssueDependency{},
&issues_model.IssueAssignees{},
&issues_model.IssueUser{},
&activities_model.Notification{},
&issues_model.Reaction{},
&issues_model.IssueWatch{},
&issues_model.Stopwatch{},
&issues_model.TrackedTime{},
&project_model.ProjectIssue{},
&repo_model.Attachment{},
&issues_model.PullRequest{},
); err != nil {
return err
}
// References to this issue in other issues
if _, err := db.DeleteByBean(ctx, &issues_model.Comment{
RefIssueID: issue.ID,
}); err != nil {
return err
}
// Delete dependencies for issues in other repositories
if _, err := db.DeleteByBean(ctx, &issues_model.IssueDependency{
DependencyID: issue.ID,
}); err != nil {
return err
}
// delete from dependent issues
if _, err := db.DeleteByBean(ctx, &issues_model.Comment{
DependentIssueID: issue.ID,
}); err != nil {
return err
}
return committer.Commit()
}