Commit a43a218c authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'releases-feature'

parents aec9f211 743d66e4
......@@ -39,6 +39,7 @@ v 8.2.0 (unreleased)
- Improve Continuous Integration graphs page
- Make color of "Accept Merge Request" button consistent with current build status
- Add ignore white space option in merge request diff and commit and compare view
- Ability to add release notes (markdown text and attachments) to git tags (aka Releases)
v 8.1.4
- Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu)
......
# Repositories
## List project repository tags
Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
```
GET /projects/:id/repository/tags
```
Parameters:
- `id` (required) - The ID of a project
```json
[
{
"commit": {
"author_name": "John Smith",
"author_email": "john@example.com",
"authored_date": "2012-05-28T04:42:42-07:00",
"committed_date": "2012-05-28T04:42:42-07:00",
"committer_name": "Jack Smith",
"committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit",
"parents_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe"
]
},
"name": "v1.0.0",
"message": null
}
]
```
## Create a new tag
Creates new tag in the repository that points to the supplied ref.
```
POST /projects/:id/repository/tags
```
Parameters:
- `id` (required) - The ID of a project
- `tag_name` (required) - The name of a tag
- `ref` (required) - Create tag using commit SHA, another tag name, or branch name.
- `message` (optional) - Creates annotated tag.
```json
{
"commit": {
"author_name": "John Smith",
"author_email": "john@example.com",
"authored_date": "2012-05-28T04:42:42-07:00",
"committed_date": "2012-05-28T04:42:42-07:00",
"committer_name": "Jack Smith",
"committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit",
"parents_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe"
]
},
"name": "v1.0.0",
"message": null
}
```
The message will be `nil` when creating a lightweight tag otherwise
it will contain the annotation.
It returns 200 if the operation succeed. In case of an error,
405 with an explaining error message is returned.
## List repository tree
Get a list of repository files and directories in a project.
......
# Tags
## List project repository tags
Get a list of repository tags from a project, sorted by name in reverse alphabetical order.
```
GET /projects/:id/repository/tags
```
Parameters:
- `id` (required) - The ID of a project
```json
[
{
"commit": {
"author_name": "John Smith",
"author_email": "john@example.com",
"authored_date": "2012-05-28T04:42:42-07:00",
"committed_date": "2012-05-28T04:42:42-07:00",
"committer_name": "Jack Smith",
"committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit",
"parents_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe"
]
},
"release": {
"tag": "1.0.0",
"description": "Amazing release. Wow"
},
"name": "v1.0.0",
"message": null
}
]
```
## Create a new tag
Creates a new tag in the repository that points to the supplied ref.
```
POST /projects/:id/repository/tags
```
Parameters:
- `id` (required) - The ID of a project
- `tag_name` (required) - The name of a tag
- `ref` (required) - Create tag using commit SHA, another tag name, or branch name.
- `message` (optional) - Creates annotated tag.
- `release_description` (optional) - Add release notes to the git tag and store it in the GitLab database.
```json
{
"commit": {
"author_name": "John Smith",
"author_email": "john@example.com",
"authored_date": "2012-05-28T04:42:42-07:00",
"committed_date": "2012-05-28T04:42:42-07:00",
"committer_name": "Jack Smith",
"committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit",
"parents_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe"
]
},
"release": {
"tag": "1.0.0",
"description": "Amazing release. Wow"
},
"name": "v1.0.0",
"message": null
}
```
The message will be `nil` when creating a lightweight tag otherwise
it will contain the annotation.
It returns 200 if the operation succeed. In case of an error,
405 with an explaining error message is returned.
## New release
Add release notes to the existing git tag
```
PUT /projects/:id/repository/:tag/release
```
Parameters:
- `id` (required) - The ID of a project
- `tag` (required) - The name of a tag
- `description` (required) - Release notes with markdown support
```json
{
"tag": "1.0.0",
"description": "Amazing release. Wow"
}
```
......@@ -13,5 +13,6 @@
- [Project users](add-user/add-user.md)
- [Protected branches](protected_branches.md)
- [Web Editor](web_editor.md)
- [Releases](releases.md)
- [Merge Requests](merge_requests.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
# Releases
You can turn any git tag into a release, by adding a note to it.
Release notes behave like any other markdown form in GitLab so you can write text and drag-n-drop files to it.
Release notes are stored in the database of GitLab.
There are several ways to add release notes:
* In the interface, when you create a new git tag with GitLab
* In the interface, by adding a note to an existing git tag
* with the GitLab API
## New tag page with release notes text area
![new_tag](releases/new_tag.png)
## Tags page with button to add or edit release notes for existing git tag
![tags](releases/tags.png)
......@@ -52,5 +52,6 @@ module API
mount Labels
mount Settings
mount Keys
mount Tags
end
end
......@@ -95,25 +95,6 @@ module API
end
end
class RepoTag < Grape::Entity
expose :name
expose :message do |repo_obj, _options|
if repo_obj.respond_to?(:message)
repo_obj.message
else
nil
end
end
expose :commit do |repo_obj, options|
if repo_obj.respond_to?(:commit)
repo_obj.commit
elsif options[:project]
options[:project].repository.commit(repo_obj.target)
end
end
end
class RepoObject < Grape::Entity
expose :name
......@@ -341,5 +322,34 @@ module API
expose :user_oauth_applications
expose :after_sign_out_path
end
class Release < Grape::Entity
expose :tag, :description
end
class RepoTag < Grape::Entity
expose :name
expose :message do |repo_obj, _options|
if repo_obj.respond_to?(:message)
repo_obj.message
else
nil
end
end
expose :commit do |repo_obj, options|
if repo_obj.respond_to?(:commit)
repo_obj.commit
elsif options[:project]
options[:project].repository.commit(repo_obj.target)
end
end
expose :release, using: Entities::Release do |repo_obj, options|
if options[:project]
options[:project].releases.find_by(tag: repo_obj.name)
end
end
end
end
end
......@@ -16,41 +16,6 @@ module API
end
end
# Get a project repository tags
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# GET /projects/:id/repository/tags
get ":id/repository/tags" do
present user_project.repo.tags.sort_by(&:name).reverse,
with: Entities::RepoTag, project: user_project
end
# Create tag
#
# Parameters:
# id (required) - The ID of a project
# tag_name (required) - The name of the tag
# ref (required) - Create tag from commit sha or branch
# message (optional) - Specifying a message creates an annotated tag.
# Example Request:
# POST /projects/:id/repository/tags
post ':id/repository/tags' do
authorize_push_project
message = params[:message] || nil
result = CreateTagService.new(user_project, current_user).
execute(params[:tag_name], params[:ref], message)
if result[:status] == :success
present result[:tag],
with: Entities::RepoTag,
project: user_project
else
render_api_error!(result[:message], 400)
end
end
# Get a project repository tree
#
# Parameters:
......
module API
# Git Tags API
class Tags < Grape::API
before { authenticate! }
before { authorize! :download_code, user_project }
resource :projects do
# Get a project repository tags
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# GET /projects/:id/repository/tags
get ":id/repository/tags" do
present user_project.repo.tags.sort_by(&:name).reverse,
with: Entities::RepoTag, project: user_project
end
# Create tag
#
# Parameters:
# id (required) - The ID of a project
# tag_name (required) - The name of the tag
# ref (required) - Create tag from commit sha or branch
# message (optional) - Specifying a message creates an annotated tag.
# Example Request:
# POST /projects/:id/repository/tags
post ':id/repository/tags' do
authorize_push_project
message = params[:message] || nil
result = CreateTagService.new(user_project, current_user).
execute(params[:tag_name], params[:ref], message, params[:release_description])
if result[:status] == :success
present result[:tag],
with: Entities::RepoTag,
project: user_project
else
render_api_error!(result[:message], 400)
end
end
# Add release notes to tag
#
# Parameters:
# id (required) - The ID of a project
# tag (required) - The name of the tag
# description (required) - Release notes with markdown support
# Example Request:
# PUT /projects/:id/repository/tags
put ':id/repository/:tag/release', requirements: { tag: /.*/ } do
authorize_push_project
required_attributes! [:description]
release = user_project.releases.find_or_initialize_by(tag: params[:tag])
release.update_attributes(description: params[:description])
present release, with: Entities::Release
end
end
end
end
namespace :grape do
desc 'Print compiled grape routes'
task routes: :environment do
API::API.routes.each do |route|
puts route
end
end
end
......@@ -11,81 +11,6 @@ describe API::API, api: true do
let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
describe "GET /projects/:id/repository/tags" do
it "should return an array of project tags" do
get api("/projects/#{project.id}/repository/tags", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name)
end
end
describe 'POST /projects/:id/repository/tags' do
context 'lightweight tags' do
it 'should create a new tag' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v7.0.1',
ref: 'master'
expect(response.status).to eq(201)
expect(json_response['name']).to eq('v7.0.1')
end
end
context 'annotated tag' do
it 'should create a new annotated tag' do
# Identity must be set in .gitconfig to create annotated tag.
repo_path = project.repository.path_to_repo
system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name}))
system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email}))
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v7.1.0',
ref: 'master',
message: 'Release 7.1.0'
expect(response.status).to eq(201)
expect(json_response['name']).to eq('v7.1.0')
expect(json_response['message']).to eq('Release 7.1.0')
end
end
it 'should deny for user without push access' do
post api("/projects/#{project.id}/repository/tags", user2),
tag_name: 'v1.9.0',
ref: '621491c677087aa243f165eab467bfdfbee00be1'
expect(response.status).to eq(403)
end
it 'should return 400 if tag name is invalid' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v 1.0.0',
ref: 'master'
expect(response.status).to eq(400)
expect(json_response['message']).to eq('Tag name invalid')
end
it 'should return 400 if tag already exists' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v8.0.0',
ref: 'master'
expect(response.status).to eq(201)
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v8.0.0',
ref: 'master'
expect(response.status).to eq(400)
expect(json_response['message']).to eq('Tag already exists')
end
it 'should return 400 if ref name is invalid' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'mytag',
ref: 'foo'
expect(response.status).to eq(400)
expect(json_response['message']).to eq('Invalid reference name')
end
end
describe "GET /projects/:id/repository/tree" do
context "authorized user" do
before { project.team << [user2, :reporter] }
......
require 'spec_helper'
require 'mime/types'
describe API::API, api: true do
include ApiHelpers
include RepoHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
describe "GET /projects/:id/repository/tags" do
let(:tag_name) { project.repository.tag_names.sort.reverse.first }
let(:description) { 'Awesome release!' }
context 'without releases' do
it "should return an array of project tags" do
get api("/projects/#{project.id}/repository/tags", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
end
end
context 'with releases' do
before do
release = project.releases.find_or_initialize_by(tag: tag_name)
release.update_attributes(description: description)
get api("/projects/#{project.id}/repository/tags", user)
end
it "should return an array of project tags with release info" do
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['name']).to eq(tag_name)
expect(json_response.first['release']['description']).to eq(description)
end
end
end
describe 'POST /projects/:id/repository/tags' do
context 'lightweight tags' do
it 'should create a new tag' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v7.0.1',
ref: 'master'
expect(response.status).to eq(201)
expect(json_response['name']).to eq('v7.0.1')
end
end
context 'lightweight tags with release notes' do
it 'should create a new tag' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v7.0.1',
ref: 'master',
release_description: 'Wow'
expect(response.status).to eq(201)
expect(json_response['name']).to eq('v7.0.1')
expect(json_response['release']['description']).to eq('Wow')
end
end
context 'annotated tag' do
it 'should create a new annotated tag' do
# Identity must be set in .gitconfig to create annotated tag.
repo_path = project.repository.path_to_repo
system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name}))
system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email}))
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v7.1.0',
ref: 'master',
message: 'Release 7.1.0'
expect(response.status).to eq(201)
expect(json_response['name']).to eq('v7.1.0')
expect(json_response['message']).to eq('Release 7.1.0')
end
end
it 'should deny for user without push access' do
post api("/projects/#{project.id}/repository/tags", user2),
tag_name: 'v1.9.0',
ref: '621491c677087aa243f165eab467bfdfbee00be1'
expect(response.status).to eq(403)
end
it 'should return 400 if tag name is invalid' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v 1.0.0',
ref: 'master'
expect(response.status).to eq(400)
expect(json_response['message']).to eq('Tag name invalid')
end
it 'should return 400 if tag already exists' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v8.0.0',
ref: 'master'
expect(response.status).to eq(201)
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v8.0.0',
ref: 'master'
expect(response.status).to eq(400)
expect(json_response['message']).to eq('Tag already exists')
end
it 'should return 400 if ref name is invalid' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'mytag',
ref: 'foo'
expect(response.status).to eq(400)
expect(json_response['message']).to eq('Invalid reference name')
end
end
describe 'PUT /projects/:id/repository/:tag/release' do
let(:tag_name) { project.repository.tag_names.first }
let(:description) { 'Awesome release!' }
it 'should create description for existing git tag' do
put api("/projects/#{project.id}/repository/#{tag_name}/release", user),
description: description
expect(response.status).to eq(200)
expect(json_response['tag']).to eq(tag_name)
expect(json_response['description']).to eq(description)
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