Commit d10e3c44 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into filter-label

parents 3a69dd20 82da19ce
......@@ -16,6 +16,7 @@ v 8.1.0 (unreleased)
- Move CI charts to project graphs area
- Fix cases where Markdown did not render links in activity feed (Stan Hu)
- Add first and last to pagination (Zeger-Jan van de Weg)
- Added Commit Status API
- Show CI status on commit page
- Show CI status on Your projects page and Starred projects page
- Remove "Continuous Integration" page from dashboard
......@@ -46,6 +47,11 @@ v 8.1.0 (unreleased)
- Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji)
- Persist filters when sorting on admin user page (Jerry Lukins)
- Allow dashboard and group issues/MRs to be filtered by label
- Add spellcheck=false to certain input fields
- Invalidate stored service password if the endpoint URL is changed
- Project names are not fully shown if group name is too big, even on group page view
- Apply new design for Files page
- Add "New Page" button to Wiki Pages tab (Stan Hu)
v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
......@@ -60,6 +66,7 @@ v 8.0.3
- Fix URL shown in Slack notifications
- Fix bug where projects would appear to be stuck in the forked import state (Stan Hu)
- Fix Error 500 in creating merge requests with > 1000 diffs (Stan Hu)
- Add work_in_progress key to MR web hooks (Ben Boeckel)
v 8.0.2
- Fix default avatar not rendering in network graph (Stan Hu)
......
......@@ -290,7 +290,7 @@ gem 'newrelic-grape'
gem 'octokit', '~> 3.7.0'
gem "mail_room", "~> 0.5.2"
gem "mail_room", "~> 0.6.0"
gem 'email_reply_parser', '~> 0.5.8'
......
......@@ -392,7 +392,7 @@ GEM
systemu (~> 2.6.2)
mail (2.6.3)
mime-types (>= 1.16, < 3)
mail_room (0.5.2)
mail_room (0.6.0)
method_source (0.8.2)
mime-types (1.25.1)
mimemagic (0.3.0)
......@@ -854,7 +854,7 @@ DEPENDENCIES
jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.16.3)
letter_opener (~> 1.1.2)
mail_room (~> 0.5.2)
mail_room (~> 0.6.0)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
......
......@@ -6,7 +6,7 @@
#
# ### Example Markup
#
# <div id="tree-content-holder">
# <div id="blob-content-holder">
# <div class="file-content">
# <div class="line-numbers">
# <a href="#L1" id="L1" data-line-number="1">1</a>
......@@ -53,7 +53,7 @@ class @LineHighlighter
$.scrollTo("#L#{range[0]}", offset: -150)
bindEvents: ->
$('#tree-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
$('#blob-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
# While it may seem odd to bind to the mousedown event and then throw away
# the click event, there is a method to our madness.
......@@ -62,7 +62,7 @@ class @LineHighlighter
# active state even when the event is cancelled, resulting in an ugly border
# around the link and/or a persisted underline text decoration.
$('#tree-content-holder').on 'click', 'a[data-line-number]', (event) ->
$('#blob-content-holder').on 'click', 'a[data-line-number]', (event) ->
event.preventDefault()
clickHandler: (event) =>
......
......@@ -11,59 +11,41 @@
*= require cal-heatmap
*/
/*
* Welcome to GitLab css!
* If you need to add or modify UI component that is common for many pages
* like a table or typography then make changes in the framework/ directory.
* If you need to add unique style that should affect only one page - use pages/
* directory.
*/
@import "base/fonts";
@import "base/variables";
@import "base/mixins";
@import "base/layout";
/**
* Customized Twitter bootstrap
/*
* GitLab UI framework
*/
@import 'base/gl_variables';
@import 'base/gl_bootstrap';
@import "framework";
/**
/*
* NProgress load bar css
*/
@import 'nprogress';
@import 'nprogress-bootstrap';
/**
/*
* Font icons
*
*/
@import "font-awesome";
/**
* UI themes:
*/
@import "themes/**/*";
/**
* Generic css (forms, nav etc):
*/
@import "generic/**/*";
/**
/*
* Page specific styles (issues, projects etc):
*/
@import "pages/**/*";
/**
/*
* Code highlight
*/
@import "highlight/**/*";
/**
/*
* Styles for JS behaviors.
*/
@import "behaviors.scss";
\ No newline at end of file
/**
* CI specific styles:
*/
@import "ci/**/*";
@import "framework/fonts";
@import "framework/variables";
@import "framework/mixins";
@import "framework/layout";
@import 'framework/tw_bootstrap_variables';
@import 'framework/tw_bootstrap';
@import "framework/avatar.scss";
@import "framework/blocks.scss";
@import "framework/buttons.scss";
@import "framework/calendar.scss";
@import "framework/callout.scss";
@import "framework/common.scss";
@import "framework/files.scss";
@import "framework/filters.scss";
@import "framework/flash.scss";
@import "framework/forms.scss";
@import "framework/gfm.scss";
@import "framework/gitlab-theme.scss";
@import "framework/header.scss";
@import "framework/highlight.scss";
@import "framework/issue_box.scss";
@import "framework/jquery.scss";
@import "framework/lists.scss";
@import "framework/markdown_area.scss";
@import "framework/mobile.scss";
@import "framework/pagination.scss";
@import "framework/selects.scss";
@import "framework/sidebar.scss";
@import "framework/tables.scss";
@import "framework/timeline.scss";
@import "framework/typography.scss";
@import "framework/zen.scss";
......@@ -6,7 +6,7 @@
font-size: 13px;
font-weight: 600;
line-height: 18px;
padding: 11px 16px;
padding: 11px $gl-padding;
letter-spacing: .4px;
&:focus,
......@@ -71,6 +71,14 @@
@include btn-default;
@include btn-white;
&.btn-sm {
padding: 5px 10px;
}
&.btn-xs {
padding: 1px 5px;
}
&.btn-success,
&.btn-new,
&.btn-create,
......
......@@ -398,7 +398,3 @@ table {
.space-right {
margin-right: 10px;
}
.in-line {
display: inline-block;
}
......@@ -22,4 +22,5 @@
.gfm-commit, .gfm-commit_range {
font-family: $monospace_font;
font-size: 90%;
}
......@@ -117,8 +117,12 @@ ul.content-list {
}
.controls {
padding-top: 10px;
padding-top: 4px;
float: right;
.btn {
padding: 10px 14px;
}
}
}
}
......
......@@ -54,147 +54,6 @@
@include box-shadow(0 0 0 3px #f1f1f1);
}
@mixin md-typography {
color: $md-text-color;
a {
color: $md-link-color;
}
img {
max-width: 100%;
}
*:first-child {
margin-top: 0;
}
code {
font-family: $monospace_font;
white-space: pre;
word-wrap: normal;
padding: 1px 2px;
}
kbd {
display: inline-block;
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
color: #555;
vertical-align: middle;
background-color: #FCFCFC;
border-width: 1px;
border-style: solid;
border-color: #CCC #CCC #BBB;
border-image: none;
border-radius: 3px;
box-shadow: 0px -1px 0px #BBB inset;
}
h1 {
font-size: 1.3em;
font-weight: 600;
margin: 24px 0 12px 0;
padding: 0 0 10px 0;
border-bottom: 1px solid #e7e9ed;
color: #313236;
}
h2 {
font-size: 1.2em;
font-weight: 600;
margin: 24px 0 12px 0;
color: #313236;
}
h3 {
margin: 24px 0 12px 0;
font-size: 1.25em;
}
h4 {
margin: 24px 0 12px 0;
font-size: 1.1em;
}
h5 {
margin: 24px 0 12px 0;
font-size: 1em;
}
h6 {
margin: 24px 0 12px 0;
font-size: 0.90em;
}
blockquote {
padding: 8px 21px;
margin: 12px 0 12px;
border-left: 3px solid #e7e9ed;
}
blockquote p {
color: #7f8fa4 !important;
font-size: 15px;
line-height: 1.5;
}
p {
color:#5c5d5e;
margin:6px 0 0 0;
}
table {
@extend .table;
@extend .table-bordered;
margin: 12px 0 12px 0;
color: #5c5d5e;
th {
background: #f8fafc;
}
}
pre {
margin: 12px 0 12px 0 !important;
background-color: #f8fafc !important;
font-size: 13px !important;
color: #5b6169 !important;
line-height: 1.6em !important;
@include border-radius(2px);
}
p > code {
font-weight: inherit;
}
ul {
color: #5c5d5e;
}
li {
line-height: 1.6em;
}
a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] {
&:before {
margin-right: 4px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
content: "\f0c6";
}
&:hover:before {
text-decoration: none;
}
}
}
@mixin str-truncated($max_width: 82%) {
display: inline-block;
overflow: hidden;
......
......@@ -23,7 +23,7 @@
margin-right: 0;
}
.issues-filters,
.issues-details-filters,
.dash-projects-filters,
.check-all-holder {
display: none;
......@@ -83,6 +83,7 @@
.center-top-menu {
height: 45px;
margin-bottom: 30px;
li a {
font-size: 14px;
......@@ -90,9 +91,11 @@
}
}
.projects-search-form {
margin: 0 -5px !important;
.activity-filter-block {
display: none;
}
.projects-search-form {
.btn {
display: none;
}
......@@ -100,6 +103,11 @@
}
@media (max-width: $screen-sm-max) {
.page-with-sidebar .content-wrapper {
padding: 0;
padding-top: 1px;
}
.issues-filters {
.milestone-filter, .labels-filter {
display: none;
......
table {
&.table {
.dropdown-menu a {
text-decoration: none;
}
.success,
.warning,
.danger,
.info {
color: #fff;
a:not(.btn) {
text-decoration: underline;
color: #fff;
}
}
tr {
td, th {
padding: 8px 10px;
......@@ -12,7 +28,7 @@ table {
border-bottom: 1px solid $border-color !important;
}
td {
border-color: #F1F1F1 !important;
border-color: $table-border-color !important;
border-bottom: 1px solid;
}
}
......
......@@ -32,8 +32,6 @@
@import "bootstrap/pager";
@import "bootstrap/labels";
@import "bootstrap/badges";
@import "bootstrap/jumbotron";
@import "bootstrap/thumbnails";
@import "bootstrap/alerts";
@import "bootstrap/progress-bars";
@import "bootstrap/list-group";
......@@ -251,23 +249,3 @@
.text-info:hover {
color: $brand-info;
}
// Tables =====================================================================
table.table {
.dropdown-menu a {
text-decoration: none;
}
.success,
.warning,
.danger,
.info {
color: #fff;
a:not(.btn) {
text-decoration: underline;
color: #fff;
}
}
}
......@@ -156,3 +156,5 @@ $nav-link-padding: 13px $gl-padding;
$pre-bg: #f8fafc !default;
$pre-color: $gl-gray !default;
$pre-border-color: #e7e9ed;
$table-bg-accent: $background-color;
@mixin md-typography {
color: $md-text-color;
a {
color: $md-link-color;
}
img {
max-width: 100%;
}
*:first-child {
margin-top: 0;
}
code {
font-family: $monospace_font;
white-space: pre;
word-wrap: normal;
padding: 1px 2px;
}
kbd {
display: inline-block;
padding: 3px 5px;
font-size: 11px;
line-height: 10px;
color: #555;
vertical-align: middle;
background-color: #FCFCFC;
border-width: 1px;
border-style: solid;
border-color: #CCC #CCC #BBB;
border-image: none;
border-radius: 3px;
box-shadow: 0px -1px 0px #BBB inset;
}
h1 {
font-size: 1.3em;
font-weight: 600;
margin: 24px 0 12px 0;
padding: 0 0 10px 0;
border-bottom: 1px solid #e7e9ed;
color: #313236;
}
h2 {
font-size: 1.2em;
font-weight: 600;
margin: 24px 0 12px 0;
color: #313236;
}
h3 {
margin: 24px 0 12px 0;
font-size: 1.25em;
}
h4 {
margin: 24px 0 12px 0;
font-size: 1.1em;
}
h5 {
margin: 24px 0 12px 0;
font-size: 1em;
}
h6 {
margin: 24px 0 12px 0;
font-size: 0.90em;
}
blockquote {
padding: 8px 21px;
margin: 12px 0 12px;
border-left: 3px solid #e7e9ed;
}
blockquote p {
color: #7f8fa4 !important;
font-size: 15px;
line-height: 1.5;
}
p {
color:#5c5d5e;
margin:6px 0 0 0;
}
table {
@extend .table;
@extend .table-bordered;
margin: 12px 0 12px 0;
color: #5c5d5e;
th {
background: #f8fafc;
}
}
pre {
margin: 12px 0 12px 0 !important;
background-color: #f8fafc !important;
font-size: 13px !important;
color: #5b6169 !important;
line-height: 1.6em !important;
@include border-radius(2px);
}
p > code {
font-weight: inherit;
}
ul {
color: #5c5d5e;
}
li {
line-height: 1.6em;
}
a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] {
&:before {
margin-right: 4px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
content: "\f0c6";
}
&:hover:before {
text-decoration: none;
}
}
}
/**
* Headers
*
......
......@@ -16,6 +16,7 @@ $avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
$border-color: #dce0e6;
$table-border-color: #eef0f2;
$background-color: #F7F8FA;
$header-height: 58px;
$fixed-layout-width: 1200px;
......
......@@ -65,7 +65,6 @@
.note-image-attach {
@extend .col-md-4;
@extend .thumbnail;
margin-left: 45px;
float: none;
}
......
......@@ -63,6 +63,7 @@
}
p {
padding: 0 $gl-padding;
color: #5c5d5e;
}
}
......@@ -510,8 +511,3 @@ pre.light-well {
margin-top: -1px;
}
}
.inline-form {
display: inline-block;
}
.tree-holder {
.tree-content-holder {
float: left;
width: 100%;
.tree-table-holder {
margin-left: -$gl-padding;
margin-right: -$gl-padding;
}
.tree_progress {
......@@ -13,10 +13,15 @@
}
.tree-table {
@extend .table;
@include border-radius(0);
margin-bottom: 0;
tr {
> td, > th {
padding: 10px $gl-padding;
line-height: 32px;
border-color: $table-border-color !important;
}
&:hover {
td {
background: $hover;
......@@ -27,9 +32,9 @@
}
&.selected {
td {
background: $background-color;
border-top: 1px solid #EEE;
border-bottom: 1px solid #EEE;
background: $gray-dark;
border-top: 1px solid $border-gray-dark;
border-bottom: 1px solid $border-gray-dark;
}
}
}
......@@ -85,19 +90,6 @@
margin-right: 15px;
}
.readme-holder {
margin: 0 auto;
.readme-file-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 20px;
color: #777;
border-bottom: 1px solid #DDD;
padding: 10px 0;
}
}
.blob-commit-info {
list-style: none;
margin: 0;
......
......@@ -88,7 +88,7 @@ class GroupsController < Groups::ApplicationController
def destroy
DestroyGroupService.new(@group, current_user).execute
redirect_to root_path, alert: "Group '#{@group.name} was deleted."
redirect_to root_path, alert: "Group '#{@group.name}' was successfully deleted."
end
protected
......
......@@ -135,6 +135,8 @@ class Ability
def project_report_rules
project_guest_rules + [
:create_commit_status,
:read_commit_statuses,
:download_code,
:fork_project,
:create_project_snippet,
......
......@@ -24,32 +24,19 @@
#
module Ci
class Build < ActiveRecord::Base
extend Ci::Model
class Build < CommitStatus
LAZY_ATTRIBUTES = ['trace']
belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :user
serialize :options
validates :commit, presence: true
validates :status, presence: true
validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref
scope :running, ->() { where(status: "running") }
scope :pending, ->() { where(status: "pending") }
scope :success, ->() { where(status: "success") }
scope :failed, ->() { where(status: "failed") }
scope :unstarted, ->() { where(runner_id: nil) }
scope :running_or_pending, ->() { where(status:[:running, :pending]) }
scope :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name, :ref)).order(stage_idx: :asc) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :for_ref, ->(ref) { where(ref: ref) }
scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
acts_as_taggable
......@@ -74,13 +61,14 @@ module Ci
def create_from(build)
new_build = build.dup
new_build.status = :pending
new_build.status = 'pending'
new_build.runner_id = nil
new_build.trigger_request_id = nil
new_build.save
end
def retry(build)
new_build = Ci::Build.new(status: :pending)
new_build = Ci::Build.new(status: 'pending')
new_build.ref = build.ref
new_build.tag = build.tag
new_build.options = build.options
......@@ -98,28 +86,7 @@ module Ci
end
state_machine :status, initial: :pending do
event :run do
transition pending: :running
end
event :drop do
transition running: :failed
end
event :success do
transition running: :success
end
event :cancel do
transition [:pending, :running] => :canceled
end
after_transition pending: :running do |build, transition|
build.update_attributes started_at: Time.now
end
after_transition any => [:success, :failed, :canceled] do |build, transition|
build.update_attributes finished_at: Time.now
project = build.project
if project.web_hooks?
......@@ -136,19 +103,10 @@ module Ci
build.update_coverage
end
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, :project, :gl_project,
to: :commit, prefix: false
def before_sha
Gitlab::Git::BLANK_SHA
def ignored?
failed? && allow_failure?
end
def trace_html
......@@ -156,22 +114,6 @@ module Ci
html || ''
end
def started?
!pending? && !canceled? && started_at
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
end
def ignored?
failed? && allow_failure?
end
def timeout
project.timeout
end
......@@ -180,14 +122,6 @@ module Ci
yaml_variables + project_variables + trigger_variables
end
def duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
end
def project
commit.project
end
......@@ -278,6 +212,25 @@ module Ci
"#{dir_to_trace}/#{id}.log"
end
def target_url
Gitlab::Application.routes.url_helpers.
namespace_project_build_url(gl_project.namespace, gl_project, self)
end
def cancel_url
if active?
Gitlab::Application.routes.url_helpers.
cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url)
end
end
def retry_url
if commands.present?
Gitlab::Application.routes.url_helpers.
cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url)
end
end
private
def yaml_variables
......
......@@ -20,7 +20,8 @@ module Ci
extend Ci::Model
belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id
has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
has_many :statuses, dependent: :destroy, class_name: 'CommitStatus'
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :sha
......@@ -47,7 +48,7 @@ module Ci
end
def retry
builds_without_retry.each do |build|
latest_builds.each do |build|
Ci::Build.retry(build)
end
end
......@@ -81,12 +82,11 @@ module Ci
end
def stage
running_or_pending = builds_without_retry.running_or_pending
running_or_pending.limit(1).pluck(:stage).first
running_or_pending = statuses.latest.running_or_pending.ordered
running_or_pending.first.try(:stage)
end
def create_builds(ref, tag, user, trigger_request = nil)
return if skip_ci? && trigger_request.blank?
return unless config_processor
config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
......@@ -94,7 +94,6 @@ module Ci
end
def create_next_builds(ref, tag, user, trigger_request)
return if skip_ci? && trigger_request.blank?
return unless config_processor
stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
......@@ -107,61 +106,60 @@ module Ci
end
def refs
builds.group(:ref).pluck(:ref)
statuses.order(:ref).pluck(:ref).uniq
end
def last_ref
builds.latest.first.try(:ref)
def latest_statuses
@latest_statuses ||= statuses.latest.to_a
end
def builds_without_retry
builds.latest
def latest_builds
@latest_builds ||= builds.latest.to_a
end
def builds_without_retry_for_ref(ref)
builds.for_ref(ref).latest
def latest_builds_for_ref(ref)
latest_builds.select { |build| build.ref == ref }
end
def retried_builds
@retried_builds ||= (builds.order(id: :desc) - builds_without_retry)
def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest)
end
def status
if skip_ci?
return 'skipped'
elsif yaml_errors.present?
if yaml_errors.present?
return 'failed'
elsif builds.none?
return 'skipped'
elsif success?
end
@status ||= begin
latest = latest_statuses
latest.reject! { |status| status.try(&:allow_failure?) }
if latest.none?
'skipped'
elsif latest.all?(&:success?)
'success'
elsif pending?
elsif latest.all?(&:pending?)
'pending'
elsif running?
elsif latest.any?(&:running?) || latest.any?(&:pending?)
'running'
elsif canceled?
elsif latest.all?(&:canceled?)
'canceled'
else
'failed'
end
end
end
def pending?
builds_without_retry.all? do |build|
build.pending?
end
status == 'pending'
end
def running?
builds_without_retry.any? do |build|
build.running? || build.pending?
end
status == 'running'
end
def success?
builds_without_retry.all? do |build|
build.success? || build.ignored?
end
status == 'success'
end
def failed?
......@@ -169,26 +167,21 @@ module Ci
end
def canceled?
builds_without_retry.all? do |build|
build.canceled?
end
status == 'canceled'
end
def duration
@duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
end
def duration_for_ref(ref)
builds_without_retry_for_ref(ref).select(&:duration).sum(&:duration).to_i
duration_array = latest_statuses.map(&:duration).compact
duration_array.reduce(:+).to_i
end
def finished_at
@finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
@finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
end
def coverage
if project.coverage_enabled?
coverage_array = builds_without_retry.map(&:coverage).compact
coverage_array = latest_builds.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
......@@ -196,7 +189,7 @@ module Ci
end
def matrix_for_ref?(ref)
builds_without_retry_for_ref(ref).pluck(:id).size > 1
latest_builds_for_ref(ref).size > 1
end
def config_processor
......@@ -217,7 +210,6 @@ module Ci
end
def skip_ci?
return false if builds.any?
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end
......
......@@ -184,4 +184,12 @@ class Commit
def parents
@parents ||= Commit.decorate(super, project)
end
def ci_commit
project.ci_commit(sha)
end
def status
ci_commit.try(:status) || :not_found
end
end
class CommitStatus < ActiveRecord::Base
self.table_name = 'ci_builds'
belongs_to :commit, class_name: 'Ci::Commit'
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 :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) }
scope :for_ref, ->(ref) { where(ref: ref) }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
state_machine :status, initial: :pending do
event :run do
transition pending: :running
end
event :drop do
transition running: :failed
end
event :success do
transition [:pending, :running] => :success
end
event :cancel do
transition [:pending, :running] => :canceled
end
after_transition pending: :running do |build, transition|
build.update_attributes started_at: Time.now
end
after_transition any => [:success, :failed, :canceled] do |build, transition|
build.update_attributes finished_at: Time.now
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, :gl_project,
to: :commit, prefix: false
# TODO: this should be removed with all references
def before_sha
Gitlab::Git::BLANK_SHA
end
def started?
!pending? && !canceled? && started_at
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
end
def duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
end
def cancel_url
nil
end
def retry_url
nil
end
end
class GenericCommitStatus < CommitStatus
before_validation :set_default_values
# GitHub compatible API
alias_attribute :context, :name
def set_default_values
self.context ||= 'default'
self.stage ||= 'external'
end
def tags
[:external]
end
end
......@@ -227,7 +227,7 @@ class MergeRequest < ActiveRecord::Base
end
def work_in_progress?
title =~ /\A\[?WIP\]?:? /i
!!(title =~ /\A\[?WIP\]?:? /i)
end
def mergeable?
......@@ -275,7 +275,8 @@ class MergeRequest < ActiveRecord::Base
attrs = {
source: source_project.hook_attrs,
target: target_project.hook_attrs,
last_commit: nil
last_commit: nil,
work_in_progress: work_in_progress?
}
unless last_commit.nil?
......
......@@ -40,12 +40,19 @@ class BambooService < CiService
attr_accessor :response
after_save :compose_service_hook, if: :activated?
before_update :reset_password
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def reset_password
if prop_updated?(:bamboo_url)
self.password = nil
end
end
def title
'Atlassian Bamboo CI'
end
......
......@@ -49,7 +49,7 @@ module Ci
commit = build.commit
return unless commit
return unless commit.builds_without_retry.include? build
return unless commit.latest_builds.include? build
case commit.status.to_sym
when :failed
......
......@@ -48,7 +48,7 @@ module Ci
# it doesn't make sense to send emails for retried builds
commit = build.commit
return unless commit
return unless commit.builds_without_retry.include?(build)
return unless commit.latest_builds.include?(build)
case build.status.to_sym
when :failed
......
......@@ -23,7 +23,7 @@ module Ci
def attachments
fields = []
commit.builds_without_retry.each do |build|
commit.latest_builds.each do |build|
next if build.allow_failure?
next unless build.failed?
fields << {
......
......@@ -48,7 +48,7 @@ module Ci
commit = build.commit
return unless commit
return unless commit.builds_without_retry.include?(build)
return unless commit.latest_builds.include?(build)
case commit.status.to_sym
when :failed
......
......@@ -37,12 +37,19 @@ class TeamcityService < CiService
attr_accessor :response
after_save :compose_service_hook, if: :activated?
before_update :reset_password
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
def reset_password
if prop_updated?(:teamcity_url)
self.password = nil
end
end
def title
'JetBrains TeamCity CI'
end
......
......@@ -117,6 +117,15 @@ class Service < ActiveRecord::Base
end
end
# ActiveRecord does not provide a mechanism to track changes in serialized keys.
# This is why we need to perform extra query to do it mannually.
def prop_updated?(prop_name)
relation_name = self.type.underscore
previous_value = project.send(relation_name).send(prop_name)
return false if previous_value.nil?
previous_value != send(prop_name)
end
def async_execute(data)
return unless supported_events.include?(data[:object_kind])
......
......@@ -17,8 +17,10 @@ module Ci
tag = origin_ref.start_with?('refs/tags/')
commit = project.gl_project.ensure_ci_commit(sha)
unless commit.skip_ci?
commit.update_committed!
commit.create_builds(ref, tag, user)
end
commit
end
......
......@@ -32,7 +32,7 @@
%hr
= form_tag admin_users_path, method: :get, class: 'form-inline' do
.form-group
= search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control'
= search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control', spellcheck: false
= hidden_field_tag "filter", params[:filter]
= button_tag class: 'btn btn-primary' do
%i.fa.fa-search
......
......@@ -27,7 +27,7 @@
.pull-left
= form_tag ci_admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
.form-group
= search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token'
= search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token', spellcheck: false
= submit_tag 'Search', class: 'btn'
.pull-right.light
......
......@@ -76,7 +76,7 @@
%td
= form_tag ci_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
.form-group
= search_field_tag :search, params[:search], class: 'form-control'
= search_field_tag :search, params[:search], class: 'form-control', spellcheck: false
= submit_tag 'Search', class: 'btn'
%td
......
......@@ -3,9 +3,8 @@
.gray-content-block
- if current_user
%ul.nav.nav-pills.event_filter.pull-right
%li.pull-right
= link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do
.pull-right
= link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do
%i.fa.fa-rss
= render 'shared/event_filter'
......
.projects-list-holder
.projects-search-form
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if current_user.can_create_project?
%span.input-group-btn
= link_to new_project_path, class: 'btn btn-green' do
......
......@@ -11,7 +11,7 @@
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search"
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search", spellcheck: false
.form-group
= button_tag 'Search', class: "btn btn-default"
......
.pull-left
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search"
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
.form-group
= button_tag 'Search', class: "btn btn-success"
......
.panel.panel-default.projects-list-holder
.panel-heading.clearfix
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if can? current_user, :create_projects, @group
%span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
New project
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
......@@ -12,7 +12,7 @@
.clearfix.js-toggle-container
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
= button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:admin_group_member, @group)
......
......@@ -25,10 +25,8 @@
.hidden-xs
- if current_user
= render "events/event_last_push", event: @last_push
%ul.nav.nav-pills.event_filter.pull-right
%li
= link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do
.pull-right
= link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
%i.fa.fa-rss
= render 'shared/event_filter'
......
......@@ -23,6 +23,7 @@
= current_user.username
.content-wrapper
= render "layouts/flash"
= yield :flash_message
%div{ class: container_class }
.content
.clearfix
......
.search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control"
= search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control", spellcheck: false
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
......
= render 'projects/last_push'
.gray-content-block.activity-filter-block
- if current_user
%ul.nav.nav-pills.event_filter.pull-right
%li
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do
.pull-right
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do
%i.fa.fa-rss
= render 'shared/event_filter'
......
......@@ -19,7 +19,7 @@
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project
%div#tree-content-holder.tree-content-holder
%div#blob-content-holder.blob-content-holder
%article.file-holder
.file-title
= blob_icon blob.mode, blob.name
......
- gl_project = build.project.gl_project
%tr.build
%td.status
= ci_status_with_icon(build.status)
%td.build-link
= link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
%strong Build ##{build.id}
- if defined?(ref)
%td
= build.ref
%td
= build.stage
%td
= build.name
.pull-right
- if build.tags.any?
- build.tag_list.each do |tag|
%span.label.label-primary
= tag
- if build.trigger_request
%span.label.label-info triggered
- if build.allow_failure
%span.label.label-danger allowed to fail
%td.duration
- if build.duration
#{duration_in_words(build.finished_at, build.started_at)}
%td.timestamp
- if build.finished_at
%span #{time_ago_in_words build.finished_at} ago
- if build.project.coverage_enabled?
%td.coverage
- if build.coverage
#{build.coverage}%
%td
- if defined?(controls) && current_user && can?(current_user, :manage_builds, gl_project)
.pull-right
- if build.active?
= link_to cancel_namespace_project_build_path(gl_project.namespace, gl_project, build, return_to: request.original_url), title: 'Cancel build' do
%i.fa.fa-remove.cred
- elsif build.commands.present?
= link_to retry_namespace_project_build_path(gl_project.namespace, gl_project, build, return_to: request.original_url), method: :post, title: 'Retry build' do
%i.fa.fa-repeat
......@@ -9,7 +9,7 @@
#up-build-trace
- if @commit.matrix_for_ref?(@build.ref)
%ul.center-top-menu.build-top-menu
- @commit.builds_without_retry_for_ref(@build.ref).each do |build|
- @commit.latest_builds_for_ref(@build.ref).each do |build|
%li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do
= ci_icon_for_status(build.status)
......@@ -20,7 +20,7 @@
= build.id
- unless @commit.builds_without_retry_for_ref(@build.ref).include?(@build)
- unless @commit.latest_builds_for_ref(@build.ref).include?(@build)
%li.active
%a
Build ##{@build.id}
......
- return unless @membership
= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline-form', id: 'notification-form' do
= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do
= hidden_field_tag :notification_type, 'project'
= hidden_field_tag :notification_id, @membership.id
= hidden_field_tag :notification_level
......
......@@ -20,19 +20,19 @@
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
- @ci_commit.refs.each do |ref|
.gray-content-block.second-block
Builds for #{ref}
- if @ci_commit.duration_for_ref(ref) > 0
.gray-content-block.second-block
Latest builds
- if @ci_commit.duration > 0
%small.pull-right
%i.fa.fa-time
#{time_interval_in_words @ci_commit.duration_for_ref(ref)}
#{time_interval_in_words @ci_commit.duration}
%table.table.builds
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Ref
%th Stage
%th Name
%th Duration
......@@ -40,10 +40,11 @@
- if @ci_project && @ci_project.coverage_enabled?
%th Coverage
%th
= render partial: "projects/builds/build", collection: @ci_commit.builds_without_retry.for_ref(ref), controls: true
- @ci_commit.refs.each do |ref|
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered, coverage: @ci_project.try(:coverage_enabled?), controls: true
- if @ci_commit.retried_builds.any?
%h3
- if @ci_commit.retried.any?
.gray-content-block.second-block
Retried builds
%table.table.builds
......@@ -59,4 +60,4 @@
- if @ci_project && @ci_project.coverage_enabled?
%th Coverage
%th
= render partial: "projects/builds/build", collection: @ci_commit.retried_builds, ref: true
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, coverage: @ci_project.try(:coverage_enabled?)
%tr.commit_status
%td.status
= ci_status_with_icon(commit_status.status)
%td.commit_status-link
- if commit_status.target_url
= link_to commit_status.target_url do
%strong Build ##{commit_status.id}
- else
%strong Build ##{commit_status.id}
%td
= commit_status.ref
%td
= commit_status.stage
%td
= commit_status.name
.pull-right
- if commit_status.tags.any?
- commit_status.tags.each do |tag|
%span.label.label-primary
= tag
- if commit_status.try(:trigger_request)
%span.label.label-info triggered
- if commit_status.try(:allow_failure)
%span.label.label-danger allowed to fail
%td.duration
- if commit_status.duration
#{duration_in_words(commit_status.finished_at, commit_status.started_at)}
%td.timestamp
- if commit_status.finished_at
%span #{time_ago_in_words commit_status.finished_at} ago
- if defined?(coverage) && coverage
%td.coverage
- if commit_status.try(:coverage)
#{commit_status.coverage}%
%td
- if defined?(controls) && controls && current_user && can?(current_user, :manage_builds, gl_project)
.pull-right
- if commit_status.cancel_url
= link_to commit_status.cancel_url, title: 'Cancel' do
%i.fa.fa-remove.cred
- elsif commit_status.retry_url
= link_to commit_status.retry_url, method: :post, title: 'Retry' do
%i.fa.fa-repeat
......@@ -111,10 +111,10 @@
- if current_user.can_create_group?
.pull-right
.light.in-line
.light.inline
.space-right
Need a group for several dependent projects?
= link_to new_group_path, class: "btn btn-xs" do
= link_to new_group_path, class: "btn" do
Create a group
.save-project-loader.hide
......
......@@ -5,7 +5,7 @@
.clearfix.js-toggle-container
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
= button_tag 'Search', class: 'btn'
- if can?(current_user, :admin_project_member, @project)
......
......@@ -3,10 +3,10 @@
- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
= link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn btn-success col-xs-10', rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%a.col-xs-2.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
......
......@@ -2,7 +2,8 @@
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "#{@project.name} activity")
- if current_user && can?(current_user, :download_code, @project)
= content_for :flash_message do
- if current_user && can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
= render 'shared/no_password'
......
%article.readme-holder#README
%article.file-holder.readme-holder#README
.file-title
= link_to '#README' do
%h4.readme-file-title
%strong
%i.fa.fa-file
= readme.name
.wiki
.file-content.wiki
= render_readme(readme)
%ul.breadcrumb.repo-breadcrumb
.gray-content-block
%ul.breadcrumb.repo-breadcrumb
%li
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
......@@ -11,7 +12,7 @@
- if allowed_tree_edit?
%li
%span.dropdown
%a.dropdown-toggle.btn.btn-xs.add-to-tree{href: '#', "data-toggle" => "dropdown"}
%a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu
%li
......@@ -28,8 +29,9 @@
= icon('folder fw')
New directory
%div#tree-content-holder.tree-content-holder.prepend-top-20
%table#tree-slider{class: "table_#{@hex_path} tree-table" }
%div#tree-content-holder.tree-content-holder
.tree-table-holder
%table.table#tree-slider{class: "table_#{@hex_path} tree-table table-striped" }
%thead
%tr
%th Name
......
......@@ -3,6 +3,7 @@
= render 'nav'
.gray-content-block
= render 'main_links'
%h3.page-title
All Pages
%ul.content-list
......
......@@ -6,7 +6,7 @@
.search-holder.clearfix
.input-group
= search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true
= search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true, spellcheck: false
%span.input-group-btn
= button_tag 'Search', class: "btn btn-primary"
- unless params[:snippets].eql? 'true'
......
= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
.append-right-10.hidden-xs.hidden-sm
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input' }
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input', spellcheck: false }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
......
......@@ -2,11 +2,12 @@
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
%ul.projects-list
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil
= render "shared/projects/project", project: project,
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci
- if projects.size > projects_limit
......
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" unless project.description.present?
%li.project-row{ class: css_class }
......@@ -11,7 +12,7 @@
= project_icon(project, alt: '', class: 'avatar project-avatar s46')
%span.project-full-name
%span.namespace-name
- if project.namespace
- if project.namespace && !skip_namespace
= project.namespace.human_name
\/
%span.project-name.filter-title
......
......@@ -12,8 +12,10 @@
# :email: "gitlab-incoming@gmail.com"
# # Email account password
# :password: "password"
# # The name of the mailbox where incoming mail will end up. Usually "inbox".
# :name: "inbox"
# # Always "sidekiq".
# :delivery_method: sidekiq
# # Always true.
......@@ -25,5 +27,13 @@
# :namespace: resque:gitlab
# # Always "incoming_email".
# :queue: incoming_email
# # Always "EmailReceiverWorker"
# # Always "EmailReceiverWorker".
# :worker: EmailReceiverWorker
# # Always "redis".
# :arbitration_method: redis
# :arbitration_options:
# # The URL to the Redis server. Should match the URL in config/resque.yml.
# :redis_url: redis://localhost:6379
# # Always "mail_room:gitlab".
# :namespace: mail_room:gitlab
class AddTypeAndDescriptionToBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :type, :string
add_column :ci_builds, :target_url, :string
add_column :ci_builds, :description, :string
add_index :ci_builds, [:commit_id, :type, :ref]
add_index :ci_builds, [:commit_id, :type, :name, :ref]
end
end
class MigrateNameToDescriptionForBuilds < ActiveRecord::Migration
def change
execute("UPDATE ci_builds SET type='Ci::Build' WHERE type IS NULL")
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151007120511) do
ActiveRecord::Schema.define(version: 20151008130321) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -103,9 +103,14 @@ ActiveRecord::Schema.define(version: 20151007120511) do
t.boolean "tag"
t.string "ref"
t.integer "user_id"
t.string "type"
t.string "target_url"
t.string "description"
end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
......
......@@ -62,7 +62,8 @@ Parameters:
"authored_date": "2012-09-20T09:06:12+03:00",
"parent_ids": [
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
]
],
"status": "running"
}
```
......@@ -156,3 +157,84 @@ Parameters:
"line_type": "new"
}
```
## Get the status of a commit
Get the statuses of a commit in a project.
```
GET /projects/:id/repository/commits/:sha/statuses
```
Parameters:
- `id` (required) - The ID of a project
- `sha` (required) - The commit SHA
- `ref` (optional) - Filter by ref name, it can be branch or tag
- `stage` (optional) - Filter by stage
- `name` (optional) - Filer by status name, eg. jenkins
- `all` (optional) - The flag to return all statuses, not only latest ones
```json
[
{
"id": 13,
"sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27",
"ref": "test",
"status": "success",
"name": "ci/jenkins",
"target_url": "http://jenkins/project/url",
"description": "Jenkins success",
"created_at": "2015-10-12T09:47:16.250Z",
"started_at": "2015-10-12T09:47:16.250Z"",
"finished_at": "2015-10-12T09:47:16.262Z",
"author": {
"id": 1,
"username": "admin",
"email": "admin@local.host",
"name": "Administrator",
"blocked": false,
"created_at": "2012-04-29T08:46:00Z"
}
}
]
```
## Post the status to commit
Adds or updates a status of a commit.
```
POST /projects/:id/statuses/:sha
```
- `id` (required) - The ID of a project
- `sha` (required) - The commit SHA
- `state` (required) - The state of the status. Can be: pending, running, success, failed, canceled
- `ref` (optional) - The ref (branch or tag) to which the status refers
- `name` or `context` (optional) - The label to differentiate this status from the status of other systems. Default: "default"
- `target_url` (optional) - The target URL to associate with this status
- `description` (optional) - The short description of the status
```json
{
"id": 13,
"sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27",
"ref": "test",
"status": "success",
"name": "ci/jenkins",
"target_url": "http://jenkins/project/url",
"description": "Jenkins success",
"created_at": "2015-10-12T09:47:16.250Z",
"started_at": "2015-10-12T09:47:16.250Z"",
"finished_at": "2015-10-12T09:47:16.262Z",
"author": {
"id": 1,
"username": "admin",
"email": "admin@local.host",
"name": "Administrator",
"blocked": false,
"created_at": "2012-04-29T08:46:00Z"
}
}
```
......@@ -188,6 +188,7 @@ Parameters:
- `title` (required) - Title of MR
- `description` (optional) - Description of MR
- `target_project_id` (optional) - The target project (numeric id)
- `labels` (optional) - Labels for MR as a comma-separated list
```json
{
......@@ -239,6 +240,7 @@ Parameters:
- `title` - Title of MR
- `description` - Description of MR
- `state_event` - New state (close|reopen|merge)
- `labels` (optional) - Labels for MR as a comma-separated list
```json
{
......
......@@ -8,31 +8,5 @@ It is possible to add a markdown-formatted welcome message to your GitLab
sign-in page. Users of GitLab Enterprise Edition should use the [branded login
page feature](/ee/customization/branded_login_page.html) instead.
## Omnibus-gitlab example
In `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_rails['extra_sign_in_text'] = <<'EOS'
# ACME GitLab
Welcome to the [ACME](http://www.example.com) GitLab server!
EOS
```
Run `sudo gitlab-ctl reconfigure` for changes to take effect.
## Installation from source
In `/home/git/gitlab/config/gitlab.yml`:
```yaml
# snip
production:
# snip
extra:
sign_in_text: |
# ACME GitLab
Welcome to the [ACME](http://www.example.com) GitLab server!
```
Run `sudo service gitlab reload` for the change to take effect.
The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI.
Admin area > Settings
\ No newline at end of file
......@@ -2,10 +2,6 @@
GitLab can be set up to allow users to comment on issues and merge requests by replying to notification emails.
**Warning**: Do not enable Reply by email if you have **multiple GitLab application servers**.
Due to an issue with the way incoming emails are read from the mail server, every incoming reply-by-email email will result in as many comments being created as you have application servers.
[A fix is being worked on.](https://github.com/tpitale/mail_room/issues/46)
## Get a mailbox
Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises.
......@@ -118,8 +114,10 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:email: "incoming"
# Email account password
:password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
# Always "sidekiq".
:delivery_method: sidekiq
# Always true.
......@@ -133,6 +131,14 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
# Always "redis".
:arbitration_method: redis
:arbitration_options:
# The URL to the Redis server. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "mail_room:gitlab".
:namespace: mail_room:gitlab
```
```yaml
......@@ -151,8 +157,10 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:email: "gitlab-incoming@gmail.com"
# Email account password
:password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
# Always "sidekiq".
:delivery_method: sidekiq
# Always true.
......@@ -166,6 +174,14 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
# Always "redis".
:arbitration_method: redis
:arbitration_options:
# The URL to the Redis server. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "mail_room:gitlab".
:namespace: mail_room:gitlab
```
5. Edit the init script configuration at `/etc/default/gitlab` to enable `mail_room`:
......@@ -228,8 +244,10 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:email: "gitlab-incoming@gmail.com"
# Email account password
:password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
# Always "sidekiq".
:delivery_method: sidekiq
# Always true.
......@@ -243,6 +261,14 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
# Always "redis".
:arbitration_method: redis
:arbitration_options:
# The URL to the Redis server. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "mail_room:gitlab".
:namespace: mail_room:gitlab
```
4. Uncomment the `mail_room` line in your `Procfile`:
......
......@@ -23,9 +23,11 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
cd /home/git/gitlab
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- Gemfile.lock db/schema.rb
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
sudo -u git -H git checkout $LATEST_TAG -b $LATEST_TAG
sudo -u git -H git checkout LATEST_TAG -b LATEST_TAG
```
Replace `LATEST_TAG` with the latest GitLab tag you want to update to, for example `v8.0.3`.
Use `git tag -l 'v*.[0-9]' --sort='v:refname'` to see a list of all tags.
Make sure to update patch versions only (check your current version with `cat VERSION`)
### 3. Update gitlab-shell to the corresponding version
......
......@@ -314,7 +314,8 @@ X-Gitlab-Event: Note Hook
"name": "John Smith",
"email": "john@example.com"
}
}
},
"work_in_progress": false
}
}
```
......@@ -500,6 +501,7 @@ X-Gitlab-Event: Merge Request Hook
"email": "gitlabdev@dv6700.(none)"
}
},
"work_in_progress": false,
"url": "http://example.com/diaspora/merge_requests/1",
"action": "open"
}
......
......@@ -118,6 +118,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'I see builds list' do
expect(page).to have_content "build: pending"
expect(page).to have_content "Builds for master"
expect(page).to have_content "Latest builds"
end
end
......@@ -46,6 +46,7 @@ module API
mount Services
mount Files
mount Commits
mount CommitStatus
mount Namespaces
mount Branches
mount Labels
......
require 'mime/types'
module API
# Project commit statuses API
class CommitStatus < Grape::API
resource :projects do
before { authenticate! }
# Get a commit's statuses
#
# Parameters:
# id (required) - The ID of a project
# sha (required) - The commit hash
# ref (optional) - The ref
# stage (optional) - The stage
# name (optional) - The name
# all (optional) - Show all statuses, default: false
# Examples:
# GET /projects/:id/repository/commits/:sha/statuses
get ':id/repository/commits/:sha/statuses' do
authorize! :read_commit_statuses, user_project
sha = params[:sha]
ci_commit = user_project.ci_commit(sha)
not_found! 'Commit' unless ci_commit
statuses = ci_commit.statuses
statuses = statuses.latest unless parse_boolean(params[:all])
statuses = statuses.where(ref: params[:ref]) if params[:ref].present?
statuses = statuses.where(stage: params[:stage]) if params[:stage].present?
statuses = statuses.where(name: params[:name]) if params[:name].present?
present paginate(statuses), with: Entities::CommitStatus
end
# Post status to commit
#
# Parameters:
# id (required) - The ID of a project
# sha (required) - The commit hash
# ref (optional) - The ref
# state (required) - The state of the status. Can be: pending, running, success, error or failure
# target_url (optional) - The target URL to associate with this status
# description (optional) - A short description of the status
# name or context (optional) - A string label to differentiate this status from the status of other systems. Default: "default"
# Examples:
# POST /projects/:id/statuses/:sha
post ':id/statuses/:sha' do
authorize! :create_commit_status, user_project
required_attributes! [:state]
attrs = attributes_for_keys [:ref, :target_url, :description, :context, :name]
commit = @project.commit(params[:sha])
not_found! 'Commit' unless commit
ci_commit = @project.ensure_ci_commit(commit.sha)
name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref])
status ||= GenericCommitStatus.new(commit: ci_commit, user: current_user)
status.update(attrs)
case params[:state].to_s
when 'running'
status.run
when 'success'
status.success
when 'failed'
status.drop
when 'canceled'
status.cancel
else
status.status = params[:state].to_s
end
if status.save
present status, with: Entities::CommitStatus
else
render_validation_error!(status)
end
end
end
end
end
......@@ -149,6 +149,7 @@ module API
class RepoCommitDetail < RepoCommit
expose :parent_ids, :committed_date, :authored_date
expose :status
end
class ProjectSnippet < Grape::Entity
......@@ -228,6 +229,12 @@ module API
expose :created_at
end
class CommitStatus < Grape::Entity
expose :id, :sha, :ref, :status, :name, :target_url, :description,
:created_at, :started_at, :finished_at
expose :author, using: Entities::UserBasic
end
class Event < Grape::Entity
expose :title, :project_id, :action_name
expose :target_id, :target_type, :author_id
......
......@@ -99,7 +99,7 @@ module API
# id (required) - The ID of a project - this will be the source of the merge request
# source_branch (required) - The source branch
# target_branch (required) - The target branch
# target_project - The target project of the merge request defaults to the :id of the project
# target_project_id - The target project of the merge request defaults to the :id of the project
# assignee_id - Assignee user ID
# title (required) - Title of MR
# description - Description of MR
......
......@@ -2,7 +2,7 @@ module Ci
module API
module Entities
class Commit < Grape::Entity
expose :id, :ref, :sha, :project_id, :before_sha, :created_at
expose :id, :sha, :project_id, :created_at
expose :status, :finished_at, :duration
expose :git_commit_message, :git_author_name, :git_author_email
end
......@@ -12,7 +12,7 @@ module Ci
end
class Build < Grape::Entity
expose :id, :commands, :ref, :sha, :project_id, :repo_url,
expose :id, :commands, :ref, :sha, :status, :project_id, :repo_url,
:before_sha, :allow_git_fetch, :project_name
expose :options do |model|
......
......@@ -35,7 +35,7 @@ module Gitlab
end
def find_by_email
::User.find_by(email: auth_hash.email)
::User.find_by(email: auth_hash.email.downcase)
end
def update_user_attributes
......
......@@ -27,6 +27,7 @@
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
name 'test'
ref 'master'
tag false
started_at 'Di 29. Okt 09:51:28 CET 2013'
......
FactoryGirl.define do
factory :commit_status, class: CommitStatus do
started_at 'Di 29. Okt 09:51:28 CET 2013'
finished_at 'Di 29. Okt 09:53:28 CET 2013'
name 'default'
status 'success'
description 'commit status'
commit factory: :ci_commit
factory :generic_commit_status, class: GenericCommitStatus do
name 'generic'
description 'external commit status'
end
end
end
......@@ -12,6 +12,7 @@ describe "Commits" do
@ci_project = project.ensure_gitlab_ci_project
@commit = FactoryGirl.create :ci_commit, gl_project: project, sha: project.commit.sha
@build = FactoryGirl.create :ci_build, commit: @commit
@generic_status = FactoryGirl.create :generic_commit_status, commit: @commit
end
before do
......
......@@ -14,11 +14,6 @@ describe LabelsHelper do
expect(label).not_to receive(:project)
link_to_label(label)
end
it 'includes option for "No Label"' do
result = project_labels_options(project)
expect(result).to include('No Label')
end
end
context 'without @project set' do
......
#tree-content-holder
#blob-content-holder
.file-content
.line-numbers
- 1.upto(25) do |i|
......
......@@ -39,7 +39,7 @@ describe 'LineHighlighter', ->
expect(spy).toHaveBeenPrevented()
it 'handles garbage input from the hash', ->
func = -> new LineHighlighter('#tree-content-holder')
func = -> new LineHighlighter('#blob-content-holder')
expect(func).not.toThrow()
describe '#clickHandler', ->
......
......@@ -13,6 +13,17 @@ describe Gitlab::LDAP::User do
let(:auth_hash) do
OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info)
end
let(:ldap_user_upper_case) { Gitlab::LDAP::User.new(auth_hash_upper_case) }
let(:info_upper_case) do
{
name: 'John',
email: 'John@Example.com', # Email address has upper case chars
nickname: 'john'
}
end
let(:auth_hash_upper_case) do
OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info_upper_case)
end
describe :changed? do
it "marks existing ldap user as changed" do
......@@ -57,6 +68,16 @@ describe Gitlab::LDAP::User do
expect(existing_user.id).to eql ldap_user.gl_user.id
end
it 'connects to existing ldap user if the extern_uid changes and email address has upper case characters' do
existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
expect{ ldap_user_upper_case.save }.not_to change{ User.count }
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
expect(existing_user.id).to eql ldap_user.gl_user.id
end
it 'maintains an identity per provider' do
existing_user = create(:omniauth_user, email: 'john@example.com', provider: 'twitter')
expect(existing_user.identities.count).to eql(1)
......
......@@ -30,17 +30,9 @@ describe Ci::Build do
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
subject { build }
it { is_expected.to belong_to(:commit) }
it { is_expected.to belong_to(:user) }
it { is_expected.to validate_presence_of :status }
it { is_expected.to validate_presence_of :ref }
it { is_expected.to respond_to :success? }
it { is_expected.to respond_to :failed? }
it { is_expected.to respond_to :running? }
it { is_expected.to respond_to :pending? }
it { is_expected.to respond_to :trace_html }
describe :first_pending do
......@@ -67,72 +59,6 @@ describe Ci::Build do
end
end
describe :started? do
subject { build.started? }
context 'without started_at' do
before { build.started_at = nil }
it { is_expected.to be_falsey }
end
%w(running success failed).each do |status|
context "if build status is #{status}" do
before { build.status = status }
it { is_expected.to be_truthy }
end
end
%w(pending canceled).each do |status|
context "if build status is #{status}" do
before { build.status = status }
it { is_expected.to be_falsey }
end
end
end
describe :active? do
subject { build.active? }
%w(pending running).each do |state|
context "if build.status is #{state}" do
before { build.status = state }
it { is_expected.to be_truthy }
end
end
%w(success failed canceled).each do |state|
context "if build.status is #{state}" do
before { build.status = state }
it { is_expected.to be_falsey }
end
end
end
describe :complete? do
subject { build.complete? }
%w(success failed canceled).each do |state|
context "if build.status is #{state}" do
before { build.status = state }
it { is_expected.to be_truthy }
end
end
%w(pending running).each do |state|
context "if build.status is #{state}" do
before { build.status = state }
it { is_expected.to be_falsey }
end
end
end
describe :ignored? do
subject { build.ignored? }
......@@ -200,31 +126,6 @@ describe Ci::Build do
it { is_expected.to eq(commit.project.timeout) }
end
describe :duration do
subject { build.duration }
it { is_expected.to eq(120.0) }
context 'if the building process has not started yet' do
before do
build.started_at = nil
build.finished_at = nil
end
it { is_expected.to be_nil }
end
context 'if the building process has started' do
before do
build.started_at = Time.now - 1.minute
build.finished_at = nil
end
it { is_expected.to be_a(Float) }
it { is_expected.to be > 0.0 }
end
end
describe :options do
let(:options) do
{
......@@ -239,18 +140,6 @@ describe Ci::Build do
it { is_expected.to eq(options) }
end
describe :sha do
subject { build.sha }
it { is_expected.to eq(commit.sha) }
end
describe :short_sha do
subject { build.short_sha }
it { is_expected.to eq(commit.short_sha) }
end
describe :allow_git_fetch do
subject { build.allow_git_fetch }
......
......@@ -23,6 +23,8 @@ describe Ci::Commit do
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
it { is_expected.to belong_to(:gl_project) }
it { is_expected.to have_many(:statuses) }
it { is_expected.to have_many(:trigger_requests) }
it { is_expected.to have_many(:builds) }
it { is_expected.to validate_presence_of :sha }
......@@ -47,10 +49,12 @@ describe Ci::Commit do
@second = FactoryGirl.create :ci_build, commit: commit
end
it "creates new build" do
it "creates only a new build" do
expect(commit.builds.count(:all)).to eq 2
expect(commit.statuses.count(:all)).to eq 2
commit.retry
expect(commit.builds.count(:all)).to eq 3
expect(commit.statuses.count(:all)).to eq 3
end
end
......@@ -78,8 +82,8 @@ describe Ci::Commit do
subject { commit.stage }
before do
@second = FactoryGirl.create :ci_build, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: :pending
@first = FactoryGirl.create :ci_build, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: :pending
@second = FactoryGirl.create :commit_status, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: 'pending'
@first = FactoryGirl.create :commit_status, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: 'pending'
end
it 'returns first running stage' do
......@@ -88,7 +92,7 @@ describe Ci::Commit do
context 'first build succeeded' do
before do
@first.update_attributes(status: :success)
@first.success
end
it 'returns last running stage' do
......@@ -98,8 +102,8 @@ describe Ci::Commit do
context 'all builds succeeded' do
before do
@first.update_attributes(status: :success)
@second.update_attributes(status: :success)
@first.success
@second.success
end
it 'returns nil' do
......@@ -111,6 +115,33 @@ describe Ci::Commit do
describe :create_next_builds do
end
describe :refs do
subject { commit.refs }
before do
FactoryGirl.create :commit_status, commit: commit, name: 'deploy'
FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'develop'
FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'master'
end
it 'returns all refs' do
is_expected.to contain_exactly('master', 'develop', nil)
end
end
describe :retried do
subject { commit.retried }
before do
@commit1 = FactoryGirl.create :ci_build, commit: commit, name: 'deploy'
@commit2 = FactoryGirl.create :ci_build, commit: commit, name: 'deploy'
end
it 'returns old builds' do
is_expected.to contain_exactly(@commit1)
end
end
describe :create_builds do
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
......@@ -194,9 +225,10 @@ describe Ci::Commit do
it 'rebuilds commit' do
expect(commit.status).to eq('skipped')
expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
expect(commit.status).to eq('pending')
# since everything in Ci::Commit is cached we need to fetch a new object
new_commit = Ci::Commit.find_by_id(commit.id)
expect(new_commit.status).to eq('pending')
end
end
end
......@@ -252,10 +284,10 @@ describe Ci::Commit do
describe :should_create_next_builds? do
before do
@build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: :success
@build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: :failed
@build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: :failed
@build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: :success
@build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: 'success'
@build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: 'failed'
@build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: 'failed'
@build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'success'
end
context 'for success' do
......@@ -266,7 +298,7 @@ describe Ci::Commit do
context 'for failed' do
before do
@build4.update_attributes(status: :failed)
@build4.update_attributes(status: 'failed')
end
it 'to not create' do
......@@ -286,7 +318,7 @@ describe Ci::Commit do
context 'for running' do
before do
@build4.update_attributes(status: :running)
@build4.update_attributes(status: 'running')
end
it 'to not create' do
......@@ -296,7 +328,7 @@ describe Ci::Commit do
context 'for retried' do
before do
@build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: :failed
@build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'failed'
end
it 'to not create' do
......
......@@ -35,7 +35,7 @@ describe Ci::MailService do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit, user: user) }
let(:build) { FactoryGirl.create(:ci_build, status: 'failed', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -58,7 +58,7 @@ describe Ci::MailService do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true, email_only_broken_builds: false) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -86,7 +86,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -115,7 +115,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -144,7 +144,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......@@ -167,7 +167,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit, user: user) }
let(:build) { FactoryGirl.create(:ci_build, status: 'failed', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
......
require 'spec_helper'
describe CommitStatus do
let(:commit) { FactoryGirl.create :ci_commit }
let(:commit_status) { FactoryGirl.create :commit_status, commit: commit }
it { is_expected.to belong_to(:commit) }
it { is_expected.to belong_to(:user) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_inclusion_of(:status).in_array(%w(pending running failed success canceled)) }
it { is_expected.to delegate_method(:sha).to(:commit) }
it { is_expected.to delegate_method(:short_sha).to(:commit) }
it { is_expected.to delegate_method(:gl_project).to(:commit) }
it { is_expected.to respond_to :success? }
it { is_expected.to respond_to :failed? }
it { is_expected.to respond_to :running? }
it { is_expected.to respond_to :pending? }
describe :author do
subject { commit_status.author }
before { commit_status.author = User.new }
it { is_expected.to eq(commit_status.user) }
end
describe :started? do
subject { commit_status.started? }
context 'without started_at' do
before { commit_status.started_at = nil }
it { is_expected.to be_falsey }
end
%w(running success failed).each do |status|
context "if commit status is #{status}" do
before { commit_status.status = status }
it { is_expected.to be_truthy }
end
end
%w(pending canceled).each do |status|
context "if commit status is #{status}" do
before { commit_status.status = status }
it { is_expected.to be_falsey }
end
end
end
describe :active? do
subject { commit_status.active? }
%w(pending running).each do |state|
context "if commit_status.status is #{state}" do
before { commit_status.status = state }
it { is_expected.to be_truthy }
end
end
%w(success failed canceled).each do |state|
context "if commit_status.status is #{state}" do
before { commit_status.status = state }
it { is_expected.to be_falsey }
end
end
end
describe :complete? do
subject { commit_status.complete? }
%w(success failed canceled).each do |state|
context "if commit_status.status is #{state}" do
before { commit_status.status = state }
it { is_expected.to be_truthy }
end
end
%w(pending running).each do |state|
context "if commit_status.status is #{state}" do
before { commit_status.status = state }
it { is_expected.to be_falsey }
end
end
end
describe :duration do
subject { commit_status.duration }
it { is_expected.to eq(120.0) }
context 'if the building process has not started yet' do
before do
commit_status.started_at = nil
commit_status.finished_at = nil
end
it { is_expected.to be_nil }
end
context 'if the building process has started' do
before do
commit_status.started_at = Time.now - 1.minute
commit_status.finished_at = nil
end
it { is_expected.to be_a(Float) }
it { is_expected.to be > 0.0 }
end
end
describe :latest do
subject { CommitStatus.latest.order(:id) }
before do
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'cc', status: 'success'
@commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'bb', status: 'success'
@commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'success'
end
it 'return unique statuses' do
is_expected.to eq([@commit2, @commit3, @commit4, @commit5])
end
end
describe :for_ref do
subject { CommitStatus.for_ref('bb').order(:id) }
before do
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success'
end
it 'return statuses with equal and nil ref set' do
is_expected.to eq([@commit1])
end
end
describe :running_or_pending do
subject { CommitStatus.running_or_pending.order(:id) }
before do
@commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running'
@commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending'
@commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success'
@commit4 = FactoryGirl.create :commit_status, commit: commit, name: 'dd', ref: nil, status: 'failed'
@commit5 = FactoryGirl.create :commit_status, commit: commit, name: 'ee', ref: nil, status: 'canceled'
end
it 'return statuses that are running or pending' do
is_expected.to eq([@commit1, @commit2])
end
end
end
require 'spec_helper'
describe GenericCommitStatus do
let(:commit) { FactoryGirl.create :ci_commit }
let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, commit: commit }
describe :context do
subject { generic_commit_status.context }
before { generic_commit_status.context = 'my_context' }
it { is_expected.to eq(generic_commit_status.name) }
end
describe :tags do
subject { generic_commit_status.tags }
it { is_expected.to eq([:external]) }
end
describe :set_default_values do
before do
generic_commit_status.context = nil
generic_commit_status.stage = nil
generic_commit_status.save
end
describe :context do
subject { generic_commit_status.context }
it { is_expected.to_not be_nil }
end
describe :stage do
subject { generic_commit_status.stage }
it { is_expected.to_not be_nil }
end
end
end
......@@ -165,6 +165,17 @@ describe MergeRequest do
end
end
describe "#hook_attrs" do
it "has all the required keys" do
attrs = subject.hook_attrs
attrs = attrs.to_h
expect(attrs).to include(:source)
expect(attrs).to include(:target)
expect(attrs).to include(:last_commit)
expect(attrs).to include(:work_in_progress)
end
end
it_behaves_like 'an editable mentionable' do
subject { create(:merge_request) }
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
# issues_events :boolean default(TRUE)
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
#
require 'spec_helper'
describe BambooService, models: true do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
before do
@bamboo_service = BambooService.create(
project: create(:project),
properties: {
bamboo_url: 'http://gitlab.com',
username: 'mic',
password: "password"
}
)
end
it "reset password if url changed" do
@bamboo_service.bamboo_url = 'http://gitlab1.com'
@bamboo_service.save
expect(@bamboo_service.password).to be_nil
end
it "does not reset password if username changed" do
@bamboo_service.username = "some_name"
@bamboo_service.save
expect(@bamboo_service.password).to eq("password")
end
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
# issues_events :boolean default(TRUE)
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
#
require 'spec_helper'
describe TeamcityService, models: true do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe "Execute" do
let(:user) { create(:user) }
let(:project) { create(:project) }
before do
@teamcity_service = TeamcityService.create(
project: create(:project),
properties: {
teamcity_url: 'http://gitlab.com',
username: 'mic',
password: "password"
}
)
end
it "reset password if url changed" do
@teamcity_service.teamcity_url = 'http://gitlab1.com'
@teamcity_service.save
expect(@teamcity_service.password).to be_nil
end
it "does not reset password if username changed" do
@teamcity_service.username = "some_name"
@teamcity_service.save
expect(@teamcity_service.password).to eq("password")
end
end
end
......@@ -103,4 +103,27 @@ describe Service do
end
end
end
describe "#prop_updated?" do
let(:service) do
BambooService.create(
project: create(:project),
properties: {
bamboo_url: 'http://gitlab.com',
username: 'mic',
password: "password"
}
)
end
it "returns false" do
service.username = "key_changed"
expect(service.prop_updated?(:bamboo_url)).to be_falsy
end
it "returns true" do
service.bamboo_url = "http://other.com"
expect(service.prop_updated?(:bamboo_url)).to be_truthy
end
end
end
require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) }
let!(:reporter) { create(:project_member, user: user, project: project, access_level: ProjectMember::REPORTER) }
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
let(:commit) { project.repository.commit }
let!(:ci_commit) { project.ensure_ci_commit(commit.id) }
let(:commit_status) { create(:commit_status, commit: ci_commit) }
describe "GET /projects/:id/repository/commits/:sha/statuses" do
context "reporter user" do
let(:statuses_id) { json_response.map { |status| status['id'] } }
before do
@status1 = create(:commit_status, commit: ci_commit, status: 'running')
@status2 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'pending')
@status3 = create(:commit_status, commit: ci_commit, name: 'coverage', ref: 'develop', status: 'running')
@status4 = create(:commit_status, commit: ci_commit, name: 'coverage', status: 'success')
@status5 = create(:commit_status, commit: ci_commit, ref: 'develop', status: 'success')
@status6 = create(:commit_status, commit: ci_commit, status: 'success')
end
it "should return latest commit statuses" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(@status3.id, @status4.id, @status5.id, @status6.id)
end
it "should return all commit statuses" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?all=1", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(@status1.id, @status2.id, @status3.id, @status4.id, @status5.id, @status6.id)
end
it "should return latest commit statuses for specific ref" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?ref=develop", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(@status3.id, @status5.id)
end
it "should return latest commit statuses for specific name" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses?name=coverage", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(@status3.id, @status4.id)
end
end
context "guest user" do
it "should not return project commits" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses", user2)
expect(response.status).to eq(403)
end
end
context "unauthorized user" do
it "should not return project commits" do
get api("/projects/#{project.id}/repository/commits/#{commit.id}/statuses")
expect(response.status).to eq(401)
end
end
end
describe 'POST /projects/:id/statuses/:sha' do
let(:post_url) { "/projects/#{project.id}/statuses/#{commit.id}" }
context 'reporter user' do
context 'should create commit status' do
it 'with only required parameters' do
post api(post_url, user), state: 'success'
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
expect(json_response['name']).to eq('default')
expect(json_response['ref']).to be_nil
expect(json_response['target_url']).to be_nil
expect(json_response['description']).to be_nil
end
it 'with all optional parameters' do
post api(post_url, user), state: 'success', context: 'coverage', ref: 'develop', target_url: 'url', description: 'test'
expect(response.status).to eq(201)
expect(json_response['sha']).to eq(commit.id)
expect(json_response['status']).to eq('success')
expect(json_response['name']).to eq('coverage')
expect(json_response['ref']).to eq('develop')
expect(json_response['target_url']).to eq('url')
expect(json_response['description']).to eq('test')
end
end
context 'should not create commit status' do
it 'with invalid state' do
post api(post_url, user), state: 'invalid'
expect(response.status).to eq(400)
end
it 'without state' do
post api(post_url, user)
expect(response.status).to eq(400)
end
it 'invalid commit' do
post api("/projects/#{project.id}/statuses/invalid_sha", user), state: 'running'
expect(response.status).to eq(404)
end
end
end
context 'guest user' do
it 'should not create commit status' do
post api(post_url, user2)
expect(response.status).to eq(403)
end
end
context 'unauthorized user' do
it 'should not create commit status' do
post api(post_url)
expect(response.status).to eq(401)
end
end
end
end
......@@ -47,6 +47,19 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/commits/invalid_sha", user)
expect(response.status).to eq(404)
end
it "should return not_found for CI status" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response.status).to eq(200)
expect(json_response['status']).to eq('not_found')
end
it "should return status for CI" do
ci_commit = project.ensure_ci_commit(project.repository.commit.sha)
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
expect(response.status).to eq(200)
expect(json_response['status']).to eq(ci_commit.status)
end
end
context "unauthorized user" do
......
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