Commit 88987766 authored by Kerri Miller's avatar Kerri Miller

Merge branch...

Merge branch '345934-introduce-a-new-get-projects-id-repository-changelog-api-endpoint-to-get-release-notes-for-a' into 'master'

Introduce a new `GET /projects/:id/repository/changelog` API endpoint

See merge request gitlab-org/gitlab!74730
parents 6eb064df dbfbb94c
...@@ -60,7 +60,7 @@ module Repositories ...@@ -60,7 +60,7 @@ module Repositories
end end
# rubocop: enable Metrics/ParameterLists # rubocop: enable Metrics/ParameterLists
def execute def execute(commit_to_changelog: true)
config = Gitlab::Changelog::Config.from_git(@project, @user) config = Gitlab::Changelog::Config.from_git(@project, @user)
from = start_of_commit_range(config) from = start_of_commit_range(config)
...@@ -93,9 +93,13 @@ module Repositories ...@@ -93,9 +93,13 @@ module Repositories
end end
end end
Gitlab::Changelog::Committer if commit_to_changelog
.new(@project, @user) Gitlab::Changelog::Committer
.commit(release: release, file: @file, branch: @branch, message: @message) .new(@project, @user)
.commit(release: release, file: @file, branch: @branch, message: @message)
else
Gitlab::Changelog::Generator.new.add(release)
end
end end
def start_of_commit_range(config) def start_of_commit_range(config)
......
...@@ -291,7 +291,7 @@ Example response: ...@@ -291,7 +291,7 @@ Example response:
} }
``` ```
## Generate changelog data ## Add changelog data to a changelog file
> [Introduced](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/351) in GitLab 13.9. > [Introduced](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/351) in GitLab 13.9.
...@@ -373,26 +373,26 @@ If the last tag is `v0.9.0` and the default branch is `main`, the range of commi ...@@ -373,26 +373,26 @@ If the last tag is `v0.9.0` and the default branch is `main`, the range of commi
included in this example is `v0.9.0..main`: included in this example is `v0.9.0..main`:
```shell ```shell
curl --header "PRIVATE-TOKEN: token" --data "version=1.0.0" "https://gitlab.com/api/v4/projects/42/repository/changelog" curl --request POST --header "PRIVATE-TOKEN: token" --data "version=1.0.0" "https://gitlab.com/api/v4/projects/42/repository/changelog"
``` ```
To generate the data on a different branch, specify the `branch` parameter. This To generate the data on a different branch, specify the `branch` parameter. This
command generates data from the `foo` branch: command generates data from the `foo` branch:
```shell ```shell
curl --header "PRIVATE-TOKEN: token" --data "version=1.0.0&branch=foo" "https://gitlab.com/api/v4/projects/42/repository/changelog" curl --request POST --header "PRIVATE-TOKEN: token" --data "version=1.0.0&branch=foo" "https://gitlab.com/api/v4/projects/42/repository/changelog"
``` ```
To use a different trailer, use the `trailer` parameter: To use a different trailer, use the `trailer` parameter:
```shell ```shell
curl --header "PRIVATE-TOKEN: token" --data "version=1.0.0&trailer=Type" "https://gitlab.com/api/v4/projects/42/repository/changelog" curl --request POST --header "PRIVATE-TOKEN: token" --data "version=1.0.0&trailer=Type" "https://gitlab.com/api/v4/projects/42/repository/changelog"
``` ```
To store the results in a different file, use the `file` parameter: To store the results in a different file, use the `file` parameter:
```shell ```shell
curl --header "PRIVATE-TOKEN: token" --data "version=1.0.0&file=NEWS" "https://gitlab.com/api/v4/projects/42/repository/changelog" curl --request POST --header "PRIVATE-TOKEN: token" --data "version=1.0.0&file=NEWS" "https://gitlab.com/api/v4/projects/42/repository/changelog"
``` ```
### How it works ### How it works
...@@ -707,3 +707,39 @@ tag_regex: '^version-(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$' ...@@ -707,3 +707,39 @@ tag_regex: '^version-(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$'
To test if your regular expression is working, you can use websites such as To test if your regular expression is working, you can use websites such as
[regex101](https://regex101.com/). If the regular expression syntax is invalid, [regex101](https://regex101.com/). If the regular expression syntax is invalid,
an error is produced when generating a changelog. an error is produced when generating a changelog.
## Generate changelog data
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345934) in GitLab 14.6.
Generate changelog data based on commits in a repository, without committing
them to a changelog file.
Works exactly like `POST /projects/:id/repository/changelog`, except the changelog
data isn't committed to any changelog file.
```plaintext
GET /projects/:id/repository/changelog
```
Supported attributes:
| Attribute | Type | Required | Description |
| :-------- | :------- | :--------- | :---------- |
| `version` | string | yes | The version to generate the changelog for. The format must follow [semantic versioning](https://semver.org/). |
| `from` | string | no | The start of the range of commits (as a SHA) to use for generating the changelog. This commit itself isn't included in the list. |
| `to` | string | no | The end of the range of commits (as a SHA) to use for the changelog. This commit _is_ included in the list. Defaults to the branch specified in the `branch` attribute. |
| `date` | datetime | no | The date and time of the release, ISO 8601 formatted. Example: `2016-03-11T03:45:40Z`. Defaults to the current time. |
| `trailer` | string | no | The Git trailer to use for including commits, defaults to `Changelog`. |
```shell
curl --header "PRIVATE-TOKEN: token" --data "version=1.0.0" "https://gitlab.com/api/v4/projects/42/repository/changelog"
```
Example Response:
```json
{
"notes": "## 1.0.0 (2021-11-17)\n\n### feature (2 changes)\n\n- [Title 2](namespace13/project13@ad608eb642124f5b3944ac0ac772fecaf570a6bf) ([merge request](namespace13/project13!2))\n- [Title 1](namespace13/project13@3c6b80ff7034fa0d585314e1571cc780596ce3c8) ([merge request](namespace13/project13!1))\n"
}
```
# frozen_string_literal: true
module API
module Entities
class Changelog < Grape::Entity
expose :to_s, as: :notes
end
end
end
...@@ -10,6 +10,32 @@ module API ...@@ -10,6 +10,32 @@ module API
helpers ::API::Helpers::HeadersHelpers helpers ::API::Helpers::HeadersHelpers
helpers do
params :release_params do
requires :version,
type: String,
regexp: Gitlab::Regex.unbounded_semver_regex,
desc: 'The version of the release, using the semantic versioning format'
optional :from,
type: String,
desc: 'The first commit in the range of commits to use for the changelog'
optional :to,
type: String,
desc: 'The last commit in the range of commits to use for the changelog'
optional :date,
type: DateTime,
desc: 'The date and time of the release'
optional :trailer,
type: String,
desc: 'The Git trailer to use for determining if commits are to be included in the changelog',
default: ::Repositories::ChangelogService::DEFAULT_TRAILER
end
end
before { authorize! :download_code, user_project } before { authorize! :download_code, user_project }
feature_category :source_code_management feature_category :source_code_management
...@@ -208,36 +234,33 @@ module API ...@@ -208,36 +234,33 @@ module API
end end
end end
desc 'Generates a changelog section for a release' do desc 'Generates a changelog section for a release and returns it' do
detail 'This feature was introduced in GitLab 13.9' detail 'This feature was introduced in GitLab 14.6'
end end
params do params do
requires :version, use :release_params
type: String, end
regexp: Gitlab::Regex.unbounded_semver_regex, get ':id/repository/changelog' do
desc: 'The version of the release, using the semantic versioning format' service = ::Repositories::ChangelogService.new(
user_project,
optional :from, current_user,
type: String, **declared_params(include_missing: false)
desc: 'The first commit in the range of commits to use for the changelog' )
changelog = service.execute(commit_to_changelog: false)
optional :to, present changelog, with: Entities::Changelog
type: String, end
desc: 'The last commit in the range of commits to use for the changelog'
optional :date, desc 'Generates a changelog section for a release and commits it in a changelog file' do
type: DateTime, detail 'This feature was introduced in GitLab 13.9'
desc: 'The date and time of the release' end
params do
use :release_params
optional :branch, optional :branch,
type: String, type: String,
desc: 'The branch to commit the changelog changes to' desc: 'The branch to commit the changelog changes to'
optional :trailer,
type: String,
desc: 'The Git trailer to use for determining if commits are to be included in the changelog',
default: ::Repositories::ChangelogService::DEFAULT_TRAILER
optional :file, optional :file,
type: String, type: String,
desc: 'The file to commit the changelog changes to', desc: 'The file to commit the changelog changes to',
...@@ -261,7 +284,7 @@ module API ...@@ -261,7 +284,7 @@ module API
**declared_params(include_missing: false) **declared_params(include_missing: false)
) )
service.execute service.execute(commit_to_changelog: true)
status(200) status(200)
rescue Gitlab::Changelog::Error => ex rescue Gitlab::Changelog::Error => ex
render_api_error!("Failed to generate the changelog: #{ex.message}", 422) render_api_error!("Failed to generate the changelog: #{ex.message}", 422)
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Entities::Changelog do
let(:changelog) { "This is a changelog" }
subject { described_class.new(changelog).as_json }
it 'exposes correct attributes' do
expect(subject).to include(:notes)
end
it 'exposes correct notes' do
expect(subject[:notes]).to eq(changelog)
end
end
...@@ -731,6 +731,71 @@ RSpec.describe API::Repositories do ...@@ -731,6 +731,71 @@ RSpec.describe API::Repositories do
end end
end end
describe 'GET /projects/:id/repository/changelog' do
it 'generates the changelog for a version' do
spy = instance_spy(Repositories::ChangelogService)
release_notes = 'Release notes'
allow(Repositories::ChangelogService)
.to receive(:new)
.with(
project,
user,
version: '1.0.0',
from: 'foo',
to: 'bar',
date: DateTime.new(2020, 1, 1),
trailer: 'Foo'
)
.and_return(spy)
expect(spy).to receive(:execute).with(commit_to_changelog: false).and_return(release_notes)
get(
api("/projects/#{project.id}/repository/changelog", user),
params: {
version: '1.0.0',
from: 'foo',
to: 'bar',
date: '2020-01-01',
trailer: 'Foo'
}
)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['notes']).to eq(release_notes)
end
it 'supports leaving out the from and to attribute' do
spy = instance_spy(Repositories::ChangelogService)
allow(Repositories::ChangelogService)
.to receive(:new)
.with(
project,
user,
version: '1.0.0',
date: DateTime.new(2020, 1, 1),
trailer: 'Foo'
)
.and_return(spy)
expect(spy).to receive(:execute).with(commit_to_changelog: false)
get(
api("/projects/#{project.id}/repository/changelog", user),
params: {
version: '1.0.0',
date: '2020-01-01',
trailer: 'Foo'
}
)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['notes']).to be_present
end
end
describe 'POST /projects/:id/repository/changelog' do describe 'POST /projects/:id/repository/changelog' do
it 'generates the changelog for a version' do it 'generates the changelog for a version' do
spy = instance_spy(Repositories::ChangelogService) spy = instance_spy(Repositories::ChangelogService)
...@@ -751,7 +816,7 @@ RSpec.describe API::Repositories do ...@@ -751,7 +816,7 @@ RSpec.describe API::Repositories do
) )
.and_return(spy) .and_return(spy)
allow(spy).to receive(:execute) allow(spy).to receive(:execute).with(commit_to_changelog: true)
post( post(
api("/projects/#{project.id}/repository/changelog", user), api("/projects/#{project.id}/repository/changelog", user),
...@@ -787,7 +852,7 @@ RSpec.describe API::Repositories do ...@@ -787,7 +852,7 @@ RSpec.describe API::Repositories do
) )
.and_return(spy) .and_return(spy)
expect(spy).to receive(:execute) expect(spy).to receive(:execute).with(commit_to_changelog: true)
post( post(
api("/projects/#{project.id}/repository/changelog", user), api("/projects/#{project.id}/repository/changelog", user),
......
...@@ -61,6 +61,8 @@ RSpec.describe Repositories::ChangelogService do ...@@ -61,6 +61,8 @@ RSpec.describe Repositories::ChangelogService do
let!(:commit2) { project.commit(sha3) } let!(:commit2) { project.commit(sha3) }
let!(:commit3) { project.commit(sha4) } let!(:commit3) { project.commit(sha4) }
let(:commit_to_changelog) { true }
it 'generates and commits a changelog section' do it 'generates and commits a changelog section' do
allow(MergeRequestDiffCommit) allow(MergeRequestDiffCommit)
.to receive(:oldest_merge_request_id_per_commit) .to receive(:oldest_merge_request_id_per_commit)
...@@ -73,7 +75,7 @@ RSpec.describe Repositories::ChangelogService do ...@@ -73,7 +75,7 @@ RSpec.describe Repositories::ChangelogService do
service = described_class service = described_class
.new(project, creator, version: '1.0.0', from: sha1, to: sha3) .new(project, creator, version: '1.0.0', from: sha1, to: sha3)
recorder = ActiveRecord::QueryRecorder.new { service.execute } recorder = ActiveRecord::QueryRecorder.new { service.execute(commit_to_changelog: commit_to_changelog) }
changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
expect(recorder.count).to eq(9) expect(recorder.count).to eq(9)
...@@ -90,7 +92,7 @@ RSpec.describe Repositories::ChangelogService do ...@@ -90,7 +92,7 @@ RSpec.describe Repositories::ChangelogService do
described_class described_class
.new(project, creator, version: '1.0.0', from: sha1) .new(project, creator, version: '1.0.0', from: sha1)
.execute .execute(commit_to_changelog: commit_to_changelog)
changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
...@@ -108,7 +110,7 @@ RSpec.describe Repositories::ChangelogService do ...@@ -108,7 +110,7 @@ RSpec.describe Repositories::ChangelogService do
described_class described_class
.new(project, creator, version: '1.0.0', from: sha1) .new(project, creator, version: '1.0.0', from: sha1)
.execute .execute(commit_to_changelog: commit_to_changelog)
changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
...@@ -119,12 +121,33 @@ RSpec.describe Repositories::ChangelogService do ...@@ -119,12 +121,33 @@ RSpec.describe Repositories::ChangelogService do
it 'uses the target branch when "to" is unspecified' do it 'uses the target branch when "to" is unspecified' do
described_class described_class
.new(project, creator, version: '1.0.0', from: sha1) .new(project, creator, version: '1.0.0', from: sha1)
.execute .execute(commit_to_changelog: commit_to_changelog)
changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data changelog = project.repository.blob_at('master', 'CHANGELOG.md')&.data
expect(changelog).to include('Title 1', 'Title 2', 'Title 3') expect(changelog).to include('Title 1', 'Title 2', 'Title 3')
end end
describe 'with commit_to_changelog: false' do
let(:commit_to_changelog) { false }
it 'generates changelog section' do
allow(MergeRequestDiffCommit)
.to receive(:oldest_merge_request_id_per_commit)
.with(project.id, [commit2.id, commit1.id])
.and_return([
{ sha: sha2, merge_request_id: mr1.id },
{ sha: sha3, merge_request_id: mr2.id }
])
service = described_class
.new(project, creator, version: '1.0.0', from: sha1, to: sha3)
changelog = service.execute(commit_to_changelog: commit_to_changelog)
expect(changelog).to include('Title 1', 'Title 2')
end
end
end end
describe '#start_of_commit_range' do describe '#start_of_commit_range' do
......
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