Expose content_version for optimistic locking on issue and PR edits (#37035)
- Add `content_version` field to Issue and PullRequest API responses
- Accept optional `content_version` in `PATCH
/repos/{owner}/{repo}/issues/{index}` and `PATCH
/repos/{owner}/{repo}/pulls/{index}` — returns 409 Conflict when stale,
succeeds silently when omitted (backward compatible)
- Pre-check `content_version` before any mutations to prevent partial
writes (e.g. title updated but body rejected)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -657,6 +657,15 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fail fast: if content_version is provided and already stale, reject
|
||||
// before any mutations. The DB-level check in ChangeContent still
|
||||
// handles concurrent requests.
|
||||
// TODO: wrap all mutations in a transaction to fully prevent partial writes.
|
||||
if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion {
|
||||
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged)
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Title) > 0 {
|
||||
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||
if err != nil {
|
||||
@@ -665,10 +674,14 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
if form.Body != nil {
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
|
||||
contentVersion := issue.ContentVersion
|
||||
if form.ContentVersion != nil {
|
||||
contentVersion = *form.ContentVersion
|
||||
}
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion)
|
||||
if err != nil {
|
||||
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user