2018-08-06 06:43:22 +02:00
// Copyright 2018 The Gitea Authors.
// Copyright 2016 The Gogs Authors.
// All rights reserved.
2022-11-27 19:20:29 +01:00
// SPDX-License-Identifier: MIT
2016-03-05 18:58:51 +01:00
2022-06-13 11:37:59 +02:00
package issues
2016-03-05 18:58:51 +01:00
import (
2021-11-19 14:39:57 +01:00
"context"
2016-03-05 18:58:51 +01:00
"fmt"
2020-06-18 16:07:09 +02:00
"strconv"
"unicode/utf8"
2016-03-05 18:58:51 +01:00
2021-09-19 13:49:59 +02:00
"code.gitea.io/gitea/models/db"
2022-06-12 17:51:54 +02:00
git_model "code.gitea.io/gitea/models/git"
2022-03-29 08:29:02 +02:00
"code.gitea.io/gitea/models/organization"
2022-03-29 16:16:31 +02:00
project_model "code.gitea.io/gitea/models/project"
2021-11-19 14:39:57 +01:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 10:49:20 +01:00
user_model "code.gitea.io/gitea/models/user"
2023-09-08 23:09:23 +02:00
"code.gitea.io/gitea/modules/container"
2019-03-27 10:33:00 +01:00
"code.gitea.io/gitea/modules/git"
2021-07-24 18:03:58 +02:00
"code.gitea.io/gitea/modules/json"
2019-08-15 16:46:21 +02:00
"code.gitea.io/gitea/modules/log"
2019-10-14 00:29:10 +02:00
"code.gitea.io/gitea/modules/references"
2019-10-14 08:10:42 +02:00
"code.gitea.io/gitea/modules/structs"
2019-08-15 16:46:21 +02:00
"code.gitea.io/gitea/modules/timeutil"
2023-08-24 07:06:17 +02:00
"code.gitea.io/gitea/modules/translation"
2022-10-18 07:50:37 +02:00
"code.gitea.io/gitea/modules/util"
2019-08-15 16:46:21 +02:00
2019-06-23 17:22:43 +02:00
"xorm.io/builder"
2019-10-17 11:26:49 +02:00
"xorm.io/xorm"
2016-03-05 18:58:51 +01:00
)
2022-06-13 11:37:59 +02:00
// ErrCommentNotExist represents a "CommentNotExist" kind of error.
type ErrCommentNotExist struct {
ID int64
IssueID int64
}
// IsErrCommentNotExist checks if an error is a ErrCommentNotExist.
func IsErrCommentNotExist ( err error ) bool {
_ , ok := err . ( ErrCommentNotExist )
return ok
}
func ( err ErrCommentNotExist ) Error ( ) string {
return fmt . Sprintf ( "comment does not exist [id: %d, issue_id: %d]" , err . ID , err . IssueID )
}
2022-10-18 07:50:37 +02:00
func ( err ErrCommentNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
2016-03-05 18:58:51 +01:00
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
type CommentType int
2023-04-20 08:39:44 +02:00
// CommentTypeUndefined is used to search for comments of any type
const CommentTypeUndefined CommentType = - 1
2017-06-21 03:00:44 +02:00
2016-03-05 18:58:51 +01:00
const (
2023-04-20 08:39:44 +02:00
CommentTypeComment CommentType = iota // 0 Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
CommentTypeReopen // 1
CommentTypeClose // 2
CommentTypeIssueRef // 3 References.
CommentTypeCommitRef // 4 Reference from a commit (not part of a pull request)
CommentTypeCommentRef // 5 Reference from a comment
CommentTypePullRef // 6 Reference from a pull request
CommentTypeLabel // 7 Labels changed
CommentTypeMilestone // 8 Milestone changed
CommentTypeAssignees // 9 Assignees changed
CommentTypeChangeTitle // 10 Change Title
CommentTypeDeleteBranch // 11 Delete Branch
CommentTypeStartTracking // 12 Start a stopwatch for time tracking
CommentTypeStopTracking // 13 Stop a stopwatch for time tracking
CommentTypeAddTimeManual // 14 Add time manual for time tracking
CommentTypeCancelTracking // 15 Cancel a stopwatch for time tracking
CommentTypeAddedDeadline // 16 Added a due date
CommentTypeModifiedDeadline // 17 Modified the due date
CommentTypeRemovedDeadline // 18 Removed a due date
CommentTypeAddDependency // 19 Dependency added
CommentTypeRemoveDependency // 20 Dependency removed
CommentTypeCode // 21 Comment a line of code
CommentTypeReview // 22 Reviews a pull request by giving general feedback
CommentTypeLock // 23 Lock an issue, giving only collaborators access
CommentTypeUnlock // 24 Unlocks a previously locked issue
CommentTypeChangeTargetBranch // 25 Change pull request's target branch
CommentTypeDeleteTimeManual // 26 Delete time manual for time tracking
CommentTypeReviewRequest // 27 add or remove Request from one
CommentTypeMergePull // 28 merge pull request
CommentTypePullRequestPush // 29 push to PR head branch
CommentTypeProject // 30 Project changed
CommentTypeProjectBoard // 31 Project board changed
CommentTypeDismissReview // 32 Dismiss Review
CommentTypeChangeIssueRef // 33 Change issue ref
CommentTypePRScheduledToAutoMerge // 34 pr was scheduled to auto merge when checks succeed
CommentTypePRUnScheduledToAutoMerge // 35 pr was un scheduled to auto merge when checks succeed
2023-05-25 15:17:19 +02:00
CommentTypePin // 36 pin Issue
CommentTypeUnpin // 37 unpin Issue
2016-03-05 18:58:51 +01:00
)
2022-01-01 15:12:25 +01:00
var commentStrings = [ ] string {
"comment" ,
"reopen" ,
"close" ,
"issue_ref" ,
"commit_ref" ,
"comment_ref" ,
"pull_ref" ,
"label" ,
"milestone" ,
"assignees" ,
"change_title" ,
"delete_branch" ,
"start_tracking" ,
"stop_tracking" ,
"add_time_manual" ,
"cancel_tracking" ,
"added_deadline" ,
"modified_deadline" ,
"removed_deadline" ,
"add_dependency" ,
"remove_dependency" ,
"code" ,
"review" ,
"lock" ,
"unlock" ,
"change_target_branch" ,
"delete_time_manual" ,
"review_request" ,
"merge_pull" ,
"pull_push" ,
"project" ,
"project_board" ,
"dismiss_review" ,
"change_issue_ref" ,
2022-05-07 19:05:52 +02:00
"pull_scheduled_merge" ,
"pull_cancel_scheduled_merge" ,
2023-05-25 15:17:19 +02:00
"pin" ,
"unpin" ,
2022-01-01 15:12:25 +01:00
}
func ( t CommentType ) String ( ) string {
return commentStrings [ t ]
}
2023-01-19 03:14:56 +01:00
func AsCommentType ( typeName string ) CommentType {
for index , name := range commentStrings {
if typeName == name {
return CommentType ( index )
}
}
2023-04-20 08:39:44 +02:00
return CommentTypeUndefined
}
func ( t CommentType ) HasContentSupport ( ) bool {
switch t {
2023-06-23 12:33:20 +02:00
case CommentTypeComment , CommentTypeCode , CommentTypeReview , CommentTypeDismissReview :
2023-04-20 08:39:44 +02:00
return true
}
return false
}
func ( t CommentType ) HasAttachmentSupport ( ) bool {
switch t {
case CommentTypeComment , CommentTypeCode , CommentTypeReview :
return true
}
return false
2023-01-19 03:14:56 +01:00
}
2023-08-24 07:06:17 +02:00
// RoleInRepo presents the user's participation in the repo
type RoleInRepo string
// RoleDescriptor defines comment "role" tags
type RoleDescriptor struct {
IsPoster bool
RoleInRepo RoleInRepo
}
2016-03-05 18:58:51 +01:00
2021-11-11 07:29:30 +01:00
// Enumerate all the role tags.
2016-03-05 18:58:51 +01:00
const (
2023-08-24 07:06:17 +02:00
RoleRepoOwner RoleInRepo = "owner"
RoleRepoMember RoleInRepo = "member"
RoleRepoCollaborator RoleInRepo = "collaborator"
RoleRepoFirstTimeContributor RoleInRepo = "first_time_contributor"
RoleRepoContributor RoleInRepo = "contributor"
2016-03-05 18:58:51 +01:00
)
2023-08-24 07:06:17 +02:00
// LocaleString returns the locale string name of the role
func ( r RoleInRepo ) LocaleString ( lang translation . Locale ) string {
return lang . Tr ( "repo.issues.role." + string ( r ) )
2021-11-11 07:29:30 +01:00
}
2023-08-24 07:06:17 +02:00
// LocaleHelper returns the locale tooltip of the role
func ( r RoleInRepo ) LocaleHelper ( lang translation . Locale ) string {
return lang . Tr ( "repo.issues.role." + string ( r ) + "_helper" )
2021-11-11 07:29:30 +01:00
}
2016-03-05 18:58:51 +01:00
// Comment represents a comment in commit and issue page.
type Comment struct {
2021-11-24 10:49:20 +01:00
ID int64 ` xorm:"pk autoincr" `
Type CommentType ` xorm:"INDEX" `
PosterID int64 ` xorm:"INDEX" `
Poster * user_model . User ` xorm:"-" `
2019-07-08 04:14:12 +02:00
OriginalAuthor string
OriginalAuthorID int64
2018-07-17 23:23:58 +02:00
IssueID int64 ` xorm:"INDEX" `
Issue * Issue ` xorm:"-" `
LabelID int64
2020-10-25 22:49:48 +01:00
Label * Label ` xorm:"-" `
AddedLabels [ ] * Label ` xorm:"-" `
RemovedLabels [ ] * Label ` xorm:"-" `
2020-08-17 05:07:38 +02:00
OldProjectID int64
ProjectID int64
2022-03-29 16:16:31 +02:00
OldProject * project_model . Project ` xorm:"-" `
Project * project_model . Project ` xorm:"-" `
2018-07-17 23:23:58 +02:00
OldMilestoneID int64
MilestoneID int64
2022-06-13 11:37:59 +02:00
OldMilestone * Milestone ` xorm:"-" `
Milestone * Milestone ` xorm:"-" `
2021-02-19 11:52:11 +01:00
TimeID int64
Time * TrackedTime ` xorm:"-" `
2018-07-17 23:23:58 +02:00
AssigneeID int64
RemovedAssignee bool
2022-03-29 08:29:02 +02:00
Assignee * user_model . User ` xorm:"-" `
AssigneeTeamID int64 ` xorm:"NOT NULL DEFAULT 0" `
AssigneeTeam * organization . Team ` xorm:"-" `
2020-04-18 15:50:25 +02:00
ResolveDoerID int64
2021-11-24 10:49:20 +01:00
ResolveDoer * user_model . User ` xorm:"-" `
2018-07-17 23:23:58 +02:00
OldTitle string
NewTitle string
2019-12-16 07:20:25 +01:00
OldRef string
NewRef string
2023-09-29 04:00:14 +02:00
DependentIssueID int64 ` xorm:"index" ` // This is used by issue_service.deleteIssue
2018-07-17 23:23:58 +02:00
DependentIssue * Issue ` xorm:"-" `
2017-02-03 16:09:10 +01:00
2017-02-01 03:36:08 +01:00
CommitID int64
2018-08-06 06:43:22 +02:00
Line int64 // - previous line / + proposed line
TreePath string
2021-08-22 17:33:05 +02:00
Content string ` xorm:"LONGTEXT" `
2016-03-10 01:53:30 +01:00
RenderedContent string ` xorm:"-" `
2018-08-06 06:43:22 +02:00
// Path represents the 4 lines of code cemented by this comment
2020-06-18 16:07:09 +02:00
Patch string ` xorm:"-" `
2021-08-22 17:33:05 +02:00
PatchQuoted string ` xorm:"LONGTEXT patch" `
2018-08-06 06:43:22 +02:00
2019-08-15 16:46:21 +02:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2016-03-05 18:58:51 +01:00
// Reference issue in commit message
CommitSHA string ` xorm:"VARCHAR(40)" `
2022-06-13 11:37:59 +02:00
Attachments [ ] * repo_model . Attachment ` xorm:"-" `
Reactions ReactionList ` xorm:"-" `
2016-03-05 18:58:51 +01:00
// For view issue page.
2021-11-11 07:29:30 +01:00
ShowRole RoleDescriptor ` xorm:"-" `
2018-08-06 06:43:22 +02:00
Review * Review ` xorm:"-" `
2019-08-05 16:29:40 +02:00
ReviewID int64 ` xorm:"index" `
2018-08-06 06:43:22 +02:00
Invalidated bool
2019-09-20 07:45:38 +02:00
// Reference an issue or pull from another comment, issue or PR
// All information is about the origin of the reference
2019-10-14 00:29:10 +02:00
RefRepoID int64 ` xorm:"index" ` // Repo where the referencing
RefIssueID int64 ` xorm:"index" `
RefCommentID int64 ` xorm:"index" ` // 0 if origin is Issue title or content (or PR's)
2021-07-08 13:38:13 +02:00
RefAction references . XRefAction ` xorm:"SMALLINT" ` // What happens if RefIssueID resolves
2019-09-20 07:45:38 +02:00
RefIsPull bool
2021-12-10 02:27:50 +01:00
RefRepo * repo_model . Repository ` xorm:"-" `
RefIssue * Issue ` xorm:"-" `
RefComment * Comment ` xorm:"-" `
2020-05-20 14:47:24 +02:00
2022-06-12 17:51:54 +02:00
Commits [ ] * git_model . SignCommitWithStatuses ` xorm:"-" `
OldCommit string ` xorm:"-" `
NewCommit string ` xorm:"-" `
CommitsNum int64 ` xorm:"-" `
IsForcePush bool ` xorm:"-" `
2020-05-20 14:47:24 +02:00
}
2021-09-19 13:49:59 +02:00
func init ( ) {
db . RegisterModel ( new ( Comment ) )
}
2020-05-20 14:47:24 +02:00
// PushActionContent is content of push pull comment
type PushActionContent struct {
IsForcePush bool ` json:"is_force_push" `
CommitIDs [ ] string ` json:"commit_ids" `
2016-03-05 18:58:51 +01:00
}
2022-11-19 09:12:33 +01:00
// LoadIssue loads the issue reference for the comment
func ( c * Comment ) LoadIssue ( ctx context . Context ) ( err error ) {
2018-05-16 16:01:55 +02:00
if c . Issue != nil {
return nil
}
2022-06-13 11:37:59 +02:00
c . Issue , err = GetIssueByID ( ctx , c . IssueID )
2022-06-20 12:02:49 +02:00
return err
2018-05-16 16:01:55 +02:00
}
2020-06-18 16:07:09 +02:00
// BeforeInsert will be invoked by XORM before inserting a record
func ( c * Comment ) BeforeInsert ( ) {
c . PatchQuoted = c . Patch
if ! utf8 . ValidString ( c . Patch ) {
c . PatchQuoted = strconv . Quote ( c . Patch )
}
}
// BeforeUpdate will be invoked by XORM before updating a record
func ( c * Comment ) BeforeUpdate ( ) {
c . PatchQuoted = c . Patch
if ! utf8 . ValidString ( c . Patch ) {
c . PatchQuoted = strconv . Quote ( c . Patch )
}
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func ( c * Comment ) AfterLoad ( session * xorm . Session ) {
c . Patch = c . PatchQuoted
if len ( c . PatchQuoted ) > 0 && c . PatchQuoted [ 0 ] == '"' {
unquoted , err := strconv . Unquote ( c . PatchQuoted )
if err == nil {
c . Patch = unquoted
}
}
}
2022-11-19 09:12:33 +01:00
// LoadPoster loads comment poster
func ( c * Comment ) LoadPoster ( ctx context . Context ) ( err error ) {
2023-09-12 09:04:46 +02:00
if c . Poster != nil {
2019-04-18 07:00:03 +02:00
return nil
}
Implement actions (#21937)
Close #13539.
Co-authored by: @lunny @appleboy @fuxiaohei and others.
Related projects:
- https://gitea.com/gitea/actions-proto-def
- https://gitea.com/gitea/actions-proto-go
- https://gitea.com/gitea/act
- https://gitea.com/gitea/act_runner
### Summary
The target of this PR is to bring a basic implementation of "Actions",
an internal CI/CD system of Gitea. That means even though it has been
merged, the state of the feature is **EXPERIMENTAL**, and please note
that:
- It is disabled by default;
- It shouldn't be used in a production environment currently;
- It shouldn't be used in a public Gitea instance currently;
- Breaking changes may be made before it's stable.
**Please comment on #13539 if you have any different product design
ideas**, all decisions reached there will be adopted here. But in this
PR, we don't talk about **naming, feature-creep or alternatives**.
### ⚠️ Breaking
`gitea-actions` will become a reserved user name. If a user with the
name already exists in the database, it is recommended to rename it.
### Some important reviews
- What is `DEFAULT_ACTIONS_URL` in `app.ini` for?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1055954954
- Why the api for runners is not under the normal `/api/v1` prefix?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1061173592
- Why DBFS?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1061301178
- Why ignore events triggered by `gitea-actions` bot?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1063254103
- Why there's no permission control for actions?
- https://github.com/go-gitea/gitea/pull/21937#discussion_r1090229868
### What it looks like
<details>
#### Manage runners
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205870657-c72f590e-2e08-4cd4-be7f-2e0abb299bbf.png">
#### List runs
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205872794-50fde990-2b45-48c1-a178-908e4ec5b627.png">
#### View logs
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205872501-9b7b9000-9542-4991-8f55-18ccdada77c3.png">
</details>
### How to try it
<details>
#### 1. Start Gitea
Clone this branch and [install from
source](https://docs.gitea.io/en-us/install-from-source).
Add additional configurations in `app.ini` to enable Actions:
```ini
[actions]
ENABLED = true
```
Start it.
If all is well, you'll see the management page of runners:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205877365-8e30a780-9b10-4154-b3e8-ee6c3cb35a59.png">
#### 2. Start runner
Clone the [act_runner](https://gitea.com/gitea/act_runner), and follow
the
[README](https://gitea.com/gitea/act_runner/src/branch/main/README.md)
to start it.
If all is well, you'll see a new runner has been added:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205878000-216f5937-e696-470d-b66c-8473987d91c3.png">
#### 3. Enable actions for a repo
Create a new repo or open an existing one, check the `Actions` checkbox
in settings and submit.
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205879705-53e09208-73c0-4b3e-a123-2dcf9aba4b9c.png">
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205879383-23f3d08f-1a85-41dd-a8b3-54e2ee6453e8.png">
If all is well, you'll see a new tab "Actions":
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205881648-a8072d8c-5803-4d76-b8a8-9b2fb49516c1.png">
#### 4. Upload workflow files
Upload some workflow files to `.gitea/workflows/xxx.yaml`, you can
follow the [quickstart](https://docs.github.com/en/actions/quickstart)
of GitHub Actions. Yes, Gitea Actions is compatible with GitHub Actions
in most cases, you can use the same demo:
```yaml
name: GitHub Actions Demo
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
Explore-GitHub-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
- run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ github.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
```
If all is well, you'll see a new run in `Actions` tab:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205884473-79a874bc-171b-4aaf-acd5-0241a45c3b53.png">
#### 5. Check the logs of jobs
Click a run and you'll see the logs:
<img width="1792" alt="image"
src="https://user-images.githubusercontent.com/9418365/205884800-994b0374-67f7-48ff-be9a-4c53f3141547.png">
#### 6. Go on
You can try more examples in [the
documents](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
of GitHub Actions, then you might find a lot of bugs.
Come on, PRs are welcome.
</details>
See also: [Feature Preview: Gitea
Actions](https://blog.gitea.io/2022/12/feature-preview-gitea-actions/)
---------
Co-authored-by: a1012112796 <1012112796@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2023-01-31 02:45:19 +01:00
c . Poster , err = user_model . GetPossibleUserByID ( ctx , c . PosterID )
2019-04-18 07:00:03 +02:00
if err != nil {
2021-11-24 10:49:20 +01:00
if user_model . IsErrUserNotExist ( err ) {
2019-04-18 07:00:03 +02:00
c . PosterID = - 1
2021-11-24 10:49:20 +01:00
c . Poster = user_model . NewGhostUser ( )
2019-04-18 07:00:03 +02:00
} else {
log . Error ( "getUserByID[%d]: %v" , c . ID , err )
}
}
return err
}
2016-11-28 14:33:09 +01:00
// AfterDelete is invoked from XORM after the object is deleted.
2023-09-15 08:13:19 +02:00
func ( c * Comment ) AfterDelete ( ctx context . Context ) {
2018-06-12 00:54:30 +02:00
if c . ID <= 0 {
return
}
2023-09-15 08:13:19 +02:00
_ , err := repo_model . DeleteAttachmentsByComment ( ctx , c . ID , true )
2016-03-05 18:58:51 +01:00
if err != nil {
log . Info ( "Could not delete files for comment %d on issue #%d: %s" , c . ID , c . IssueID , err )
}
}
2016-12-22 09:29:26 +01:00
// HTMLURL formats a URL-string to the issue-comment
2023-09-29 15:35:01 +02:00
func ( c * Comment ) HTMLURL ( ctx context . Context ) string {
err := c . LoadIssue ( ctx )
2016-12-22 09:29:26 +01:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 09:48:31 +02:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2016-12-22 09:29:26 +01:00
return ""
}
2023-09-29 15:35:01 +02:00
err = c . Issue . LoadRepo ( ctx )
2018-12-13 16:55:43 +01:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 09:48:31 +02:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 16:55:43 +01:00
return ""
}
2023-09-29 15:35:01 +02:00
return c . Issue . HTMLURL ( ) + c . hashLink ( ctx )
2023-02-06 19:09:18 +01:00
}
// Link formats a relative URL-string to the issue-comment
2023-09-29 15:35:01 +02:00
func ( c * Comment ) Link ( ctx context . Context ) string {
err := c . LoadIssue ( ctx )
2023-02-06 19:09:18 +01:00
if err != nil { // Silently dropping errors :unamused:
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
return ""
}
2023-09-29 15:35:01 +02:00
err = c . Issue . LoadRepo ( ctx )
2023-02-06 19:09:18 +01:00
if err != nil { // Silently dropping errors :unamused:
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
return ""
}
2023-09-29 15:35:01 +02:00
return c . Issue . Link ( ) + c . hashLink ( ctx )
2023-02-06 19:09:18 +01:00
}
2023-09-29 15:35:01 +02:00
func ( c * Comment ) hashLink ( ctx context . Context ) string {
2018-08-06 06:43:22 +02:00
if c . Type == CommentTypeCode {
if c . ReviewID == 0 {
2023-02-06 19:09:18 +01:00
return "/files#" + c . HashTag ( )
2018-08-06 06:43:22 +02:00
}
if c . Review == nil {
2023-09-29 15:35:01 +02:00
if err := c . LoadReview ( ctx ) ; err != nil {
2018-08-06 06:43:22 +02:00
log . Warn ( "LoadReview(%d): %v" , c . ReviewID , err )
2023-02-06 19:09:18 +01:00
return "/files#" + c . HashTag ( )
2018-08-06 06:43:22 +02:00
}
}
if c . Review . Type <= ReviewTypePending {
2023-02-06 19:09:18 +01:00
return "/files#" + c . HashTag ( )
2018-08-06 06:43:22 +02:00
}
}
2023-02-06 19:09:18 +01:00
return "#" + c . HashTag ( )
2016-12-22 09:29:26 +01:00
}
2020-01-09 12:56:32 +01:00
// APIURL formats a API-string to the issue-comment
2023-09-29 15:35:01 +02:00
func ( c * Comment ) APIURL ( ctx context . Context ) string {
err := c . LoadIssue ( ctx )
2020-01-09 12:56:32 +01:00
if err != nil { // Silently dropping errors :unamused:
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
return ""
}
2023-09-29 15:35:01 +02:00
err = c . Issue . LoadRepo ( ctx )
2020-01-09 12:56:32 +01:00
if err != nil { // Silently dropping errors :unamused:
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
return ""
}
2020-01-14 16:37:19 +01:00
return fmt . Sprintf ( "%s/issues/comments/%d" , c . Issue . Repo . APIURL ( ) , c . ID )
2020-01-09 12:56:32 +01:00
}
2016-12-22 09:29:26 +01:00
// IssueURL formats a URL-string to the issue
2023-09-29 15:35:01 +02:00
func ( c * Comment ) IssueURL ( ctx context . Context ) string {
err := c . LoadIssue ( ctx )
2016-12-22 09:29:26 +01:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 09:48:31 +02:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2016-12-22 09:29:26 +01:00
return ""
}
2018-05-16 16:01:55 +02:00
if c . Issue . IsPull {
2016-12-22 09:29:26 +01:00
return ""
}
2018-12-13 16:55:43 +01:00
2023-09-29 15:35:01 +02:00
err = c . Issue . LoadRepo ( ctx )
2018-12-13 16:55:43 +01:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 09:48:31 +02:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 16:55:43 +01:00
return ""
}
2018-05-16 16:01:55 +02:00
return c . Issue . HTMLURL ( )
2016-12-22 09:29:26 +01:00
}
// PRURL formats a URL-string to the pull-request
2023-09-29 15:35:01 +02:00
func ( c * Comment ) PRURL ( ctx context . Context ) string {
err := c . LoadIssue ( ctx )
2016-12-22 09:29:26 +01:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 09:48:31 +02:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2016-12-22 09:29:26 +01:00
return ""
}
2023-09-29 15:35:01 +02:00
err = c . Issue . LoadRepo ( ctx )
2018-12-13 16:55:43 +01:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 09:48:31 +02:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 16:55:43 +01:00
return ""
}
2018-05-16 16:01:55 +02:00
if ! c . Issue . IsPull {
2016-12-22 09:29:26 +01:00
return ""
}
2018-05-16 16:01:55 +02:00
return c . Issue . HTMLURL ( )
2016-12-22 09:29:26 +01:00
}
2018-05-16 16:01:55 +02:00
// CommentHashTag returns unique hash tag for comment id.
func CommentHashTag ( id int64 ) string {
return fmt . Sprintf ( "issuecomment-%d" , id )
}
2016-03-05 18:58:51 +01:00
// HashTag returns unique hash tag for comment.
func ( c * Comment ) HashTag ( ) string {
2018-05-16 16:01:55 +02:00
return CommentHashTag ( c . ID )
2016-03-05 18:58:51 +01:00
}
// EventTag returns unique event hash tag for comment.
func ( c * Comment ) EventTag ( ) string {
2020-12-25 10:59:32 +01:00
return fmt . Sprintf ( "event-%d" , c . ID )
2016-03-05 18:58:51 +01:00
}
2017-01-30 13:46:45 +01:00
// LoadLabel if comment.Type is CommentTypeLabel, then load Label
2023-09-29 15:35:01 +02:00
func ( c * Comment ) LoadLabel ( ctx context . Context ) error {
2017-01-30 13:46:45 +01:00
var label Label
2023-09-29 15:35:01 +02:00
has , err := db . GetEngine ( ctx ) . ID ( c . LabelID ) . Get ( & label )
2017-01-30 13:46:45 +01:00
if err != nil {
return err
2017-02-11 13:56:57 +01:00
} else if has {
c . Label = & label
} else {
// Ignore Label is deleted, but not clear this table
log . Warn ( "Commit %d cannot load label %d" , c . ID , c . LabelID )
2017-01-30 13:46:45 +01:00
}
2017-02-11 13:56:57 +01:00
2017-01-30 13:46:45 +01:00
return nil
}
2020-08-17 05:07:38 +02:00
// LoadProject if comment.Type is CommentTypeProject, then load project.
2023-09-29 15:35:01 +02:00
func ( c * Comment ) LoadProject ( ctx context . Context ) error {
2020-08-17 05:07:38 +02:00
if c . OldProjectID > 0 {
2022-03-29 16:16:31 +02:00
var oldProject project_model . Project
2023-09-29 15:35:01 +02:00
has , err := db . GetEngine ( ctx ) . ID ( c . OldProjectID ) . Get ( & oldProject )
2020-08-17 05:07:38 +02:00
if err != nil {
return err
} else if has {
c . OldProject = & oldProject
}
}
if c . ProjectID > 0 {
2022-03-29 16:16:31 +02:00
var project project_model . Project
2023-09-29 15:35:01 +02:00
has , err := db . GetEngine ( ctx ) . ID ( c . ProjectID ) . Get ( & project )
2020-08-17 05:07:38 +02:00
if err != nil {
return err
} else if has {
c . Project = & project
}
}
return nil
}
2017-02-01 03:36:08 +01:00
// LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone
2022-11-19 09:12:33 +01:00
func ( c * Comment ) LoadMilestone ( ctx context . Context ) error {
2017-02-01 03:36:08 +01:00
if c . OldMilestoneID > 0 {
2022-06-13 11:37:59 +02:00
var oldMilestone Milestone
2022-11-19 09:12:33 +01:00
has , err := db . GetEngine ( ctx ) . ID ( c . OldMilestoneID ) . Get ( & oldMilestone )
2017-02-01 03:36:08 +01:00
if err != nil {
return err
2017-06-17 06:51:28 +02:00
} else if has {
c . OldMilestone = & oldMilestone
2017-02-01 03:36:08 +01:00
}
}
if c . MilestoneID > 0 {
2022-06-13 11:37:59 +02:00
var milestone Milestone
2022-11-19 09:12:33 +01:00
has , err := db . GetEngine ( ctx ) . ID ( c . MilestoneID ) . Get ( & milestone )
2017-02-01 03:36:08 +01:00
if err != nil {
return err
2017-06-17 06:51:28 +02:00
} else if has {
c . Milestone = & milestone
2017-02-01 03:36:08 +01:00
}
}
return nil
}
2022-01-18 18:28:38 +01:00
// LoadAttachments loads attachments (it never returns error, the error during `GetAttachmentsByCommentIDCtx` is ignored)
2022-11-19 09:12:33 +01:00
func ( c * Comment ) LoadAttachments ( ctx context . Context ) error {
2018-12-13 16:55:43 +01:00
if len ( c . Attachments ) > 0 {
return nil
}
var err error
2022-11-19 09:12:33 +01:00
c . Attachments , err = repo_model . GetAttachmentsByCommentID ( ctx , c . ID )
2018-12-13 16:55:43 +01:00
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "getAttachmentsByCommentID[%d]: %v" , c . ID , err )
2018-12-13 16:55:43 +01:00
}
return nil
}
2019-10-15 14:19:32 +02:00
// UpdateAttachments update attachments by UUIDs for the comment
2023-09-29 15:35:01 +02:00
func ( c * Comment ) UpdateAttachments ( ctx context . Context , uuids [ ] string ) error {
ctx , committer , err := db . TxContext ( ctx )
2021-11-19 14:39:57 +01:00
if err != nil {
2019-10-15 14:19:32 +02:00
return err
}
2021-11-19 14:39:57 +01:00
defer committer . Close ( )
attachments , err := repo_model . GetAttachmentsByUUIDs ( ctx , uuids )
2019-10-15 14:19:32 +02:00
if err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "getAttachmentsByUUIDs [uuids: %v]: %w" , uuids , err )
2019-10-15 14:19:32 +02:00
}
for i := 0 ; i < len ( attachments ) ; i ++ {
attachments [ i ] . IssueID = c . IssueID
attachments [ i ] . CommentID = c . ID
2022-05-20 16:08:52 +02:00
if err := repo_model . UpdateAttachment ( ctx , attachments [ i ] ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "update attachment [id: %d]: %w" , attachments [ i ] . ID , err )
2019-10-15 14:19:32 +02:00
}
}
2021-11-19 14:39:57 +01:00
return committer . Commit ( )
2019-10-15 14:19:32 +02:00
}
2020-10-12 21:55:13 +02:00
// LoadAssigneeUserAndTeam if comment.Type is CommentTypeAssignees, then load assignees
2023-09-29 15:35:01 +02:00
func ( c * Comment ) LoadAssigneeUserAndTeam ( ctx context . Context ) error {
2017-02-03 16:09:10 +01:00
var err error
2020-10-12 21:55:13 +02:00
if c . AssigneeID > 0 && c . Assignee == nil {
2023-09-29 15:35:01 +02:00
c . Assignee , err = user_model . GetUserByID ( ctx , c . AssigneeID )
2017-02-03 16:09:10 +01:00
if err != nil {
2021-11-24 10:49:20 +01:00
if ! user_model . IsErrUserNotExist ( err ) {
2018-01-07 10:13:10 +01:00
return err
}
2021-11-24 10:49:20 +01:00
c . Assignee = user_model . NewGhostUser ( )
2017-02-03 16:09:10 +01:00
}
2020-10-12 21:55:13 +02:00
} else if c . AssigneeTeamID > 0 && c . AssigneeTeam == nil {
2023-09-29 15:35:01 +02:00
if err = c . LoadIssue ( ctx ) ; err != nil {
2020-10-12 21:55:13 +02:00
return err
}
2023-09-29 15:35:01 +02:00
if err = c . Issue . LoadRepo ( ctx ) ; err != nil {
2020-10-12 21:55:13 +02:00
return err
}
2023-09-29 15:35:01 +02:00
if err = c . Issue . Repo . LoadOwner ( ctx ) ; err != nil {
2020-10-12 21:55:13 +02:00
return err
}
if c . Issue . Repo . Owner . IsOrganization ( ) {
2023-09-29 15:35:01 +02:00
c . AssigneeTeam , err = organization . GetTeamByID ( ctx , c . AssigneeTeamID )
2022-03-29 08:29:02 +02:00
if err != nil && ! organization . IsErrTeamNotExist ( err ) {
2020-10-12 21:55:13 +02:00
return err
}
}
2017-02-03 16:09:10 +01:00
}
return nil
}
2020-04-18 15:50:25 +02:00
// LoadResolveDoer if comment.Type is CommentTypeCode and ResolveDoerID not zero, then load resolveDoer
2023-09-29 15:35:01 +02:00
func ( c * Comment ) LoadResolveDoer ( ctx context . Context ) ( err error ) {
2020-04-18 15:50:25 +02:00
if c . ResolveDoerID == 0 || c . Type != CommentTypeCode {
return nil
}
2023-09-29 15:35:01 +02:00
c . ResolveDoer , err = user_model . GetUserByID ( ctx , c . ResolveDoerID )
2020-04-18 15:50:25 +02:00
if err != nil {
2021-11-24 10:49:20 +01:00
if user_model . IsErrUserNotExist ( err ) {
c . ResolveDoer = user_model . NewGhostUser ( )
2020-04-18 15:50:25 +02:00
err = nil
}
}
2022-06-20 12:02:49 +02:00
return err
2020-04-18 15:50:25 +02:00
}
// IsResolved check if an code comment is resolved
func ( c * Comment ) IsResolved ( ) bool {
return c . ResolveDoerID != 0 && c . Type == CommentTypeCode
}
2018-07-17 23:23:58 +02:00
// LoadDepIssueDetails loads Dependent Issue Details
2023-09-29 15:35:01 +02:00
func ( c * Comment ) LoadDepIssueDetails ( ctx context . Context ) ( err error ) {
2018-07-17 23:23:58 +02:00
if c . DependentIssueID <= 0 || c . DependentIssue != nil {
return nil
}
2023-09-29 15:35:01 +02:00
c . DependentIssue , err = GetIssueByID ( ctx , c . DependentIssueID )
2018-07-17 23:23:58 +02:00
return err
}
2021-02-19 11:52:11 +01:00
// LoadTime loads the associated time for a CommentTypeAddTimeManual
func ( c * Comment ) LoadTime ( ) error {
if c . Time != nil || c . TimeID == 0 {
return nil
}
var err error
c . Time , err = GetTrackedTimeByID ( c . TimeID )
return err
}
2022-03-31 11:20:39 +02:00
func ( c * Comment ) loadReactions ( ctx context . Context , repo * repo_model . Repository ) ( err error ) {
2017-12-04 00:14:26 +01:00
if c . Reactions != nil {
return nil
}
2022-06-13 11:37:59 +02:00
c . Reactions , _ , err = FindReactions ( ctx , FindReactionsOptions {
2017-12-04 00:14:26 +01:00
IssueID : c . IssueID ,
CommentID : c . ID ,
} )
if err != nil {
return err
}
// Load reaction user data
2022-03-31 11:20:39 +02:00
if _ , err := c . Reactions . LoadUsers ( ctx , repo ) ; err != nil {
2017-12-04 00:14:26 +01:00
return err
}
return nil
}
// LoadReactions loads comment reactions
2023-09-29 15:35:01 +02:00
func ( c * Comment ) LoadReactions ( ctx context . Context , repo * repo_model . Repository ) error {
return c . loadReactions ( ctx , repo )
2017-12-04 00:14:26 +01:00
}
2022-05-20 16:08:52 +02:00
func ( c * Comment ) loadReview ( ctx context . Context ) ( err error ) {
2024-02-13 23:29:33 +01:00
if c . ReviewID == 0 {
return nil
}
2018-12-13 16:55:43 +01:00
if c . Review == nil {
2022-05-20 16:08:52 +02:00
if c . Review , err = GetReviewByID ( ctx , c . ReviewID ) ; err != nil {
2024-02-13 23:29:33 +01:00
// review request which has been replaced by actual reviews doesn't exist in database anymore, so ignorem them.
if c . Type == CommentTypeReviewRequest {
return nil
}
2018-12-13 16:55:43 +01:00
return err
}
2018-08-06 06:43:22 +02:00
}
2019-05-06 14:09:31 +02:00
c . Review . Issue = c . Issue
2018-08-06 06:43:22 +02:00
return nil
}
// LoadReview loads the associated review
2023-09-29 15:35:01 +02:00
func ( c * Comment ) LoadReview ( ctx context . Context ) error {
return c . loadReview ( ctx )
2018-08-06 06:43:22 +02:00
}
// DiffSide returns "previous" if Comment.Line is a LOC of the previous changes and "proposed" if it is a LOC of the proposed changes.
func ( c * Comment ) DiffSide ( ) string {
if c . Line < 0 {
return "previous"
}
return "proposed"
}
// UnsignedLine returns the LOC of the code comment without + or -
func ( c * Comment ) UnsignedLine ( ) uint64 {
if c . Line < 0 {
return uint64 ( c . Line * - 1 )
}
return uint64 ( c . Line )
}
2023-02-06 19:09:18 +01:00
// CodeCommentLink returns the url to a comment in code
2023-09-29 15:35:01 +02:00
func ( c * Comment ) CodeCommentLink ( ctx context . Context ) string {
err := c . LoadIssue ( ctx )
2018-08-06 06:43:22 +02:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 09:48:31 +02:00
log . Error ( "LoadIssue(%d): %v" , c . IssueID , err )
2018-08-06 06:43:22 +02:00
return ""
}
2023-09-29 15:35:01 +02:00
err = c . Issue . LoadRepo ( ctx )
2018-12-13 16:55:43 +01:00
if err != nil { // Silently dropping errors :unamused:
2019-04-02 09:48:31 +02:00
log . Error ( "loadRepo(%d): %v" , c . Issue . RepoID , err )
2018-12-13 16:55:43 +01:00
return ""
}
2023-02-06 19:09:18 +01:00
return fmt . Sprintf ( "%s/files#%s" , c . Issue . Link ( ) , c . HashTag ( ) )
2018-08-06 06:43:22 +02:00
}
2020-05-20 14:47:24 +02:00
// LoadPushCommits Load push commits
2022-01-20 00:26:57 +01:00
func ( c * Comment ) LoadPushCommits ( ctx context . Context ) ( err error ) {
2022-01-21 18:59:26 +01:00
if c . Content == "" || c . Commits != nil || c . Type != CommentTypePullRequestPush {
2020-05-20 14:47:24 +02:00
return nil
}
var data PushActionContent
err = json . Unmarshal ( [ ] byte ( c . Content ) , & data )
if err != nil {
2023-07-09 13:58:06 +02:00
return err
2020-05-20 14:47:24 +02:00
}
c . IsForcePush = data . IsForcePush
if c . IsForcePush {
if len ( data . CommitIDs ) != 2 {
return nil
}
c . OldCommit = data . CommitIDs [ 0 ]
c . NewCommit = data . CommitIDs [ 1 ]
} else {
repoPath := c . Issue . Repo . RepoPath ( )
2022-01-20 00:26:57 +01:00
gitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , repoPath )
2020-05-20 14:47:24 +02:00
if err != nil {
return err
}
2022-01-20 00:26:57 +01:00
defer closer . Close ( )
2020-05-20 14:47:24 +02:00
2023-01-09 04:50:54 +01:00
c . Commits = git_model . ConvertFromGitCommit ( ctx , gitRepo . GetCommitsFromIDs ( data . CommitIDs ) , c . Issue . Repo )
2021-08-09 20:08:51 +02:00
c . CommitsNum = int64 ( len ( c . Commits ) )
2020-05-20 14:47:24 +02:00
}
return err
}
2022-12-10 03:46:31 +01:00
// CreateComment creates comment with context
func CreateComment ( ctx context . Context , opts * CreateCommentOptions ) ( _ * Comment , err error ) {
2023-08-04 15:34:34 +02:00
ctx , committer , err := db . TxContext ( ctx )
if err != nil {
return nil , err
}
defer committer . Close ( )
2021-11-19 14:39:57 +01:00
e := db . GetEngine ( ctx )
2017-01-30 13:46:45 +01:00
var LabelID int64
if opts . Label != nil {
LabelID = opts . Label . ID
}
2018-07-17 23:23:58 +02:00
2016-03-05 18:58:51 +01:00
comment := & Comment {
2018-07-17 23:23:58 +02:00
Type : opts . Type ,
PosterID : opts . Doer . ID ,
Poster : opts . Doer ,
IssueID : opts . Issue . ID ,
LabelID : LabelID ,
OldMilestoneID : opts . OldMilestoneID ,
MilestoneID : opts . MilestoneID ,
2020-08-17 05:07:38 +02:00
OldProjectID : opts . OldProjectID ,
ProjectID : opts . ProjectID ,
2021-02-19 11:52:11 +01:00
TimeID : opts . TimeID ,
2018-07-17 23:23:58 +02:00
RemovedAssignee : opts . RemovedAssignee ,
AssigneeID : opts . AssigneeID ,
2020-10-12 21:55:13 +02:00
AssigneeTeamID : opts . AssigneeTeamID ,
2018-07-17 23:23:58 +02:00
CommitID : opts . CommitID ,
CommitSHA : opts . CommitSHA ,
Line : opts . LineNum ,
Content : opts . Content ,
OldTitle : opts . OldTitle ,
NewTitle : opts . NewTitle ,
2019-12-16 07:20:25 +01:00
OldRef : opts . OldRef ,
NewRef : opts . NewRef ,
2018-07-17 23:23:58 +02:00
DependentIssueID : opts . DependentIssueID ,
2018-08-06 06:43:22 +02:00
TreePath : opts . TreePath ,
ReviewID : opts . ReviewID ,
Patch : opts . Patch ,
2019-09-20 07:45:38 +02:00
RefRepoID : opts . RefRepoID ,
RefIssueID : opts . RefIssueID ,
RefCommentID : opts . RefCommentID ,
RefAction : opts . RefAction ,
RefIsPull : opts . RefIsPull ,
2020-05-20 14:47:24 +02:00
IsForcePush : opts . IsForcePush ,
2020-11-09 07:15:09 +01:00
Invalidated : opts . Invalidated ,
2016-03-05 18:58:51 +01:00
}
[FEAT] allow setting the update date on issues and comments
This field adds the possibility to set the update date when modifying
an issue through the API.
A 'NoAutoDate' in-memory field is added in the Issue struct.
If the update_at field is set, NoAutoDate is set to true and the
Issue's UpdatedUnix field is filled.
That information is passed down to the functions that actually updates
the database, which have been modified to not auto update dates if
requested.
A guard is added to the 'EditIssue' API call, to checks that the
udpate_at date is between the issue's creation date and the current
date (to avoid 'malicious' changes). It also limits the new feature
to project's owners and admins.
(cherry picked from commit c524d33402c76bc4cccea2806f289e08a009baae)
Add a SetIssueUpdateDate() function in services/issue.go
That function is used by some API calls to set the NoAutoDate and
UpdatedUnix fields of an Issue if an updated_at date is provided.
(cherry picked from commit f061caa6555e0c9e922ee1e73dd2e4337360e9fe)
Add an updated_at field to the API calls related to Issue's Labels.
The update date is applied to the issue's comment created to inform
about the modification of the issue's labels.
(cherry picked from commit ea36cf80f58f0ab20c565a8f5d063b90fd741f97)
Add an updated_at field to the API call for issue's attachment creation
The update date is applied to the issue's comment created to inform
about the modification of the issue's content, and is set as the
asset creation date.
(cherry picked from commit 96150971ca31b97e97e84d5f5eb95a177cc44e2e)
Checking Issue changes, with and without providing an updated_at date
Those unit tests are added:
- TestAPIEditIssueWithAutoDate
- TestAPIEditIssueWithNoAutoDate
- TestAPIAddIssueLabelsWithAutoDate
- TestAPIAddIssueLabelsWithNoAutoDate
- TestAPICreateIssueAttachmentWithAutoDate
- TestAPICreateIssueAttachmentWithNoAutoDate
(cherry picked from commit 4926a5d7a28581003545256632213bf4136b193d)
Add an updated_at field to the API call for issue's comment creation
The update date is used as the comment creation date, and is applied to
the issue as the update creation date.
(cherry picked from commit 76c8faecdc6cba48ca4fe07d1a916d1f1a4b37b4)
Add an updated_at field to the API call for issue's comment edition
The update date is used as the comment update date, and is applied to
the issue as an update date.
(cherry picked from commit cf787ad7fdb8e6273fdc35d7b5cc164b400207e9)
Add an updated_at field to the API call for comment's attachment creation
The update date is applied to the comment, and is set as the asset
creation date.
(cherry picked from commit 1e4ff424d39db7a4256cd9abf9c58b8d3e1b5c14)
Checking Comment changes, with and without providing an updated_at date
Those unit tests are added:
- TestAPICreateCommentWithAutoDate
- TestAPICreateCommentWithNoAutoDate
- TestAPIEditCommentWithAutoDate
- TestAPIEditCommentWithNoAutoDate
- TestAPICreateCommentAttachmentWithAutoDate
- TestAPICreateCommentAttachmentWithNoAutoDate
(cherry picked from commit da932152f1deb3039a399516a51c8b6757059c91)
Pettier code to set the update time of comments
Now uses sess.AllCols().NoAutoToime().SetExpr("updated_unix", ...)
XORM is smart enough to compose one single SQL UPDATE which all
columns + updated_unix.
(cherry picked from commit 1f6a42808dd739c0c2e49e6b7ae2967f120f43c2)
Issue edition: Keep the max of the milestone and issue update dates.
When editing an issue via the API, an updated_at date can be provided.
If the EditIssue call changes the issue's milestone, the milestone's
update date is to be changed accordingly, but only with a greater
value.
This ensures that a milestone's update date is the max of all issue's
update dates.
(cherry picked from commit 8f22ea182e6b49e933dc6534040160dd739ff18a)
Rewrite the 'AutoDate' tests using subtests
Also add a test to check the permissions to set a date, and a test
to check update dates on milestones.
The tests related to 'AutoDate' are:
- TestAPIEditIssueAutoDate
- TestAPIAddIssueLabelsAutoDate
- TestAPIEditIssueMilestoneAutoDate
- TestAPICreateIssueAttachmentAutoDate
- TestAPICreateCommentAutoDate
- TestAPIEditCommentWithDate
- TestAPICreateCommentAttachmentAutoDate
(cherry picked from commit 961fd13c551b3e50040acb7c914a00ead92de63f)
(cherry picked from commit d52f4eea44692ee773010cb66a69a603663947d5)
(cherry picked from commit 3540ea2a43155ca8cf5ab1a4a246babfb829db16)
Conflicts:
services/issue/issue.go
https://codeberg.org/forgejo/forgejo/pulls/1415
(cherry picked from commit 56720ade008c09122d825959171aa5346d645987)
Conflicts:
routers/api/v1/repo/issue_label.go
https://codeberg.org/forgejo/forgejo/pulls/1462
(cherry picked from commit 47c78927d6c7e7a50298fa67efad1e73723a0981)
(cherry picked from commit 2030f3b965cde401976821083c3250b404954ecc)
(cherry picked from commit f02aeb76981cd688ceaf6613f142a8a725be1437)
(cherry picked from commit 2e43e49961c1cd5791744fa4e7994ce929c31837)
(cherry picked from commit 3bfb6cc1c085a1ae11885d0eb138d7e977fa1a16)
(cherry picked from commit 38918d5f5cb148b8f53d6707fe6bc677c19c7f79)
(cherry picked from commit 174f6ac3453c7ba1a88655af5d0fff807eb94dc1)
(cherry picked from commit 08a2bed45dc48547c0ab79fe1de2e3c62c823ae2)
2023-05-30 18:42:58 +02:00
if opts . Issue . NoAutoTime {
comment . CreatedUnix = opts . Issue . UpdatedUnix
comment . UpdatedUnix = opts . Issue . UpdatedUnix
e . NoAutoTime ( )
}
2016-03-05 18:58:51 +01:00
if _ , err = e . Insert ( comment ) ; err != nil {
return nil , err
}
2023-02-18 13:11:03 +01:00
if err = opts . Repo . LoadOwner ( ctx ) ; err != nil {
2017-01-30 13:46:45 +01:00
return nil , err
}
2021-11-19 14:39:57 +01:00
if err = updateCommentInfos ( ctx , opts , comment ) ; err != nil {
2019-11-06 14:39:29 +01:00
return nil , err
}
2022-06-13 11:37:59 +02:00
if err = comment . AddCrossReferences ( ctx , opts . Doer , false ) ; err != nil {
2019-09-20 07:45:38 +02:00
return nil , err
}
2023-08-04 15:34:34 +02:00
if err = committer . Commit ( ) ; err != nil {
return nil , err
}
2018-08-06 06:43:22 +02:00
return comment , nil
}
2021-11-19 14:39:57 +01:00
func updateCommentInfos ( ctx context . Context , opts * CreateCommentOptions , comment * Comment ) ( err error ) {
2016-03-05 18:58:51 +01:00
// Check comment type.
switch opts . Type {
2018-08-06 06:43:22 +02:00
case CommentTypeCode :
if comment . ReviewID != 0 {
if comment . Review == nil {
2022-05-20 16:08:52 +02:00
if err := comment . loadReview ( ctx ) ; err != nil {
2018-08-06 06:43:22 +02:00
return err
}
}
if comment . Review . Type <= ReviewTypePending {
return nil
}
}
fallthrough
2016-11-07 17:30:04 +01:00
case CommentTypeComment :
2022-05-20 16:08:52 +02:00
if _ , err = db . Exec ( ctx , "UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?" , opts . Issue . ID ) ; err != nil {
2018-08-06 06:43:22 +02:00
return err
2016-03-05 18:58:51 +01:00
}
2022-01-13 17:50:43 +01:00
fallthrough
case CommentTypeReview :
2016-03-05 18:58:51 +01:00
// Check attachments
2021-11-19 14:39:57 +01:00
attachments , err := repo_model . GetAttachmentsByUUIDs ( ctx , opts . Attachments )
2019-12-11 01:01:52 +01:00
if err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "getAttachmentsByUUIDs [uuids: %v]: %w" , opts . Attachments , err )
2016-03-05 18:58:51 +01:00
}
for i := range attachments {
attachments [ i ] . IssueID = opts . Issue . ID
attachments [ i ] . CommentID = comment . ID
// No assign value could be 0, so ignore AllCols().
2022-05-20 16:08:52 +02:00
if _ , err = db . GetEngine ( ctx ) . ID ( attachments [ i ] . ID ) . Update ( attachments [ i ] ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "update attachment [%d]: %w" , attachments [ i ] . ID , err )
2016-03-05 18:58:51 +01:00
}
}
2022-12-09 07:35:56 +01:00
comment . Attachments = attachments
2019-11-06 14:39:29 +01:00
case CommentTypeReopen , CommentTypeClose :
2022-10-25 14:47:46 +02:00
if err = repo_model . UpdateRepoIssueNumbers ( ctx , opts . Issue . RepoID , opts . Issue . IsPull , true ) ; err != nil {
2019-11-06 14:39:29 +01:00
return err
}
}
// update the issue's updated_unix column
2022-04-08 11:11:15 +02:00
return UpdateIssueCols ( ctx , opts . Issue , "updated_unix" )
2019-11-06 14:39:29 +01:00
}
2016-03-05 18:58:51 +01:00
2021-11-24 10:49:20 +01:00
func createDeadlineComment ( ctx context . Context , doer * user_model . User , issue * Issue , newDeadlineUnix timeutil . TimeStamp ) ( * Comment , error ) {
2018-05-01 21:05:28 +02:00
var content string
var commentType CommentType
// newDeadline = 0 means deleting
if newDeadlineUnix == 0 {
commentType = CommentTypeRemovedDeadline
content = issue . DeadlineUnix . Format ( "2006-01-02" )
} else if issue . DeadlineUnix == 0 {
// Check if the new date was added or modified
// If the actual deadline is 0 => deadline added
commentType = CommentTypeAddedDeadline
content = newDeadlineUnix . Format ( "2006-01-02" )
} else { // Otherwise modified
commentType = CommentTypeModifiedDeadline
content = newDeadlineUnix . Format ( "2006-01-02" ) + "|" + issue . DeadlineUnix . Format ( "2006-01-02" )
}
2022-04-08 11:11:15 +02:00
if err := issue . LoadRepo ( ctx ) ; err != nil {
2018-12-27 16:02:43 +01:00
return nil , err
}
2021-03-14 19:52:12 +01:00
opts := & CreateCommentOptions {
2018-05-01 21:05:28 +02:00
Type : commentType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
Content : content ,
2019-12-01 03:44:39 +01:00
}
2022-12-10 03:46:31 +01:00
comment , err := CreateComment ( ctx , opts )
2019-12-01 03:44:39 +01:00
if err != nil {
return nil , err
}
2019-12-02 15:43:39 +01:00
return comment , nil
2018-05-01 21:05:28 +02:00
}
2018-07-17 23:23:58 +02:00
// Creates issue dependency comment
2021-11-24 10:49:20 +01:00
func createIssueDependencyComment ( ctx context . Context , doer * user_model . User , issue , dependentIssue * Issue , add bool ) ( err error ) {
2018-07-17 23:23:58 +02:00
cType := CommentTypeAddDependency
if ! add {
cType = CommentTypeRemoveDependency
}
2022-04-08 11:11:15 +02:00
if err = issue . LoadRepo ( ctx ) ; err != nil {
2023-07-09 13:58:06 +02:00
return err
2019-01-27 12:31:40 +01:00
}
2018-07-17 23:23:58 +02:00
// Make two comments, one in each issue
2021-03-14 19:52:12 +01:00
opts := & CreateCommentOptions {
2018-07-17 23:23:58 +02:00
Type : cType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : issue ,
DependentIssueID : dependentIssue . ID ,
2019-12-01 03:44:39 +01:00
}
2022-12-10 03:46:31 +01:00
if _ , err = CreateComment ( ctx , opts ) ; err != nil {
2023-07-09 13:58:06 +02:00
return err
2018-07-17 23:23:58 +02:00
}
2019-12-01 03:44:39 +01:00
opts = & CreateCommentOptions {
2018-07-17 23:23:58 +02:00
Type : cType ,
Doer : doer ,
Repo : issue . Repo ,
Issue : dependentIssue ,
DependentIssueID : issue . ID ,
2019-12-01 03:44:39 +01:00
}
2022-12-10 03:46:31 +01:00
_ , err = CreateComment ( ctx , opts )
2022-06-20 12:02:49 +02:00
return err
2018-07-17 23:23:58 +02:00
}
2016-11-28 14:33:09 +01:00
// CreateCommentOptions defines options for creating comment
2016-03-05 18:58:51 +01:00
type CreateCommentOptions struct {
Type CommentType
2021-11-24 10:49:20 +01:00
Doer * user_model . User
2021-12-10 02:27:50 +01:00
Repo * repo_model . Repository
2016-03-05 18:58:51 +01:00
Issue * Issue
2017-01-30 13:46:45 +01:00
Label * Label
2016-03-05 18:58:51 +01:00
2018-07-17 23:23:58 +02:00
DependentIssueID int64
OldMilestoneID int64
MilestoneID int64
2020-08-17 05:07:38 +02:00
OldProjectID int64
ProjectID int64
2021-02-19 11:52:11 +01:00
TimeID int64
2018-07-17 23:23:58 +02:00
AssigneeID int64
2020-10-12 21:55:13 +02:00
AssigneeTeamID int64
2018-07-17 23:23:58 +02:00
RemovedAssignee bool
OldTitle string
NewTitle string
2019-12-16 07:20:25 +01:00
OldRef string
NewRef string
2018-07-17 23:23:58 +02:00
CommitID int64
CommitSHA string
2018-08-06 06:43:22 +02:00
Patch string
2018-07-17 23:23:58 +02:00
LineNum int64
2018-08-06 06:43:22 +02:00
TreePath string
ReviewID int64
2018-07-17 23:23:58 +02:00
Content string
Attachments [ ] string // UUIDs of attachments
2019-09-20 07:45:38 +02:00
RefRepoID int64
RefIssueID int64
RefCommentID int64
2019-10-14 00:29:10 +02:00
RefAction references . XRefAction
2019-09-20 07:45:38 +02:00
RefIsPull bool
2020-05-20 14:47:24 +02:00
IsForcePush bool
2020-11-09 07:15:09 +01:00
Invalidated bool
2016-03-05 18:58:51 +01:00
}
// GetCommentByID returns the comment by given ID.
2022-05-20 16:08:52 +02:00
func GetCommentByID ( ctx context . Context , id int64 ) ( * Comment , error ) {
2016-03-05 18:58:51 +01:00
c := new ( Comment )
2022-05-20 16:08:52 +02:00
has , err := db . GetEngine ( ctx ) . ID ( id ) . Get ( c )
2016-03-05 18:58:51 +01:00
if err != nil {
return nil , err
} else if ! has {
2016-08-26 22:40:53 +02:00
return nil , ErrCommentNotExist { id , 0 }
2016-03-05 18:58:51 +01:00
}
return c , nil
}
2017-06-21 03:00:44 +02:00
// FindCommentsOptions describes the conditions to Find comments
type FindCommentsOptions struct {
2021-09-24 13:32:56 +02:00
db . ListOptions
2023-01-17 22:03:44 +01:00
RepoID int64
IssueID int64
ReviewID int64
Since int64
Before int64
Line int64
TreePath string
Type CommentType
IssueIDs [ ] int64
Invalidated util . OptionalBool
2023-11-26 00:43:23 +01:00
IsPull util . OptionalBool
2017-06-21 03:00:44 +02:00
}
2023-01-17 22:03:44 +01:00
// ToConds implements FindOptions interface
func ( opts * FindCommentsOptions ) ToConds ( ) builder . Cond {
2021-03-14 19:52:12 +01:00
cond := builder . NewCond ( )
2017-06-21 03:00:44 +02:00
if opts . RepoID > 0 {
cond = cond . And ( builder . Eq { "issue.repo_id" : opts . RepoID } )
2016-08-26 22:40:53 +02:00
}
2017-06-21 03:00:44 +02:00
if opts . IssueID > 0 {
cond = cond . And ( builder . Eq { "comment.issue_id" : opts . IssueID } )
2023-01-17 22:03:44 +01:00
} else if len ( opts . IssueIDs ) > 0 {
cond = cond . And ( builder . In ( "comment.issue_id" , opts . IssueIDs ) )
2017-06-21 03:00:44 +02:00
}
2018-08-06 06:43:22 +02:00
if opts . ReviewID > 0 {
cond = cond . And ( builder . Eq { "comment.review_id" : opts . ReviewID } )
}
2017-06-21 03:00:44 +02:00
if opts . Since > 0 {
cond = cond . And ( builder . Gte { "comment.updated_unix" : opts . Since } )
}
2020-01-13 17:02:24 +01:00
if opts . Before > 0 {
cond = cond . And ( builder . Lte { "comment.updated_unix" : opts . Before } )
}
2023-04-20 08:39:44 +02:00
if opts . Type != CommentTypeUndefined {
2017-06-21 03:00:44 +02:00
cond = cond . And ( builder . Eq { "comment.type" : opts . Type } )
}
2021-01-08 22:49:55 +01:00
if opts . Line != 0 {
2020-11-09 07:15:09 +01:00
cond = cond . And ( builder . Eq { "comment.line" : opts . Line } )
}
if len ( opts . TreePath ) > 0 {
cond = cond . And ( builder . Eq { "comment.tree_path" : opts . TreePath } )
}
2023-01-17 22:03:44 +01:00
if ! opts . Invalidated . IsNone ( ) {
cond = cond . And ( builder . Eq { "comment.invalidated" : opts . Invalidated . IsTrue ( ) } )
}
2023-11-26 00:43:23 +01:00
if opts . IsPull != util . OptionalBoolNone {
cond = cond . And ( builder . Eq { "issue.is_pull" : opts . IsPull . IsTrue ( ) } )
}
2017-06-21 03:00:44 +02:00
return cond
2016-08-26 22:40:53 +02:00
}
2022-05-20 16:08:52 +02:00
// FindComments returns all comments according options
2023-05-21 14:48:28 +02:00
func FindComments ( ctx context . Context , opts * FindCommentsOptions ) ( CommentList , error ) {
2016-12-22 09:29:26 +01:00
comments := make ( [ ] * Comment , 0 , 10 )
2023-01-17 22:03:44 +01:00
sess := db . GetEngine ( ctx ) . Where ( opts . ToConds ( ) )
2023-11-26 00:43:23 +01:00
if opts . RepoID > 0 || opts . IsPull != util . OptionalBoolNone {
2017-06-21 03:00:44 +02:00
sess . Join ( "INNER" , "issue" , "issue.id = comment.issue_id" )
2016-12-22 09:29:26 +01:00
}
2020-01-24 20:00:29 +01:00
if opts . Page != 0 {
2021-09-24 13:32:56 +02:00
sess = db . SetSessionPagination ( sess , opts )
2020-01-24 20:00:29 +01:00
}
2020-11-09 07:15:09 +01:00
// WARNING: If you change this order you will need to fix createCodeComment
2017-06-21 03:00:44 +02:00
return comments , sess .
Asc ( "comment.created_unix" ) .
2017-11-03 04:11:42 +01:00
Asc ( "comment.id" ) .
2017-06-21 03:00:44 +02:00
Find ( & comments )
2016-12-22 09:29:26 +01:00
}
2021-08-12 14:43:08 +02:00
// CountComments count all comments according options by ignoring pagination
2023-09-29 15:35:01 +02:00
func CountComments ( ctx context . Context , opts * FindCommentsOptions ) ( int64 , error ) {
sess := db . GetEngine ( ctx ) . Where ( opts . ToConds ( ) )
2021-08-12 14:43:08 +02:00
if opts . RepoID > 0 {
sess . Join ( "INNER" , "issue" , "issue.id = comment.issue_id" )
}
return sess . Count ( & Comment { } )
}
2023-01-17 22:03:44 +01:00
// UpdateCommentInvalidate updates comment invalidated column
func UpdateCommentInvalidate ( ctx context . Context , c * Comment ) error {
_ , err := db . GetEngine ( ctx ) . ID ( c . ID ) . Cols ( "invalidated" ) . Update ( c )
return err
}
2016-03-05 18:58:51 +01:00
// UpdateComment updates information of comment.
2023-09-29 15:35:01 +02:00
func UpdateComment ( ctx context . Context , c * Comment , doer * user_model . User ) error {
ctx , committer , err := db . TxContext ( ctx )
2021-11-19 14:39:57 +01:00
if err != nil {
2017-09-16 22:16:21 +02:00
return err
}
2021-11-19 14:39:57 +01:00
defer committer . Close ( )
2018-05-16 16:01:55 +02:00
[FEAT] allow setting the update date on issues and comments
This field adds the possibility to set the update date when modifying
an issue through the API.
A 'NoAutoDate' in-memory field is added in the Issue struct.
If the update_at field is set, NoAutoDate is set to true and the
Issue's UpdatedUnix field is filled.
That information is passed down to the functions that actually updates
the database, which have been modified to not auto update dates if
requested.
A guard is added to the 'EditIssue' API call, to checks that the
udpate_at date is between the issue's creation date and the current
date (to avoid 'malicious' changes). It also limits the new feature
to project's owners and admins.
(cherry picked from commit c524d33402c76bc4cccea2806f289e08a009baae)
Add a SetIssueUpdateDate() function in services/issue.go
That function is used by some API calls to set the NoAutoDate and
UpdatedUnix fields of an Issue if an updated_at date is provided.
(cherry picked from commit f061caa6555e0c9e922ee1e73dd2e4337360e9fe)
Add an updated_at field to the API calls related to Issue's Labels.
The update date is applied to the issue's comment created to inform
about the modification of the issue's labels.
(cherry picked from commit ea36cf80f58f0ab20c565a8f5d063b90fd741f97)
Add an updated_at field to the API call for issue's attachment creation
The update date is applied to the issue's comment created to inform
about the modification of the issue's content, and is set as the
asset creation date.
(cherry picked from commit 96150971ca31b97e97e84d5f5eb95a177cc44e2e)
Checking Issue changes, with and without providing an updated_at date
Those unit tests are added:
- TestAPIEditIssueWithAutoDate
- TestAPIEditIssueWithNoAutoDate
- TestAPIAddIssueLabelsWithAutoDate
- TestAPIAddIssueLabelsWithNoAutoDate
- TestAPICreateIssueAttachmentWithAutoDate
- TestAPICreateIssueAttachmentWithNoAutoDate
(cherry picked from commit 4926a5d7a28581003545256632213bf4136b193d)
Add an updated_at field to the API call for issue's comment creation
The update date is used as the comment creation date, and is applied to
the issue as the update creation date.
(cherry picked from commit 76c8faecdc6cba48ca4fe07d1a916d1f1a4b37b4)
Add an updated_at field to the API call for issue's comment edition
The update date is used as the comment update date, and is applied to
the issue as an update date.
(cherry picked from commit cf787ad7fdb8e6273fdc35d7b5cc164b400207e9)
Add an updated_at field to the API call for comment's attachment creation
The update date is applied to the comment, and is set as the asset
creation date.
(cherry picked from commit 1e4ff424d39db7a4256cd9abf9c58b8d3e1b5c14)
Checking Comment changes, with and without providing an updated_at date
Those unit tests are added:
- TestAPICreateCommentWithAutoDate
- TestAPICreateCommentWithNoAutoDate
- TestAPIEditCommentWithAutoDate
- TestAPIEditCommentWithNoAutoDate
- TestAPICreateCommentAttachmentWithAutoDate
- TestAPICreateCommentAttachmentWithNoAutoDate
(cherry picked from commit da932152f1deb3039a399516a51c8b6757059c91)
Pettier code to set the update time of comments
Now uses sess.AllCols().NoAutoToime().SetExpr("updated_unix", ...)
XORM is smart enough to compose one single SQL UPDATE which all
columns + updated_unix.
(cherry picked from commit 1f6a42808dd739c0c2e49e6b7ae2967f120f43c2)
Issue edition: Keep the max of the milestone and issue update dates.
When editing an issue via the API, an updated_at date can be provided.
If the EditIssue call changes the issue's milestone, the milestone's
update date is to be changed accordingly, but only with a greater
value.
This ensures that a milestone's update date is the max of all issue's
update dates.
(cherry picked from commit 8f22ea182e6b49e933dc6534040160dd739ff18a)
Rewrite the 'AutoDate' tests using subtests
Also add a test to check the permissions to set a date, and a test
to check update dates on milestones.
The tests related to 'AutoDate' are:
- TestAPIEditIssueAutoDate
- TestAPIAddIssueLabelsAutoDate
- TestAPIEditIssueMilestoneAutoDate
- TestAPICreateIssueAttachmentAutoDate
- TestAPICreateCommentAutoDate
- TestAPIEditCommentWithDate
- TestAPICreateCommentAttachmentAutoDate
(cherry picked from commit 961fd13c551b3e50040acb7c914a00ead92de63f)
(cherry picked from commit d52f4eea44692ee773010cb66a69a603663947d5)
(cherry picked from commit 3540ea2a43155ca8cf5ab1a4a246babfb829db16)
Conflicts:
services/issue/issue.go
https://codeberg.org/forgejo/forgejo/pulls/1415
(cherry picked from commit 56720ade008c09122d825959171aa5346d645987)
Conflicts:
routers/api/v1/repo/issue_label.go
https://codeberg.org/forgejo/forgejo/pulls/1462
(cherry picked from commit 47c78927d6c7e7a50298fa67efad1e73723a0981)
(cherry picked from commit 2030f3b965cde401976821083c3250b404954ecc)
(cherry picked from commit f02aeb76981cd688ceaf6613f142a8a725be1437)
(cherry picked from commit 2e43e49961c1cd5791744fa4e7994ce929c31837)
(cherry picked from commit 3bfb6cc1c085a1ae11885d0eb138d7e977fa1a16)
(cherry picked from commit 38918d5f5cb148b8f53d6707fe6bc677c19c7f79)
(cherry picked from commit 174f6ac3453c7ba1a88655af5d0fff807eb94dc1)
(cherry picked from commit 08a2bed45dc48547c0ab79fe1de2e3c62c823ae2)
2023-05-30 18:42:58 +02:00
sess := db . GetEngine ( ctx ) . ID ( c . ID ) . AllCols ( )
if c . Issue . NoAutoTime {
// update the DataBase
sess = sess . NoAutoTime ( ) . SetExpr ( "updated_unix" , c . Issue . UpdatedUnix )
// the UpdatedUnix value of the Comment also has to be set,
// to return the adequate valuè
// see https://codeberg.org/forgejo/forgejo/pulls/764#issuecomment-1023801
c . UpdatedUnix = c . Issue . UpdatedUnix
}
if _ , err := sess . Update ( c ) ; err != nil {
2018-12-13 16:55:43 +01:00
return err
}
2022-11-19 09:12:33 +01:00
if err := c . LoadIssue ( ctx ) ; err != nil {
2018-05-16 16:01:55 +02:00
return err
}
2022-06-13 11:37:59 +02:00
if err := c . AddCrossReferences ( ctx , doer , true ) ; err != nil {
2019-09-20 07:45:38 +02:00
return err
}
2021-11-19 14:39:57 +01:00
if err := committer . Commit ( ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "Commit: %w" , err )
2019-09-20 07:45:38 +02:00
}
2018-05-16 16:01:55 +02:00
2017-09-16 22:16:21 +02:00
return nil
2016-03-05 18:58:51 +01:00
}
2016-07-25 20:48:17 +02:00
2017-01-25 03:43:02 +01:00
// DeleteComment deletes the comment
2022-06-13 11:37:59 +02:00
func DeleteComment ( ctx context . Context , comment * Comment ) error {
2022-03-31 11:20:39 +02:00
e := db . GetEngine ( ctx )
2022-03-01 01:20:15 +01:00
if _ , err := e . ID ( comment . ID ) . NoAutoCondition ( ) . Delete ( comment ) ; err != nil {
2016-07-25 20:48:17 +02:00
return err
}
2022-06-13 11:37:59 +02:00
if _ , err := db . DeleteByBean ( ctx , & ContentHistory {
2021-10-11 00:40:03 +02:00
CommentID : comment . ID ,
} ) ; err != nil {
return err
}
2016-11-07 17:30:04 +01:00
if comment . Type == CommentTypeComment {
2022-03-17 23:04:09 +01:00
if _ , err := e . ID ( comment . IssueID ) . Decr ( "num_comments" ) . Update ( new ( Issue ) ) ; err != nil {
2016-07-25 20:48:17 +02:00
return err
}
}
2022-06-13 11:37:59 +02:00
if _ , err := e . Table ( "action" ) .
Where ( "comment_id = ?" , comment . ID ) .
2023-07-04 20:36:08 +02:00
Update ( map [ string ] any {
2022-06-13 11:37:59 +02:00
"is_deleted" : true ,
} ) ; err != nil {
2017-07-04 03:30:41 +02:00
return err
}
2016-07-25 20:48:17 +02:00
2022-05-20 16:08:52 +02:00
if err := comment . neuterCrossReferences ( ctx ) ; err != nil {
2019-09-20 07:45:38 +02:00
return err
}
2022-06-13 11:37:59 +02:00
return DeleteReaction ( ctx , & ReactionOptions { CommentID : comment . ID } )
2016-07-25 20:48:17 +02:00
}
2018-08-06 06:43:22 +02:00
2019-10-14 08:10:42 +02:00
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
2023-09-29 15:35:01 +02:00
func UpdateCommentsMigrationsByType ( ctx context . Context , tp structs . GitServiceType , originalAuthorID string , posterID int64 ) error {
_ , err := db . GetEngine ( ctx ) . Table ( "comment" ) .
2023-12-31 06:57:36 +01:00
Join ( "INNER" , "issue" , "issue.id = comment.issue_id" ) .
Join ( "INNER" , "repository" , "issue.repo_id = repository.id" ) .
Where ( "repository.original_service_type = ?" , tp ) .
2019-10-14 08:10:42 +02:00
And ( "comment.original_author_id = ?" , originalAuthorID ) .
2023-07-04 20:36:08 +02:00
Update ( map [ string ] any {
2019-10-14 08:10:42 +02:00
"poster_id" : posterID ,
"original_author" : "" ,
"original_author_id" : 0 ,
} )
return err
}
2020-05-20 14:47:24 +02:00
2022-05-08 15:46:34 +02:00
// CreateAutoMergeComment is a internal function, only use it for CommentTypePRScheduledToAutoMerge and CommentTypePRUnScheduledToAutoMerge CommentTypes
func CreateAutoMergeComment ( ctx context . Context , typ CommentType , pr * PullRequest , doer * user_model . User ) ( comment * Comment , err error ) {
if typ != CommentTypePRScheduledToAutoMerge && typ != CommentTypePRUnScheduledToAutoMerge {
return nil , fmt . Errorf ( "comment type %d cannot be used to create an auto merge comment" , typ )
}
2022-11-19 09:12:33 +01:00
if err = pr . LoadIssue ( ctx ) ; err != nil {
2023-07-09 13:58:06 +02:00
return nil , err
2022-05-08 15:46:34 +02:00
}
2022-11-19 09:12:33 +01:00
if err = pr . LoadBaseRepo ( ctx ) ; err != nil {
2023-07-09 13:58:06 +02:00
return nil , err
2022-05-08 15:46:34 +02:00
}
2022-12-10 03:46:31 +01:00
comment , err = CreateComment ( ctx , & CreateCommentOptions {
2022-05-08 15:46:34 +02:00
Type : typ ,
Doer : doer ,
Repo : pr . BaseRepo ,
Issue : pr . Issue ,
} )
2022-06-20 12:02:49 +02:00
return comment , err
2022-05-08 15:46:34 +02:00
}
2022-02-01 19:20:28 +01:00
// RemapExternalUser ExternalUserRemappable interface
func ( c * Comment ) RemapExternalUser ( externalName string , externalID , userID int64 ) error {
c . OriginalAuthor = externalName
c . OriginalAuthorID = externalID
c . PosterID = userID
return nil
}
// GetUserID ExternalUserRemappable interface
func ( c * Comment ) GetUserID ( ) int64 { return c . PosterID }
// GetExternalName ExternalUserRemappable interface
func ( c * Comment ) GetExternalName ( ) string { return c . OriginalAuthor }
// GetExternalID ExternalUserRemappable interface
func ( c * Comment ) GetExternalID ( ) int64 { return c . OriginalAuthorID }
2022-06-13 11:37:59 +02:00
// CountCommentTypeLabelWithEmptyLabel count label comments with empty label
2022-11-19 09:12:33 +01:00
func CountCommentTypeLabelWithEmptyLabel ( ctx context . Context ) ( int64 , error ) {
return db . GetEngine ( ctx ) . Where ( builder . Eq { "type" : CommentTypeLabel , "label_id" : 0 } ) . Count ( new ( Comment ) )
2022-06-13 11:37:59 +02:00
}
// FixCommentTypeLabelWithEmptyLabel count label comments with empty label
2022-11-19 09:12:33 +01:00
func FixCommentTypeLabelWithEmptyLabel ( ctx context . Context ) ( int64 , error ) {
return db . GetEngine ( ctx ) . Where ( builder . Eq { "type" : CommentTypeLabel , "label_id" : 0 } ) . Delete ( new ( Comment ) )
2022-06-13 11:37:59 +02:00
}
// CountCommentTypeLabelWithOutsideLabels count label comments with outside label
2022-11-19 09:12:33 +01:00
func CountCommentTypeLabelWithOutsideLabels ( ctx context . Context ) ( int64 , error ) {
return db . GetEngine ( ctx ) . Where ( "comment.type = ? AND ((label.org_id = 0 AND issue.repo_id != label.repo_id) OR (label.repo_id = 0 AND label.org_id != repository.owner_id))" , CommentTypeLabel ) .
2022-06-13 11:37:59 +02:00
Table ( "comment" ) .
Join ( "inner" , "label" , "label.id = comment.label_id" ) .
Join ( "inner" , "issue" , "issue.id = comment.issue_id " ) .
Join ( "inner" , "repository" , "issue.repo_id = repository.id" ) .
Count ( )
}
// FixCommentTypeLabelWithOutsideLabels count label comments with outside label
2022-11-19 09:12:33 +01:00
func FixCommentTypeLabelWithOutsideLabels ( ctx context . Context ) ( int64 , error ) {
res , err := db . GetEngine ( ctx ) . Exec ( ` DELETE FROM comment WHERE comment . id IN (
2022-06-13 11:37:59 +02:00
SELECT il_too . id FROM (
SELECT com . id
FROM comment AS com
INNER JOIN label ON com . label_id = label . id
INNER JOIN issue on issue . id = com . issue_id
INNER JOIN repository ON issue . repo_id = repository . id
WHERE
com . type = ? AND ( ( label . org_id = 0 AND issue . repo_id != label . repo_id ) OR ( label . repo_id = 0 AND label . org_id != repository . owner_id ) )
) AS il_too ) ` , CommentTypeLabel )
if err != nil {
return 0 , err
}
return res . RowsAffected ( )
}
2023-02-15 18:29:13 +01:00
// HasOriginalAuthor returns if a comment was migrated and has an original author.
func ( c * Comment ) HasOriginalAuthor ( ) bool {
return c . OriginalAuthor != "" && c . OriginalAuthorID != 0
}
2023-09-08 23:09:23 +02:00
// InsertIssueComments inserts many comments of issues.
2023-09-29 15:35:01 +02:00
func InsertIssueComments ( ctx context . Context , comments [ ] * Comment ) error {
2023-09-08 23:09:23 +02:00
if len ( comments ) == 0 {
return nil
}
issueIDs := make ( container . Set [ int64 ] )
for _ , comment := range comments {
issueIDs . Add ( comment . IssueID )
}
2023-09-29 15:35:01 +02:00
ctx , committer , err := db . TxContext ( ctx )
2023-09-08 23:09:23 +02:00
if err != nil {
return err
}
defer committer . Close ( )
for _ , comment := range comments {
if _ , err := db . GetEngine ( ctx ) . NoAutoTime ( ) . Insert ( comment ) ; err != nil {
return err
}
for _ , reaction := range comment . Reactions {
reaction . IssueID = comment . IssueID
reaction . CommentID = comment . ID
}
if len ( comment . Reactions ) > 0 {
if err := db . Insert ( ctx , comment . Reactions ) ; err != nil {
return err
}
}
}
for issueID := range issueIDs {
if _ , err := db . Exec ( ctx , "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?" ,
issueID , CommentTypeComment , issueID ) ; err != nil {
return err
}
}
return committer . Commit ( )
}