diff --git a/CHANGELOG b/CHANGELOG
index 6a5b1dce5c727f416557b3758b4226706e863086..0bc1c61004cd5d63998a7644bba620bbda37b00b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -22,6 +22,7 @@ v 8.8.0 (unreleased)
   - Updated search UI
   - Display informative message when new milestone is created
   - Sanitize milestones and labels titles
+  - Support multi-line tag messages. !3833 (Calin Seciu)
   - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
   - Added button to toggle whitespaces changes on diff view
   - Backport GitHub Enterprise import support from EE
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index b40a6e5cb2d3c1c297995c9f47ce84e36264b76f..f93064532974f551cd62c7b55f3e546c5d343ca4 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -23,7 +23,7 @@
   .form-group
     = label_tag :message, nil, class: 'control-label'
     .col-sm-10
-      = text_field_tag :message, nil, required: false, tabindex: 3, class: 'form-control'
+      = text_area_tag :message, nil, required: false, tabindex: 3, class: 'form-control', rows: 5
       .help-block Optionally, enter a message to create an annotated tag.
   %hr
   .form-group
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 9c916fd02de7b53679dadeccee4135d457a67be7..9f1424aecc7bc9d8844a65ae329ff7431dca85d7 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -19,15 +19,13 @@
           %i.fa.fa-trash-o
   .title
     %span.item-title= @tag.name
-    - if @tag.message.present?
-      %span.light
-         
-        = strip_gpg_signature(@tag.message)
   - if @commit
     = render 'projects/branches/commit', commit: @commit, project: @project
   - else
     Cant find HEAD commit for this tag
-
+  - if @tag.message.present?
+    %pre.body
+      = strip_gpg_signature(@tag.message)
 
 .append-bottom-default.prepend-top-default
   - if @release.description.present?
diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature
deleted file mode 100644
index a4be39b2d40899542487794da6c94682a154d203..0000000000000000000000000000000000000000
--- a/features/project/commits/tags.feature
+++ /dev/null
@@ -1,46 +0,0 @@
-@project_commits
-Feature: Project Commits Tags
-  Background:
-    Given I sign in as a user
-    And I own project "Shop"
-    Given I visit project tags page
-
-  Scenario: I can see all git tags
-    Then I should see "Shop" all tags list
-
-  Scenario: I create a tag
-    And I click new tag link
-    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
-    Then I should see new an error that tag is invalid
-
-  Scenario: I create a tag with invalid reference
-    And I click new tag link
-    And I submit new tag form with invalid reference
-    Then I should see new an error that tag ref is invalid
-
-  Scenario: I create a tag that already exists
-    And I click new tag link
-    And I submit new tag form with tag that already exists
-    Then I should see new an error that tag already exists
-
-  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'
-
-  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
diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb
deleted file mode 100644
index 912ba580efd7a334d314f3b490d2d0b1a76ad419..0000000000000000000000000000000000000000
--- a/features/steps/project/commits/tags.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
-  include SharedAuthentication
-  include SharedProject
-  include SharedPaths
-
-  step 'I should see "Shop" all tags list' do
-    expect(page).to have_content "Tags"
-    expect(page).to have_content "v1.0.0"
-  end
-
-  step 'I click new tag link' do
-    click_link 'New tag'
-  end
-
-  step 'I submit new tag form' do
-    fill_in 'tag_name', with: 'v7.0'
-    fill_in 'ref', with: 'master'
-    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'
-    click_button 'Create tag'
-  end
-
-  step 'I submit new tag form with invalid reference' do
-    fill_in 'tag_name', with: 'foo'
-    fill_in 'ref', with: 'foo'
-    click_button 'Create tag'
-  end
-
-  step 'I submit new tag form with tag that already exists' do
-    fill_in 'tag_name', with: 'v1.0.0'
-    fill_in 'ref', with: 'master'
-    click_button 'Create tag'
-  end
-
-  step 'I should see new tag created' do
-    expect(page).to have_content 'v7.0'
-  end
-
-  step 'I should see new an error that tag is invalid' do
-    expect(page).to have_content 'Tag name invalid'
-  end
-
-  step 'I should see new an error that tag ref is invalid' do
-    expect(page).to have_content 'Target foo is invalid'
-  end
-
-  step 'I should see new an error that tag already exists' do
-    expect(page).to have_content 'Tag v1.0.0 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('.content') do
-      first('.btn-remove').click
-    end
-  end
-
-  step "I should not see tag 'v1.1.0'" do
-    page.within '.tags' do
-      expect(page).not_to have_link 'v1.1.0'
-    end
-  end
-
-  step 'I click edit tag link' do
-    click_link 'Edit release notes'
-  end
-
-  step 'I should see tag release notes' do
-    expect(page).to have_content 'Awesome release notes'
-  end
-end
diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..08a97085a9c9e566befca9fdacc9196015fdf3d6
--- /dev/null
+++ b/spec/features/tags/master_creates_tag_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+feature 'Master creates tag', feature: true do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, namespace: user.namespace) }
+
+  before do
+    project.team << [user, :master]
+    login_with(user)
+    visit namespace_project_tags_path(project.namespace, project)
+  end
+
+  scenario 'with an invalid name displays an error' do
+    create_tag_in_form(tag: 'v 1.0', ref: 'master')
+
+    expect(page).to have_content 'Tag name invalid'
+  end
+
+  scenario 'with an invalid reference displays an error' do
+    create_tag_in_form(tag: 'v2.0', ref: 'foo')
+
+    expect(page).to have_content 'Target foo is invalid'
+  end
+
+  scenario 'that already exists displays an error' do
+    create_tag_in_form(tag: 'v1.1.0', ref: 'master')
+
+    expect(page).to have_content 'Tag v1.1.0 already exists'
+  end
+
+  scenario 'with multiline message displays the message in a <pre> block' do
+    create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world")
+
+    expect(current_path).to eq(
+      namespace_project_tag_path(project.namespace, project, 'v3.0'))
+    expect(page).to have_content 'v3.0'
+    page.within 'pre.body' do
+      expect(page).to have_content "Awesome tag message\n\n- hello\n- world"
+    end
+  end
+
+  scenario 'with multiline release notes parses the release note as Markdown' do
+    create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world")
+
+    expect(current_path).to eq(
+      namespace_project_tag_path(project.namespace, project, 'v4.0'))
+    expect(page).to have_content 'v4.0'
+    page.within '.description' do
+      expect(page).to have_content 'Awesome release notes'
+      expect(page).to have_selector('ul li', count: 2)
+    end
+  end
+
+  def create_tag_in_form(tag:, ref:, message: nil, desc: nil)
+    click_link 'New tag'
+    fill_in 'tag_name', with: tag
+    fill_in 'ref', with: ref
+    fill_in 'message', with: message unless message.nil?
+    fill_in 'release_description', with: desc unless desc.nil?
+    click_button 'Create tag'
+  end
+end
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f0990118e3c04ab46cc3e101522690303610e6fc
--- /dev/null
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+feature 'Master deletes tag', feature: true do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, namespace: user.namespace) }
+
+  before do
+    project.team << [user, :master]
+    login_with(user)
+    visit namespace_project_tags_path(project.namespace, project)
+  end
+
+  context 'from the tags list page' do
+    scenario 'deletes the tag' do
+      expect(page).to have_content 'v1.1.0'
+
+      page.within('.content') do
+        first('.btn-remove').click
+      end
+
+      expect(current_path).to eq(
+        namespace_project_tags_path(project.namespace, project))
+      expect(page).not_to have_content 'v1.1.0'
+    end
+
+  end
+
+  context 'from a specific tag page' do
+    scenario 'deletes the tag' do
+      click_on 'v1.0.0'
+      expect(current_path).to eq(
+        namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+
+      click_on 'Delete tag'
+
+      expect(current_path).to eq(
+        namespace_project_tags_path(project.namespace, project))
+      expect(page).not_to have_content 'v1.0.0'
+    end
+  end
+end
diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c926e9841f31f23fe87ab85b763d4ff6b23bc336
--- /dev/null
+++ b/spec/features/tags/master_updates_tag_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+feature 'Master updates tag', feature: true do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, namespace: user.namespace) }
+
+  before do
+    project.team << [user, :master]
+    login_with(user)
+    visit namespace_project_tags_path(project.namespace, project)
+  end
+
+  context 'from the tags list page' do
+    scenario 'updates the release notes' do
+      page.within(first('.controls')) do
+        click_link 'Edit release notes'
+      end
+
+      fill_in 'release_description', with: 'Awesome release notes'
+      click_button 'Save changes'
+
+      expect(current_path).to eq(
+        namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+      expect(page).to have_content 'v1.1.0'
+      expect(page).to have_content 'Awesome release notes'
+    end
+  end
+
+  context 'from a specific tag page' do
+    scenario 'updates the release notes' do
+      click_on 'v1.1.0'
+      click_link 'Edit release notes'
+      fill_in 'release_description', with: 'Awesome release notes'
+      click_button 'Save changes'
+
+      expect(current_path).to eq(
+        namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+      expect(page).to have_content 'v1.1.0'
+      expect(page).to have_content 'Awesome release notes'
+    end
+  end
+end
diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..29d2c2447205635e9e8f0af6c013ca6b4b2092a6
--- /dev/null
+++ b/spec/features/tags/master_views_tags_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+feature 'Master views tags', feature: true do
+  let(:user) { create(:user) }
+
+  before do
+    project.team << [user, :master]
+    login_with(user)
+  end
+
+  context 'when project has no tags' do
+    let(:project) { create(:project_empty_repo) }
+    before do
+      visit namespace_project_path(project.namespace, project)
+      click_on 'README'
+      fill_in :commit_message, with: 'Add a README file', visible: true
+      # Remove pre-receive hook so we can push without auth
+      FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
+      click_button 'Commit Changes'
+      visit namespace_project_tags_path(project.namespace, project)
+    end
+
+    scenario 'displays a specific message' do
+      expect(page).to have_content 'Repository has no tags yet.'
+    end
+  end
+
+  context 'when project has tags' do
+    let(:project) { create(:project, namespace: user.namespace) }
+    before do
+      visit namespace_project_tags_path(project.namespace, project)
+    end
+
+    scenario 'views the tags list page' do
+      expect(page).to have_content 'v1.0.0'
+    end
+
+    scenario 'views a specific tag page' do
+      click_on 'v1.0.0'
+
+      expect(current_path).to eq(
+        namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+      expect(page).to have_content 'v1.0.0'
+      expect(page).to have_content 'This tag has no release notes.'
+    end
+
+    describe 'links on the tag page' do
+      scenario 'has a button to browse files' do
+        click_on 'v1.0.0'
+
+        expect(current_path).to eq(
+          namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+
+        click_on 'Browse files'
+
+        expect(current_path).to eq(
+          namespace_project_tree_path(project.namespace, project, 'v1.0.0'))
+      end
+
+      scenario 'has a button to browse commits' do
+        click_on 'v1.0.0'
+
+        expect(current_path).to eq(
+          namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+
+        click_on 'Browse commits'
+
+        expect(current_path).to eq(
+          namespace_project_commits_path(project.namespace, project, 'v1.0.0'))
+      end
+    end
+  end
+end