mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Fix no edit history after editing issue's title and content (#30814)
Fix #30807 reuse functions in services
This commit is contained in:
		| @@ -429,62 +429,6 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // UpdateIssueByAPI updates all allowed fields of given issue. | ||||
| // If the issue status is changed a statusChangeComment is returned | ||||
| // similarly if the title is changed the titleChanged bool is set to true | ||||
| func UpdateIssueByAPI(ctx context.Context, issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) { | ||||
| 	ctx, committer, err := db.TxContext(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, false, err | ||||
| 	} | ||||
| 	defer committer.Close() | ||||
|  | ||||
| 	if err := issue.LoadRepo(ctx); err != nil { | ||||
| 		return nil, false, fmt.Errorf("loadRepo: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Reload the issue | ||||
| 	currentIssue, err := GetIssueByID(ctx, issue.ID) | ||||
| 	if err != nil { | ||||
| 		return nil, false, err | ||||
| 	} | ||||
|  | ||||
| 	if _, err := db.GetEngine(ctx).ID(issue.ID).Cols( | ||||
| 		"name", "content", "milestone_id", "priority", | ||||
| 		"deadline_unix", "updated_unix", "is_locked"). | ||||
| 		Update(issue); err != nil { | ||||
| 		return nil, false, err | ||||
| 	} | ||||
|  | ||||
| 	titleChanged = currentIssue.Title != issue.Title | ||||
| 	if titleChanged { | ||||
| 		opts := &CreateCommentOptions{ | ||||
| 			Type:     CommentTypeChangeTitle, | ||||
| 			Doer:     doer, | ||||
| 			Repo:     issue.Repo, | ||||
| 			Issue:    issue, | ||||
| 			OldTitle: currentIssue.Title, | ||||
| 			NewTitle: issue.Title, | ||||
| 		} | ||||
| 		_, err := CreateComment(ctx, opts) | ||||
| 		if err != nil { | ||||
| 			return nil, false, fmt.Errorf("createComment: %w", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if currentIssue.IsClosed != issue.IsClosed { | ||||
| 		statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false) | ||||
| 		if err != nil { | ||||
| 			return nil, false, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err := issue.AddCrossReferences(ctx, doer, true); err != nil { | ||||
| 		return nil, false, err | ||||
| 	} | ||||
| 	return statusChangeComment, titleChanged, committer.Commit() | ||||
| } | ||||
|  | ||||
| // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it. | ||||
| func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) { | ||||
| 	// if the deadline hasn't changed do nothing | ||||
|   | ||||
| @@ -85,7 +85,7 @@ type CreatePullRequestOption struct { | ||||
| // EditPullRequestOption options when modify pull request | ||||
| type EditPullRequestOption struct { | ||||
| 	Title     string   `json:"title"` | ||||
| 	Body      string   `json:"body"` | ||||
| 	Body      *string  `json:"body"` | ||||
| 	Base      string   `json:"base"` | ||||
| 	Assignee  string   `json:"assignee"` | ||||
| 	Assignees []string `json:"assignees"` | ||||
|   | ||||
| @@ -29,7 +29,6 @@ import ( | ||||
| 	"code.gitea.io/gitea/services/context" | ||||
| 	"code.gitea.io/gitea/services/convert" | ||||
| 	issue_service "code.gitea.io/gitea/services/issue" | ||||
| 	notify_service "code.gitea.io/gitea/services/notify" | ||||
| ) | ||||
|  | ||||
| // SearchIssues searches for issues across the repositories that the user has access to | ||||
| @@ -803,12 +802,19 @@ func EditIssue(ctx *context.APIContext) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	oldTitle := issue.Title | ||||
| 	if len(form.Title) > 0 { | ||||
| 		issue.Title = form.Title | ||||
| 		err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusInternalServerError, "ChangeTitle", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if form.Body != nil { | ||||
| 		issue.Content = *form.Body | ||||
| 		err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusInternalServerError, "ChangeContent", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if form.Ref != nil { | ||||
| 		err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref) | ||||
| @@ -880,24 +886,14 @@ func EditIssue(ctx *context.APIContext) { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		issue.IsClosed = api.StateClosed == api.StateType(*form.State) | ||||
| 	} | ||||
| 	statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer) | ||||
| 	if err != nil { | ||||
| 		if issues_model.IsErrDependenciesLeft(err) { | ||||
| 			ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") | ||||
| 		if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil { | ||||
| 			if issues_model.IsErrDependenciesLeft(err) { | ||||
| 				ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") | ||||
| 				return | ||||
| 			} | ||||
| 			ctx.Error(http.StatusInternalServerError, "ChangeStatus", err) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if titleChanged { | ||||
| 		notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle) | ||||
| 	} | ||||
|  | ||||
| 	if statusChangeComment != nil { | ||||
| 		notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed) | ||||
| 	} | ||||
|  | ||||
| 	// Refetch from database to assign some automatic values | ||||
|   | ||||
| @@ -602,12 +602,19 @@ func EditPullRequest(ctx *context.APIContext) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	oldTitle := issue.Title | ||||
| 	if len(form.Title) > 0 { | ||||
| 		issue.Title = form.Title | ||||
| 		err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusInternalServerError, "ChangeTitle", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if len(form.Body) > 0 { | ||||
| 		issue.Content = form.Body | ||||
| 	if form.Body != nil { | ||||
| 		err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusInternalServerError, "ChangeContent", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Update or remove deadline if set | ||||
| @@ -686,24 +693,14 @@ func EditPullRequest(ctx *context.APIContext) { | ||||
| 			ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged") | ||||
| 			return | ||||
| 		} | ||||
| 		issue.IsClosed = api.StateClosed == api.StateType(*form.State) | ||||
| 	} | ||||
| 	statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer) | ||||
| 	if err != nil { | ||||
| 		if issues_model.IsErrDependenciesLeft(err) { | ||||
| 			ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies") | ||||
| 		if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil { | ||||
| 			if issues_model.IsErrDependenciesLeft(err) { | ||||
| 				ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies") | ||||
| 				return | ||||
| 			} | ||||
| 			ctx.Error(http.StatusInternalServerError, "ChangeStatus", err) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if titleChanged { | ||||
| 		notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle) | ||||
| 	} | ||||
|  | ||||
| 	if statusChangeComment != nil { | ||||
| 		notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed) | ||||
| 	} | ||||
|  | ||||
| 	// change pull target branch | ||||
|   | ||||
| @@ -194,6 +194,10 @@ func TestAPIEditIssue(t *testing.T) { | ||||
| 	issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) | ||||
| 	repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) | ||||
|  | ||||
| 	// check comment history | ||||
| 	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issueAfter.ID, OldTitle: issueBefore.Title, NewTitle: title}) | ||||
| 	unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: issueAfter.ID, ContentText: body, IsFirstCreated: false}) | ||||
|  | ||||
| 	// check deleted user | ||||
| 	assert.Equal(t, int64(500), issueAfter.PosterID) | ||||
| 	assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext)) | ||||
|   | ||||
| @@ -223,23 +223,33 @@ func TestAPIEditPull(t *testing.T) { | ||||
|  | ||||
| 	session := loginUser(t, owner10.Name) | ||||
| 	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | ||||
| 	title := "create a success pr" | ||||
| 	req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{ | ||||
| 		Head:  "develop", | ||||
| 		Base:  "master", | ||||
| 		Title: "create a success pr", | ||||
| 		Title: title, | ||||
| 	}).AddTokenAuth(token) | ||||
| 	pull := new(api.PullRequest) | ||||
| 	apiPull := new(api.PullRequest) | ||||
| 	resp := MakeRequest(t, req, http.StatusCreated) | ||||
| 	DecodeJSON(t, resp, pull) | ||||
| 	assert.EqualValues(t, "master", pull.Base.Name) | ||||
| 	DecodeJSON(t, resp, apiPull) | ||||
| 	assert.EqualValues(t, "master", apiPull.Base.Name) | ||||
|  | ||||
| 	req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{ | ||||
| 	newTitle := "edit a this pr" | ||||
| 	newBody := "edited body" | ||||
| 	req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, apiPull.Index), &api.EditPullRequestOption{ | ||||
| 		Base:  "feature/1", | ||||
| 		Title: "edit a this pr", | ||||
| 		Title: newTitle, | ||||
| 		Body:  &newBody, | ||||
| 	}).AddTokenAuth(token) | ||||
| 	resp = MakeRequest(t, req, http.StatusCreated) | ||||
| 	DecodeJSON(t, resp, pull) | ||||
| 	assert.EqualValues(t, "feature/1", pull.Base.Name) | ||||
| 	DecodeJSON(t, resp, apiPull) | ||||
| 	assert.EqualValues(t, "feature/1", apiPull.Base.Name) | ||||
| 	// check comment history | ||||
| 	pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID}) | ||||
| 	err := pull.LoadIssue(db.DefaultContext) | ||||
| 	assert.NoError(t, err) | ||||
| 	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: pull.Issue.ID, OldTitle: title, NewTitle: newTitle}) | ||||
| 	unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: pull.Issue.ID, ContentText: newBody, IsFirstCreated: false}) | ||||
|  | ||||
| 	req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{ | ||||
| 		Base: "not-exist", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user