Commit e1d70c4f authored by Han Loong Liauw's avatar Han Loong Liauw

Merge branch 'master' into add-dates-snippets-show

Also removed fixes to btn-sm as they have appears to
be already merged into master
parents de026375 82da19ce
...@@ -16,6 +16,7 @@ v 8.1.0 (unreleased) ...@@ -16,6 +16,7 @@ v 8.1.0 (unreleased)
- Move CI charts to project graphs area - Move CI charts to project graphs area
- Fix cases where Markdown did not render links in activity feed (Stan Hu) - Fix cases where Markdown did not render links in activity feed (Stan Hu)
- Add first and last to pagination (Zeger-Jan van de Weg) - 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 commit page
- Show CI status on Your projects page and Starred projects page - Show CI status on Your projects page and Starred projects page
- Remove "Continuous Integration" page from dashboard - Remove "Continuous Integration" page from dashboard
...@@ -46,6 +47,11 @@ v 8.1.0 (unreleased) ...@@ -46,6 +47,11 @@ v 8.1.0 (unreleased)
- Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji)
- Persist filters when sorting on admin user page (Jerry Lukins) - Persist filters when sorting on admin user page (Jerry Lukins)
- Added last modified date to snippets#show (Han Loong Liauw) - Added last modified date to snippets#show (Han Loong Liauw)
- 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 v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) - 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 ...@@ -60,6 +66,7 @@ v 8.0.3
- Fix URL shown in Slack notifications - Fix URL shown in Slack notifications
- Fix bug where projects would appear to be stuck in the forked import state (Stan Hu) - 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) - 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 v 8.0.2
- Fix default avatar not rendering in network graph (Stan Hu) - Fix default avatar not rendering in network graph (Stan Hu)
......
...@@ -290,7 +290,7 @@ gem 'newrelic-grape' ...@@ -290,7 +290,7 @@ gem 'newrelic-grape'
gem 'octokit', '~> 3.7.0' gem 'octokit', '~> 3.7.0'
gem "mail_room", "~> 0.5.2" gem "mail_room", "~> 0.6.0"
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
......
...@@ -392,7 +392,7 @@ GEM ...@@ -392,7 +392,7 @@ GEM
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.3) mail (2.6.3)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
mail_room (0.5.2) mail_room (0.6.0)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mimemagic (0.3.0) mimemagic (0.3.0)
...@@ -854,7 +854,7 @@ DEPENDENCIES ...@@ -854,7 +854,7 @@ DEPENDENCIES
jquery-ui-rails (~> 4.2.1) jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.16.3) kaminari (~> 0.16.3)
letter_opener (~> 1.1.2) letter_opener (~> 1.1.2)
mail_room (~> 0.5.2) mail_room (~> 0.6.0)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16) mysql2 (~> 0.3.16)
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
# #
# ### Example Markup # ### Example Markup
# #
# <div id="tree-content-holder"> # <div id="blob-content-holder">
# <div class="file-content"> # <div class="file-content">
# <div class="line-numbers"> # <div class="line-numbers">
# <a href="#L1" id="L1" data-line-number="1">1</a> # <a href="#L1" id="L1" data-line-number="1">1</a>
...@@ -53,7 +53,7 @@ class @LineHighlighter ...@@ -53,7 +53,7 @@ class @LineHighlighter
$.scrollTo("#L#{range[0]}", offset: -150) $.scrollTo("#L#{range[0]}", offset: -150)
bindEvents: -> 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 # 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. # the click event, there is a method to our madness.
...@@ -62,7 +62,7 @@ class @LineHighlighter ...@@ -62,7 +62,7 @@ class @LineHighlighter
# active state even when the event is cancelled, resulting in an ugly border # active state even when the event is cancelled, resulting in an ugly border
# around the link and/or a persisted underline text decoration. # 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() event.preventDefault()
clickHandler: (event) => clickHandler: (event) =>
......
...@@ -11,59 +11,41 @@ ...@@ -11,59 +11,41 @@
*= require cal-heatmap *= 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"; * GitLab UI framework
@import "base/mixins";
@import "base/layout";
/**
* Customized Twitter bootstrap
*/ */
@import 'base/gl_variables'; @import "framework";
@import 'base/gl_bootstrap';
/** /*
* NProgress load bar css * NProgress load bar css
*/ */
@import 'nprogress'; @import 'nprogress';
@import 'nprogress-bootstrap'; @import 'nprogress-bootstrap';
/** /*
* Font icons * Font icons
*
*/ */
@import "font-awesome"; @import "font-awesome";
/** /*
* UI themes:
*/
@import "themes/**/*";
/**
* Generic css (forms, nav etc):
*/
@import "generic/**/*";
/**
* Page specific styles (issues, projects etc): * Page specific styles (issues, projects etc):
*/ */
@import "pages/**/*"; @import "pages/**/*";
/** /*
* Code highlight * Code highlight
*/ */
@import "highlight/**/*"; @import "highlight/**/*";
/** /*
* Styles for JS behaviors. * Styles for JS behaviors.
*/ */
@import "behaviors.scss"; @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 @@ ...@@ -6,7 +6,7 @@
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
line-height: 18px; line-height: 18px;
padding: 11px 16px; padding: 11px $gl-padding;
letter-spacing: .4px; letter-spacing: .4px;
&:focus, &:focus,
...@@ -22,12 +22,6 @@ ...@@ -22,12 +22,6 @@
padding: 11px 24px; padding: 11px 24px;
} }
@mixin btn-sm {
@include btn-default;
@include border-radius(2px);
padding: 5px 10px;
}
@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) { @mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) {
background-color: $light; background-color: $light;
border-color: $border-light; border-color: $border-light;
...@@ -78,7 +72,11 @@ ...@@ -78,7 +72,11 @@
@include btn-white; @include btn-white;
&.btn-sm { &.btn-sm {
@include btn-sm; padding: 5px 10px;
}
&.btn-xs {
padding: 1px 5px;
} }
&.btn-success, &.btn-success,
......
...@@ -398,7 +398,3 @@ table { ...@@ -398,7 +398,3 @@ table {
.space-right { .space-right {
margin-right: 10px; margin-right: 10px;
} }
.in-line {
display: inline-block;
}
...@@ -22,4 +22,5 @@ ...@@ -22,4 +22,5 @@
.gfm-commit, .gfm-commit_range { .gfm-commit, .gfm-commit_range {
font-family: $monospace_font; font-family: $monospace_font;
font-size: 90%;
} }
...@@ -117,8 +117,12 @@ ul.content-list { ...@@ -117,8 +117,12 @@ ul.content-list {
} }
.controls { .controls {
padding-top: 10px; padding-top: 4px;
float: right; float: right;
.btn {
padding: 10px 14px;
}
} }
} }
} }
......
...@@ -54,147 +54,6 @@ ...@@ -54,147 +54,6 @@
@include box-shadow(0 0 0 3px #f1f1f1); @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%) { @mixin str-truncated($max_width: 82%) {
display: inline-block; display: inline-block;
overflow: hidden; overflow: hidden;
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
margin-right: 0; margin-right: 0;
} }
.issues-filters, .issues-details-filters,
.dash-projects-filters, .dash-projects-filters,
.check-all-holder { .check-all-holder {
display: none; display: none;
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
.center-top-menu { .center-top-menu {
height: 45px; height: 45px;
margin-bottom: 30px;
li a { li a {
font-size: 14px; font-size: 14px;
...@@ -90,9 +91,11 @@ ...@@ -90,9 +91,11 @@
} }
} }
.projects-search-form { .activity-filter-block {
margin: 0 -5px !important; display: none;
}
.projects-search-form {
.btn { .btn {
display: none; display: none;
} }
...@@ -100,6 +103,11 @@ ...@@ -100,6 +103,11 @@
} }
@media (max-width: $screen-sm-max) { @media (max-width: $screen-sm-max) {
.page-with-sidebar .content-wrapper {
padding: 0;
padding-top: 1px;
}
.issues-filters { .issues-filters {
.milestone-filter, .labels-filter { .milestone-filter, .labels-filter {
display: none; display: none;
......
table { table {
&.table { &.table {
.dropdown-menu a {
text-decoration: none;
}
.success,
.warning,
.danger,
.info {
color: #fff;
a:not(.btn) {
text-decoration: underline;
color: #fff;
}
}
tr { tr {
td, th { td, th {
padding: 8px 10px; padding: 8px 10px;
...@@ -12,7 +28,7 @@ table { ...@@ -12,7 +28,7 @@ table {
border-bottom: 1px solid $border-color !important; border-bottom: 1px solid $border-color !important;
} }
td { td {
border-color: #F1F1F1 !important; border-color: $table-border-color !important;
border-bottom: 1px solid; border-bottom: 1px solid;
} }
} }
......
...@@ -32,8 +32,6 @@ ...@@ -32,8 +32,6 @@
@import "bootstrap/pager"; @import "bootstrap/pager";
@import "bootstrap/labels"; @import "bootstrap/labels";
@import "bootstrap/badges"; @import "bootstrap/badges";
@import "bootstrap/jumbotron";
@import "bootstrap/thumbnails";
@import "bootstrap/alerts"; @import "bootstrap/alerts";
@import "bootstrap/progress-bars"; @import "bootstrap/progress-bars";
@import "bootstrap/list-group"; @import "bootstrap/list-group";
...@@ -251,23 +249,3 @@ ...@@ -251,23 +249,3 @@
.text-info:hover { .text-info:hover {
color: $brand-info; 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; ...@@ -156,3 +156,5 @@ $nav-link-padding: 13px $gl-padding;
$pre-bg: #f8fafc !default; $pre-bg: #f8fafc !default;
$pre-color: $gl-gray !default; $pre-color: $gl-gray !default;
$pre-border-color: #e7e9ed; $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 * Headers
* *
......
...@@ -16,6 +16,7 @@ $avatar_radius: 50%; ...@@ -16,6 +16,7 @@ $avatar_radius: 50%;
$code_font_size: 13px; $code_font_size: 13px;
$code_line_height: 1.5; $code_line_height: 1.5;
$border-color: #dce0e6; $border-color: #dce0e6;
$table-border-color: #eef0f2;
$background-color: #F7F8FA; $background-color: #F7F8FA;
$header-height: 58px; $header-height: 58px;
$fixed-layout-width: 1200px; $fixed-layout-width: 1200px;
......
...@@ -65,7 +65,6 @@ ...@@ -65,7 +65,6 @@
.note-image-attach { .note-image-attach {
@extend .col-md-4; @extend .col-md-4;
@extend .thumbnail;
margin-left: 45px; margin-left: 45px;
float: none; float: none;
} }
......
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
} }
p { p {
padding: 0 $gl-padding;
color: #5c5d5e; color: #5c5d5e;
} }
} }
...@@ -510,8 +511,3 @@ pre.light-well { ...@@ -510,8 +511,3 @@ pre.light-well {
margin-top: -1px; margin-top: -1px;
} }
} }
.inline-form {
display: inline-block;
}
.tree-holder { .tree-holder {
.tree-content-holder { .tree-table-holder {
float: left; margin-left: -$gl-padding;
width: 100%; margin-right: -$gl-padding;
} }
.tree_progress { .tree_progress {
...@@ -13,10 +13,15 @@ ...@@ -13,10 +13,15 @@
} }
.tree-table { .tree-table {
@extend .table; margin-bottom: 0;
@include border-radius(0);
tr { tr {
> td, > th {
padding: 10px $gl-padding;
line-height: 32px;
border-color: $table-border-color !important;
}
&:hover { &:hover {
td { td {
background: $hover; background: $hover;
...@@ -27,9 +32,9 @@ ...@@ -27,9 +32,9 @@
} }
&.selected { &.selected {
td { td {
background: $background-color; background: $gray-dark;
border-top: 1px solid #EEE; border-top: 1px solid $border-gray-dark;
border-bottom: 1px solid #EEE; border-bottom: 1px solid $border-gray-dark;
} }
} }
} }
...@@ -85,19 +90,6 @@ ...@@ -85,19 +90,6 @@
margin-right: 15px; 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 { .blob-commit-info {
list-style: none; list-style: none;
margin: 0; margin: 0;
......
...@@ -88,7 +88,7 @@ class GroupsController < Groups::ApplicationController ...@@ -88,7 +88,7 @@ class GroupsController < Groups::ApplicationController
def destroy def destroy
DestroyGroupService.new(@group, current_user).execute 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 end
protected protected
......
...@@ -135,6 +135,8 @@ class Ability ...@@ -135,6 +135,8 @@ class Ability
def project_report_rules def project_report_rules
project_guest_rules + [ project_guest_rules + [
:create_commit_status,
:read_commit_statuses,
:download_code, :download_code,
:fork_project, :fork_project,
:create_project_snippet, :create_project_snippet,
......
...@@ -24,32 +24,19 @@ ...@@ -24,32 +24,19 @@
# #
module Ci module Ci
class Build < ActiveRecord::Base class Build < CommitStatus
extend Ci::Model
LAZY_ATTRIBUTES = ['trace'] LAZY_ATTRIBUTES = ['trace']
belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :runner, class_name: 'Ci::Runner' belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
belongs_to :user
serialize :options serialize :options
validates :commit, presence: true
validates :status, presence: true
validates :coverage, numericality: true, allow_blank: true validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref 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 :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 :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) } scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
acts_as_taggable acts_as_taggable
...@@ -74,13 +61,14 @@ module Ci ...@@ -74,13 +61,14 @@ module Ci
def create_from(build) def create_from(build)
new_build = build.dup new_build = build.dup
new_build.status = :pending new_build.status = 'pending'
new_build.runner_id = nil new_build.runner_id = nil
new_build.trigger_request_id = nil
new_build.save new_build.save
end end
def retry(build) def retry(build)
new_build = Ci::Build.new(status: :pending) new_build = Ci::Build.new(status: 'pending')
new_build.ref = build.ref new_build.ref = build.ref
new_build.tag = build.tag new_build.tag = build.tag
new_build.options = build.options new_build.options = build.options
...@@ -98,28 +86,7 @@ module Ci ...@@ -98,28 +86,7 @@ module Ci
end end
state_machine :status, initial: :pending do 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| after_transition any => [:success, :failed, :canceled] do |build, transition|
build.update_attributes finished_at: Time.now
project = build.project project = build.project
if project.web_hooks? if project.web_hooks?
...@@ -136,19 +103,10 @@ module Ci ...@@ -136,19 +103,10 @@ module Ci
build.update_coverage build.update_coverage
end end
end end
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
state :success, value: 'success'
state :canceled, value: 'canceled'
end end
delegate :sha, :short_sha, :project, :gl_project, def ignored?
to: :commit, prefix: false failed? && allow_failure?
def before_sha
Gitlab::Git::BLANK_SHA
end end
def trace_html def trace_html
...@@ -156,22 +114,6 @@ module Ci ...@@ -156,22 +114,6 @@ module Ci
html || '' html || ''
end 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 def timeout
project.timeout project.timeout
end end
...@@ -180,14 +122,6 @@ module Ci ...@@ -180,14 +122,6 @@ module Ci
yaml_variables + project_variables + trigger_variables yaml_variables + project_variables + trigger_variables
end end
def duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
end
def project def project
commit.project commit.project
end end
...@@ -278,6 +212,25 @@ module Ci ...@@ -278,6 +212,25 @@ module Ci
"#{dir_to_trace}/#{id}.log" "#{dir_to_trace}/#{id}.log"
end 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 private
def yaml_variables def yaml_variables
......
...@@ -20,7 +20,8 @@ module Ci ...@@ -20,7 +20,8 @@ module Ci
extend Ci::Model extend Ci::Model
belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id 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' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :sha validates_presence_of :sha
...@@ -47,7 +48,7 @@ module Ci ...@@ -47,7 +48,7 @@ module Ci
end end
def retry def retry
builds_without_retry.each do |build| latest_builds.each do |build|
Ci::Build.retry(build) Ci::Build.retry(build)
end end
end end
...@@ -81,12 +82,11 @@ module Ci ...@@ -81,12 +82,11 @@ module Ci
end end
def stage def stage
running_or_pending = builds_without_retry.running_or_pending running_or_pending = statuses.latest.running_or_pending.ordered
running_or_pending.limit(1).pluck(:stage).first running_or_pending.first.try(:stage)
end end
def create_builds(ref, tag, user, trigger_request = nil) def create_builds(ref, tag, user, trigger_request = nil)
return if skip_ci? && trigger_request.blank?
return unless config_processor return unless config_processor
config_processor.stages.any? do |stage| config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
...@@ -94,7 +94,6 @@ module Ci ...@@ -94,7 +94,6 @@ module Ci
end end
def create_next_builds(ref, tag, user, trigger_request) def create_next_builds(ref, tag, user, trigger_request)
return if skip_ci? && trigger_request.blank?
return unless config_processor return unless config_processor
stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage) stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
...@@ -107,61 +106,60 @@ module Ci ...@@ -107,61 +106,60 @@ module Ci
end end
def refs def refs
builds.group(:ref).pluck(:ref) statuses.order(:ref).pluck(:ref).uniq
end end
def last_ref def latest_statuses
builds.latest.first.try(:ref) @latest_statuses ||= statuses.latest.to_a
end end
def builds_without_retry def latest_builds
builds.latest @latest_builds ||= builds.latest.to_a
end end
def builds_without_retry_for_ref(ref) def latest_builds_for_ref(ref)
builds.for_ref(ref).latest latest_builds.select { |build| build.ref == ref }
end end
def retried_builds def retried
@retried_builds ||= (builds.order(id: :desc) - builds_without_retry) @retried ||= (statuses.order(id: :desc) - statuses.latest)
end end
def status def status
if skip_ci? if yaml_errors.present?
return 'skipped'
elsif yaml_errors.present?
return 'failed' return 'failed'
elsif builds.none? end
return 'skipped'
elsif success? @status ||= begin
latest = latest_statuses
latest.reject! { |status| status.try(&:allow_failure?) }
if latest.none?
'skipped'
elsif latest.all?(&:success?)
'success' 'success'
elsif pending? elsif latest.all?(&:pending?)
'pending' 'pending'
elsif running? elsif latest.any?(&:running?) || latest.any?(&:pending?)
'running' 'running'
elsif canceled? elsif latest.all?(&:canceled?)
'canceled' 'canceled'
else else
'failed' 'failed'
end end
end end
end
def pending? def pending?
builds_without_retry.all? do |build| status == 'pending'
build.pending?
end
end end
def running? def running?
builds_without_retry.any? do |build| status == 'running'
build.running? || build.pending?
end
end end
def success? def success?
builds_without_retry.all? do |build| status == 'success'
build.success? || build.ignored?
end
end end
def failed? def failed?
...@@ -169,26 +167,21 @@ module Ci ...@@ -169,26 +167,21 @@ module Ci
end end
def canceled? def canceled?
builds_without_retry.all? do |build| status == 'canceled'
build.canceled?
end
end end
def duration def duration
@duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i duration_array = latest_statuses.map(&:duration).compact
end duration_array.reduce(:+).to_i
def duration_for_ref(ref)
builds_without_retry_for_ref(ref).select(&:duration).sum(&:duration).to_i
end end
def finished_at 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 end
def coverage def coverage
if project.coverage_enabled? if project.coverage_enabled?
coverage_array = builds_without_retry.map(&:coverage).compact coverage_array = latest_builds.map(&:coverage).compact
if coverage_array.size >= 1 if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size) '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end end
...@@ -196,7 +189,7 @@ module Ci ...@@ -196,7 +189,7 @@ module Ci
end end
def matrix_for_ref?(ref) def matrix_for_ref?(ref)
builds_without_retry_for_ref(ref).pluck(:id).size > 1 latest_builds_for_ref(ref).size > 1
end end
def config_processor def config_processor
...@@ -217,7 +210,6 @@ module Ci ...@@ -217,7 +210,6 @@ module Ci
end end
def skip_ci? def skip_ci?
return false if builds.any?
git_commit_message =~ /(\[ci skip\])/ if git_commit_message git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end end
......
...@@ -184,4 +184,12 @@ class Commit ...@@ -184,4 +184,12 @@ class Commit
def parents def parents
@parents ||= Commit.decorate(super, project) @parents ||= Commit.decorate(super, project)
end end
def ci_commit
project.ci_commit(sha)
end
def status
ci_commit.try(:status) || :not_found
end
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 ...@@ -227,7 +227,7 @@ class MergeRequest < ActiveRecord::Base
end end
def work_in_progress? def work_in_progress?
title =~ /\A\[?WIP\]?:? /i !!(title =~ /\A\[?WIP\]?:? /i)
end end
def mergeable? def mergeable?
...@@ -275,7 +275,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -275,7 +275,8 @@ class MergeRequest < ActiveRecord::Base
attrs = { attrs = {
source: source_project.hook_attrs, source: source_project.hook_attrs,
target: target_project.hook_attrs, target: target_project.hook_attrs,
last_commit: nil last_commit: nil,
work_in_progress: work_in_progress?
} }
unless last_commit.nil? unless last_commit.nil?
......
...@@ -40,12 +40,19 @@ class BambooService < CiService ...@@ -40,12 +40,19 @@ class BambooService < CiService
attr_accessor :response attr_accessor :response
after_save :compose_service_hook, if: :activated? after_save :compose_service_hook, if: :activated?
before_update :reset_password
def compose_service_hook def compose_service_hook
hook = service_hook || build_service_hook hook = service_hook || build_service_hook
hook.save hook.save
end end
def reset_password
if prop_updated?(:bamboo_url)
self.password = nil
end
end
def title def title
'Atlassian Bamboo CI' 'Atlassian Bamboo CI'
end end
......
...@@ -49,7 +49,7 @@ module Ci ...@@ -49,7 +49,7 @@ module Ci
commit = build.commit commit = build.commit
return unless commit return unless commit
return unless commit.builds_without_retry.include? build return unless commit.latest_builds.include? build
case commit.status.to_sym case commit.status.to_sym
when :failed when :failed
......
...@@ -48,7 +48,7 @@ module Ci ...@@ -48,7 +48,7 @@ module Ci
# it doesn't make sense to send emails for retried builds # it doesn't make sense to send emails for retried builds
commit = build.commit commit = build.commit
return unless commit return unless commit
return unless commit.builds_without_retry.include?(build) return unless commit.latest_builds.include?(build)
case build.status.to_sym case build.status.to_sym
when :failed when :failed
......
...@@ -23,7 +23,7 @@ module Ci ...@@ -23,7 +23,7 @@ module Ci
def attachments def attachments
fields = [] fields = []
commit.builds_without_retry.each do |build| commit.latest_builds.each do |build|
next if build.allow_failure? next if build.allow_failure?
next unless build.failed? next unless build.failed?
fields << { fields << {
......
...@@ -48,7 +48,7 @@ module Ci ...@@ -48,7 +48,7 @@ module Ci
commit = build.commit commit = build.commit
return unless commit return unless commit
return unless commit.builds_without_retry.include?(build) return unless commit.latest_builds.include?(build)
case commit.status.to_sym case commit.status.to_sym
when :failed when :failed
......
...@@ -37,12 +37,19 @@ class TeamcityService < CiService ...@@ -37,12 +37,19 @@ class TeamcityService < CiService
attr_accessor :response attr_accessor :response
after_save :compose_service_hook, if: :activated? after_save :compose_service_hook, if: :activated?
before_update :reset_password
def compose_service_hook def compose_service_hook
hook = service_hook || build_service_hook hook = service_hook || build_service_hook
hook.save hook.save
end end
def reset_password
if prop_updated?(:teamcity_url)
self.password = nil
end
end
def title def title
'JetBrains TeamCity CI' 'JetBrains TeamCity CI'
end end
......
...@@ -117,6 +117,15 @@ class Service < ActiveRecord::Base ...@@ -117,6 +117,15 @@ class Service < ActiveRecord::Base
end end
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) def async_execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
......
...@@ -17,8 +17,10 @@ module Ci ...@@ -17,8 +17,10 @@ module Ci
tag = origin_ref.start_with?('refs/tags/') tag = origin_ref.start_with?('refs/tags/')
commit = project.gl_project.ensure_ci_commit(sha) commit = project.gl_project.ensure_ci_commit(sha)
unless commit.skip_ci?
commit.update_committed! commit.update_committed!
commit.create_builds(ref, tag, user) commit.create_builds(ref, tag, user)
end
commit commit
end end
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
%hr %hr
= form_tag admin_users_path, method: :get, class: 'form-inline' do = form_tag admin_users_path, method: :get, class: 'form-inline' do
.form-group .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] = hidden_field_tag "filter", params[:filter]
= button_tag class: 'btn btn-primary' do = button_tag class: 'btn btn-primary' do
%i.fa.fa-search %i.fa.fa-search
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
.pull-left .pull-left
= form_tag ci_admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do = form_tag ci_admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
.form-group .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' = submit_tag 'Search', class: 'btn'
.pull-right.light .pull-right.light
......
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
%td %td
= form_tag ci_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do = form_tag ci_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
.form-group .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' = submit_tag 'Search', class: 'btn'
%td %td
......
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
.gray-content-block .gray-content-block
- if current_user - if current_user
%ul.nav.nav-pills.event_filter.pull-right .pull-right
%li.pull-right = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do
= link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do
%i.fa.fa-rss %i.fa.fa-rss
= render 'shared/event_filter' = render 'shared/event_filter'
......
.projects-list-holder .projects-list-holder
.projects-search-form .projects-search-form
.input-group .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? - if current_user.can_create_project?
%span.input-group-btn %span.input-group-btn
= link_to new_project_path, class: 'btn btn-green' do = link_to new_project_path, class: 'btn btn-green' do
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort = hidden_field_tag :sort, @sort
.form-group .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 .form-group
= button_tag 'Search', class: "btn btn-default" = button_tag 'Search', class: "btn btn-default"
......
.pull-left .pull-left
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f| = form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group .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 .form-group
= button_tag 'Search', class: "btn btn-success" = button_tag 'Search', class: "btn btn-success"
......
.panel.panel-default.projects-list-holder .panel.panel-default.projects-list-holder
.panel-heading.clearfix .panel-heading.clearfix
.input-group .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 - if can? current_user, :create_projects, @group
%span.input-group-btn %span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
New project 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 @@ ...@@ -12,7 +12,7 @@
.clearfix.js-toggle-container .clearfix.js-toggle-container
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group .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' = button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:admin_group_member, @group) - if current_user && current_user.can?(:admin_group_member, @group)
......
...@@ -25,10 +25,8 @@ ...@@ -25,10 +25,8 @@
.hidden-xs .hidden-xs
- if current_user - if current_user
= render "events/event_last_push", event: @last_push = render "events/event_last_push", event: @last_push
.pull-right
%ul.nav.nav-pills.event_filter.pull-right = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
%li
= link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do
%i.fa.fa-rss %i.fa.fa-rss
= render 'shared/event_filter' = render 'shared/event_filter'
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
= current_user.username = current_user.username
.content-wrapper .content-wrapper
= render "layouts/flash" = render "layouts/flash"
= yield :flash_message
%div{ class: container_class } %div{ class: container_class }
.content .content
.clearfix .clearfix
......
.search .search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| = 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) = hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted? - if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id = hidden_field_tag :project_id, @project.id
......
= render 'projects/last_push' = render 'projects/last_push'
.gray-content-block.activity-filter-block .gray-content-block.activity-filter-block
- if current_user - if current_user
%ul.nav.nav-pills.event_filter.pull-right .pull-right
%li = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do
%i.fa.fa-rss %i.fa.fa-rss
= render 'shared/event_filter' = render 'shared/event_filter'
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path) - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project = render blob_commit, project: @project
%div#tree-content-holder.tree-content-holder %div#blob-content-holder.blob-content-holder
%article.file-holder %article.file-holder
.file-title .file-title
= blob_icon blob.mode, blob.name = 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 @@ ...@@ -9,7 +9,7 @@
#up-build-trace #up-build-trace
- if @commit.matrix_for_ref?(@build.ref) - if @commit.matrix_for_ref?(@build.ref)
%ul.center-top-menu.build-top-menu %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) } %li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do = link_to namespace_project_build_path(@project.namespace, @project, build) do
= ci_icon_for_status(build.status) = ci_icon_for_status(build.status)
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= build.id = 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 %li.active
%a %a
Build ##{@build.id} Build ##{@build.id}
......
- return unless @membership - 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_type, 'project'
= hidden_field_tag :notification_id, @membership.id = hidden_field_tag :notification_id, @membership.id
= hidden_field_tag :notification_level = hidden_field_tag :notification_level
......
...@@ -20,19 +20,19 @@ ...@@ -20,19 +20,19 @@
.bs-callout.bs-callout-warning .bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit \.gitlab-ci.yml not found in this commit
- @ci_commit.refs.each do |ref| .gray-content-block.second-block
.gray-content-block.second-block Latest builds
Builds for #{ref} - if @ci_commit.duration > 0
- if @ci_commit.duration_for_ref(ref) > 0
%small.pull-right %small.pull-right
%i.fa.fa-time %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 %thead
%tr %tr
%th Status %th Status
%th Build ID %th Build ID
%th Ref
%th Stage %th Stage
%th Name %th Name
%th Duration %th Duration
...@@ -40,10 +40,11 @@ ...@@ -40,10 +40,11 @@
- if @ci_project && @ci_project.coverage_enabled? - if @ci_project && @ci_project.coverage_enabled?
%th Coverage %th Coverage
%th %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? - if @ci_commit.retried.any?
%h3 .gray-content-block.second-block
Retried builds Retried builds
%table.table.builds %table.table.builds
...@@ -59,4 +60,4 @@ ...@@ -59,4 +60,4 @@
- if @ci_project && @ci_project.coverage_enabled? - if @ci_project && @ci_project.coverage_enabled?
%th Coverage %th Coverage
%th %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 @@ ...@@ -111,10 +111,10 @@
- if current_user.can_create_group? - if current_user.can_create_group?
.pull-right .pull-right
.light.in-line .light.inline
.space-right .space-right
Need a group for several dependent projects? 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 Create a group
.save-project-loader.hide .save-project-loader.hide
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.clearfix.js-toggle-container .clearfix.js-toggle-container
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group .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' = button_tag 'Search', class: 'btn'
- if can?(current_user, :admin_project_member, @project) - if can?(current_user, :admin_project_member, @project)
......
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
- split_button = split_button || false - split_button = split_button || false
- if split_button == true - if split_button == true
%span.btn-group{class: btn_class} %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 %i.fa.fa-download
%span Download zip %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.caret
%span.sr-only %span.sr-only
Select Archive Format Select Archive Format
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
- if current_user - 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") = 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_ssh'
= render 'shared/no_password' = render 'shared/no_password'
......
%article.readme-holder#README %article.file-holder.readme-holder#README
.file-title
= link_to '#README' do = link_to '#README' do
%h4.readme-file-title %strong
%i.fa.fa-file %i.fa.fa-file
= readme.name = readme.name
.wiki .file-content.wiki
= render_readme(readme) = render_readme(readme)
%ul.breadcrumb.repo-breadcrumb .gray-content-block
%ul.breadcrumb.repo-breadcrumb
%li %li
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path = @project.path
...@@ -11,7 +12,7 @@ ...@@ -11,7 +12,7 @@
- if allowed_tree_edit? - if allowed_tree_edit?
%li %li
%span.dropdown %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') = icon('plus')
%ul.dropdown-menu %ul.dropdown-menu
%li %li
...@@ -28,8 +29,9 @@ ...@@ -28,8 +29,9 @@
= icon('folder fw') = icon('folder fw')
New directory New directory
%div#tree-content-holder.tree-content-holder.prepend-top-20 %div#tree-content-holder.tree-content-holder
%table#tree-slider{class: "table_#{@hex_path} tree-table" } .tree-table-holder
%table.table#tree-slider{class: "table_#{@hex_path} tree-table table-striped" }
%thead %thead
%tr %tr
%th Name %th Name
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
= render 'nav' = render 'nav'
.gray-content-block .gray-content-block
= render 'main_links'
%h3.page-title %h3.page-title
All Pages All Pages
%ul.content-list %ul.content-list
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.search-holder.clearfix .search-holder.clearfix
.input-group .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 %span.input-group-btn
= button_tag 'Search', class: "btn btn-primary" = button_tag 'Search', class: "btn btn-primary"
- unless params[:snippets].eql? 'true' - unless params[:snippets].eql? 'true'
......
= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do = form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
.append-right-10.hidden-xs.hidden-sm .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 :state, params['state']
= hidden_field_tag :scope, params['scope'] = hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id'] = hidden_field_tag :assignee_id, params['assignee_id']
......
...@@ -2,11 +2,12 @@ ...@@ -2,11 +2,12 @@
- avatar = true unless local_assigns[:avatar] == false - avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false - stars = true unless local_assigns[:stars] == false
- ci = false unless local_assigns[:ci] == true - ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
%ul.projects-list %ul.projects-list
- projects.each_with_index do |project, i| - projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil - 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 avatar: avatar, stars: stars, css_class: css_class, ci: ci
- if projects.size > projects_limit - if projects.size > projects_limit
......
- avatar = true unless local_assigns[:avatar] == false - avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false - stars = true unless local_assigns[:stars] == false
- ci = false unless local_assigns[:ci] == true - 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 = '' unless local_assigns[:css_class]
- css_class += " no-description" unless project.description.present? - css_class += " no-description" unless project.description.present?
%li.project-row{ class: css_class } %li.project-row{ class: css_class }
...@@ -11,7 +12,7 @@ ...@@ -11,7 +12,7 @@
= project_icon(project, alt: '', class: 'avatar project-avatar s46') = project_icon(project, alt: '', class: 'avatar project-avatar s46')
%span.project-full-name %span.project-full-name
%span.namespace-name %span.namespace-name
- if project.namespace - if project.namespace && !skip_namespace
= project.namespace.human_name = project.namespace.human_name
\/ \/
%span.project-name.filter-title %span.project-name.filter-title
......
...@@ -12,8 +12,10 @@ ...@@ -12,8 +12,10 @@
# :email: "gitlab-incoming@gmail.com" # :email: "gitlab-incoming@gmail.com"
# # Email account password # # Email account password
# :password: "password" # :password: "password"
# # The name of the mailbox where incoming mail will end up. Usually "inbox". # # The name of the mailbox where incoming mail will end up. Usually "inbox".
# :name: "inbox" # :name: "inbox"
# # Always "sidekiq". # # Always "sidekiq".
# :delivery_method: sidekiq # :delivery_method: sidekiq
# # Always true. # # Always true.
...@@ -25,5 +27,13 @@ ...@@ -25,5 +27,13 @@
# :namespace: resque:gitlab # :namespace: resque:gitlab
# # Always "incoming_email". # # Always "incoming_email".
# :queue: incoming_email # :queue: incoming_email
# # Always "EmailReceiverWorker" # # Always "EmailReceiverWorker".
# :worker: 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 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -103,9 +103,14 @@ ActiveRecord::Schema.define(version: 20151007120511) do ...@@ -103,9 +103,14 @@ ActiveRecord::Schema.define(version: 20151007120511) do
t.boolean "tag" t.boolean "tag"
t.string "ref" t.string "ref"
t.integer "user_id" t.integer "user_id"
t.string "type"
t.string "target_url"
t.string "description"
end 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", "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", ["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", "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 add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
......
...@@ -62,7 +62,8 @@ Parameters: ...@@ -62,7 +62,8 @@ Parameters:
"authored_date": "2012-09-20T09:06:12+03:00", "authored_date": "2012-09-20T09:06:12+03:00",
"parent_ids": [ "parent_ids": [
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba" "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
] ],
"status": "running"
} }
``` ```
...@@ -156,3 +157,84 @@ Parameters: ...@@ -156,3 +157,84 @@ Parameters:
"line_type": "new" "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: ...@@ -188,6 +188,7 @@ Parameters:
- `title` (required) - Title of MR - `title` (required) - Title of MR
- `description` (optional) - Description of MR - `description` (optional) - Description of MR
- `target_project_id` (optional) - The target project (numeric id) - `target_project_id` (optional) - The target project (numeric id)
- `labels` (optional) - Labels for MR as a comma-separated list
```json ```json
{ {
...@@ -239,6 +240,7 @@ Parameters: ...@@ -239,6 +240,7 @@ Parameters:
- `title` - Title of MR - `title` - Title of MR
- `description` - Description of MR - `description` - Description of MR
- `state_event` - New state (close|reopen|merge) - `state_event` - New state (close|reopen|merge)
- `labels` (optional) - Labels for MR as a comma-separated list
```json ```json
{ {
......
...@@ -8,31 +8,5 @@ It is possible to add a markdown-formatted welcome message to your GitLab ...@@ -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 sign-in page. Users of GitLab Enterprise Edition should use the [branded login
page feature](/ee/customization/branded_login_page.html) instead. page feature](/ee/customization/branded_login_page.html) instead.
## Omnibus-gitlab example The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI.
Admin area > Settings
In `/etc/gitlab/gitlab.rb`: \ No newline at end of file
```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.
...@@ -2,10 +2,6 @@ ...@@ -2,10 +2,6 @@
GitLab can be set up to allow users to comment on issues and merge requests by replying to notification emails. 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 ## 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. 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 ...@@ -118,8 +114,10 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:email: "incoming" :email: "incoming"
# Email account password # Email account password
:password: "[REDACTED]" :password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox". # The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox" :name: "inbox"
# Always "sidekiq". # Always "sidekiq".
:delivery_method: sidekiq :delivery_method: sidekiq
# Always true. # Always true.
...@@ -133,6 +131,14 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these ...@@ -133,6 +131,14 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:queue: incoming_email :queue: incoming_email
# Always "EmailReceiverWorker" # Always "EmailReceiverWorker"
:worker: 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 ```yaml
...@@ -151,8 +157,10 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these ...@@ -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: "gitlab-incoming@gmail.com"
# Email account password # Email account password
:password: "[REDACTED]" :password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox". # The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox" :name: "inbox"
# Always "sidekiq". # Always "sidekiq".
:delivery_method: sidekiq :delivery_method: sidekiq
# Always true. # Always true.
...@@ -166,6 +174,14 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these ...@@ -166,6 +174,14 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:queue: incoming_email :queue: incoming_email
# Always "EmailReceiverWorker" # Always "EmailReceiverWorker"
:worker: 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`: 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 ...@@ -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: "gitlab-incoming@gmail.com"
# Email account password # Email account password
:password: "[REDACTED]" :password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox". # The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox" :name: "inbox"
# Always "sidekiq". # Always "sidekiq".
:delivery_method: sidekiq :delivery_method: sidekiq
# Always true. # Always true.
...@@ -243,6 +261,14 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these ...@@ -243,6 +261,14 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
:queue: incoming_email :queue: incoming_email
# Always "EmailReceiverWorker" # Always "EmailReceiverWorker"
:worker: 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`: 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 ...@@ -23,9 +23,11 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
cd /home/git/gitlab cd /home/git/gitlab
sudo -u git -H git fetch --all sudo -u git -H git fetch --all
sudo -u git -H git checkout -- Gemfile.lock db/schema.rb 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 ### 3. Update gitlab-shell to the corresponding version
......
...@@ -314,7 +314,8 @@ X-Gitlab-Event: Note Hook ...@@ -314,7 +314,8 @@ X-Gitlab-Event: Note Hook
"name": "John Smith", "name": "John Smith",
"email": "john@example.com" "email": "john@example.com"
} }
} },
"work_in_progress": false
} }
} }
``` ```
...@@ -500,6 +501,7 @@ X-Gitlab-Event: Merge Request Hook ...@@ -500,6 +501,7 @@ X-Gitlab-Event: Merge Request Hook
"email": "gitlabdev@dv6700.(none)" "email": "gitlabdev@dv6700.(none)"
} }
}, },
"work_in_progress": false,
"url": "http://example.com/diaspora/merge_requests/1", "url": "http://example.com/diaspora/merge_requests/1",
"action": "open" "action": "open"
} }
......
...@@ -118,6 +118,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -118,6 +118,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'I see builds list' do step 'I see builds list' do
expect(page).to have_content "build: pending" expect(page).to have_content "build: pending"
expect(page).to have_content "Builds for master" expect(page).to have_content "Latest builds"
end end
end end
...@@ -46,6 +46,7 @@ module API ...@@ -46,6 +46,7 @@ module API
mount Services mount Services
mount Files mount Files
mount Commits mount Commits
mount CommitStatus
mount Namespaces mount Namespaces
mount Branches mount Branches
mount Labels 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 ...@@ -149,6 +149,7 @@ module API
class RepoCommitDetail < RepoCommit class RepoCommitDetail < RepoCommit
expose :parent_ids, :committed_date, :authored_date expose :parent_ids, :committed_date, :authored_date
expose :status
end end
class ProjectSnippet < Grape::Entity class ProjectSnippet < Grape::Entity
...@@ -228,6 +229,12 @@ module API ...@@ -228,6 +229,12 @@ module API
expose :created_at expose :created_at
end 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 class Event < Grape::Entity
expose :title, :project_id, :action_name expose :title, :project_id, :action_name
expose :target_id, :target_type, :author_id expose :target_id, :target_type, :author_id
......
...@@ -99,7 +99,7 @@ module API ...@@ -99,7 +99,7 @@ module API
# id (required) - The ID of a project - this will be the source of the merge request # id (required) - The ID of a project - this will be the source of the merge request
# source_branch (required) - The source branch # source_branch (required) - The source branch
# target_branch (required) - The target 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 # assignee_id - Assignee user ID
# title (required) - Title of MR # title (required) - Title of MR
# description - Description of MR # description - Description of MR
......
...@@ -2,7 +2,7 @@ module Ci ...@@ -2,7 +2,7 @@ module Ci
module API module API
module Entities module Entities
class Commit < Grape::Entity 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 :status, :finished_at, :duration
expose :git_commit_message, :git_author_name, :git_author_email expose :git_commit_message, :git_author_name, :git_author_email
end end
...@@ -12,7 +12,7 @@ module Ci ...@@ -12,7 +12,7 @@ module Ci
end end
class Build < Grape::Entity 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 :before_sha, :allow_git_fetch, :project_name
expose :options do |model| expose :options do |model|
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
FactoryGirl.define do FactoryGirl.define do
factory :ci_build, class: Ci::Build do factory :ci_build, class: Ci::Build do
name 'test'
ref 'master' ref 'master'
tag false tag false
started_at 'Di 29. Okt 09:51:28 CET 2013' 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 ...@@ -12,6 +12,7 @@ describe "Commits" do
@ci_project = project.ensure_gitlab_ci_project @ci_project = project.ensure_gitlab_ci_project
@commit = FactoryGirl.create :ci_commit, gl_project: project, sha: project.commit.sha @commit = FactoryGirl.create :ci_commit, gl_project: project, sha: project.commit.sha
@build = FactoryGirl.create :ci_build, commit: @commit @build = FactoryGirl.create :ci_build, commit: @commit
@generic_status = FactoryGirl.create :generic_commit_status, commit: @commit
end end
before do before do
......
#tree-content-holder #blob-content-holder
.file-content .file-content
.line-numbers .line-numbers
- 1.upto(25) do |i| - 1.upto(25) do |i|
......
...@@ -39,7 +39,7 @@ describe 'LineHighlighter', -> ...@@ -39,7 +39,7 @@ describe 'LineHighlighter', ->
expect(spy).toHaveBeenPrevented() expect(spy).toHaveBeenPrevented()
it 'handles garbage input from the hash', -> it 'handles garbage input from the hash', ->
func = -> new LineHighlighter('#tree-content-holder') func = -> new LineHighlighter('#blob-content-holder')
expect(func).not.toThrow() expect(func).not.toThrow()
describe '#clickHandler', -> describe '#clickHandler', ->
......
...@@ -30,17 +30,9 @@ describe Ci::Build do ...@@ -30,17 +30,9 @@ describe Ci::Build do
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project } let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
let(:build) { FactoryGirl.create :ci_build, commit: commit } 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 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 } it { is_expected.to respond_to :trace_html }
describe :first_pending do describe :first_pending do
...@@ -67,72 +59,6 @@ describe Ci::Build do ...@@ -67,72 +59,6 @@ describe Ci::Build do
end end
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 describe :ignored? do
subject { build.ignored? } subject { build.ignored? }
...@@ -200,31 +126,6 @@ describe Ci::Build do ...@@ -200,31 +126,6 @@ describe Ci::Build do
it { is_expected.to eq(commit.project.timeout) } it { is_expected.to eq(commit.project.timeout) }
end 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 describe :options do
let(:options) do let(:options) do
{ {
...@@ -239,18 +140,6 @@ describe Ci::Build do ...@@ -239,18 +140,6 @@ describe Ci::Build do
it { is_expected.to eq(options) } it { is_expected.to eq(options) }
end 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 describe :allow_git_fetch do
subject { build.allow_git_fetch } subject { build.allow_git_fetch }
......
...@@ -23,6 +23,8 @@ describe Ci::Commit do ...@@ -23,6 +23,8 @@ describe Ci::Commit do
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
it { is_expected.to belong_to(: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 have_many(:builds) }
it { is_expected.to validate_presence_of :sha } it { is_expected.to validate_presence_of :sha }
...@@ -47,10 +49,12 @@ describe Ci::Commit do ...@@ -47,10 +49,12 @@ describe Ci::Commit do
@second = FactoryGirl.create :ci_build, commit: commit @second = FactoryGirl.create :ci_build, commit: commit
end end
it "creates new build" do it "creates only a new build" do
expect(commit.builds.count(:all)).to eq 2 expect(commit.builds.count(:all)).to eq 2
expect(commit.statuses.count(:all)).to eq 2
commit.retry commit.retry
expect(commit.builds.count(:all)).to eq 3 expect(commit.builds.count(:all)).to eq 3
expect(commit.statuses.count(:all)).to eq 3
end end
end end
...@@ -78,8 +82,8 @@ describe Ci::Commit do ...@@ -78,8 +82,8 @@ describe Ci::Commit do
subject { commit.stage } subject { commit.stage }
before do before do
@second = FactoryGirl.create :ci_build, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: :pending @second = FactoryGirl.create :commit_status, 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 @first = FactoryGirl.create :commit_status, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: 'pending'
end end
it 'returns first running stage' do it 'returns first running stage' do
...@@ -88,7 +92,7 @@ describe Ci::Commit do ...@@ -88,7 +92,7 @@ describe Ci::Commit do
context 'first build succeeded' do context 'first build succeeded' do
before do before do
@first.update_attributes(status: :success) @first.success
end end
it 'returns last running stage' do it 'returns last running stage' do
...@@ -98,8 +102,8 @@ describe Ci::Commit do ...@@ -98,8 +102,8 @@ describe Ci::Commit do
context 'all builds succeeded' do context 'all builds succeeded' do
before do before do
@first.update_attributes(status: :success) @first.success
@second.update_attributes(status: :success) @second.success
end end
it 'returns nil' do it 'returns nil' do
...@@ -111,6 +115,33 @@ describe Ci::Commit do ...@@ -111,6 +115,33 @@ describe Ci::Commit do
describe :create_next_builds do describe :create_next_builds do
end 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 describe :create_builds do
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project } let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
...@@ -194,9 +225,10 @@ describe Ci::Commit do ...@@ -194,9 +225,10 @@ describe Ci::Commit do
it 'rebuilds commit' do it 'rebuilds commit' do
expect(commit.status).to eq('skipped') expect(commit.status).to eq('skipped')
expect(create_builds(trigger_request)).to be_truthy expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2) # since everything in Ci::Commit is cached we need to fetch a new object
expect(commit.status).to eq('pending') new_commit = Ci::Commit.find_by_id(commit.id)
expect(new_commit.status).to eq('pending')
end end
end end
end end
...@@ -252,10 +284,10 @@ describe Ci::Commit do ...@@ -252,10 +284,10 @@ describe Ci::Commit do
describe :should_create_next_builds? do describe :should_create_next_builds? do
before do before do
@build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', 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 @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 @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 @build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'success'
end end
context 'for success' do context 'for success' do
...@@ -266,7 +298,7 @@ describe Ci::Commit do ...@@ -266,7 +298,7 @@ describe Ci::Commit do
context 'for failed' do context 'for failed' do
before do before do
@build4.update_attributes(status: :failed) @build4.update_attributes(status: 'failed')
end end
it 'to not create' do it 'to not create' do
...@@ -286,7 +318,7 @@ describe Ci::Commit do ...@@ -286,7 +318,7 @@ describe Ci::Commit do
context 'for running' do context 'for running' do
before do before do
@build4.update_attributes(status: :running) @build4.update_attributes(status: 'running')
end end
it 'to not create' do it 'to not create' do
...@@ -296,7 +328,7 @@ describe Ci::Commit do ...@@ -296,7 +328,7 @@ describe Ci::Commit do
context 'for retried' do context 'for retried' do
before 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 end
it 'to not create' do it 'to not create' do
......
...@@ -35,7 +35,7 @@ describe Ci::MailService do ...@@ -35,7 +35,7 @@ describe Ci::MailService do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true) } let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_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 before do
allow(mail).to receive_messages( allow(mail).to receive_messages(
...@@ -58,7 +58,7 @@ describe Ci::MailService do ...@@ -58,7 +58,7 @@ describe Ci::MailService do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true, email_only_broken_builds: false) } 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(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_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 before do
allow(mail).to receive_messages( allow(mail).to receive_messages(
...@@ -86,7 +86,7 @@ describe Ci::MailService do ...@@ -86,7 +86,7 @@ describe Ci::MailService do
end end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_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 before do
allow(mail).to receive_messages( allow(mail).to receive_messages(
...@@ -115,7 +115,7 @@ describe Ci::MailService do ...@@ -115,7 +115,7 @@ describe Ci::MailService do
end end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_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 before do
allow(mail).to receive_messages( allow(mail).to receive_messages(
...@@ -144,7 +144,7 @@ describe Ci::MailService do ...@@ -144,7 +144,7 @@ describe Ci::MailService do
end end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_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 before do
allow(mail).to receive_messages( allow(mail).to receive_messages(
...@@ -167,7 +167,7 @@ describe Ci::MailService do ...@@ -167,7 +167,7 @@ describe Ci::MailService do
end end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_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 before do
allow(mail).to receive_messages( 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 ...@@ -165,6 +165,17 @@ describe MergeRequest do
end end
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 it_behaves_like 'an editable mentionable' do
subject { create(:merge_request) } 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 ...@@ -103,4 +103,27 @@ describe Service do
end end
end 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 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 ...@@ -47,6 +47,19 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/commits/invalid_sha", user) get api("/projects/#{project.id}/repository/commits/invalid_sha", user)
expect(response.status).to eq(404) expect(response.status).to eq(404)
end 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 end
context "unauthorized user" do 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