summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG13
-rw-r--r--Procfile4
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Black.ttf.woff2bin0 -> 82052 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-BlackIt.ttf.woff2bin0 -> 34812 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Bold.ttf.woff2bin0 -> 85604 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-BoldIt.ttf.woff2bin0 -> 35864 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2bin0 -> 82808 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2bin0 -> 34560 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-It.ttf.woff2bin0 -> 36016 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Light.ttf.woff2bin0 -> 86336 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-LightIt.ttf.woff2bin0 -> 35952 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Regular.ttf.woff2bin0 -> 86844 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Semibold.ttf.woff2bin0 -> 86196 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2bin0 -> 35984 bytes
-rw-r--r--app/assets/javascripts/awards_handler.coffee2
-rw-r--r--app/assets/javascripts/issue.js.coffee16
-rw-r--r--app/assets/javascripts/merge_request.js.coffee16
-rw-r--r--app/assets/javascripts/notes.js.coffee10
-rw-r--r--app/assets/stylesheets/framework/common.scss2
-rw-r--r--app/assets/stylesheets/framework/fonts.scss24
-rw-r--r--app/assets/stylesheets/pages/branches.scss3
-rw-r--r--app/assets/stylesheets/pages/commit.scss4
-rw-r--r--app/assets/stylesheets/pages/diff.scss1
-rw-r--r--app/assets/stylesheets/pages/groups.scss5
-rw-r--r--app/assets/stylesheets/pages/issuable.scss5
-rw-r--r--app/assets/stylesheets/pages/tags.scss3
-rw-r--r--app/controllers/abuse_reports_controller.rb1
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/projects/artifacts_controller.rb2
-rw-r--r--app/controllers/projects/builds_controller.rb4
-rw-r--r--app/controllers/projects/commit_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects/snippets_controller.rb2
-rw-r--r--app/models/ability.rb2
-rw-r--r--app/models/abuse_report.rb2
-rw-r--r--app/models/ci/build.rb8
-rw-r--r--app/models/ci/trigger.rb4
-rw-r--r--app/uploaders/artifact_uploader.rb4
-rw-r--r--app/views/abuse_reports/new.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml4
-rw-r--r--app/views/projects/commit/builds.html.haml3
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/empty.html.haml71
-rw-r--r--app/views/projects/tags/_tag.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml4
-rw-r--r--app/views/shared/groups/_group.html.haml4
-rw-r--r--app/views/users/show.html.haml2
-rwxr-xr-xbin/background_jobs9
-rwxr-xr-xbin/web11
-rw-r--r--config/environments/development.rb2
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/build_triggers.md108
-rw-r--r--doc/ci/api/builds.md62
-rw-r--r--doc/integration/saml.md16
-rw-r--r--doc/workflow/importing/github_importer/importer.pngbin39335 -> 0 bytes
-rw-r--r--doc/workflow/importing/github_importer/new_project_page.pngbin46276 -> 0 bytes
-rw-r--r--doc/workflow/importing/img/import_projects_from_github_importer.pngbin0 -> 28033 bytes
-rw-r--r--doc/workflow/importing/img/import_projects_from_github_new_project_page.pngbin0 -> 17225 bytes
-rw-r--r--doc/workflow/importing/import_projects_from_github.md46
-rw-r--r--features/project/builds/artifacts.feature (renamed from features/project/builds.feature)11
-rw-r--r--features/project/builds/permissions.feature18
-rw-r--r--features/project/builds/summary.feature11
-rw-r--r--features/steps/project/builds/artifacts.rb (renamed from features/steps/project/builds.rb)15
-rw-r--r--features/steps/project/builds/permissions.rb7
-rw-r--r--features/steps/project/builds/summary.rb14
-rw-r--r--features/steps/shared/builds.rb13
-rw-r--r--features/steps/shared/project.rb18
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/triggers.rb69
-rw-r--r--lib/ci/api/builds.rb4
-rw-r--r--lib/ci/api/entities.rb17
-rw-r--r--lib/gitlab/o_auth/user.rb5
-rw-r--r--lib/tasks/gitlab/task_helpers.rake3
-rw-r--r--spec/factories/ci/trigger_requests.rb2
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/build_spec.rb24
-rw-r--r--spec/requests/api/triggers_spec.rb139
-rw-r--r--spec/requests/ci/api/builds_spec.rb12
-rw-r--r--spec/support/gitlab_stubs/gitlab_ci.yml8
80 files changed, 736 insertions, 153 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 8d00d890f8e..a15bbfbc49e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,8 +1,5 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 8.5.0 (unreleased)
- - Remove gray background from layout in UI
-
v 8.4.0 (unreleased)
- Add pagination headers to already paginated API resources
- Properly generate diff of orphan commits, like the first commit in a repository
@@ -19,8 +16,11 @@ v 8.4.0 (unreleased)
- Fix missing date of month in network graph when commits span a month (Stan Hu)
- Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu)
- Don't notify users twice if they are both project watchers and subscribers (Stan Hu)
+ - Remove gray background from layout in UI
+ - Fix signup for OAuth providers that don't provide a name
- Implement new UI for group page
- Implement search inside emoji picker
+ - Let the CI runner know about builds that this build depends on
- Add API support for looking up a user by username (Stan Hu)
- Add project permissions to all project API endpoints (Stan Hu)
- Link to milestone in "Milestone changed" system note
@@ -50,11 +50,14 @@ v 8.4.0 (unreleased)
- Allow subsequent validations in CI Linter
- Show referenced MRs & Issues only when the current viewer can access them
- Fix Encoding::CompatibilityError bug when markdown content has some complex URL (Jason Lee)
- - Add API support for managing builds of a project
- - Add API support for managing build variables of project
+ - Add API support for managing project's builds
+ - Add API support for managing project's build triggers
+ - Add API support for managing project's build variables
- Allow broadcast messages to be edited
- Autosize Markdown textareas
- Import GitHub wiki into GitLab
+ - Add reporters ability to download and browse build artifacts (Andrew Johnson)
+ - Autofill referring url in message box when reporting user abuse. (Josh Frye)
v 8.3.4
- Use gitlab-workhorse 0.5.4 (fixes API routing bug)
diff --git a/Procfile b/Procfile
index 9cfdee7040f..cad738d4292 100644
--- a/Procfile
+++ b/Procfile
@@ -2,6 +2,6 @@
# https://gitlab.com/gitlab-org/omnibus-gitlab or the init scripts in
# lib/support/init.d, which call scripts in bin/ .
#
-web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
-worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
+web: RAILS_ENV=development bin/web start_foreground
+worker: RAILS_ENV=development bin/background_jobs start_foreground
# mail_room: bundle exec mail_room -q -c config/mail_room.yml
diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff2 b/app/assets/fonts/SourceSansPro-Black.ttf.woff2
new file mode 100755
index 00000000000..c90d078406c
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Black.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2
new file mode 100755
index 00000000000..b87e22c41b5
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Bold.ttf.woff2
new file mode 100755
index 00000000000..0f46f3e833a
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Bold.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2
new file mode 100755
index 00000000000..8007df6df32
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2
new file mode 100755
index 00000000000..b715f274082
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2
new file mode 100755
index 00000000000..d8f9d29d4aa
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff2 b/app/assets/fonts/SourceSansPro-It.ttf.woff2
new file mode 100755
index 00000000000..a00852641f8
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-It.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff2 b/app/assets/fonts/SourceSansPro-Light.ttf.woff2
new file mode 100755
index 00000000000..d8b610ad76e
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Light.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2
new file mode 100755
index 00000000000..e0eebac8273
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 b/app/assets/fonts/SourceSansPro-Regular.ttf.woff2
new file mode 100755
index 00000000000..0dd3464c74b
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Regular.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2
new file mode 100755
index 00000000000..2526d2e1b60
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2
new file mode 100755
index 00000000000..606935af089
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2
Binary files differ
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 619abb1fb07..4670c95344d 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -5,7 +5,7 @@ class @AwardsHandler
event.preventDefault()
$(".emoji-menu").show()
- $("html").click ->
+ $("html").on 'click', (event) ->
if !$(event.target).closest(".emoji-menu").length
if $(".emoji-menu").is(":visible")
$(".emoji-menu").hide()
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 0d26c58a81d..cbc70cd846c 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -6,11 +6,25 @@ class @Issue
constructor: ->
# Prevent duplicate event bindings
@disableTaskList()
-
+ @fixAffixScroll()
if $('a.btn-close').length
@initTaskList()
@initIssueBtnEventListeners()
+ fixAffixScroll: ->
+ fixAffix = ->
+ $discussion = $('.issuable-discussion')
+ $sidebar = $('.issuable-sidebar')
+ if $sidebar.hasClass('no-affix')
+ $sidebar.removeClass(['affix-top','affix'])
+ discussionHeight = $discussion.height()
+ sidebarHeight = $sidebar.height()
+ if sidebarHeight > discussionHeight
+ $discussion.height(sidebarHeight + 50)
+ $sidebar.addClass('no-affix')
+ $(window).on('resize', fixAffix)
+ fixAffix()
+
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 1f46e331427..6af5a48a0bb 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -15,6 +15,8 @@ class @MergeRequest
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
+ @fixAffixScroll();
+
@initTabs()
# Prevent duplicate event bindings
@@ -28,6 +30,20 @@ class @MergeRequest
$: (selector) ->
this.$el.find(selector)
+ fixAffixScroll: ->
+ fixAffix = ->
+ $discussion = $('.issuable-discussion')
+ $sidebar = $('.issuable-sidebar')
+ if $sidebar.hasClass('no-affix')
+ $sidebar.removeClass(['affix-top','affix'])
+ discussionHeight = $discussion.height()
+ sidebarHeight = $sidebar.height()
+ if sidebarHeight > discussionHeight
+ $discussion.height(sidebarHeight + 50)
+ $sidebar.addClass('no-affix')
+ $(window).on('resize', fixAffix)
+ fixAffix()
+
initTabs: ->
if @opts.action != 'new'
# `MergeRequests#new` has no tab-persisting or lazy-loading behavior
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 356fb6aa08c..2bfc5cb2d9c 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -320,6 +320,7 @@ class @Notes
form.show()
textarea = form.find("textarea")
textarea.focus()
+ autosize(textarea)
# HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
# The textarea has the correct value, Chrome just won't show it unless we
@@ -355,7 +356,7 @@ class @Notes
$('.note[id="' + note_id + '"]').each ->
note = $(this)
notes = note.closest(".notes")
- count = notes.closest(".notes_holder").find(".discussion-notes-count")
+ count = notes.closest(".issuable-details").find(".notes-tab .badge")
# check if this is the last note for this line
if notes.find(".note").length is 1
@@ -365,9 +366,10 @@ class @Notes
# for diff lines
notes.closest("tr").remove()
- else
- # update notes count
- count.get(0).lastChild.nodeValue = " #{notes.children().length - 1}"
+
+ # update notes count
+ oldNum = parseInt(count.text())
+ count.text(oldNum - 1)
note.remove()
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 05645116268..585a9d83913 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -75,6 +75,8 @@ hr {
@include str-truncated;
}
+.item-title { font-weight: 600; }
+
/** FLASH message **/
.author_link {
color: $gl-link-color;
diff --git a/app/assets/stylesheets/framework/fonts.scss b/app/assets/stylesheets/framework/fonts.scss
index 20988f7b430..7a946109e3a 100644
--- a/app/assets/stylesheets/framework/fonts.scss
+++ b/app/assets/stylesheets/framework/fonts.scss
@@ -3,23 +3,39 @@
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
- src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), font-url('SourceSansPro-Light.ttf.woff');
+ src:
+ local('Source Sans Pro Light'),
+ local('SourceSansPro-Light'),
+ font-url('SourceSansPro-Light.ttf.woff2') format('woff2'),
+ font-url('SourceSansPro-Light.ttf.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
- src: local('Source Sans Pro'), local('SourceSansPro-Regular'), font-url('SourceSansPro-Regular.ttf.woff');
+ src:
+ local('Source Sans Pro'),
+ local('SourceSansPro-Regular'),
+ font-url('SourceSansPro-Regular.ttf.woff2') format('woff2'),
+ font-url('SourceSansPro-Regular.ttf.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
- src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), font-url('SourceSansPro-Semibold.ttf.woff');
+ src:
+ local('Source Sans Pro Semibold'),
+ local('SourceSansPro-Semibold'),
+ font-url('SourceSansPro-Semibold.ttf.woff2') format('woff2'),
+ font-url('SourceSansPro-Semibold.ttf.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
- src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), font-url('SourceSansPro-Bold.ttf.woff');
+ src:
+ local('Source Sans Pro Bold'),
+ local('SourceSansPro-Bold'),
+ font-url('SourceSansPro-Bold.ttf.woff2') format('woff2'),
+ font-url('SourceSansPro-Bold.ttf.woff') format('woff');
}
diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss
deleted file mode 100644
index abae5c3d0a5..00000000000
--- a/app/assets/stylesheets/pages/branches.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.branch-name{
- font-weight: 600;
-}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 6ec88bdd804..e53d6fc6bdc 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -2,10 +2,6 @@
display: block;
}
-.commit-row-title .commit-title {
- font-weight: 600;
-}
-
.commit-author, .commit-committer{
display: block;
color: #999;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 1e2b8b51827..04e9a58e1cf 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -66,6 +66,7 @@
width: 100%;
font-family: $monospace_font;
border: none;
+ border-collapse: separate;
margin: 0px;
padding: 0px;
.line_holder td {
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 3404c2631e1..263993f59a5 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -11,8 +11,3 @@
height: 42px;
}
}
-
-.content-list .group-name {
- font-weight: 600;
- color: #4c4e54;
-}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index eae3590a189..977ada0ff38 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -20,6 +20,11 @@
position: fixed;
top: 70px;
margin-right: 35px;
+
+ &.no-affix {
+ position: relative;
+ top: 0;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/tags.scss b/app/assets/stylesheets/pages/tags.scss
deleted file mode 100644
index e9cd6dc6c5e..00000000000
--- a/app/assets/stylesheets/pages/tags.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.tag-name{
- font-weight: 600;
-}
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 38814459f66..2eac0cabf7a 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -2,6 +2,7 @@ class AbuseReportsController < ApplicationController
def new
@abuse_report = AbuseReport.new
@abuse_report.user_id = params[:user_id]
+ @ref_url = params.fetch(:ref_url, '')
end
def create
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 81cb1367e2c..bf99b2e777d 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -115,7 +115,7 @@ class ApplicationController < ActionController::Base
# localhost/group/project
#
if id =~ /\.git\Z/
- redirect_to request.original_url.gsub(/\.git\Z/, '') and return
+ redirect_to request.original_url.gsub(/\.git\/?\Z/, '') and return
end
project_path = "#{namespace}/#{id}"
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index dff0732bdfe..f159a6d6dc6 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -8,7 +8,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
end
unless artifacts_file.exists?
- return not_found!
+ return render_404
end
send_file artifacts_file.path, disposition: 'attachment'
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 0e965966ffa..92d9699fe84 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -42,7 +42,7 @@ class Projects::BuildsController < Projects::ApplicationController
def retry
unless @build.retryable?
- return page_404
+ return render_404
end
build = Ci::Build.retry(@build)
@@ -72,7 +72,7 @@ class Projects::BuildsController < Projects::ApplicationController
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
- return page_404
+ return render_404
end
end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 0aaba3792bf..870f6795219 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -79,7 +79,7 @@ class Projects::CommitController < Projects::ApplicationController
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
- return page_404
+ return render_404
end
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index f476afb2d92..68244883803 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -49,7 +49,7 @@ class Projects::IssuesController < Projects::ApplicationController
assignee_id: ""
)
- @issue = @project.issues.new(issue_params)
+ @issue = @noteable = @project.issues.new(issue_params)
respond_with(@issue)
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index de948d271c8..a6284a24223 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -90,6 +90,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def new
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
+ @noteable = @merge_request
@target_branches = if @merge_request.target_project
@merge_request.target_project.repository.branch_names
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 2104c7a7a71..92b0caa2efb 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -25,7 +25,7 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def new
- @snippet = @project.snippets.build
+ @snippet = @noteable = @project.snippets.build
end
def create
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 5375148a654..ab59a3506a2 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -160,6 +160,7 @@ class Ability
@project_report_rules ||= project_guest_rules + [
:create_commit_status,
:read_commit_statuses,
+ :read_build_artifacts,
:download_code,
:fork_project,
:create_project_snippet,
@@ -175,7 +176,6 @@ class Ability
:create_merge_request,
:create_wiki,
:manage_builds,
- :read_build_artifacts,
:push_code
]
end
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 2bc15c60d57..cc59aa4e911 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -17,7 +17,7 @@ class AbuseReport < ActiveRecord::Base
validates :reporter, presence: true
validates :user, presence: true
validates :message, presence: true
- validates :user_id, uniqueness: true
+ validates :user_id, uniqueness: { message: 'has already been reported' }
def remove_user
user.block
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 6cc26abce66..16a5b03f591 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -128,6 +128,14 @@ module Ci
!self.commit.latest_builds_for_ref(self.ref).include?(self)
end
+ def depends_on_builds
+ # Get builds of the same type
+ latest_builds = self.commit.builds.similar(self).latest
+
+ # Return builds from previous stages
+ latest_builds.where('stage_idx < ?', stage_idx)
+ end
+
def trace_html
html = Ci::Ansi2html::convert(trace) if trace.present?
html || ''
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index bb98cd5c7da..2b9a457c8ab 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -33,6 +33,10 @@ module Ci
trigger_requests.last
end
+ def last_used
+ last_trigger_request.try(:created_at)
+ end
+
def short_token
token[0...10]
end
diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb
index 1b0ae6c0056..1cd93263c9f 100644
--- a/app/uploaders/artifact_uploader.rb
+++ b/app/uploaders/artifact_uploader.rb
@@ -32,6 +32,10 @@ class ArtifactUploader < CarrierWave::Uploader::Base
self.class.storage == CarrierWave::Storage::File
end
+ def filename
+ file.try(:filename)
+ end
+
def exists?
file.try(:exists?)
end
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index 3e5cdd2ce4a..f125ecf7be5 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -16,7 +16,7 @@
.form-group
= f.label :message, class: 'control-label'
.col-sm-10
- = f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true
+ = f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true, value: sanitize(@ref_url)
.help-block
Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment.
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index d276e5932d1..76a823d3828 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,12 +1,12 @@
- commit = @repository.commit(branch.target)
- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
-- diverging_commit_counts = @repository.diverging_commit_counts(branch)
+- diverging_commit_counts = @repository.diverging_commit_counts(branch)
- number_commits_behind = diverging_commit_counts[:behind]
- number_commits_ahead = diverging_commit_counts[:ahead]
%li(class="js-branch-#{branch.name}")
%div
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
- .branch-name.str-truncated= branch.name
+ %span.item-title.str-truncated= branch.name
&nbsp;
- if branch.name == @repository.root_ref
%span.label.label-primary default
diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml
index 99d62503a94..7118a4846c6 100644
--- a/app/views/projects/commit/builds.html.haml
+++ b/app/views/projects/commit/builds.html.haml
@@ -1,6 +1,7 @@
- page_title "Builds", "#{@commit.title} (#{@commit.short_id})", "Commits"
= render "projects/commits/header_title"
-= render "commit_box"
+.prepend-top-default
+ = render "commit_box"
= render "ci_menu"
= render "builds"
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 4d4b410ee29..7f2903589a9 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -11,7 +11,7 @@
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title
- .commit-title.str-truncated
+ %span.item-title.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description?
%a.text-expander.js-toggle-button ...
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 503d156661e..b34d106d565 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,3 +1,5 @@
+- @no_container = true
+
= content_for :flash_message do
- if current_user && can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
@@ -17,40 +19,41 @@
file to this project.
- if can?(current_user, :download_code, @project)
- .prepend-top-20
- .empty_wrapper
- %h3.page-title-empty
- Command line instructions
- %div.git-empty
- %fieldset
- %h5 Git global setup
- %pre.light-well
- :preserve
- git config --global user.name "#{h git_user_name}"
- git config --global user.email "#{h git_user_email}"
+ %div{ class: container_class }
+ .prepend-top-20
+ .empty_wrapper
+ %h3.page-title-empty
+ Command line instructions
+ %div.git-empty
+ %fieldset
+ %h5 Git global setup
+ %pre.light-well
+ :preserve
+ git config --global user.name "#{h git_user_name}"
+ git config --global user.email "#{h git_user_email}"
- %fieldset
- %h5 Create a new repository
- %pre.light-well
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- cd #{h @project.path}
- touch README.md
- git add README.md
- git commit -m "add README"
- git push -u origin master
+ %fieldset
+ %h5 Create a new repository
+ %pre.light-well
+ :preserve
+ git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ cd #{h @project.path}
+ touch README.md
+ git add README.md
+ git commit -m "add README"
+ git push -u origin master
- %fieldset
- %h5 Existing folder or Git repository
- %pre.light-well
- :preserve
- cd existing_folder
- git init
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- git add .
- git commit
- git push -u origin master
+ %fieldset
+ %h5 Existing folder or Git repository
+ %pre.light-well
+ :preserve
+ cd existing_folder
+ git init
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git add .
+ git commit
+ git push -u origin master
- - if can? current_user, :remove_project, @project
- .prepend-top-20
- = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
+ - if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 56a7ced1236..399782273d3 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -3,7 +3,7 @@
%li
%div
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do
- .tag-name
+ %span.item-title
= icon('tag')
= tag.name
- if tag.message.present?
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index dbb20347860..8c7f93f93b6 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -17,8 +17,8 @@
.pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
- .tag-name.title
- = @tag.name
+ .title
+ %span.item-title= @tag.name
- if @tag.message.present?
%span.light
&nbsp;
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index f4cfa29ae56..778b20fb4f2 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -10,7 +10,8 @@
%i.fa.fa-sign-out
= image_tag group_icon(group), class: "avatar s46 hidden-xs"
- = link_to group.name, group, class: 'group-name'
+ = link_to group, class: 'group-name' do
+ %span.item-title= group.name
- if group_member
as
@@ -18,4 +19,3 @@
%div.light
#{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
-
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 849304ee2b6..3bfd781e51d 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -20,7 +20,7 @@
data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
= icon('exclamation-circle')
- else
- = link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray',
+ = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray',
title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
= icon('exclamation-circle')
- if current_user
diff --git a/bin/background_jobs b/bin/background_jobs
index 5c85fb339e6..1f67d732949 100755
--- a/bin/background_jobs
+++ b/bin/background_jobs
@@ -27,17 +27,17 @@ restart()
stop
fi
killall
- start_sidekiq -d -L $sidekiq_logfile
+ start_sidekiq -d -L $sidekiq_logfile >> $sidekiq_logfile 2>&1
}
start_no_deamonize()
{
- start_sidekiq
+ start_sidekiq >> $sidekiq_logfile 2>&1
}
start_sidekiq()
{
- bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
+ bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@"
}
load_ok()
@@ -66,6 +66,9 @@ case "$1" in
start_no_deamonize)
start_no_deamonize
;;
+ start_foreground)
+ start_sidekiq
+ ;;
restart)
restart
;;
diff --git a/bin/web b/bin/web
index 67f236eb0bb..03fe7a6354b 100755
--- a/bin/web
+++ b/bin/web
@@ -5,6 +5,7 @@ app_root=$(pwd)
unicorn_pidfile="$app_root/tmp/pids/unicorn.pid"
unicorn_config="$app_root/config/unicorn.rb"
+unicorn_cmd="bundle exec unicorn_rails -c $unicorn_config -E $RAILS_ENV"
get_unicorn_pid()
{
@@ -18,7 +19,12 @@ get_unicorn_pid()
start()
{
- bundle exec unicorn_rails -D -c $unicorn_config -E $RAILS_ENV
+ $unicorn_cmd -D
+}
+
+start_foreground()
+{
+ $unicorn_cmd
}
stop()
@@ -37,6 +43,9 @@ case "$1" in
start)
start
;;
+ start_foreground)
+ start_foreground
+ ;;
stop)
stop
;;
diff --git a/config/environments/development.rb b/config/environments/development.rb
index c22722c606b..257c163720a 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -34,6 +34,8 @@ Rails.application.configure do
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
# Open sent mails in browser
config.action_mailer.delivery_method = :letter_opener
+ # Don't make a mess when bootstrapping a development environment
+ config.action_mailer.perform_deliveries = (ENV['BOOTSTRAP'] != '1')
config.eager_load = false
end
diff --git a/doc/api/README.md b/doc/api/README.md
index a9c4e47b35d..2fa177ff4dd 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -24,6 +24,7 @@
- [Settings](settings.md)
- [Keys](keys.md)
- [Builds](builds.md)
+- [Build triggers](build_triggers.md)
- [Build Variables](build_variables.md)
## Clients
diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md
new file mode 100644
index 00000000000..4a12e962b62
--- /dev/null
+++ b/doc/api/build_triggers.md
@@ -0,0 +1,108 @@
+# Build triggers
+
+You can read more about [triggering builds through the API](../ci/triggers/README.md).
+
+## List project triggers
+
+Get a list of project's build triggers.
+
+```
+GET /projects/:id/triggers
+```
+
+| Attribute | Type | required | Description |
+|-----------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+
+```
+curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers"
+```
+
+```json
+[
+ {
+ "created_at": "2015-12-23T16:24:34.716Z",
+ "deleted_at": null,
+ "last_used": "2016-01-04T15:41:21.986Z",
+ "token": "fbdb730c2fbdb095a0862dbd8ab88b",
+ "updated_at": "2015-12-23T16:24:34.716Z"
+ },
+ {
+ "created_at": "2015-12-23T16:25:56.760Z",
+ "deleted_at": null,
+ "last_used": null,
+ "token": "7b9148c158980bbd9bcea92c17522d",
+ "updated_at": "2015-12-23T16:25:56.760Z"
+ }
+]
+```
+
+## Get trigger details
+
+Get details of project's build trigger.
+
+```
+GET /projects/:id/triggers/:token
+```
+
+| Attribute | Type | required | Description |
+|-----------|---------|----------|--------------------------|
+| `id` | integer | yes | The ID of a project |
+| `token` | string | yes | The `token` of a trigger |
+
+```
+curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
+```
+
+```json
+{
+ "created_at": "2015-12-23T16:25:56.760Z",
+ "deleted_at": null,
+ "last_used": null,
+ "token": "7b9148c158980bbd9bcea92c17522d",
+ "updated_at": "2015-12-23T16:25:56.760Z"
+}
+```
+
+## Create a project trigger
+
+Create a build trigger for a project.
+
+```
+POST /projects/:id/triggers
+```
+
+| Attribute | Type | required | Description |
+|-----------|---------|----------|--------------------------|
+| `id` | integer | yes | The ID of a project |
+
+```
+curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers"
+```
+
+```json
+{
+ "created_at": "2016-01-07T09:53:58.235Z",
+ "deleted_at": null,
+ "last_used": null,
+ "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
+ "updated_at": "2016-01-07T09:53:58.235Z"
+}
+```
+
+## Remove a project trigger
+
+Remove a project's build trigger.
+
+```
+DELETE /projects/:id/triggers/:token
+```
+
+| Attribute | Type | required | Description |
+|-----------|---------|----------|--------------------------|
+| `id` | integer | yes | The ID of a project |
+| `token` | string | yes | The `token` of a project |
+
+```
+curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
+```
diff --git a/doc/ci/api/builds.md b/doc/ci/api/builds.md
index 3b5008ccdb4..018ca22dbbd 100644
--- a/doc/ci/api/builds.md
+++ b/doc/ci/api/builds.md
@@ -18,18 +18,62 @@ Returns:
```json
{
- "id" : 79,
- "commands" : "",
- "path" : "",
- "ref" : "",
- "sha" : "",
- "project_id" : 6,
- "repo_url" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
- "before_sha" : ""
+ "id": 48584,
+ "ref": "0.1.1",
+ "tag": true,
+ "sha": "d63117656af6ff57d99e50cc270f854691f335ad",
+ "status": "success",
+ "name": "pages",
+ "token": "9dd60b4f1a439d1765357446c1084c",
+ "stage": "test",
+ "project_id": 479,
+ "project_name": "test",
+ "commands": "echo commands",
+ "repo_url": "http://gitlab-ci-token:token@gitlab.example/group/test.git",
+ "before_sha": "0000000000000000000000000000000000000000",
+ "allow_git_fetch": false,
+ "options": {
+ "image": "docker:image",
+ "artifacts": {
+ "paths": [
+ "public"
+ ]
+ },
+ "cache": {
+ "paths": [
+ "vendor"
+ ]
+ }
+ },
+ "timeout": 3600,
+ "variables": [
+ {
+ "key": "CI_BUILD_TAG",
+ "value": "0.1.1",
+ "public": true
+ }
+ ],
+ "depends_on_builds": [
+ {
+ "id": 48584,
+ "ref": "0.1.1",
+ "tag": true,
+ "sha": "d63117656af6ff57d99e50cc270f854691f335ad",
+ "status": "success",
+ "name": "build",
+ "token": "9dd60b4f1a439d1765357446c1084c",
+ "stage": "build",
+ "project_id": 479,
+ "project_name": "test",
+ "artifacts_file": {
+ "filename": "artifacts.zip",
+ "size": 0
+ }
+ }
+ ]
}
```
-
### Update details of an existing build
PUT /ci/builds/:id
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 1632e42f701..8841dbdb7c6 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -78,6 +78,18 @@ On the sign in page there should now be a SAML button below the regular sign in
## Troubleshooting
-If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page, this likely indicates that GitLab could not get the email address for the SAML user.
+If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page,
+this likely indicates that GitLab could not get the email address for the SAML user.
-Make sure the IdP provides a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username. \ No newline at end of file
+Make sure the IdP provides a claim containing the user's email address, using claim name
+'email' or 'mail'. The email will be used to automatically generate the GitLab username.
+
+If after signing in into your SAML server you are redirected back to the sign in page and
+no error is displayed, check your `production.log` file. It will most likely contain the
+message `Can't verify CSRF token authenticity`. This means that there is an error during
+the SAML request, but this error never reaches GitLab due to the CSRF check.
+
+To bypass this you can add `skip_before_action :verify_authenticity_token` to the
+`omniauth_callbacks_controller.rb` file. This will allow the error to hit GitLab,
+where it can then be seen in the usual logs, or as a flash message in the login
+screen. \ No newline at end of file
diff --git a/doc/workflow/importing/github_importer/importer.png b/doc/workflow/importing/github_importer/importer.png
deleted file mode 100644
index 57636717571..00000000000
--- a/doc/workflow/importing/github_importer/importer.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/importing/github_importer/new_project_page.png b/doc/workflow/importing/github_importer/new_project_page.png
deleted file mode 100644
index 002f22d81d7..00000000000
--- a/doc/workflow/importing/github_importer/new_project_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/importing/img/import_projects_from_github_importer.png b/doc/workflow/importing/img/import_projects_from_github_importer.png
new file mode 100644
index 00000000000..f744dc06f81
--- /dev/null
+++ b/doc/workflow/importing/img/import_projects_from_github_importer.png
Binary files differ
diff --git a/doc/workflow/importing/img/import_projects_from_github_new_project_page.png b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png
new file mode 100644
index 00000000000..86be35acb37
--- /dev/null
+++ b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png
Binary files differ
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index 2027a055c37..f693f430a42 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -1,20 +1,44 @@
# Import your project from GitHub to GitLab
-It takes just a couple of steps to import your existing GitHub projects to GitLab. Keep in mind that it is possible only if
-GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
+_**Note:** In order to enable the GitHub import setting, you should first
+enable the [GitHub integration][gh-import] in your GitLab instance._
-If you want to import from a GitHub Enterprise instance, you need to use GitLab Enterprise; please see the [EE docs for the GitHub integration](http://doc.gitlab.com/ee/integration/github.html).
+At its current state, GitHub importer can import:
-* Sign in to GitLab.com and go to your dashboard.
-* To get to the importer page, you need to go to the "New project" page.
+- the repository description (introduced in GitLab 7.7)
+- the git repository data (introduced in GitLab 7.7)
+- the issues (introduced in GitLab 7.7)
+- the pull requests (introduced in GitLab 8.4)
+- the wiki pages (introduced in GitLab 8.4)
-![New project page](github_importer/new_project_page.png)
+It is not yet possible to import your labels, milestones and cross-repository
+pull requests (those from forks). We are working on improving this in the near
+future.
-* Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
+The importer page is visible when you [create a new project][new-project].
+Click on the **GitHub** link and you will be redirected to GitHub for
+permission to access your projects. After accepting, you'll be automatically
+redirected to the importer.
-![Importer page](github_importer/importer.png)
+![New project page on GitLab](img/import_projects_from_github_new_project_page.png)
-* To import a project, you can simple click "Add". The importer will import your repository, issues, and pull requests. Once the importer is done, a new GitLab project will be created with your imported data.
+---
-### Note
-When you import your projects from GitHub, it is not possible to keep your labels, milestones, and cross-repository pull requests. We are working on improving this in the near future.
+While at the GitHub importer page, you can see the import statuses of your
+GitHub projects. Those that are being imported will show a _started_ status,
+those already imported will be green, whereas those that are not yet imported
+have an **Import** button on the right side of the table. If you want, you can
+import all your GitHub projects in one go by hitting **Import all projects**
+in the upper left corner.
+
+![GitHub importer page](img/import_projects_from_github_importer.png)
+
+---
+
+The importer will create any new namespaces if they don't exist or in the
+case the namespace is taken, the project will be imported on the user's
+namespace.
+
+[gh-import]: ../../integration/github.md "GitHub integration"
+[ee-gh]: http://doc.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
+[new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab"
diff --git a/features/project/builds.feature b/features/project/builds/artifacts.feature
index c00b0a7ae07..7a7dbb71b18 100644
--- a/features/project/builds.feature
+++ b/features/project/builds/artifacts.feature
@@ -1,14 +1,9 @@
-Feature: Project Builds
+Feature: Project Builds Artifacts
Background:
Given I sign in as a user
And I own a project
- And CI is enabled
- And I have recent build for my project
-
- Scenario: I browse build summary page
- When I visit recent build summary page
- Then I see summary for build
- And I see build trace
+ And project has CI enabled
+ And project has a recent build
Scenario: I download build artifacts
Given recent build has artifacts available
diff --git a/features/project/builds/permissions.feature b/features/project/builds/permissions.feature
new file mode 100644
index 00000000000..1193bcd74f6
--- /dev/null
+++ b/features/project/builds/permissions.feature
@@ -0,0 +1,18 @@
+Feature: Project Builds Permissions
+ Background:
+ Given I sign in as a user
+ And project exists in some group namespace
+ And project has CI enabled
+ And project has a recent build
+
+ Scenario: I try to download build artifacts as guest
+ Given I am member of a project with a guest role
+ And recent build has artifacts available
+ When I access artifacts download page
+ Then page status code should be 404
+
+ Scenario: I try to download build artifacts as reporter
+ Given I am member of a project with a reporter role
+ And recent build has artifacts available
+ When I access artifacts download page
+ Then download of build artifacts archive starts
diff --git a/features/project/builds/summary.feature b/features/project/builds/summary.feature
new file mode 100644
index 00000000000..e90ea592aab
--- /dev/null
+++ b/features/project/builds/summary.feature
@@ -0,0 +1,11 @@
+Feature: Project Builds Summary
+ Background:
+ Given I sign in as a user
+ And I own a project
+ And project has CI enabled
+ And project has a recent build
+
+ Scenario: I browse build summary page
+ When I visit recent build summary page
+ Then I see summary for build
+ And I see build trace
diff --git a/features/steps/project/builds.rb b/features/steps/project/builds/artifacts.rb
index 28395281077..f2c87da4717 100644
--- a/features/steps/project/builds.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -1,26 +1,13 @@
-class Spinach::Features::ProjectBuilds < Spinach::FeatureSteps
+class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedBuilds
include RepoHelpers
- step 'I see summary for build' do
- expect(page).to have_content "Build ##{@build.id}"
- end
-
- step 'I see build trace' do
- expect(page).to have_css '#build-trace'
- end
-
step 'I click artifacts download button' do
page.within('.artifacts') { click_link 'Download' }
end
- step 'download of build artifacts archive starts' do
- expect(page.response_headers['Content-Type']).to eq 'application/zip'
- expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary'
- end
-
step 'I click artifacts browse button' do
page.within('.artifacts') { click_link 'Browse' }
end
diff --git a/features/steps/project/builds/permissions.rb b/features/steps/project/builds/permissions.rb
new file mode 100644
index 00000000000..6e9d6504fd5
--- /dev/null
+++ b/features/steps/project/builds/permissions.rb
@@ -0,0 +1,7 @@
+class Spinach::Features::ProjectBuildsPermissions < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedBuilds
+ include SharedPaths
+ include RepoHelpers
+end
diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb
new file mode 100644
index 00000000000..2439d48fbef
--- /dev/null
+++ b/features/steps/project/builds/summary.rb
@@ -0,0 +1,14 @@
+class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedBuilds
+ include RepoHelpers
+
+ step 'I see summary for build' do
+ expect(page).to have_content "Build ##{@build.id}"
+ end
+
+ step 'I see build trace' do
+ expect(page).to have_css '#build-trace'
+ end
+end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index a83d74e5946..f88b01af84e 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -1,11 +1,11 @@
module SharedBuilds
include Spinach::DSL
- step 'CI is enabled' do
+ step 'project has CI enabled' do
@project.enable_ci
end
- step 'I have recent build for my project' do
+ step 'project has a recent build' do
ci_commit = create :ci_commit, project: @project, sha: sample_commit.id
@build = create :ci_build, commit: ci_commit
end
@@ -25,4 +25,13 @@ module SharedBuilds
gzip = fixture_file_upload(metadata, 'application/x-gzip')
@build.update_attributes(artifacts_metadata: gzip)
end
+
+ step 'download of build artifacts archive starts' do
+ expect(page.response_headers['Content-Type']).to eq 'application/zip'
+ expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary'
+ end
+
+ step 'I access artifacts download page' do
+ visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build)
+ end
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index d3501b5f5cb..d9c75d12238 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -7,6 +7,11 @@ module SharedProject
@project.team << [@user, :master]
end
+ step "project exists in some group namespace" do
+ @group = create(:group, name: 'some group')
+ @project = create(:project, namespace: @group)
+ end
+
# Create a specific project called "Shop"
step 'I own project "Shop"' do
@project = Project.find_by(name: "Shop")
@@ -98,6 +103,18 @@ module SharedProject
end
# ----------------------------------------
+ # Project permissions
+ # ----------------------------------------
+
+ step 'I am member of a project with a guest role' do
+ @project.team << [@user, Gitlab::Access::GUEST]
+ end
+
+ step 'I am member of a project with a reporter role' do
+ @project.team << [@user, Gitlab::Access::REPORTER]
+ end
+
+ # ----------------------------------------
# Visibility of archived project
# ----------------------------------------
@@ -229,5 +246,4 @@ module SharedProject
project ||= create(:empty_project, visibility, name: project_name, namespace: user.namespace)
project.team << [user, :master]
end
-
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index cce46886672..82a75734de0 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -394,6 +394,10 @@ module API
expose :runner, with: Runner
end
+ class Trigger < Grape::Entity
+ expose :token, :created_at, :updated_at, :deleted_at, :last_used
+ end
+
class Variable < Grape::Entity
expose :key, :value
end
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 2781f1cf191..5e4964f446c 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -43,6 +43,75 @@ module API
render_api_error!(errors, 400)
end
end
+
+ # Get triggers list
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # page (optional) - The page number for pagination
+ # per_page (optional) - The value of items per page to show
+ # Example Request:
+ # GET /projects/:id/triggers
+ get ':id/triggers' do
+ authenticate!
+ authorize_admin_project
+
+ triggers = user_project.triggers.includes(:trigger_requests)
+ triggers = paginate(triggers)
+
+ present triggers, with: Entities::Trigger
+ end
+
+ # Get specific trigger of a project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # token (required) - The `token` of a trigger
+ # Example Request:
+ # GET /projects/:id/triggers/:token
+ get ':id/triggers/:token' do
+ authenticate!
+ authorize_admin_project
+
+ trigger = user_project.triggers.find_by(token: params[:token].to_s)
+ return not_found!('Trigger') unless trigger
+
+ present trigger, with: Entities::Trigger
+ end
+
+ # Create trigger
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # POST /projects/:id/triggers
+ post ':id/triggers' do
+ authenticate!
+ authorize_admin_project
+
+ trigger = user_project.triggers.create
+
+ present trigger, with: Entities::Trigger
+ end
+
+ # Delete trigger
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # token (required) - The `token` of a trigger
+ # Example Request:
+ # DELETE /projects/:id/triggers/:token
+ delete ':id/triggers/:token' do
+ authenticate!
+ authorize_admin_project
+
+ trigger = user_project.triggers.find_by(token: params[:token].to_s)
+ return not_found!('Trigger') unless trigger
+
+ trigger.destroy
+
+ present trigger, with: Entities::Trigger
+ end
end
end
end
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index fb87637b94f..690bbf97a89 100644
--- a/lib/ci/api/builds.rb
+++ b/lib/ci/api/builds.rb
@@ -20,7 +20,7 @@ module Ci
if build
update_runner_info
- present build, with: Entities::Build
+ present build, with: Entities::BuildDetails
else
not_found!
end
@@ -111,7 +111,7 @@ module Ci
build.artifacts_metadata = metadata
if build.save
- present(build, with: Entities::Build)
+ present(build, with: Entities::BuildDetails)
else
render_validation_error!(build)
end
diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb
index e4ac0545ea2..b25e0e573a8 100644
--- a/lib/ci/api/entities.rb
+++ b/lib/ci/api/entities.rb
@@ -16,10 +16,19 @@ module Ci
end
class Build < Grape::Entity
- expose :id, :commands, :ref, :sha, :status, :project_id, :repo_url,
- :before_sha, :allow_git_fetch, :project_name
-
+ expose :id, :ref, :tag, :sha, :status
expose :name, :token, :stage
+ expose :project_id
+ expose :project_name
+ expose :artifacts_file, using: ArtifactFile, if: lambda { |build, opts| build.artifacts? }
+ end
+
+ class BuildDetails < Build
+ expose :commands
+ expose :repo_url
+ expose :before_sha
+ expose :allow_git_fetch
+ expose :token
expose :options do |model|
model.options
@@ -30,7 +39,7 @@ module Ci
end
expose :variables
- expose :artifacts_file, using: ArtifactFile
+ expose :depends_on_builds, using: Build
end
class Runner < Grape::Entity
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index f1a362f5303..e3d2cc65a8f 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -141,9 +141,12 @@ module Gitlab
username = auth_hash.username
email = auth_hash.email
end
+
+ name = auth_hash.name
+ name = ::Namespace.clean_path(username) if name.strip.empty?
{
- name: auth_hash.name,
+ name: name,
username: ::Namespace.clean_path(username),
email: email,
password: auth_hash.password,
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index 8c63877e51c..d33b5b31e18 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -4,6 +4,9 @@ end
String.disable_colorization = true unless STDOUT.isatty
+# Prevent StateMachine warnings from outputting during a cron task
+StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
+
namespace :gitlab do
# Ask if the user wants to continue
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index db053c610cd..2c0d004d267 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -3,6 +3,8 @@
FactoryGirl.define do
factory :ci_trigger_request, class: Ci::TriggerRequest do
factory :ci_trigger_request_with_variables do
+ trigger factory: :ci_trigger
+
variables do
{
TRIGGER_KEY: 'TRIGGER_VALUE'
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index f9be8fcbcfe..4799bbaa57c 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe AbuseReport, type: :model do
it { is_expected.to validate_presence_of(:reporter) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:message) }
- it { is_expected.to validate_uniqueness_of(:user_id) }
+ it { is_expected.to validate_uniqueness_of(:user_id).with_message('has already been reported') }
end
describe '#remove_user' do
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 0e13456723d..d12b9e65c82 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -426,6 +426,30 @@ describe Ci::Build, models: true do
it { is_expected.to include(project.web_url[7..-1]) }
end
+ describe :depends_on_builds do
+ let!(:build) { FactoryGirl.create :ci_build, commit: commit, name: 'build', stage_idx: 0, stage: 'build' }
+ let!(:rspec_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rspec', stage_idx: 1, stage: 'test' }
+ let!(:rubocop_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rubocop', stage_idx: 1, stage: 'test' }
+ let!(:staging) { FactoryGirl.create :ci_build, commit: commit, name: 'staging', stage_idx: 2, stage: 'deploy' }
+
+ it 'to have no dependents if this is first build' do
+ expect(build.depends_on_builds).to be_empty
+ end
+
+ it 'to have one dependent if this is test' do
+ expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id)
+ end
+
+ it 'to have all builds from build and test stage if this is last' do
+ expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id)
+ end
+
+ it 'to have retried builds instead the original ones' do
+ retried_rspec = Ci::Build.retry(rspec_test)
+ expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
+ end
+ end
+
def create_mr(build, commit, factory: :merge_request, created_at: Time.now)
FactoryGirl.create(factory,
source_project_id: commit.gl_project_id,
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 314bd7ddc59..2a86b60bc4d 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -3,11 +3,19 @@ require 'spec_helper'
describe API::API do
include ApiHelpers
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let!(:trigger_token) { 'secure_token' }
+ let!(:trigger_token_2) { 'secure_token_2' }
+ let!(:project) { create(:project, creator_id: user.id) }
+ let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
+ let!(:developer) { create(:project_member, user: user2, project: project, access_level: ProjectMember::DEVELOPER) }
+ let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
+ let!(:trigger2) { create(:ci_trigger, project: project, token: trigger_token_2) }
+ let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') }
+
describe 'POST /projects/:project_id/trigger' do
- let!(:trigger_token) { 'secure token' }
- let!(:project) { FactoryGirl.create(:project) }
- let!(:project2) { FactoryGirl.create(:empty_project) }
- let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
+ let!(:project2) { create(:empty_project) }
let(:options) do
{
token: trigger_token
@@ -77,4 +85,127 @@ describe API::API do
end
end
end
+
+ describe 'GET /projects/:id/triggers' do
+ context 'authenticated user with valid permissions' do
+ it 'should return list of triggers' do
+ get api("/projects/#{project.id}/triggers", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a(Array)
+ expect(json_response[0]).to have_key('token')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/triggers/:token' do
+ context 'authenticated user with valid permissions' do
+ it 'should return trigger details' do
+ get api("/projects/#{project.id}/triggers/#{trigger.token}", user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a(Hash)
+ end
+
+ it 'should respond with 404 Not Found if requesting non-existing trigger' do
+ get api("/projects/#{project.id}/triggers/abcdef012345", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not return triggers list' do
+ get api("/projects/#{project.id}/triggers/#{trigger.token}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/triggers' do
+ context 'authenticated user with valid permissions' do
+ it 'should create trigger' do
+ expect do
+ post api("/projects/#{project.id}/triggers", user)
+ end.to change{project.triggers.count}.by(1)
+
+ expect(response.status).to eq(201)
+ expect(json_response).to be_a(Hash)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not create trigger' do
+ post api("/projects/#{project.id}/triggers", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not create trigger' do
+ post api("/projects/#{project.id}/triggers")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/triggers/:token' do
+ context 'authenticated user with valid permissions' do
+ it 'should delete trigger' do
+ expect do
+ delete api("/projects/#{project.id}/triggers/#{trigger.token}", user)
+ end.to change{project.triggers.count}.by(-1)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should respond with 404 Not Found if requesting non-existing trigger' do
+ delete api("/projects/#{project.id}/triggers/abcdef012345", user)
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'should not delete trigger' do
+ delete api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'should not delete trigger' do
+ delete api("/projects/#{project.id}/triggers/#{trigger.token}")
+
+ expect(response.status).to eq(401)
+ end
+ end
+ end
end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 648ea0d5f50..eec927102aa 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -101,6 +101,18 @@ describe Ci::API::API do
{ "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false },
])
end
+
+ it "returns dependent builds" do
+ commit = FactoryGirl.create(:ci_commit, project: project)
+ commit.create_builds('master', false, nil, nil)
+ commit.builds.where(stage: 'test').each(&:success)
+
+ post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
+
+ expect(response.status).to eq(201)
+ expect(json_response["depends_on_builds"].count).to eq(2)
+ expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec")
+ end
end
describe "PUT /builds/:id" do
diff --git a/spec/support/gitlab_stubs/gitlab_ci.yml b/spec/support/gitlab_stubs/gitlab_ci.yml
index 3482145404e..a5b256bd3ec 100644
--- a/spec/support/gitlab_stubs/gitlab_ci.yml
+++ b/spec/support/gitlab_stubs/gitlab_ci.yml
@@ -36,8 +36,8 @@ staging:
script: "cap deploy stating"
type: deploy
tags:
- - capistrano
- - debian
+ - ruby
+ - mysql
except:
- stable
@@ -47,8 +47,8 @@ production:
- cap deploy production
- cap notify
tags:
- - capistrano
- - debian
+ - ruby
+ - mysql
only:
- master
- /^deploy-.*$/