Commit 952e2dc6 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into eReGeBe/gitlab-ce-feature/milestone-md

parents 79108533 7acea6bd
......@@ -115,6 +115,11 @@ bundler:audit:
script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
db-migrate-reset:
stage: test
script:
- RAILS_ENV=test bundle exec rake db:migrate:reset
# Ruby 2.2 jobs
spec:feature:ruby22:
......
......@@ -2,8 +2,14 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.8.0 (unreleased)
v 8.7.0 (unreleased)
v 8.7.1 (unreleased)
- Use the `can?` helper instead of `current_user.can?`
v 8.7.0
- Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented
- Fix vulnerability that made it possible to gain access to private labels and milestones
- The number of InfluxDB points stored per UDP packet can now be configured
- Fix error when cross-project label reference used with non-existent project
- Transactions for /internal/allowed now have an "action" tag set
- Method instrumentation now uses Module#prepend instead of aliasing methods
- Repository.clean_old_archives is now instrumented
......@@ -50,8 +56,9 @@ v 8.7.0 (unreleased)
- Add endpoints to archive or unarchive a project !3372
- Fix a bug whith trailing slash in bamboo_url
- Add links to CI setup documentation from project settings and builds pages
- Display project members page to all members
- Handle nil descriptions in Slack issue messages (Stan Hu)
- Add automated repository integrity checks
- Add automated repository integrity checks (OFF by default)
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
- API: Ability to star and unstar a project (Robert Schilling)
- Add default scope to projects to exclude projects pending deletion
......@@ -68,6 +75,7 @@ v 8.7.0 (unreleased)
- Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Allow issues and merge requests to be assigned to the author !2765
- Make Ci::Commit to group only similar builds and make it stateful (ref, tag)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu)
......@@ -77,6 +85,7 @@ v 8.7.0 (unreleased)
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- Fix admin/projects when using visibility levels on search (PotHix)
- Build status notifications
- Update email confirmation interface
- API: Expose user location (Robert Schilling)
- API: Do not leak group existence via return code (Robert Schilling)
- ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591
......@@ -87,7 +96,6 @@ v 8.7.0 (unreleased)
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms
- Show JavaScript errors in sentry
- Diff design updates (colors, button styles, etc)
- Copying and pasting a diff no longer pastes the line numbers or +/-
- Add null check to formData when updating profile content to fix Firefox bug
......@@ -105,6 +113,7 @@ v 8.7.0 (unreleased)
- Updated print style for issues
- Use GitHub Issue/PR number as iid to keep references
- Import GitHub labels
- Add option to filter by "Owned projects" on dashboard page
- Import GitHub milestones
- Fix emoji catgories in the emoji picker
- Execute system web hooks on push to the project
......
......@@ -134,7 +134,7 @@ GEM
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
concurrent-ruby (1.0.0)
concurrent-ruby (1.0.1)
connection_pool (2.2.0)
coveralls (0.8.13)
json (~> 1.8)
......@@ -629,7 +629,7 @@ GEM
recaptcha (1.0.2)
json
redcarpet (3.3.3)
redis (3.2.2)
redis (3.3.0)
redis-actionpack (4.0.1)
actionpack (~> 4)
redis-rack (~> 1.5.0)
......@@ -736,10 +736,9 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (4.0.1)
sidekiq (4.1.1)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
sidekiq-cron (0.4.0)
redis-namespace (>= 1.5.2)
......
......@@ -105,6 +105,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about giving feedback to merge requests is in
the [Thoughtbot code review guide].
## Feature Freeze
5 working days before the 22nd the stable branches for the upcoming release will
be frozen for major changes. Merge requests may still be merged into master
during this period. By freezing the stable branches prior to a release there's
no need to worry about last minute merge requests potentially breaking a lot of
things.
What is considered to be a major change is determined on a case by case basis as
this definition depends very much on the context of changes. For example, a 5
line change might have a big impact on the entire application. Ultimately the
decision will be made by those reviewing a merge request and the release
manager.
During the feature freeze all merge requests that are meant to go into the next
release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set. Merge requests without a milestone and this label will
not be merged into any stable branches.
## Copy & paste responses
### Improperly formatted issue
......
......@@ -55,7 +55,6 @@
#= require_tree .
#= require fuzzaldrin-plus
#= require cropper
#= require raven
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
......
class @CommitsList
@timer = null
@init: (ref, limit) ->
@init: (limit) ->
$("body").on "click", ".day-commits-table li.commit", (event) ->
if event.target.nodeName != "A"
location.href = $(this).attr("url")
......
......@@ -221,6 +221,9 @@ class GitLabDropdown
menu.toggleClass PAGE_TWO_CLASS
# Focus first visible input on active page
@dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus()
parseData: (data) ->
@renderedData = data
......@@ -240,7 +243,8 @@ class GitLabDropdown
shouldPropagate: (e) =>
if @options.multiSelect
$target = $(e.target)
if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link')
e.stopPropagation()
return false
else
......@@ -375,7 +379,6 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
field.remove()
......
......@@ -19,23 +19,19 @@ class @LabelsSelect
$form = $dropdown.closest('form')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value')
$loading = $block.find('.block-loading').fadeOut()
if newLabelField.length
$newLabelCreateButton = $('.js-new-label-btn')
$colorPreview = $('.js-dropdown-label-color-preview')
$newLabelError = $dropdown.parent().find('.js-label-error')
$newLabelError.hide()
$newLabelError = $('.js-label-error')
$colorPreview = $('.js-dropdown-label-color-preview')
$newLabelCreateButton = $('.js-new-label-btn')
# Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on 'click', (e) ->
$newLabelError.hide()
$loading = $block.find('.block-loading').fadeOut()
issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
if issueUpdateURL
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= _.escape(label.title) %>">
<span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>;">
<span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;">
<%= _.escape(label.title) %>
</span>
</a>
......@@ -43,7 +39,9 @@ class @LabelsSelect
)
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
if newLabelField.length and $dropdown.hasClass 'js-extra-options'
if newLabelField.length
# Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on "click", (e) ->
e.preventDefault()
e.stopPropagation()
......@@ -82,26 +80,25 @@ class @LabelsSelect
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelError.hide()
$('.js-new-label-btn').disable()
# Create new label with API
Api.newLabel projectId, {
name: newLabelField.val()
color: newColorField.val()
}, (label) ->
$('.js-new-label-btn').enable()
if label.message?
$newLabelError
.text label.message
.show()
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
saveLabel = ->
# Create new label with API
Api.newLabel projectId, {
name: newLabelField.val()
color: newColorField.val()
}, (label) ->
$newLabelCreateButton.enable()
if label.message?
$newLabelError
.text label.message
.show()
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
newLabelField.on 'keyup change', enableLabelCreateButton
newColorField.on 'keyup change', enableLabelCreateButton
......@@ -112,24 +109,7 @@ class @LabelsSelect
.on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelError.hide()
$('.js-new-label-btn').disable()
# Create new label with API
Api.newLabel projectId, {
name: newLabelField.val()
color: newColorField.val()
}, (label) ->
$('.js-new-label-btn').enable()
if label.message?
$newLabelError
.text label.message
.show()
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
saveLabel()
saveLabelData = ->
selected = $dropdown
......@@ -243,7 +223,7 @@ class @LabelsSelect
fieldName: $dropdown.data('field-name')
id: (label) ->
if $dropdown.hasClass("js-filter-submit") and not label.isAny?
label.title
_.escape label.title
else
label.id
......
......@@ -87,8 +87,8 @@ class @MergeRequestTabs
if window.location.hash
navBarHeight = $('.navbar-gitlab').outerHeight()
$el = $("#{container} #{window.location.hash}")
$.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
$el = $("#{container} #{window.location.hash}:not(.match)")
$.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
......@@ -176,12 +176,12 @@ class @MergeRequestTabs
if locationHash isnt ''
hashClassString = ".#{locationHash.replace('#', '')}"
$diffLine = $(locationHash)
$diffLine = $("#{locationHash}:not(.match)", $('#diffs'))
if $diffLine.is ':not(tr)'
$diffLine = $("td#{locationHash}, td#{hashClassString}")
if not $diffLine.is 'tr'
$diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}")
else
$diffLine = $('td', $diffLine)
$diffLine = $diffLine.find('td')
if $diffLine.length
$diffLine.addClass 'hll'
......
@raven =
init: ->
if gon.sentry_dsn?
Raven.config(gon.sentry_dsn, {
includePaths: [/gon.relative_url_root/]
ignoreErrors: [
# Random plugins/extensions
'top.GLOBALS',
# See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error. html
'originalCreateNotification',
'canvas.contentDocument',
'MyApp_RemoveAllHighlights',
'http://tt.epicplay.com',
'Can\'t find variable: ZiteReader',
'jigsaw is not defined',
'ComboSearch is not defined',
'http://loading.retry.widdit.com/',
'atomicFindClose',
# ISP "optimizing" proxy - `Cache-Control: no-transform` seems to
# reduce this. (thanks @acdha)
# See http://stackoverflow.com/questions/4113268
'bmi_SafeAddOnload',
'EBCallBackMessageReceived',
# See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
'conduitPage'
],
ignoreUrls: [
# Chrome extensions
/extensions\//i,
/^chrome:\/\//i,
# Other plugins
/127\.0\.0\.1:4001\/isrunning/i, # Cacaoweb
/webappstoolbarba\.texthelp\.com\//i,
/metrics\.itunes\.apple\.com\.edgesuite\.net\//i
]
}).install()
if gon.current_user_id
Raven.setUserContext({
id: gon.current_user_id
})
$ ->
raven.init()
......@@ -144,6 +144,10 @@
}
}
.btn-lg {
padding: 12px 20px;
}
.btn-transparent {
color: $btn-transparent-color;
background-color: transparent;
......
......@@ -320,7 +320,7 @@
}
}
.dropdown-input-field {
.dropdown-input-field, .default-dropdown-input {
width: 100%;
padding: 0 7px;
color: $dropdown-input-color;
......
......@@ -38,12 +38,14 @@
.filename {
&.old {
display: inline-block;
span.idiff {
background-color: #f8cbcb;
}
}
&.new {
display: inline-block;
span.idiff {
background-color: #a6f3a6;
}
......@@ -129,6 +131,11 @@
td.line-numbers {
float: none;
border-left: 1px solid #ddd;
i {
float: none;
margin-right: 0;
}
}
td.lines {
padding: 0;
......
.well-confirmation {
margin-bottom: 20px;
border-bottom: 1px solid #eee;
> h1 {
font-weight: 400;
}
.lead {
margin-bottom: 20px;
}
}
.confirmation-content {
a {
color: $md-link-color;
}
}
......@@ -249,6 +249,10 @@
background: $gray-dark;
border: 1px solid $border-gray-dark;
}
&.btn-primary {
@extend .btn-primary
}
}
a:not(.issuable-pager) {
......
......@@ -183,6 +183,9 @@ ul.notes {
}
}
.author_link {
color: $gl-gray;
}
}
.note-headline-light,
......
......@@ -10,6 +10,8 @@ module FilterProjects
def filter_projects(projects)
projects = projects.search(params[:filter_projects]) if params[:filter_projects].present?
projects = projects.non_archived if params[:archived].blank?
projects = projects.personal(current_user) if params[:personal].present? && current_user
projects
end
end
class ConfirmationsController < Devise::ConfirmationsController
def almost_there
flash[:notice] = nil
render layout: "devise_empty"
end
protected
def after_resending_confirmation_instructions_path_for(resource)
users_almost_there_path
end
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
after_sign_in_path_for(resource)
......
......@@ -83,8 +83,7 @@ class Projects::ApplicationController < ApplicationController
end
def apply_diff_view_cookie!
view = params[:view] || cookies[:diff_view]
cookies.permanent[:diff_view] = params[:view] = view if view
cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
end
def builds_enabled
......
......@@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController
end
def cancel_builds
ci_commit.builds.running_or_pending.each(&:cancel)
ci_builds.running_or_pending.each(&:cancel)
redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha)
end
def retry_builds
ci_commit.builds.latest.failed.each do |build|
ci_builds.latest.failed.each do |build|
if build.retryable?
Ci::Build.retry(build)
end
......@@ -99,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController
@commit ||= @project.commit(params[:id])
end
def ci_commit
@ci_commit ||= project.ci_commit(commit.sha)
def ci_commits
@ci_commits ||= project.ci_commits.where(sha: commit.sha)
end
def ci_builds
@ci_builds ||= Ci::Build.where(commit: ci_commits)
end
def define_show_vars
......@@ -113,7 +117,8 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
@statuses = ci_commit.statuses if ci_commit
@statuses = CommitStatus.where(commit: ci_commits)
@builds = Ci::Build.where(commit: ci_commits)
end
def assign_change_commit_vars(mr_source_branch)
......
......@@ -101,7 +101,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
respond_to do |format|
format.js
format.html do
if @issue.valid?
redirect_to issue_path(@issue)
......@@ -110,7 +109,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
format.json do
render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
end
......
......@@ -149,13 +149,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.valid?
respond_to do |format|
format.js
format.html do
redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
@merge_request.target_project, @merge_request])
end
format.json do
render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
end
else
......@@ -321,6 +320,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_widget_vars
@ci_commit = @merge_request.ci_commit
@ci_commits = [@ci_commit].compact
closes_issues
end
......
class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project_member!, except: :leave
before_action :authorize_admin_project_member!, except: [:leave, :index]
def index
@project_members = @project.project_members
......
......@@ -31,11 +31,11 @@ class RegistrationsController < Devise::RegistrationsController
end
def after_sign_up_path_for(_resource)
new_user_session_path
users_almost_there_path
end
def after_inactive_sign_up_path_for(_resource)
new_user_session_path
users_almost_there_path
end
private
......
......@@ -272,16 +272,13 @@ class IssuableFinder
items = items.without_label
else
items = items.with_label(label_names)
if projects
items = items.where(labels: { project_id: projects })
end
end
end
# When filtering by multiple labels we may end up duplicating issues (if one
# has multiple labels). This ensures we only return unique issues.
items.distinct
items
end
def by_due_date(items)
......@@ -321,7 +318,7 @@ class IssuableFinder
end
def label_names
params[:label_name].split(',')
params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
end
def current_user_related?
......
......@@ -4,14 +4,6 @@ module CiStatusHelper
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end
def ci_status_icon(ci_commit)
ci_icon_for_status(ci_commit.status)
end
def ci_status_label(ci_commit)
ci_label_for_status(ci_commit.status)
end
def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
klass = "ci-status ci-#{status}"
......@@ -47,10 +39,13 @@ module CiStatusHelper
end
def render_ci_status(ci_commit, tooltip_placement: 'auto left')
link_to ci_status_icon(ci_commit),
# TODO: split this method into
# - render_commit_status
# - render_pipeline_status
link_to ci_icon_for_status(ci_commit.status),
ci_status_path(ci_commit),
class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
title: "Build #{ci_status_label(ci_commit)}",
title: "Build #{ci_label_for_status(ci_commit.status)}",
data: { toggle: 'tooltip', placement: tooltip_placement }
end
......
......@@ -9,7 +9,13 @@ module DiffHelper
end
def diff_view
params[:view] == 'parallel' ? 'parallel' : 'inline'
diff_views = %w(inline parallel)
if diff_views.include?(cookies[:diff_view])
cookies[:diff_view]
else
diff_views.first
end
end
def diff_hard_limit_enabled?
......
......@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end
def project_pipelines_path(project, *args)
namespace_project_pipelines_path(project.namespace, project, *args)
end
def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args)
end
......
......@@ -84,6 +84,14 @@ module PageLayoutHelper
end
end
def nav(name = nil)
if name
@nav = name
else
@nav
end
end
def fluid_layout(enabled = false)
if @fluid_layout.nil?
@fluid_layout = (current_user && current_user.layout == "fluid") || enabled
......
......@@ -144,6 +144,10 @@ module ProjectsHelper
nav_tabs << :settings
end
if can?(current_user, :read_project_member, project)
nav_tabs << :team
end
if can?(current_user, :read_issue, project)
nav_tabs << :issues
end
......
......@@ -110,4 +110,12 @@ module TabHelper
'active'
end
end
def profile_tab_class
if controller.controller_path =~ /\Aprofiles/
return 'active'
end
'active' if current_controller?('oauth/applications')
end
end
......@@ -37,8 +37,6 @@
module Ci
class Build < CommitStatus
LAZY_ATTRIBUTES = ['trace']
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :erased_by, class_name: 'User'
......@@ -50,25 +48,17 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader
acts_as_taggable
# To prevent db load megabytes of data from trace
default_scope -> { select(Ci::Build.columns_without_lazy) }
before_destroy { project }
class << self
def columns_without_lazy
(column_names - LAZY_ATTRIBUTES).map do |column_name|
"#{table_name}.#{column_name}"
end
end
after_create :execute_hooks
class << self
def last_month
where('created_at > ?', Date.today - 1.month)
end
......@@ -126,12 +116,16 @@ module Ci
end
def retried?
!self.commit.latest_statuses_for_ref(self.ref).include?(self)
!self.commit.statuses.latest.include?(self)
end
def retry
Ci::Build.retry(self)
end
def depends_on_builds
# Get builds of the same type
latest_builds = self.commit.builds.similar(self).latest
latest_builds = self.commit.builds.latest
# Return builds from previous stages
latest_builds.where('stage_idx < ?', stage_idx)
......
......@@ -19,21 +19,28 @@
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
include Statuseable
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :statuses, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
delegate :stages, to: :statuses
validates_presence_of :sha
validates_presence_of :status
validate :valid_commit_sha
# Invalidate object and save if when touched
after_touch :update_state
def self.truncate_sha(sha)
sha[0...8]
end
def to_param
sha
def self.stages
CommitStatus.where(commit: all).stages
end
def project_id
......@@ -68,15 +75,20 @@ module Ci
nil
end
def stage
running_or_pending = statuses.latest.running_or_pending.ordered
running_or_pending.first.try(:stage)
def branch?
!tag?
end
def retryable?
builds.latest.any? do |build|
build.failed? && build.retryable?
end
end
def create_builds(ref, tag, user, trigger_request = nil)
def create_builds(user, trigger_request = nil)
return unless config_processor
config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present?
end
end
......@@ -84,7 +96,7 @@ module Ci
return unless config_processor
# don't create other builds if this one is retried
latest_builds = builds.similar(build).latest
latest_builds = builds.latest
return unless latest_builds.exists?(build.id)
# get list of stages after this build
......@@ -92,88 +104,21 @@ module Ci
next_stages.delete(build.stage)
# get status for all prior builds
prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) }
status = Ci::Status.get_status(prior_builds)
prior_builds = latest_builds.where.not(stage: next_stages)
prior_status = prior_builds.status
# create builds for next stages based
next_stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present?
end
end
def refs
statuses.order(:ref).pluck(:ref).uniq
end
def latest_statuses
@latest_statuses ||= statuses.latest.to_a
end
def latest_statuses_for_ref(ref)
latest_statuses.select { |status| status.ref == ref }
end
def matrix_builds(build = nil)
matrix_builds = builds.latest.ordered
matrix_builds = matrix_builds.similar(build) if build
matrix_builds.to_a
end
def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest)
end
def status
if yaml_errors.present?
return 'failed'
end
@status ||= Ci::Status.get_status(latest_statuses)
end
def pending?
status == 'pending'
end
def running?
status == 'running'
end
def success?
status == 'success'
end
def failed?
status == 'failed'
end
def canceled?
status == 'canceled'
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
end
def duration
duration_array = statuses.map(&:duration).compact
duration_array.reduce(:+).to_i
end
def started_at
@started_at ||= statuses.order('started_at ASC').first.try(:started_at)
end
def finished_at
@finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
end
def coverage
coverage_array = latest_statuses.map(&:coverage).compact
coverage_array = statuses.latest.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
......@@ -181,23 +126,29 @@ module Ci
def config_processor
return nil unless ci_yaml_file
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message)
nil
rescue
save_yaml_error("Undefined error")
nil
return @config_processor if defined?(@config_processor)
@config_processor ||= begin
Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace)
rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message)
nil
rescue
save_yaml_error("Undefined error")
nil
end
end
def ci_yaml_file
return @ci_yaml_file if defined?(@ci_yaml_file)
@ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob.load_all_data!(project.repository)
blob.data
rescue
nil
end
rescue
nil
end
def skip_ci?
......@@ -206,10 +157,23 @@ module Ci
private
def update_state
statuses.reload
self.status = if yaml_errors.blank?
statuses.latest.status || 'skipped'
else
'failed'
end
self.started_at = statuses.started_at
self.finished_at = statuses.finished_at
self.duration = statuses.latest.duration
save
end
def save_yaml_error(error)
return if self.yaml_errors?
self.yaml_errors = error
save
update_state
end
end
end
......@@ -207,12 +207,13 @@ class Commit
@raw.short_id(7)
end
def ci_commit
project.ci_commit(sha)
def ci_commits
@ci_commits ||= project.ci_commits.where(sha: sha)
end
def status
ci_commit.try(:status) || :not_found
return @status if defined?(@status)
@status ||= ci_commits.status
end
def revert_branch_name
......
......@@ -33,30 +33,23 @@
#
class CommitStatus < ActiveRecord::Base
include Statuseable
self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :commit, class_name: 'Ci::Commit', touch: true
belongs_to :user
validates :commit, presence: true
validates :status, inclusion: { in: %w(pending running failed success canceled) }
validates_presence_of :name
alias_attribute :author, :user
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
scope :success, -> { where(status: 'success') }
scope :failed, -> { where(status: 'failed') }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) }
scope :for_ref, ->(ref) { where(ref: ref) }
AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled']
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do
event :run do
......@@ -86,31 +79,24 @@ class CommitStatus < ActiveRecord::Base
after_transition [:pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
state :success, value: 'success'
state :canceled, value: 'canceled'
end
delegate :sha, :short_sha, to: :commit, prefix: false
delegate :sha, :short_sha, to: :commit
# TODO: this should be removed with all references
def before_sha
Gitlab::Git::BLANK_SHA
commit.before_sha || Gitlab::Git::BLANK_SHA
end
def started?
!pending? && !canceled? && started_at
def self.stages
order_by = 'max(stage_idx)'
group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
def self.stages_status
all.stages.inject({}) do |h, stage|
h[stage] = all.where(stage: stage).status
h
end
end
def ignored?
......@@ -118,11 +104,13 @@ class CommitStatus < ActiveRecord::Base
end
def duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
duration =
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
duration
end
def stuck?
......
......@@ -37,7 +37,6 @@ module Issuable
scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
......@@ -122,6 +121,14 @@ module Issuable
joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
end
def with_label(title)
if title.is_a?(Array) && title.count > 1
joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}")
else
joins(:labels).where(labels: { title: title })
end
end
end
def today?
......
module Statuseable
extend ActiveSupport::Concern
AVAILABLE_STATUSES = %w(pending running success failed canceled skipped)
class_methods do
def status_sql
builds = all.select('count(*)').to_sql
success = all.success.select('count(*)').to_sql
ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored)
ignored ||= '0'
pending = all.pending.select('count(*)').to_sql
running = all.running.select('count(*)').to_sql
canceled = all.canceled.select('count(*)').to_sql
skipped = all.skipped.select('count(*)').to_sql
deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL
WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
WHEN (#{builds})=(#{pending}) THEN 'pending'
WHEN (#{builds})=(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{running})+(#{pending})>0 THEN 'running'
ELSE 'failed'
END)"
deduce_status
end
def status
all.pluck(self.status_sql).first
end
def duration
duration_array = all.map(&:duration).compact
duration_array.reduce(:+)
end
def started_at
all.minimum(:started_at)
end
def finished_at
all.maximum(:finished_at)
end
end
included do
validates :status, inclusion: { in: AVAILABLE_STATUSES }
state_machine :status, initial: :pending do
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
state :success, value: 'success'
state :canceled, value: 'canceled'
state :skipped, value: 'skipped'
end
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
scope :success, -> { where(status: 'success') }
scope :failed, -> { where(status: 'failed') }
scope :canceled, -> { where(status: 'canceled') }
scope :skipped, -> { where(status: 'skipped') }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
end
def started?
!pending? && !canceled? && started_at
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
end
end
......@@ -586,7 +586,7 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
@ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project
end
def diff_refs
......
......@@ -963,12 +963,12 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
def ci_commit(sha)
ci_commits.find_by(sha: sha)
def ci_commit(sha, ref)
ci_commits.order(id: :desc).find_by(sha: sha, ref: ref)
end
def ensure_ci_commit(sha)
ci_commit(sha) || ci_commits.create(sha: sha)
def ensure_ci_commit(sha, ref)
ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref)
end
def enable_ci
......
module Ci
class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request, status)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request)
def initialize(commit)
@commit = commit
end
def execute(stage, user, status, trigger_request = nil)
builds_attrs = config_processor.builds_for_stage_and_ref(stage, @commit.ref, @commit.tag, trigger_request)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
......@@ -17,7 +21,8 @@ module Ci
builds_attrs.map do |build_attrs|
# don't create the same build twice
unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag,
trigger_request: trigger_request, name: build_attrs[:name])
build_attrs.slice!(:name,
:commands,
:tag_list,
......@@ -26,17 +31,21 @@ module Ci
:stage,
:stage_idx)
build_attrs.merge!(ref: ref,
tag: tag,
build_attrs.merge!(ref: @commit.ref,
tag: @commit.tag,
trigger_request: trigger_request,
user: user,
project: commit.project)
project: @commit.project)
build = commit.builds.create!(build_attrs)
build.execute_hooks
build
@commit.builds.create!(build_attrs)
end
end
end
private
def config_processor
@config_processor ||= @commit.config_processor
end
end
end
......@@ -7,14 +7,14 @@ module Ci
# check if ref is tag
tag = project.repository.find_tag(ref).present?
ci_commit = project.ensure_ci_commit(commit.sha)
ci_commit = project.ci_commits.create(sha: commit.sha, ref: ref, tag: tag)
trigger_request = trigger.trigger_requests.create!(
variables: variables,
commit: ci_commit,
)
if ci_commit.create_builds(ref, tag, nil, trigger_request)
if ci_commit.create_builds(nil, trigger_request)
trigger_request
end
end
......
......@@ -3,8 +3,9 @@ module Ci
def execute(project, opts)
sha = opts[:sha] || ref_sha(project, opts[:ref])
commit = project.ci_commits.find_by(sha: sha)
image_name = image_for_commit(commit)
ci_commits = project.ci_commits.where(sha: sha)
ci_commits = ci_commits.where(ref: opts[:ref]) if opts[:ref]
image_name = image_for_status(ci_commits.status)
image_path = Rails.root.join('public/ci', image_name)
OpenStruct.new(path: image_path, name: image_name)
......@@ -16,9 +17,9 @@ module Ci
project.commit(ref).try(:sha) if ref
end
def image_for_commit(commit)
return 'build-unknown.svg' unless commit
'build-' + commit.status + ".svg"
def image_for_status(status)
status ||= 'unknown'
'build-' + status + ".svg"
end
end
end
......@@ -2,6 +2,7 @@ class CreateCommitBuildsService
def execute(project, user, params)
return false unless project.builds_enabled?
before_sha = params[:checkout_sha] || params[:before]
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
......@@ -10,15 +11,16 @@ class CreateCommitBuildsService
end
ref = Gitlab::Git.ref_name(origin_ref)
tag = Gitlab::Git.tag_ref?(origin_ref)
# Skip branch removal
if sha == Gitlab::Git::BLANK_SHA
return false
end
commit = project.ci_commit(sha)
commit = project.ci_commit(sha, ref)
unless commit
commit = project.ci_commits.new(sha: sha)
commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag)
# Skip creating ci_commit when no gitlab-ci.yml is found
unless commit.ci_yaml_file
......@@ -32,10 +34,10 @@ class CreateCommitBuildsService
# Skip creating builds for commits that have [ci skip]
unless commit.skip_ci?
# Create builds for commit
tag = Gitlab::Git.tag_ref?(origin_ref)
commit.create_builds(ref, tag, user)
commit.create_builds(user)
end
commit.touch
commit
end
end
......@@ -37,8 +37,9 @@ class IssuableBaseService < BaseService
end
def filter_params(issuable_ability_name = :issue)
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
filter_assignee
filter_milestone
filter_labels
ability = :"admin_#{issuable_ability_name}"
......@@ -49,6 +50,29 @@ class IssuableBaseService < BaseService
end
end
def filter_assignee
if params[:assignee_id] == IssuableFinder::NONE
params[:assignee_id] = ''
end
end
def filter_milestone
milestone_id = params[:milestone_id]
return unless milestone_id
if milestone_id == IssuableFinder::NONE ||
project.milestones.find_by(id: milestone_id).nil?
params[:milestone_id] = ''
end
end
def filter_labels
return if params[:label_ids].to_a.empty?
params[:label_ids] =
project.labels.where(id: params[:label_ids]).pluck(:id)
end
def update(issuable)
change_state(issuable)
filter_params
......
.well-confirmation.text-center
%h1.prepend-top-0
Almost there...
%p.lead
Please check your email to confirm your account
%p.confirmation-content.text-center
No confirmation email received? Please check your spam folder or
.append-bottom-20.prepend-top-20.text-center
%a.btn.btn-lg.btn-success{ href: new_user_confirmation_path }
Request new confirmation email
- page_title "Applications"
- header_title page_title, applications_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
......
......@@ -25,6 +25,10 @@
.content-wrapper
= render "layouts/flash"
= yield :flash_message
- if defined?(nav) && nav
.layout-nav
%div{ class: container_class }
= render "layouts/nav/#{nav}"
%div{ class: (container_class unless @no_container) }
.content
.clearfix
......
......@@ -6,6 +6,6 @@
= yield :scripts_body_top
= render "layouts/header/default", title: header_title
= render 'layouts/page', sidebar: sidebar
= render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
!!! 5
%html{ lang: "en"}
= render "layouts/head"
%body.ui_charcoal.login-page.application.navless
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
.content
= render "layouts/flash"
= yield
%hr
.container
.footer-links
= link_to "Explore", explore_root_path
= link_to "Help", help_path
= link_to "About GitLab", "https://about.gitlab.com/"
......@@ -48,8 +48,7 @@
%span
Help
%li.separate-item
= nav_link(controller: :profile) do
= nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
= icon('user fw')
%span
......
%ul.nav.nav-sidebar
= nav_link do
= link_to root_path, title: 'Go to dashboard', class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Go to dashboard
%li.separate-item
%ul.nav-links
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
= icon('user fw')
%span
Profile Settings
Profile
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account' do
= icon('gear fw')
......@@ -27,7 +19,6 @@
= icon('envelope-o fw')
%span
Emails
%span.count= number_with_delimiter(current_user.emails.count + 1)
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
......@@ -45,7 +36,6 @@
= icon('key fw')
%span
SSH Keys
%span.count= number_with_delimiter(current_user.keys.count)
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
......
......@@ -77,7 +77,7 @@
Merge Requests
%span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- if project_nav_tab? :settings
- if project_nav_tab? :team
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
= icon('users fw')
......
- page_title "Profile Settings"
- header_title "Profile Settings", profile_path unless header_title
- sidebar "profile"
- sidebar "dashboard"
- nav "profile"
= render template: "layouts/application"
- page_title "Account"
- header_title page_title, profile_account_path
- if current_user.ldap_user?
.alert.alert-info
......
- page_title "Audit Log"
- header_title page_title, audit_log_profile_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
......
- page_title "Emails"
- header_title page_title, profile_emails_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
......
- page_title "SSH Keys"
- header_title page_title, profile_keys_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
......
- page_title "Notifications"
- header_title page_title, profile_notifications_path
%div
- if @user.errors.any?
......
- page_title "Password"
- header_title page_title, edit_profile_password_path
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
......
- page_title 'Preferences'
- header_title page_title, profile_preferences_path
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
.col-lg-3.profile-settings-sidebar
......
......@@ -55,6 +55,9 @@
%li
gcovr (C/C++) -
%code ^TOTAL.*\s+(\d+\%)$
%li
tap --coverage-report=text-summary (Node.js) -
%code ^Statements\s*:\s*([^%]+)
.form-group
.col-sm-offset-2.col-sm-10
......
.project-last-commit
- ci_commit = project.ci_commit(commit.sha)
- if ci_commit
= link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
= ci_status_icon(ci_commit)
= ci_status_label(ci_commit)
- if commit.status
= link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do
= ci_icon_for_status(commit.status)
= ci_label_for_status(commit.status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
......
- if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom?
%tr.line_holder{ id: @form.since }
%tr.line_holder
= render "projects/diffs/match_line", { line: @match_line,
line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
- @lines.each_with_index do |line, index|
- line_new = index + @form.since
- line_old = line_new - @form.offset
%tr.line_holder
%tr.line_holder{ id: line_old }
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
= link_to raw(line_old), "#"
= link_to raw(line_old), "##{line_old}"
%td.new_line.diff-line-num{ data: { linenumber: line_old } }
= link_to raw(line_new) , "#"
= link_to raw(line_new) , "##{line_old}"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
......
......@@ -58,6 +58,6 @@
%th Coverage
%th
= render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
= render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled?
= paginate @builds, theme: 'gitlab'
......@@ -13,7 +13,7 @@
= link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request)
#up-build-trace
- builds = @build.commit.matrix_builds(@build)
- builds = @build.commit.builds.latest.to_a
- if builds.size > 1
%ul.nav-links.no-top.no-bottom
- builds.each do |build|
......
......@@ -19,11 +19,12 @@
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
%td
- if build.ref
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- else
.light none
- if defined?(ref) && ref
%td
- if build.ref
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- else
.light none
- if defined?(runner) && runner
%td
......@@ -48,6 +49,8 @@
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
- if defined?(retried) && retried
%span.label.label-warning retried
%td.duration
- if build.duration
......
.gray-content-block.middle-block
.pull-right
- if can?(current_user, :update_build, @ci_commit.project)
- if @ci_commit.builds.latest.failed.any?(&:retryable?)
= link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
- if @ci_commit.builds.running_or_pending.any?
= link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
.oneline
= pluralize @statuses.count(:id), "build"
- if defined?(link_to_commit) && link_to_commit
for commit
= link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace"
- if @ci_commit.duration > 0
in
= time_interval_in_words @ci_commit.duration
- if @ci_commit.yaml_errors.present?
.bs-callout.bs-callout-danger
%h4 Found errors in your .gitlab-ci.yml:
%ul
- @ci_commit.yaml_errors.split(",").each do |error|
%li= error
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
.table-holder
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Ref
%th Stage
%th Name
%th Duration
%th Finished at
- if @ci_commit.project.build_coverage_enabled?
%th Coverage
%th
- @ci_commit.refs.each do |ref|
- builds = @ci_commit.statuses.for_ref(ref).latest.ordered
= render builds, coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true
- if @ci_commit.retried.any?
.gray-content-block.second-block
Retried builds
.table-holder
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Ref
%th Stage
%th Name
%th Duration
%th Finished at
- if @ci_commit.project.build_coverage_enabled?
%th Coverage
%th
= render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true
- @ci_commits.each do |ci_commit|
= render "ci_commit", ci_commit: ci_commit
.gray-content-block.middle-block
.pull-right
- if can?(current_user, :update_build, @project)
- if ci_commit.builds.latest.failed.any?(&:retryable?)
= link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post
- if ci_commit.builds.running_or_pending.any?
= link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post
.oneline
= pluralize ci_commit.statuses.count(:id), "build"
- if ci_commit.ref
for
%span.label.label-info
= ci_commit.ref
- if defined?(link_to_commit) && link_to_commit
for commit
= link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace"
- if ci_commit.duration > 0
in
= time_interval_in_words ci_commit.duration
- if ci_commit.yaml_errors.present?
.bs-callout.bs-callout-danger
%h4 Found errors in your .gitlab-ci.yml:
%ul
- ci_commit.yaml_errors.split(",").each do |error|
%li= error
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
- if @project.builds_enabled? && !ci_commit.ci_yaml_file
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
.table-holder
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Stage
%th Name
%th Duration
%th Finished at
- if @project.build_coverage_enabled?
%th Coverage
%th
- builds = ci_commit.statuses.latest.ordered
= render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true
- if ci_commit.retried.any?
.gray-content-block.second-block
Retried builds
.table-holder
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Ref
%th Stage
%th Name
%th Duration
%th Finished at
- if @project.build_coverage_enabled?
%th Coverage
%th
= render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false
......@@ -43,12 +43,12 @@
- @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace"
- if @ci_commit
- if @commit.status
.pull-right
= link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
= ci_status_icon(@ci_commit)
= link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do
= ci_icon_for_status(@commit.status)
build:
= ci_status_label(@ci_commit)
= ci_label_for_status(@commit.status)
.commit-info-row.branches
%i.fa.fa-spinner.fa-spin
......
......@@ -5,7 +5,7 @@
.prepend-top-default
= render "commit_box"
- if @ci_commit
- if @commit.status
= render "ci_menu"
- else
%div.block-connector
......
......@@ -4,9 +4,8 @@
- notes = commit.notes
- note_count = notes.user.count
- ci_commit = project.ci_commit(commit.sha)
- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
- cache_key.push(ci_commit.status) if ci_commit
- cache_key.push(commit.status) if commit.status
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
......@@ -17,8 +16,8 @@
%a.text-expander.js-toggle-button ...
.pull-right
- if ci_commit
= render_ci_status(ci_commit)
- if commit.status
= render_ci_status(commit)
= clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
......
......@@ -39,4 +39,4 @@
= spinner
:javascript
CommitsList.init("#{@ref}", #{@limit});
CommitsList.init(#{@limit});
......@@ -15,12 +15,13 @@
- if defined?(commit_sha) && commit_sha
%td
= link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace"
%td
- if generic_commit_status.ref
= link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
- else
.light none
- if defined?(ref) && ref
%td
- if generic_commit_status.ref
= link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
- else
.light none
- if defined?(runner) && runner
%td
......
......@@ -5,7 +5,7 @@
- @related_branches.each do |branch|
%li
- sha = @project.repository.find_branch(branch).target
- ci_commit = @project.ci_commit(sha) if sha
- ci_commit = @project.ci_commit(sha, branch) if sha
- if ci_commit
%span.related-branch-ci-status
= render_ci_status(ci_commit)
......
......@@ -3,7 +3,7 @@
- page_card_attributes @merge_request.card_attributes
- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
- if params[:view] == 'parallel'
- if diff_view == 'parallel'
- fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)}
......
- page_title "#{@merge_request.title} (#{merge_request.to_reference}", "Merge Requests"
- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
= render "header_title"
.merge-request
......
= render "projects/commit/builds", link_to_commit: true
= render "projects/commit/ci_commit", ci_commit: @ci_commit, link_to_commit: true
......@@ -8,7 +8,7 @@
group, members with
%strong #{group_links.human_access}
role (#{shared_group_users_count})
- if current_user.can?(:admin_group, shared_group)
- if can?(current_user, :admin_group, shared_group)
.panel-head-actions
= link_to group_group_members_path(shared_group), class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o
......
......@@ -8,39 +8,7 @@
= h(multi_label_name(params[:label_name], "Label"))
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
.dropdown-page-one
= dropdown_title("Filter by label")
= dropdown_filter("Search labels")
= dropdown_content
- if @project
= dropdown_footer do
%ul.dropdown-footer-list
- if can? current_user, :admin_label, @project
%li
%a.dropdown-toggle-page{href: "#"}
Create new
%li
= link_to namespace_project_labels_path(@project.namespace, @project) do
- if can? current_user, :admin_label, @project
Manage labels
- else
View labels
= render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label" }
- if can? current_user, :admin_label, @project and @project
.dropdown-page-two.dropdown-new-label
= dropdown_title("Create new label", back: true)
= dropdown_content do
.dropdown-labels-error.js-label-error
%input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"}
.suggest-colors.suggest-colors-dropdown
- suggested_colors.each do |color|
= link_to '#', style: "background-color: #{color}", data: { color: color } do
&nbsp
.dropdown-label-color-input
.dropdown-label-color-preview.js-dropdown-label-color-preview
%input#new_label_color.dropdown-input-field{ type: "text" }
.clearfix
%button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"}
Create
%button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"}
Cancel
= render partial: "shared/issuable/label_page_create"
= dropdown_loading
.dropdown-page-two.dropdown-new-label
= dropdown_title("Create new label", back: true)
= dropdown_content do
.dropdown-labels-error.js-label-error
%input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" }
.suggest-colors.suggest-colors-dropdown
- suggested_colors.each do |color|
= link_to '#', style: "background-color: #{color}", data: { color: color } do
&nbsp
.dropdown-label-color-input
.dropdown-label-color-preview.js-dropdown-label-color-preview
%input#new_label_color.default-dropdown-input{ type: "text" }
.clearfix
%button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" }
Create
%button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" }
Cancel
- title = local_assigns.fetch(:title, 'Assign labels')
- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels')
.dropdown-page-one
= dropdown_title(title)
= dropdown_filter(filter_placeholder)
= dropdown_content
- if @project
= dropdown_footer do
%ul.dropdown-footer-list
- if can? current_user, :admin_label, @project
%li
%a.dropdown-toggle-page{href: "#"}
Create new
%li
= link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do
- if can? current_user, :admin_label, @project
Manage labels
- else
View labels
= dropdown_loading
\ No newline at end of file
......@@ -20,7 +20,7 @@
%a.btn.btn-default.issuable-pager.disabled{href: '#'}
Next
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
.sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)}
- if issuable.assignee
......@@ -129,24 +129,9 @@
Label
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
.dropdown-page-one
= dropdown_title("Assign labels")
= dropdown_filter("Search labels")
= dropdown_content
- if @project
= dropdown_footer do
%ul.dropdown-footer-list
- if can? current_user, :admin_label, @project
%li
%a.dropdown-toggle-page{href: "#"}
Create new
%li
= link_to namespace_project_labels_path(@project.namespace, @project) do
- if can? current_user, :admin_label, @project
Manage labels
- else
View labels
= dropdown_loading
= render partial: "shared/issuable/label_page_default"
- if can? current_user, :admin_label, @project and @project
= render partial: "shared/issuable/label_page_create"
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
......
- @sort ||= sort_value_recently_updated
- personal = params[:personal]
- archived = params[:archived]
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
......@@ -10,7 +11,7 @@
Sort by
- projects_sort_options_hash.each do |value, title|
%li
= link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do
= link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do
= title
%li.divider
......@@ -20,3 +21,11 @@
%li
= link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
Show archived projects
- if current_user
%li.divider
%li
= link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do
Owned by anyone
%li
= link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do
Owned by me
......@@ -6,9 +6,8 @@
- css_class = '' unless local_assigns[:css_class]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit
- cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3']
- cache_key.push(ci_commit.status) if ci_commit
- cache_key.push(project.commit.status) if project.commit.try(:status)
%li.project-row{ class: css_class }
= cache(cache_key) do
......@@ -16,9 +15,9 @@
- if project.main_language
%span
= project.main_language
- if ci_commit
- if project.commit.try(:status)
%span
= render_ci_status(ci_commit)
= render_ci_status(project.commit)
- if forks
%span
= icon('code-fork')
......
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require_relative '../config/boot'
require 'rake'
Rake.application.run
#!/usr/bin/env ruby
begin
load File.expand_path("../spring", __FILE__)
rescue LoadError
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('rspec-core', 'rspec')
#!/usr/bin/env ruby
begin
load File.expand_path("../spring", __FILE__)
rescue LoadError
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('spinach', 'spinach')
......@@ -4,12 +4,12 @@
# It gets overwritten when you run the `spring binstub` command.
unless defined?(Spring)
require "rubygems"
require "bundler"
require 'rubygems'
require 'bundler'
if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)
Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq }
gem "spring", match[1]
require "spring/binstub"
if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m))
Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) }
gem 'spring', match[1]
require 'spring/binstub'
end
end
#!/usr/bin/env ruby
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('teaspoon', 'teaspoon')
......@@ -107,6 +107,10 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
# Instrument the classes used for checking if somebody has push access.
config.instrument_instance_methods(Gitlab::GitAccess)
config.instrument_instance_methods(Gitlab::GitAccessWiki)
end
GC::Profiler.enable
......
......@@ -418,6 +418,7 @@ Rails.application.routes.draw do
devise_scope :user do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
get '/users/almost_there' => 'confirmations#almost_there'
end
root to: "root#index"
......
......@@ -19,7 +19,7 @@ class Gitlab::Seeder::Builds
commits = @project.repository.commits('master', nil, 5)
commits_sha = commits.map { |commit| commit.raw.id }
commits_sha.map do |sha|
@project.ensure_ci_commit(sha)
@project.ensure_ci_commit(sha, 'master')
end
rescue
[]
......
......@@ -4,6 +4,8 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
def up
return unless Gitlab::Database.postgresql?
create_trigrams_extension
unless trigrams_enabled?
raise 'You must enable the pg_trgm extension. You can do so by running ' \
'"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be ' \
......@@ -37,6 +39,15 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration
row && row['enabled'] == 't' ? true : false
end
def create_trigrams_extension
# This may not work if the user doesn't have permission. We attempt in
# case we do have permission, particularly for test/dev environments.
begin
enable_extension 'pg_trgm'
rescue
end
end
def to_index
{
ci_runners: [:token, :description],
......
class AddFieldsToCiCommit < ActiveRecord::Migration
def change
add_column :ci_commits, :status, :string
add_column :ci_commits, :started_at, :timestamp
add_column :ci_commits, :finished_at, :timestamp
add_column :ci_commits, :duration, :integer
end
end
class UpdateCiCommit < ActiveRecord::Migration
# This migration can be run online, but needs to be executed for the second time after restarting Unicorn workers
# Otherwise Offline migration should be used.
def change
execute("UPDATE ci_commits SET status=#{status}, ref=#{ref}, tag=#{tag} WHERE status IS NULL")
end
private
def status
builds = '(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id)'
success = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='success')"
ignored = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND (status='failed' OR status='canceled') AND allow_failure)"
pending = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='pending')"
running = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='running')"
canceled = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='canceled')"
"(CASE
WHEN #{builds}=0 THEN 'skipped'
WHEN #{builds}=#{success}+#{ignored} THEN 'success'
WHEN #{builds}=#{pending} THEN 'pending'
WHEN #{builds}=#{canceled} THEN 'canceled'
WHEN #{running}+#{pending}>0 THEN 'running'
ELSE 'failed'
END)"
end
def ref
'(SELECT ref FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
end
def tag
'(SELECT tag FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)'
end
end
class AddCiCommitIndexes < ActiveRecord::Migration
disable_ddl_transaction!
def change
add_index :ci_commits, [:gl_project_id, :sha], index_options
add_index :ci_commits, [:gl_project_id, :status], index_options
add_index :ci_commits, [:status], index_options
end
private
def index_options
if Gitlab::Database.postgresql?
{ algorithm: :concurrently }
else
{ }
end
end
end
class DisableRepositoryChecks < ActiveRecord::Migration
def up
change_column_default :application_settings, :repository_checks_enabled, false
execute 'UPDATE application_settings SET repository_checks_enabled = false'
end
def down
change_column_default :application_settings, :repository_checks_enabled, true
execute 'UPDATE application_settings SET repository_checks_enabled = true'
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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