diff --git a/models/issues/comment.go b/models/issues/comment.go index cd62bcaa4f..bbfc5cc418 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -818,6 +818,9 @@ func CreateComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment, Invalidated: opts.Invalidated, } if opts.Issue.NoAutoTime { + // Preload the comment with the Issue containing the forced update + // date. This is needed to propagate those data in AddCrossReferences() + comment.Issue = opts.Issue comment.CreatedUnix = opts.Issue.UpdatedUnix comment.UpdatedUnix = opts.Issue.UpdatedUnix e.NoAutoTime() @@ -1101,21 +1104,22 @@ func UpdateComment(ctx context.Context, c *Comment, doer *user_model.User) error } defer committer.Close() + if err := c.LoadIssue(ctx); err != nil { + return err + } + 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è + // to return the adequate value // see https://codeberg.org/forgejo/forgejo/pulls/764#issuecomment-1023801 c.UpdatedUnix = c.Issue.UpdatedUnix } if _, err := sess.Update(c); err != nil { return err } - if err := c.LoadIssue(ctx); err != nil { - return err - } if err := c.AddCrossReferences(ctx, doer, true); err != nil { return err } diff --git a/models/issues/issue_xref.go b/models/issues/issue_xref.go index 5a39d045cd..9b1a447471 100644 --- a/models/issues/issue_xref.go +++ b/models/issues/issue_xref.go @@ -46,11 +46,15 @@ func neuterCrossReferences(ctx context.Context, issueID, commentID int64) error for i, c := range active { ids[i] = c.ID } - return neuterCrossReferencesIds(ctx, ids) + return neuterCrossReferencesIds(ctx, nil, ids) } -func neuterCrossReferencesIds(ctx context.Context, ids []int64) error { - _, err := db.GetEngine(ctx).In("id", ids).Cols("`ref_action`").Update(&Comment{RefAction: references.XRefActionNeutered}) +func neuterCrossReferencesIds(stdCtx context.Context, ctx *crossReferencesContext, ids []int64) error { + sess := db.GetEngine(stdCtx).In("id", ids).Cols("`ref_action`") + if ctx != nil && ctx.OrigIssue.NoAutoTime { + sess.SetExpr("updated_unix", ctx.OrigIssue.UpdatedUnix).NoAutoTime() + } + _, err := sess.Update(&Comment{RefAction: references.XRefActionNeutered}) return err } @@ -100,7 +104,7 @@ func (issue *Issue) createCrossReferences(stdCtx context.Context, ctx *crossRefe } } if len(ids) > 0 { - if err = neuterCrossReferencesIds(stdCtx, ids); err != nil { + if err = neuterCrossReferencesIds(stdCtx, ctx, ids); err != nil { return err } } diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go index 339ffdbe0e..cce65ab0dd 100644 --- a/tests/integration/api_comment_test.go +++ b/tests/integration/api_comment_test.go @@ -16,6 +16,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/references" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/tests" @@ -163,6 +164,104 @@ func TestAPICreateCommentAutoDate(t *testing.T) { }) } +func TestAPICommentXRefAutoDate(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) + + t.Run("WithAutoDate", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // Create a comment mentioning issue #2 and check that a xref comment was added + // in issue #2 + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments?token=%s", + repoOwner.Name, repo.Name, issue.Index, token) + + commentBody := "mention #2" + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueCommentOption{ + Body: commentBody, + }) + resp := MakeRequest(t, req, http.StatusCreated) + var createdComment api.Comment + DecodeJSON(t, resp, &createdComment) + + ref := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: 2, RefIssueID: 1, RefCommentID: createdComment.ID}) + assert.Equal(t, issues_model.CommentTypeCommentRef, ref.Type) + assert.Equal(t, references.XRefActionNone, ref.RefAction) + // the execution of the API call supposedly lasted less than one minute + updatedSince := time.Since(ref.UpdatedUnix.AsTime()) + assert.LessOrEqual(t, updatedSince, time.Minute) + + // Remove the mention to issue #2 and check that the xref was neutered + urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s", + repoOwner.Name, repo.Name, createdComment.ID, token) + + newCommentBody := "no mention" + req = NewRequestWithJSON(t, "PATCH", urlStr, &api.EditIssueCommentOption{ + Body: newCommentBody, + }) + resp = MakeRequest(t, req, http.StatusOK) + var updatedComment api.Comment + DecodeJSON(t, resp, &updatedComment) + + ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: 2, RefIssueID: 1, RefCommentID: updatedComment.ID}) + assert.Equal(t, issues_model.CommentTypeCommentRef, ref.Type) + assert.Equal(t, references.XRefActionNeutered, ref.RefAction) + // the execution of the API call supposedly lasted less than one minute + updatedSince = time.Since(ref.UpdatedUnix.AsTime()) + assert.LessOrEqual(t, updatedSince, time.Minute) + }) + + t.Run("WithUpdateDate", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // dates will be converted into the same tz, in order to compare them + utcTZ, _ := time.LoadLocation("UTC") + + // Create a comment mentioning issue #2 and check that a xref comment was added + // in issue #2 + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments?token=%s", + repoOwner.Name, repo.Name, issue.Index, token) + + commentBody := "re-mention #2" + updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueCommentOption{ + Body: commentBody, + Updated: &updatedAt, + }) + resp := MakeRequest(t, req, http.StatusCreated) + var createdComment api.Comment + DecodeJSON(t, resp, &createdComment) + + ref := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: 2, RefIssueID: 1, RefCommentID: createdComment.ID}) + assert.Equal(t, issues_model.CommentTypeCommentRef, ref.Type) + assert.Equal(t, references.XRefActionNone, ref.RefAction) + assert.Equal(t, updatedAt.In(utcTZ), ref.UpdatedUnix.AsTimeInLocation(utcTZ)) + + // Remove the mention to issue #2 and check that the xref was neutered + urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s", + repoOwner.Name, repo.Name, createdComment.ID, token) + + newCommentBody := "no mention" + updatedAt = time.Now().Add(-time.Hour).Truncate(time.Second) + req = NewRequestWithJSON(t, "PATCH", urlStr, &api.EditIssueCommentOption{ + Body: newCommentBody, + Updated: &updatedAt, + }) + resp = MakeRequest(t, req, http.StatusOK) + var updatedComment api.Comment + DecodeJSON(t, resp, &updatedComment) + + ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: 2, RefIssueID: 1, RefCommentID: updatedComment.ID}) + assert.Equal(t, issues_model.CommentTypeCommentRef, ref.Type) + assert.Equal(t, references.XRefActionNeutered, ref.RefAction) + assert.Equal(t, updatedAt.In(utcTZ), ref.UpdatedUnix.AsTimeInLocation(utcTZ)) + }) +} + func TestAPIGetComment(t *testing.T) { defer tests.PrepareTestEnv(t)()