Commit 354b69dd authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge remote-tracking branch 'origin/release-notes'

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
parents 2ee3f838 b7619dad
......@@ -32,6 +32,7 @@ v 8.2.0 (unreleased)
v 8.1.4
- Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu)
- Prevent redirect loop when home_page_url is set to the root URL
- Ability to add release notes (markdown text and attachments) to git tags
v 8.1.3
- Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu)
......
......@@ -39,6 +39,12 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.merge-request-form'))
new IssuableForm($('.merge-request-form'))
when 'projects:tags:new'
new ZenMode()
new DropzoneInput($('.tag-form'))
when 'projects:releases:edit'
new ZenMode()
new DropzoneInput($('.release-form'))
when 'projects:merge_requests:show'
new Diff()
shortcut_handler = new ShortcutsIssuable()
......
......@@ -9,9 +9,9 @@
.bs-callout {
margin: 20px 0;
padding: 20px;
border-left: 3px solid #eee;
color: #666;
background: #f9f9f9;
border-left: 3px solid $border-color;
color: $text-color;
background: $background-color;
}
.bs-callout h4 {
margin-top: 0;
......
......@@ -16,6 +16,7 @@
.append-bottom-10 { margin-bottom:10px }
.append-bottom-15 { margin-bottom:15px }
.append-bottom-20 { margin-bottom:20px }
.append-bottom-default { margin-bottom: $gl-padding; }
.inline { display: inline-block }
.center { text-align: center }
......
......@@ -117,7 +117,7 @@ ul.content-list {
}
.controls {
padding-top: 4px;
padding-top: 1px;
float: right;
.btn {
......
......@@ -72,9 +72,10 @@
list-style: none;
> li {
@include clearfix;
padding: 10px 0;
border-bottom: 1px solid #EEE;
overflow: hidden;
display: block;
margin: 0px;
......
......@@ -115,3 +115,10 @@ li.commit {
}
}
}
.branch-commit {
color: $gl-gray;
.commit-id, .commit-row-message {
color: $gl-gray;
}
}
class Projects::ReleasesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :authorize_push_code!
before_action :tag
before_action :release
def edit
end
def update
release.update_attributes(release_params)
redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
end
private
def tag
@tag ||= @repository.find_tag(params[:tag_id])
end
def release
@release ||= @project.releases.find_or_initialize_by(tag: @tag.name)
end
def release_params
params.require(:release).permit(:description)
end
end
......@@ -8,15 +8,23 @@ class Projects::TagsController < Projects::ApplicationController
def index
sorted = VersionSorter.rsort(@repository.tag_names)
@tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE)
@releases = project.releases.where(tag: @tags)
end
def show
@tag = @repository.find_tag(params[:id])
@release = @project.releases.find_or_initialize_by(tag: @tag.name)
@commit = @repository.commit(@tag.target)
end
def create
result = CreateTagService.new(@project, current_user).
execute(params[:tag_name], params[:ref], params[:message])
execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
if result[:status] == :success
@tag = result[:tag]
redirect_to namespace_project_tags_path(@project.namespace, @project)
redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
else
@error = result[:message]
render action: 'new'
......@@ -26,12 +34,6 @@ class Projects::TagsController < Projects::ApplicationController
def destroy
DeleteTagService.new(project, current_user).execute(params[:id])
respond_to do |format|
format.html do
redirect_to namespace_project_tags_path(@project.namespace,
@project)
end
format.js
end
redirect_to namespace_project_tags_path(@project.namespace, @project)
end
end
......@@ -122,6 +122,7 @@ class Project < ActiveRecord::Base
has_many :starrers, through: :users_star_projects, source: :user
has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_many :releases, dependent: :destroy
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id
......@@ -248,7 +249,7 @@ class Project < ActiveRecord::Base
joins(:namespace).
iwhere('namespaces.path' => namespace_path)
projects.where('projects.path' => project_path).take ||
projects.where('projects.path' => project_path).take ||
projects.iwhere('projects.path' => project_path).take
end
......
class Release < ActiveRecord::Base
belongs_to :project
validates :description, :project, :tag, presence: true
end
require_relative 'base_service'
class CreateTagService < BaseService
def execute(tag_name, ref, message)
def execute(tag_name, ref, message, release_description = nil)
valid_tag = Gitlab::GitRefValidator.validate(tag_name)
if valid_tag == false
return error('Tag name invalid')
......@@ -19,8 +19,12 @@ class CreateTagService < BaseService
new_tag = repository.find_tag(tag_name)
if new_tag
push_data = create_push_data(project, current_user, new_tag)
if release_description
release = project.releases.find_or_initialize_by(tag: tag_name)
release.update_attributes(description: release_description)
end
push_data = create_push_data(project, current_user, new_tag)
EventCreateService.new.push(project, current_user, push_data)
project.execute_hooks(push_data.dup, :tag_push_hooks)
project.execute_services(push_data.dup, :tag_push_hooks)
......
......@@ -11,8 +11,10 @@ class DeleteTagService < BaseService
end
if repository.rm_tag(tag_name)
release = project.releases.find_by(tag: tag_name)
release.destroy if release
push_data = build_push_data(tag)
EventCreateService.new.push(project, current_user, push_data)
project.execute_hooks(push_data.dup, :tag_push_hooks)
project.execute_services(push_data.dup, :tag_push_hooks)
......
......@@ -32,7 +32,7 @@
Files
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches)) do
= nav_link(controller: %w(commit commits compare repositories tags branches releases)) do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
= icon('history fw')
%span
......
.branch-commit.light
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
.branch-commit
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id"
&middot;
%span.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
......
......@@ -12,7 +12,7 @@
Branches
%span.badge.js-totalbranch-count= @repository.branches.size
= nav_link(controller: :tags) do
= nav_link(controller: [:tags, :releases]) do
= link_to namespace_project_tags_path(@project.namespace, @project) do
Tags
%span.badge.js-totaltags-count= @repository.tags.length
- page_title "Edit", @tag.name, "Tags"
= render "projects/commits/header_title"
= render "projects/commits/head"
.gray-content-block
.oneline
.title
Release notes for tag
%strong #{@tag.name}
.prepend-top-default
= form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form' }) do |f|
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit'
= render 'projects/notes/hints'
.error-alert
.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save'
= link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
%span.btn-group.btn-grouped
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), class: 'btn btn-default', rel: 'nofollow' do
%i.fa.fa-download
%span source code
%a.btn.btn-default.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
%ul.col-xs-10.dropdown-menu{ role: 'menu' }
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
= link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
- commit = @repository.commit(tag.target)
- release = @releases.find { |release| release.tag == tag.name }
%li
%div
= link_to namespace_project_commits_path(@project.namespace, @project, tag.name), class: "" do
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do
%strong
%i.fa.fa-tag
= icon('tag')
= tag.name
- if tag.message.present?
&nbsp;
= strip_gpg_signature(tag.message)
.controls
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn-grouped btn' do
= icon("pencil")
- if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-xs'
- if can?(current_user, :admin_project, @project)
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-xs btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do
%i.fa.fa-trash-o
= render 'projects/tags/download', ref: tag.name, project: @project
- if commit
= render 'projects/branches/commit', commit: commit, project: @project
- else
%p
Cant find HEAD commit for this tag
- if release && release.description.present?
.description.prepend-top-default
.wiki
= preserve do
= markdown release.description
$('.js-totaltags-count').html("#{@repository.tags.size}")
- if @repository.tags.size == 0
$('.tags').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
......@@ -5,10 +5,12 @@
.alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
= @error
%h3.page-title
%i.fa.fa-code-fork
New tag
= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal" do
New git tag
%hr
= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal tag-form" do
.form-group
= label_tag :tag_name, 'Name for new tag', class: 'control-label'
.col-sm-10
......@@ -17,12 +19,29 @@
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
= text_field_tag :ref, params[:ref], placeholder: 'master', required: true, tabindex: 2, class: 'form-control'
.light Branch name or commit SHA
.help-block Branch name or commit SHA
.form-group
= label_tag :message, 'Message', class: 'control-label'
.col-sm-10
= text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
.light (Optional) Entering a message will create an annotated tag.
.help-block (Optional) Entering a message will create an annotated tag.
%hr
.form-group
= label_tag :release_description, 'Release notes', class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
.zennable
%input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
.zen-backdrop
= text_area_tag :release_description, nil, class: 'js-gfm-input markdown-area description js-quick-submit form-control', placeholder: ''
%a.zen-enter-link(tabindex="-1" href="#")
= icon('expand')
Edit in fullscreen
%a.zen-leave-link(href="#")
= icon('compress')
= render 'projects/notes/hints'
.help-block (Optional) You can add release notes to your tag. It will be stored in the GitLab database and shown on the tags page
.form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
......
- page_title @tag.name, "Tags"
= render "projects/commits/header_title"
= render "projects/commits/head"
.gray-content-block
.pull-right
- if can?(current_user, :push_code, @project)
= link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn', title: 'Edit release notes' do
= icon("pencil")
= link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse source code' do
= icon('files-o')
= link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped', title: 'Browse commits' do
= icon('history')
- if can? current_user, :download_code, @project
= render 'projects/tags/download', ref: @tag.name, project: @project
- if can?(current_user, :admin_project, @project)
.pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'} do
%i.fa.fa-trash-o
.title
%strong= @tag.name
- if @tag.message.present?
%span.light
&nbsp;
= strip_gpg_signature(@tag.message)
- if @commit
= render 'projects/branches/commit', commit: @commit, project: @project
- else
Cant find HEAD commit for this tag
.append-bottom-default.prepend-top-default
- if @release.description.present?
.description
.wiki
= preserve do
= markdown @release.description
- else
This tag has no release notes.
......@@ -583,7 +583,10 @@ Gitlab::Application.routes.draw do
end
resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do
resource :release, only: [:edit, :update]
end
resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :destroy]
......
class CreateReleases < ActiveRecord::Migration
def change
create_table :releases do |t|
t.string :tag
t.text :description
t.integer :project_id
t.timestamps
end
add_index :releases, :project_id
add_index :releases, [:project_id, :tag]
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151103133339) do
ActiveRecord::Schema.define(version: 20151105094515) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -639,6 +639,17 @@ ActiveRecord::Schema.define(version: 20151103133339) do
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
create_table "releases", force: true do |t|
t.string "tag"
t.text "description"
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree
add_index "releases", ["project_id"], name: "index_releases_on_project_id", using: :btree
create_table "sent_notifications", force: true do |t|
t.integer "project_id"
t.integer "noteable_id"
......
......@@ -12,6 +12,12 @@ Feature: Project Commits Tags
And I submit new tag form
Then I should see new tag created
Scenario: I create a tag with release notes
Given I click new tag link
And I submit new tag form with release notes
Then I should see new tag created
And I should see tag release notes
Scenario: I create a tag with invalid name
And I click new tag link
And I submit new tag form with invalid name
......@@ -27,15 +33,13 @@ Feature: Project Commits Tags
And I submit new tag form with tag that already exists
Then I should see new an error that tag already exists
@javascript
Scenario: I delete a tag
Given I visit tag 'v1.1.0' page
Given I delete tag 'v1.1.0'
Then I should not see tag 'v1.1.0'
@javascript
Scenario: I delete all tags and see info message
Given I delete all tags
Then I should see tags info message
# @wip
# Scenario: I can download project by tag
Scenario: I add release notes to the tag
Given I visit tag 'v1.1.0' page
When I click edit tag link
And I fill release notes and submit form
Then I should see tag release notes
......@@ -18,6 +18,18 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
click_button 'Create tag'
end
step 'I submit new tag form with release notes' do
fill_in 'tag_name', with: 'v7.0'
fill_in 'ref', with: 'master'
fill_in 'release_description', with: 'Awesome release notes'
click_button 'Create tag'
end
step 'I fill release notes and submit form' do
fill_in 'release_description', with: 'Awesome release notes'
click_button 'Save changes'
end
step 'I submit new tag form with invalid name' do
fill_in 'tag_name', with: 'v 1.0'
fill_in 'ref', with: 'master'
......@@ -52,31 +64,27 @@ class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
expect(page).to have_content 'Tag already exists'
end
step "I visit tag 'v1.1.0' page" do
click_link 'v1.1.0'
end
step "I delete tag 'v1.1.0'" do
page.within '.tags' do
page.within('.content') do
first('.btn-remove').click
sleep 0.05
end
end
step "I should not see tag 'v1.1.0'" do
page.within '.tags' do
expect(page.all(visible: true)).not_to have_content 'v1.1.0'
expect(page).not_to have_link 'v1.1.0'
end
end
step 'I delete all tags' do
page.within '.tags' do
page.all('.btn-remove').each do |remove|
remove.click
sleep 0.05
end
end
step 'I click edit tag link' do
click_link 'Edit release notes'
end
step 'I should see tags info message' do
page.within '.tags' do
expect(page).to have_content 'Repository has no tags yet.'
end
step 'I should see tag release notes' do
expect(page).to have_content 'Awesome release notes'
end
end
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :release do
tag "v1.1.0"
description "Awesome release"
project
end
end
require 'rails_helper'
RSpec.describe Release, type: :model do
let(:release) { create(:release) }
it { expect(release).to be_valid }
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
describe 'validation' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:description) }
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