Commit 69c310ae authored by Sean McGivern's avatar Sean McGivern

Merge branch 'rs-cherry-pick-revert-dry-run' into 'master'

Support dry-run cherry-picks and reverts

Closes #231032

See merge request gitlab-org/gitlab!37240
parents bd10bff3 0e076f80
...@@ -462,7 +462,7 @@ group :ed25519 do ...@@ -462,7 +462,7 @@ group :ed25519 do
end end
# Gitaly GRPC protocol definitions # Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.2.0.pre.rc2' gem 'gitaly', '~> 13.3.0-rc1'
gem 'grpc', '~> 1.24.0' gem 'grpc', '~> 1.24.0'
......
...@@ -400,7 +400,7 @@ GEM ...@@ -400,7 +400,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
git (1.5.0) git (1.5.0)
gitaly (13.2.0.pre.rc2) gitaly (13.3.0.pre.rc1)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-chronic (0.10.5) gitlab-chronic (0.10.5)
...@@ -1262,7 +1262,7 @@ DEPENDENCIES ...@@ -1262,7 +1262,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.2.0.pre.rc2) gitaly (~> 13.3.0.pre.rc1)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.12.1) gitlab-labkit (= 0.12.1)
......
...@@ -852,7 +852,7 @@ class Repository ...@@ -852,7 +852,7 @@ class Repository
def revert( def revert(
user, commit, branch_name, message, user, commit, branch_name, message,
start_branch_name: nil, start_project: project) start_branch_name: nil, start_project: project, dry_run: false)
with_cache_hooks do with_cache_hooks do
raw_repository.revert( raw_repository.revert(
...@@ -861,14 +861,15 @@ class Repository ...@@ -861,14 +861,15 @@ class Repository
branch_name: branch_name, branch_name: branch_name,
message: message, message: message,
start_branch_name: start_branch_name, start_branch_name: start_branch_name,
start_repository: start_project.repository.raw_repository start_repository: start_project.repository.raw_repository,
dry_run: dry_run
) )
end end
end end
def cherry_pick( def cherry_pick(
user, commit, branch_name, message, user, commit, branch_name, message,
start_branch_name: nil, start_project: project) start_branch_name: nil, start_project: project, dry_run: false)
with_cache_hooks do with_cache_hooks do
raw_repository.cherry_pick( raw_repository.cherry_pick(
...@@ -877,7 +878,8 @@ class Repository ...@@ -877,7 +878,8 @@ class Repository
branch_name: branch_name, branch_name: branch_name,
message: message, message: message,
start_branch_name: start_branch_name, start_branch_name: start_branch_name,
start_repository: start_project.repository.raw_repository start_repository: start_project.repository.raw_repository,
dry_run: dry_run
) )
end end
end end
......
...@@ -22,7 +22,9 @@ module Commits ...@@ -22,7 +22,9 @@ module Commits
@branch_name, @branch_name,
message, message,
start_project: @start_project, start_project: @start_project,
start_branch_name: @start_branch) start_branch_name: @start_branch,
dry_run: @dry_run
)
rescue Gitlab::Git::Repository::CreateTreeError => ex rescue Gitlab::Git::Repository::CreateTreeError => ex
act = action.to_s.dasherize act = action.to_s.dasherize
type = @commit.change_type_title(current_user) type = @commit.change_type_title(current_user)
......
...@@ -21,6 +21,7 @@ module Commits ...@@ -21,6 +21,7 @@ module Commits
@start_sha = params[:start_sha] @start_sha = params[:start_sha]
@branch_name = params[:branch_name] @branch_name = params[:branch_name]
@force = params[:force] || false @force = params[:force] || false
@dry_run = params[:dry_run] || false
end end
def execute def execute
......
---
title: Support dry-run cherry-picks and reverts via API
merge_request: 37240
author:
type: added
...@@ -299,9 +299,10 @@ Parameters: ...@@ -299,9 +299,10 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `sha` | string | yes | The commit hash | | `sha` | string | yes | The commit hash |
| `branch` | string | yes | The name of the branch | | `branch` | string | yes | The name of the branch |
| `dry_run` | boolean | no | Does not commit any changes. Default is false. [Introduced in GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/issues/231032) |
```shell ```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/cherry_pick" curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/master/cherry_pick"
...@@ -345,6 +346,19 @@ indicates that the commit already exists in the target branch. The other ...@@ -345,6 +346,19 @@ indicates that the commit already exists in the target branch. The other
possible error code is `conflict`, which indicates that there was a merge possible error code is `conflict`, which indicates that there was a merge
conflict. conflict.
When `dry_run` is enabled, the server will attempt to apply the cherry-pick _but
not actually commit any resulting changes_. If the cherry-pick applies cleanly,
the API will respond with `200 OK`:
```json
{
"dry_run": "success"
}
```
In the event of a failure, you'll see an error identical to a failure without
dry run.
## Revert a commit ## Revert a commit
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22919) in GitLab 11.5. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/22919) in GitLab 11.5.
...@@ -362,6 +376,7 @@ Parameters: ...@@ -362,6 +376,7 @@ Parameters:
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `sha` | string | yes | Commit SHA to revert | | `sha` | string | yes | Commit SHA to revert |
| `branch` | string | yes | Target branch name | | `branch` | string | yes | Target branch name |
| `dry_run` | boolean | no | Does not commit any changes. Default is false. [Introduced in GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/issues/231032) |
```shell ```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/a738f717824ff53aebad8b090c1b79a14f2bd9e8/revert" curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/a738f717824ff53aebad8b090c1b79a14f2bd9e8/revert"
...@@ -400,6 +415,19 @@ In this case, the revert failed because the attempted revert generated a merge ...@@ -400,6 +415,19 @@ In this case, the revert failed because the attempted revert generated a merge
conflict. The other possible error code is `empty`, which indicates that the conflict. The other possible error code is `empty`, which indicates that the
changeset was empty, likely due to the change having already been reverted. changeset was empty, likely due to the change having already been reverted.
When `dry_run` is enabled, the server will attempt to apply the revert _but not
actually commit any resulting changes_. If the revert applies cleanly, the API
will respond with `200 OK`:
```json
{
"dry_run": "success"
}
```
In the event of a failure, you'll see an error identical to a failure without
dry run.
## Get the diff of a commit ## Get the diff of a commit
Get the diff of a commit in a project. Get the diff of a commit in a project.
......
...@@ -203,6 +203,7 @@ module API ...@@ -203,6 +203,7 @@ module API
params do params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked' requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked'
requires :branch, type: String, desc: 'The name of the branch', allow_blank: false requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes"
end end
post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
authorize_push_to_branch!(params[:branch]) authorize_push_to_branch!(params[:branch])
...@@ -215,7 +216,8 @@ module API ...@@ -215,7 +216,8 @@ module API
commit_params = { commit_params = {
commit: commit, commit: commit,
start_branch: params[:branch], start_branch: params[:branch],
branch_name: params[:branch] branch_name: params[:branch],
dry_run: params[:dry_run]
} }
result = ::Commits::CherryPickService result = ::Commits::CherryPickService
...@@ -223,10 +225,18 @@ module API ...@@ -223,10 +225,18 @@ module API
.execute .execute
if result[:status] == :success if result[:status] == :success
if params[:dry_run]
present dry_run: :success
status :ok
else
present user_project.repository.commit(result[:result]), present user_project.repository.commit(result[:result]),
with: Entities::Commit with: Entities::Commit
end
else else
error!(result.slice(:message, :error_code), 400, header) response = result.slice(:message, :error_code)
response[:dry_run] = :error if params[:dry_run]
error!(response, 400, header)
end end
end end
...@@ -237,6 +247,7 @@ module API ...@@ -237,6 +247,7 @@ module API
params do params do
requires :sha, type: String, desc: 'Commit SHA to revert' requires :sha, type: String, desc: 'Commit SHA to revert'
requires :branch, type: String, desc: 'Target branch name', allow_blank: false requires :branch, type: String, desc: 'Target branch name', allow_blank: false
optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes"
end end
post ':id/repository/commits/:sha/revert', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do post ':id/repository/commits/:sha/revert', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
authorize_push_to_branch!(params[:branch]) authorize_push_to_branch!(params[:branch])
...@@ -249,7 +260,8 @@ module API ...@@ -249,7 +260,8 @@ module API
commit_params = { commit_params = {
commit: commit, commit: commit,
start_branch: params[:branch], start_branch: params[:branch],
branch_name: params[:branch] branch_name: params[:branch],
dry_run: params[:dry_run]
} }
result = ::Commits::RevertService result = ::Commits::RevertService
...@@ -257,10 +269,18 @@ module API ...@@ -257,10 +269,18 @@ module API
.execute .execute
if result[:status] == :success if result[:status] == :success
if params[:dry_run]
present dry_run: :success
status :ok
else
present user_project.repository.commit(result[:result]), present user_project.repository.commit(result[:result]),
with: Entities::Commit with: Entities::Commit
end
else else
error!(result.slice(:message, :error_code), 400, header) response = result.slice(:message, :error_code)
response[:dry_run] = :error if params[:dry_run]
error!(response, 400, header)
end end
end end
......
...@@ -598,14 +598,15 @@ module Gitlab ...@@ -598,14 +598,15 @@ module Gitlab
end end
end end
def revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) def revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false)
args = { args = {
user: user, user: user,
commit: commit, commit: commit,
branch_name: branch_name, branch_name: branch_name,
message: message, message: message,
start_branch_name: start_branch_name, start_branch_name: start_branch_name,
start_repository: start_repository start_repository: start_repository,
dry_run: dry_run
} }
wrapped_gitaly_errors do wrapped_gitaly_errors do
...@@ -613,14 +614,15 @@ module Gitlab ...@@ -613,14 +614,15 @@ module Gitlab
end end
end end
def cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) def cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false)
args = { args = {
user: user, user: user,
commit: commit, commit: commit,
branch_name: branch_name, branch_name: branch_name,
message: message, message: message,
start_branch_name: start_branch_name, start_branch_name: start_branch_name,
start_repository: start_repository start_repository: start_repository,
dry_run: dry_run
} }
wrapped_gitaly_errors do wrapped_gitaly_errors do
......
...@@ -187,24 +187,26 @@ module Gitlab ...@@ -187,24 +187,26 @@ module Gitlab
raise Gitlab::Git::CommitError, e raise Gitlab::Git::CommitError, e
end end
def user_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) def user_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false)
call_cherry_pick_or_revert(:cherry_pick, call_cherry_pick_or_revert(:cherry_pick,
user: user, user: user,
commit: commit, commit: commit,
branch_name: branch_name, branch_name: branch_name,
message: message, message: message,
start_branch_name: start_branch_name, start_branch_name: start_branch_name,
start_repository: start_repository) start_repository: start_repository,
dry_run: dry_run)
end end
def user_revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) def user_revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run: false)
call_cherry_pick_or_revert(:revert, call_cherry_pick_or_revert(:revert,
user: user, user: user,
commit: commit, commit: commit,
branch_name: branch_name, branch_name: branch_name,
message: message, message: message,
start_branch_name: start_branch_name, start_branch_name: start_branch_name,
start_repository: start_repository) start_repository: start_repository,
dry_run: dry_run)
end end
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:, push_options: []) def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:, push_options: [])
...@@ -390,7 +392,7 @@ module Gitlab ...@@ -390,7 +392,7 @@ module Gitlab
response response
end end
def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:, dry_run:)
request_class = "Gitaly::User#{rpc.to_s.camelcase}Request".constantize request_class = "Gitaly::User#{rpc.to_s.camelcase}Request".constantize
request = request_class.new( request = request_class.new(
...@@ -400,7 +402,8 @@ module Gitlab ...@@ -400,7 +402,8 @@ module Gitlab
branch_name: encode_binary(branch_name), branch_name: encode_binary(branch_name),
message: encode_binary(message), message: encode_binary(message),
start_branch_name: encode_binary(start_branch_name.to_s), start_branch_name: encode_binary(start_branch_name.to_s),
start_repository: start_repository.gitaly_repository start_repository: start_repository.gitaly_repository,
dry_run: dry_run
) )
response = GitalyClient.call( response = GitalyClient.call(
......
...@@ -1462,6 +1462,16 @@ RSpec.describe API::Commits do ...@@ -1462,6 +1462,16 @@ RSpec.describe API::Commits do
expect(json_response['author_name']).to eq(commit.author_name) expect(json_response['author_name']).to eq(commit.author_name)
expect(json_response['committer_name']).to eq(user.name) expect(json_response['committer_name']).to eq(user.name)
end end
it 'supports dry-run without applying changes' do
head = project.commit(branch)
post api(route, current_user), params: { branch: branch, dry_run: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq("dry_run" => "success")
expect(project.commit(branch)).to eq(head)
end
end end
context 'when repository is disabled' do context 'when repository is disabled' do
...@@ -1533,6 +1543,14 @@ RSpec.describe API::Commits do ...@@ -1533,6 +1543,14 @@ RSpec.describe API::Commits do
expect(json_response['error_code']).to eq 'empty' expect(json_response['error_code']).to eq 'empty'
end end
it 'includes an additional dry_run error field when enabled' do
post api(route, current_user), params: { branch: 'markdown', dry_run: true }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error_code']).to eq 'empty'
expect(json_response['dry_run']).to eq 'error'
end
end end
context 'when ref contains a dot' do context 'when ref contains a dot' do
...@@ -1623,6 +1641,16 @@ RSpec.describe API::Commits do ...@@ -1623,6 +1641,16 @@ RSpec.describe API::Commits do
expect(json_response['committer_name']).to eq(user.name) expect(json_response['committer_name']).to eq(user.name)
expect(json_response['parent_ids']).to contain_exactly(commit_id) expect(json_response['parent_ids']).to contain_exactly(commit_id)
end end
it 'supports dry-run without applying changes' do
head = project.commit(branch)
post api(route, current_user), params: { branch: branch, dry_run: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq("dry_run" => "success")
expect(project.commit(branch)).to eq(head)
end
end end
context 'when repository is disabled' do context 'when repository is disabled' do
...@@ -1704,6 +1732,18 @@ RSpec.describe API::Commits do ...@@ -1704,6 +1732,18 @@ RSpec.describe API::Commits do
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error_code']).to eq 'empty' expect(json_response['error_code']).to eq 'empty'
end end
it 'includes an additional dry_run error field when enabled' do
# First one actually reverts
post api(route, current_user), params: { branch: 'markdown' }
# Second one is redundant and should be empty
post api(route, current_user), params: { branch: 'markdown', dry_run: true }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error_code']).to eq 'empty'
expect(json_response['dry_run']).to eq 'error'
end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment