summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG1
-rw-r--r--app/assets/stylesheets/application.scss50
-rw-r--r--app/assets/stylesheets/framework.scss33
-rw-r--r--app/assets/stylesheets/framework/avatar.scss (renamed from app/assets/stylesheets/generic/avatar.scss)0
-rw-r--r--app/assets/stylesheets/framework/blocks.scss (renamed from app/assets/stylesheets/generic/blocks.scss)0
-rw-r--r--app/assets/stylesheets/framework/buttons.scss (renamed from app/assets/stylesheets/generic/buttons.scss)10
-rw-r--r--app/assets/stylesheets/framework/calendar.scss (renamed from app/assets/stylesheets/generic/calendar.scss)0
-rw-r--r--app/assets/stylesheets/framework/callout.scss (renamed from app/assets/stylesheets/generic/callout.scss)0
-rw-r--r--app/assets/stylesheets/framework/common.scss (renamed from app/assets/stylesheets/generic/common.scss)4
-rw-r--r--app/assets/stylesheets/framework/files.scss (renamed from app/assets/stylesheets/generic/files.scss)0
-rw-r--r--app/assets/stylesheets/framework/filters.scss (renamed from app/assets/stylesheets/generic/filters.scss)0
-rw-r--r--app/assets/stylesheets/framework/flash.scss (renamed from app/assets/stylesheets/generic/flash.scss)0
-rw-r--r--app/assets/stylesheets/framework/fonts.scss (renamed from app/assets/stylesheets/base/fonts.scss)0
-rw-r--r--app/assets/stylesheets/framework/forms.scss (renamed from app/assets/stylesheets/generic/forms.scss)0
-rw-r--r--app/assets/stylesheets/framework/gfm.scss (renamed from app/assets/stylesheets/generic/gfm.scss)0
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss (renamed from app/assets/stylesheets/themes/gitlab-theme.scss)0
-rw-r--r--app/assets/stylesheets/framework/header.scss (renamed from app/assets/stylesheets/generic/header.scss)0
-rw-r--r--app/assets/stylesheets/framework/highlight.scss (renamed from app/assets/stylesheets/generic/highlight.scss)0
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss (renamed from app/assets/stylesheets/generic/issue_box.scss)0
-rw-r--r--app/assets/stylesheets/framework/jquery.scss (renamed from app/assets/stylesheets/generic/jquery.scss)0
-rw-r--r--app/assets/stylesheets/framework/layout.scss (renamed from app/assets/stylesheets/base/layout.scss)0
-rw-r--r--app/assets/stylesheets/framework/lists.scss (renamed from app/assets/stylesheets/generic/lists.scss)6
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss (renamed from app/assets/stylesheets/generic/markdown_area.scss)0
-rw-r--r--app/assets/stylesheets/framework/mixins.scss (renamed from app/assets/stylesheets/base/mixins.scss)141
-rw-r--r--app/assets/stylesheets/framework/mobile.scss (renamed from app/assets/stylesheets/generic/mobile.scss)14
-rw-r--r--app/assets/stylesheets/framework/pagination.scss (renamed from app/assets/stylesheets/generic/pagination.scss)0
-rw-r--r--app/assets/stylesheets/framework/selects.scss (renamed from app/assets/stylesheets/generic/selects.scss)0
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss (renamed from app/assets/stylesheets/generic/sidebar.scss)0
-rw-r--r--app/assets/stylesheets/framework/tables.scss (renamed from app/assets/stylesheets/generic/tables.scss)16
-rw-r--r--app/assets/stylesheets/framework/timeline.scss (renamed from app/assets/stylesheets/generic/timeline.scss)0
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss (renamed from app/assets/stylesheets/base/gl_bootstrap.scss)22
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss (renamed from app/assets/stylesheets/base/gl_variables.scss)0
-rw-r--r--app/assets/stylesheets/framework/typography.scss271
-rw-r--r--app/assets/stylesheets/framework/variables.scss (renamed from app/assets/stylesheets/base/variables.scss)0
-rw-r--r--app/assets/stylesheets/framework/zen.scss (renamed from app/assets/stylesheets/generic/zen.scss)0
-rw-r--r--app/assets/stylesheets/generic/typography.scss130
-rw-r--r--app/assets/stylesheets/pages/builds.scss (renamed from app/assets/stylesheets/ci/builds.scss)0
-rw-r--r--app/assets/stylesheets/pages/ci_projects.scss (renamed from app/assets/stylesheets/ci/projects.scss)0
-rw-r--r--app/assets/stylesheets/pages/lint.scss (renamed from app/assets/stylesheets/ci/lint.scss)0
-rw-r--r--app/assets/stylesheets/pages/note_form.scss1
-rw-r--r--app/assets/stylesheets/pages/projects.scss6
-rw-r--r--app/assets/stylesheets/pages/runners.scss (renamed from app/assets/stylesheets/ci/runners.scss)0
-rw-r--r--app/assets/stylesheets/pages/status.scss (renamed from app/assets/stylesheets/ci/status.scss)0
-rw-r--r--app/assets/stylesheets/pages/xterm.scss (renamed from app/assets/stylesheets/ci/xterm.scss)0
-rw-r--r--app/models/ability.rb2
-rw-r--r--app/models/ci/build.rb97
-rw-r--r--app/models/ci/commit.rb94
-rw-r--r--app/models/commit.rb8
-rw-r--r--app/models/commit_status.rb91
-rw-r--r--app/models/generic_commit_status.rb15
-rw-r--r--app/models/project_services/ci/hip_chat_service.rb2
-rw-r--r--app/models/project_services/ci/mail_service.rb2
-rw-r--r--app/models/project_services/ci/slack_message.rb2
-rw-r--r--app/models/project_services/ci/slack_service.rb2
-rw-r--r--app/services/ci/create_commit_service.rb6
-rw-r--r--app/views/dashboard/_activities.html.haml7
-rw-r--r--app/views/groups/show.html.haml8
-rw-r--r--app/views/layouts/_page.html.haml3
-rw-r--r--app/views/projects/_activity.html.haml7
-rw-r--r--app/views/projects/builds/_build.html.haml50
-rw-r--r--app/views/projects/builds/show.html.haml4
-rw-r--r--app/views/projects/buttons/_notifications.html.haml2
-rw-r--r--app/views/projects/commit/ci.html.haml49
-rw-r--r--app/views/projects/commit_statuses/_commit_status.html.haml51
-rw-r--r--app/views/projects/new.html.haml4
-rw-r--r--app/views/projects/show.html.haml7
-rw-r--r--db/migrate/20151008123042_add_type_and_description_to_builds.rb9
-rw-r--r--db/migrate/20151008130321_migrate_name_to_description_for_builds.rb5
-rw-r--r--db/schema.rb7
-rw-r--r--doc/api/commits.md84
-rw-r--r--features/steps/project/commits/commits.rb2
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/commit_statuses.rb80
-rw-r--r--lib/api/entities.rb7
-rw-r--r--lib/ci/api/entities.rb4
-rw-r--r--spec/factories/ci/builds.rb1
-rw-r--r--spec/factories/commit_statuses.rb15
-rw-r--r--spec/features/commits_spec.rb1
-rw-r--r--spec/models/build_spec.rb (renamed from spec/models/ci/build_spec.rb)111
-rw-r--r--spec/models/ci/commit_spec.rb64
-rw-r--r--spec/models/ci/project_services/mail_service_spec.rb12
-rw-r--r--spec/models/commit_status_spec.rb164
-rw-r--r--spec/models/generic_commit_status_spec.rb39
-rw-r--r--spec/requests/api/commit_status_spec.rb135
-rw-r--r--spec/requests/api/commits_spec.rb13
85 files changed, 1267 insertions, 705 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 063b85d0eb7..fc409b630e2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -16,6 +16,7 @@ v 8.1.0 (unreleased)
- Move CI charts to project graphs area
- Fix cases where Markdown did not render links in activity feed (Stan Hu)
- Add first and last to pagination (Zeger-Jan van de Weg)
+ - Added Commit Status API
- Show CI status on commit page
- Show CI status on Your projects page and Starred projects page
- Remove "Continuous Integration" page from dashboard
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index d9ede637944..7b060ce4853 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -11,59 +11,41 @@
*= require cal-heatmap
*/
+/*
+ * Welcome to GitLab css!
+ * If you need to add or modify UI component that is common for many pages
+ * like a table or typography then make changes in the framework/ directory.
+ * If you need to add unique style that should affect only one page - use pages/
+ * directory.
+ */
-@import "base/fonts";
-@import "base/variables";
-@import "base/mixins";
-@import "base/layout";
-
-
-/**
- * Customized Twitter bootstrap
+/*
+ * GitLab UI framework
*/
-@import 'base/gl_variables';
-@import 'base/gl_bootstrap';
+@import "framework";
-/**
+/*
* NProgress load bar css
*/
@import 'nprogress';
@import 'nprogress-bootstrap';
-/**
+/*
* Font icons
- *
*/
@import "font-awesome";
-/**
- * UI themes:
- */
-@import "themes/**/*";
-
-/**
- * Generic css (forms, nav etc):
- */
-@import "generic/**/*";
-
-/**
+/*
* Page specific styles (issues, projects etc):
*/
-
@import "pages/**/*";
-/**
+/*
* Code highlight
*/
@import "highlight/**/*";
-/**
+/*
* Styles for JS behaviors.
*/
-@import "behaviors.scss";
-
-/**
- * CI specific styles:
- */
-@import "ci/**/*";
-
+@import "behaviors.scss"; \ No newline at end of file
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
new file mode 100644
index 00000000000..1ec9d2fd84f
--- /dev/null
+++ b/app/assets/stylesheets/framework.scss
@@ -0,0 +1,33 @@
+@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";
diff --git a/app/assets/stylesheets/generic/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 36e582d4854..36e582d4854 100644
--- a/app/assets/stylesheets/generic/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 6ce34b5c3e8..6ce34b5c3e8 100644
--- a/app/assets/stylesheets/generic/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 11acbe3adfa..e5f0c0ad9ef 100644
--- a/app/assets/stylesheets/generic/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -6,7 +6,7 @@
font-size: 13px;
font-weight: 600;
line-height: 18px;
- padding: 11px 16px;
+ padding: 11px $gl-padding;
letter-spacing: .4px;
&:focus,
@@ -71,6 +71,14 @@
@include btn-default;
@include btn-white;
+ &.btn-sm {
+ padding: 5px 10px;
+ }
+
+ &.btn-xs {
+ padding: 1px 5px;
+ }
+
&.btn-success,
&.btn-new,
&.btn-create,
diff --git a/app/assets/stylesheets/generic/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index a36fefe22c5..a36fefe22c5 100644
--- a/app/assets/stylesheets/generic/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
diff --git a/app/assets/stylesheets/generic/callout.scss b/app/assets/stylesheets/framework/callout.scss
index f1699d21c9b..f1699d21c9b 100644
--- a/app/assets/stylesheets/generic/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/framework/common.scss
index 03919f15f1f..e1a1793be9c 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -398,7 +398,3 @@ table {
.space-right {
margin-right: 10px;
}
-
-.in-line {
- display: inline-block;
-}
diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/framework/files.scss
index 9dd77747884..9dd77747884 100644
--- a/app/assets/stylesheets/generic/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
diff --git a/app/assets/stylesheets/generic/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 8e6922c9231..8e6922c9231 100644
--- a/app/assets/stylesheets/generic/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
diff --git a/app/assets/stylesheets/generic/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 82eb50ad4be..82eb50ad4be 100644
--- a/app/assets/stylesheets/generic/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
diff --git a/app/assets/stylesheets/base/fonts.scss b/app/assets/stylesheets/framework/fonts.scss
index e214567eca1..e214567eca1 100644
--- a/app/assets/stylesheets/base/fonts.scss
+++ b/app/assets/stylesheets/framework/fonts.scss
diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 0edfe24f195..0edfe24f195 100644
--- a/app/assets/stylesheets/generic/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
diff --git a/app/assets/stylesheets/generic/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index bd9200ace23..bd9200ace23 100644
--- a/app/assets/stylesheets/generic/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 8d9a0aae568..8d9a0aae568 100644
--- a/app/assets/stylesheets/themes/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/framework/header.scss
index 91e6975e269..91e6975e269 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 2e13ee842e0..2e13ee842e0 100644
--- a/app/assets/stylesheets/generic/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 93377e45e70..93377e45e70 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
diff --git a/app/assets/stylesheets/generic/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index 871b808bad4..871b808bad4 100644
--- a/app/assets/stylesheets/generic/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/framework/layout.scss
index c7b3b60e769..c7b3b60e769 100644
--- a/app/assets/stylesheets/base/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 3bfed8de772..c5764c36597 100644
--- a/app/assets/stylesheets/generic/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -117,8 +117,12 @@ ul.content-list {
}
.controls {
- padding-top: 10px;
+ padding-top: 4px;
float: right;
+
+ .btn {
+ padding: 10px 14px;
+ }
}
}
}
diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index ed0333d2336..ed0333d2336 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index c74a6d39824..089e6958eeb 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -54,147 +54,6 @@
@include box-shadow(0 0 0 3px #f1f1f1);
}
-@mixin md-typography {
- color: $md-text-color;
-
- a {
- color: $md-link-color;
- }
-
- img {
- max-width: 100%;
- }
-
- *:first-child {
- margin-top: 0;
- }
-
- code {
- font-family: $monospace_font;
- white-space: pre;
- word-wrap: normal;
- padding: 1px 2px;
- }
-
- kbd {
- display: inline-block;
- padding: 3px 5px;
- font-size: 11px;
- line-height: 10px;
- color: #555;
- vertical-align: middle;
- background-color: #FCFCFC;
- border-width: 1px;
- border-style: solid;
- border-color: #CCC #CCC #BBB;
- border-image: none;
- border-radius: 3px;
- box-shadow: 0px -1px 0px #BBB inset;
- }
-
- h1 {
- font-size: 1.3em;
- font-weight: 600;
- margin: 24px 0 12px 0;
- padding: 0 0 10px 0;
- border-bottom: 1px solid #e7e9ed;
- color: #313236;
- }
-
- h2 {
- font-size: 1.2em;
- font-weight: 600;
- margin: 24px 0 12px 0;
- color: #313236;
- }
-
- h3 {
- margin: 24px 0 12px 0;
- font-size: 1.25em;
- }
-
- h4 {
- margin: 24px 0 12px 0;
- font-size: 1.1em;
- }
-
- h5 {
- margin: 24px 0 12px 0;
- font-size: 1em;
- }
-
- h6 {
- margin: 24px 0 12px 0;
- font-size: 0.90em;
- }
-
- blockquote {
- padding: 8px 21px;
- margin: 12px 0 12px;
- border-left: 3px solid #e7e9ed;
- }
-
- blockquote p {
- color: #7f8fa4 !important;
- font-size: 15px;
- line-height: 1.5;
- }
-
- p {
- color:#5c5d5e;
- margin:6px 0 0 0;
- }
-
- table {
- @extend .table;
- @extend .table-bordered;
- margin: 12px 0 12px 0;
- color: #5c5d5e;
- th {
- background: #f8fafc;
- }
- }
-
- pre {
- margin: 12px 0 12px 0 !important;
- background-color: #f8fafc !important;
- font-size: 13px !important;
- color: #5b6169 !important;
- line-height: 1.6em !important;
- @include border-radius(2px);
- }
-
- p > code {
- font-weight: inherit;
- }
-
-
- ul {
- color: #5c5d5e;
- }
-
- li {
- line-height: 1.6em;
- }
-
- a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] {
- &:before {
- margin-right: 4px;
-
- font: normal normal normal 14px/1 FontAwesome;
- font-size: inherit;
- text-rendering: auto;
- -webkit-font-smoothing: antialiased;
- content: "\f0c6";
- }
-
- &:hover:before {
- text-decoration: none;
- }
- }
-}
-
-
@mixin str-truncated($max_width: 82%) {
display: inline-block;
overflow: hidden;
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 36ae126f865..cea47fba192 100644
--- a/app/assets/stylesheets/generic/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -23,7 +23,7 @@
margin-right: 0;
}
- .issues-filters,
+ .issues-details-filters,
.dash-projects-filters,
.check-all-holder {
display: none;
@@ -83,6 +83,7 @@
.center-top-menu {
height: 45px;
+ margin-bottom: 30px;
li a {
font-size: 14px;
@@ -90,9 +91,11 @@
}
}
- .projects-search-form {
- margin: 0 -5px !important;
+ .activity-filter-block {
+ display: none;
+ }
+ .projects-search-form {
.btn {
display: none;
}
@@ -100,6 +103,11 @@
}
@media (max-width: $screen-sm-max) {
+ .page-with-sidebar .content-wrapper {
+ padding: 0;
+ padding-top: 1px;
+ }
+
.issues-filters {
.milestone-filter, .labels-filter {
display: none;
diff --git a/app/assets/stylesheets/generic/pagination.scss b/app/assets/stylesheets/framework/pagination.scss
index 6677f94dafd..6677f94dafd 100644
--- a/app/assets/stylesheets/generic/pagination.scss
+++ b/app/assets/stylesheets/framework/pagination.scss
diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/framework/selects.scss
index cba621635b6..cba621635b6 100644
--- a/app/assets/stylesheets/generic/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index c5ea3aca7ca..c5ea3aca7ca 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
diff --git a/app/assets/stylesheets/generic/tables.scss b/app/assets/stylesheets/framework/tables.scss
index a66e45577de..76163b3a05e 100644
--- a/app/assets/stylesheets/generic/tables.scss
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -1,5 +1,21 @@
table {
&.table {
+ .dropdown-menu a {
+ text-decoration: none;
+ }
+
+ .success,
+ .warning,
+ .danger,
+ .info {
+ color: #fff;
+
+ a:not(.btn) {
+ text-decoration: underline;
+ color: #fff;
+ }
+ }
+
tr {
td, th {
padding: 8px 10px;
diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index bf21d7fce76..bf21d7fce76 100644
--- a/app/assets/stylesheets/generic/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
diff --git a/app/assets/stylesheets/base/gl_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index eb8d23d6453..99d028d1228 100644
--- a/app/assets/stylesheets/base/gl_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -32,8 +32,6 @@
@import "bootstrap/pager";
@import "bootstrap/labels";
@import "bootstrap/badges";
-@import "bootstrap/jumbotron";
-@import "bootstrap/thumbnails";
@import "bootstrap/alerts";
@import "bootstrap/progress-bars";
@import "bootstrap/list-group";
@@ -251,23 +249,3 @@
.text-info:hover {
color: $brand-info;
}
-
-// Tables =====================================================================
-
-table.table {
- .dropdown-menu a {
- text-decoration: none;
- }
-
- .success,
- .warning,
- .danger,
- .info {
- color: #fff;
-
- a:not(.btn) {
- text-decoration: underline;
- color: #fff;
- }
- }
-}
diff --git a/app/assets/stylesheets/base/gl_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 18632da4f2a..18632da4f2a 100644
--- a/app/assets/stylesheets/base/gl_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
new file mode 100644
index 00000000000..bf36f96cc97
--- /dev/null
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -0,0 +1,271 @@
+@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
+ *
+ */
+body {
+ text-rendering:optimizeLegibility;
+ -webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px;
+}
+
+.page-title {
+ margin-top: 0px;
+ line-height: 1.3;
+ font-size: 1.25em;
+ font-weight: 600;
+}
+
+.page-title-empty {
+ margin-top: 0px;
+ line-height: 1.3;
+ font-size: 1.25em;
+ font-weight: 600;
+ margin: 12px 7px 12px 7px;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: $gl-header-color;
+ font-weight: 500;
+}
+
+/** CODE **/
+pre {
+ font-family: $monospace_font;
+
+ &.dark {
+ background: #333;
+ color: $background-color;
+ }
+
+ &.plain-readme {
+ background: none;
+ border: none;
+ padding: 0;
+ margin: 0;
+ font-size: 14px;
+ }
+}
+
+.monospace {
+ font-family: $monospace_font;
+}
+
+code {
+ &.key-fingerprint {
+ background: $body-bg;
+ color: $text-color;
+ }
+}
+
+a > code {
+ color: $link-color;
+}
+
+/**
+ * Wiki typography
+ *
+ */
+.wiki {
+ @include md-typography;
+
+ word-wrap: break-word;
+ padding: 7px;
+
+ /* Link to current header. */
+ h1, h2, h3, h4, h5, h6 {
+ position: relative;
+
+ a.anchor {
+ // Setting `display: none` would prevent the anchor being scrolled to, so
+ // instead we set the height to 0 and it gets updated on hover.
+ height: 0;
+ }
+
+ &:hover > a.anchor {
+ $size: 16px;
+ position: absolute;
+ right: 100%;
+ top: 50%;
+ margin-top: -$size/2;
+ margin-right: 0px;
+ padding-right: 20px;
+ display: inline-block;
+ width: $size;
+ height: $size;
+ background-image: image-url("icon-link.png");
+ background-size: contain;
+ background-repeat: no-repeat;
+ }
+ }
+
+ ul,ol {
+ padding: 0;
+ margin: 6px 0 6px 18px !important;
+ }
+ ol {
+ color: #5c5d5e;
+ }
+}
+
+.md-area {
+ @include md-typography;
+}
+
+.md {
+ @include md-typography;
+}
+
+/**
+ * Textareas intended for GFM
+ *
+ */
+textarea.js-gfm-input {
+ font-family: $monospace_font;
+}
+
+.md-preview {
+}
+
+.strikethrough {
+ text-decoration: line-through;
+}
diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/framework/variables.scss
index eb9a2966389..eb9a2966389 100644
--- a/app/assets/stylesheets/base/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/framework/zen.scss
index 32e2c020e06..32e2c020e06 100644
--- a/app/assets/stylesheets/generic/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
deleted file mode 100644
index 6a3cb49baae..00000000000
--- a/app/assets/stylesheets/generic/typography.scss
+++ /dev/null
@@ -1,130 +0,0 @@
-/**
- * Headers
- *
- */
-body {
- text-rendering:optimizeLegibility;
- -webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px;
-}
-
-.page-title {
- margin-top: 0px;
- line-height: 1.3;
- font-size: 1.25em;
- font-weight: 600;
-}
-
-.page-title-empty {
- margin-top: 0px;
- line-height: 1.3;
- font-size: 1.25em;
- font-weight: 600;
- margin: 12px 7px 12px 7px;
-}
-
-h1, h2, h3, h4, h5, h6 {
- color: $gl-header-color;
- font-weight: 500;
-}
-
-/** CODE **/
-pre {
- font-family: $monospace_font;
-
- &.dark {
- background: #333;
- color: $background-color;
- }
-
- &.plain-readme {
- background: none;
- border: none;
- padding: 0;
- margin: 0;
- font-size: 14px;
- }
-}
-
-.monospace {
- font-family: $monospace_font;
-}
-
-code {
- &.key-fingerprint {
- background: $body-bg;
- color: $text-color;
- }
-}
-
-a > code {
- color: $link-color;
-}
-
-/**
- * Wiki typography
- *
- */
-.wiki {
- @include md-typography;
-
- word-wrap: break-word;
- padding: 7px;
-
- /* Link to current header. */
- h1, h2, h3, h4, h5, h6 {
- position: relative;
-
- a.anchor {
- // Setting `display: none` would prevent the anchor being scrolled to, so
- // instead we set the height to 0 and it gets updated on hover.
- height: 0;
- }
-
- &:hover > a.anchor {
- $size: 16px;
- position: absolute;
- right: 100%;
- top: 50%;
- margin-top: -$size/2;
- margin-right: 0px;
- padding-right: 20px;
- display: inline-block;
- width: $size;
- height: $size;
- background-image: image-url("icon-link.png");
- background-size: contain;
- background-repeat: no-repeat;
- }
- }
-
- ul,ol {
- padding: 0;
- margin: 6px 0 6px 18px !important;
- }
- ol {
- color: #5c5d5e;
- }
-}
-
-.md-area {
- @include md-typography;
-}
-
-.md {
- @include md-typography;
-}
-
-/**
- * Textareas intended for GFM
- *
- */
-textarea.js-gfm-input {
- font-family: $monospace_font;
-}
-
-.md-preview {
-}
-
-.strikethrough {
- text-decoration: line-through;
-} \ No newline at end of file
diff --git a/app/assets/stylesheets/ci/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 74dc3e321c1..74dc3e321c1 100644
--- a/app/assets/stylesheets/ci/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
diff --git a/app/assets/stylesheets/ci/projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
index 8c5273abcda..8c5273abcda 100644
--- a/app/assets/stylesheets/ci/projects.scss
+++ b/app/assets/stylesheets/pages/ci_projects.scss
diff --git a/app/assets/stylesheets/ci/lint.scss b/app/assets/stylesheets/pages/lint.scss
index 6d2bd33b28b..6d2bd33b28b 100644
--- a/app/assets/stylesheets/ci/lint.scss
+++ b/app/assets/stylesheets/pages/lint.scss
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index dcd1aed7196..4392f08942b 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -65,7 +65,6 @@
.note-image-attach {
@extend .col-md-4;
- @extend .thumbnail;
margin-left: 45px;
float: none;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 0031ab5151b..f7a22849003 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -63,6 +63,7 @@
}
p {
+ padding: 0 $gl-padding;
color: #5c5d5e;
}
}
@@ -510,8 +511,3 @@ pre.light-well {
margin-top: -1px;
}
}
-
-.inline-form {
- display: inline-block;
-}
-
diff --git a/app/assets/stylesheets/ci/runners.scss b/app/assets/stylesheets/pages/runners.scss
index 2b15ab83129..2b15ab83129 100644
--- a/app/assets/stylesheets/ci/runners.scss
+++ b/app/assets/stylesheets/pages/runners.scss
diff --git a/app/assets/stylesheets/ci/status.scss b/app/assets/stylesheets/pages/status.scss
index a7d3b2197f1..a7d3b2197f1 100644
--- a/app/assets/stylesheets/ci/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
diff --git a/app/assets/stylesheets/ci/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
index 9a50096c0d0..9a50096c0d0 100644
--- a/app/assets/stylesheets/ci/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
diff --git a/app/models/ability.rb b/app/models/ability.rb
index a020b24a550..77c121ca5e8 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -135,6 +135,8 @@ class Ability
def project_report_rules
project_guest_rules + [
+ :create_commit_status,
+ :read_commit_statuses,
:download_code,
:fork_project,
:create_project_snippet,
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5d17f4418ed..f8c731a7bf7 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -24,32 +24,19 @@
#
module Ci
- class Build < ActiveRecord::Base
- extend Ci::Model
-
+ class Build < CommitStatus
LAZY_ATTRIBUTES = ['trace']
- belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
- belongs_to :user
serialize :options
- validates :commit, presence: true
- validates :status, presence: true
validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref
- scope :running, ->() { where(status: "running") }
- scope :pending, ->() { where(status: "pending") }
- scope :success, ->() { where(status: "success") }
- scope :failed, ->() { where(status: "failed") }
scope :unstarted, ->() { where(runner_id: nil) }
- scope :running_or_pending, ->() { where(status:[:running, :pending]) }
- scope :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name, :ref)).order(stage_idx: :asc) }
scope :ignore_failures, ->() { where(allow_failure: false) }
- scope :for_ref, ->(ref) { where(ref: ref) }
scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
acts_as_taggable
@@ -74,13 +61,14 @@ module Ci
def create_from(build)
new_build = build.dup
- new_build.status = :pending
+ new_build.status = 'pending'
new_build.runner_id = nil
+ new_build.trigger_request_id = nil
new_build.save
end
def retry(build)
- new_build = Ci::Build.new(status: :pending)
+ new_build = Ci::Build.new(status: 'pending')
new_build.ref = build.ref
new_build.tag = build.tag
new_build.options = build.options
@@ -98,28 +86,7 @@ module Ci
end
state_machine :status, initial: :pending do
- event :run do
- transition pending: :running
- end
-
- event :drop do
- transition running: :failed
- end
-
- event :success do
- transition running: :success
- end
-
- event :cancel do
- transition [:pending, :running] => :canceled
- end
-
- after_transition pending: :running do |build, transition|
- build.update_attributes started_at: Time.now
- end
-
after_transition any => [:success, :failed, :canceled] do |build, transition|
- build.update_attributes finished_at: Time.now
project = build.project
if project.web_hooks?
@@ -136,19 +103,10 @@ module Ci
build.update_coverage
end
end
-
- state :pending, value: 'pending'
- state :running, value: 'running'
- state :failed, value: 'failed'
- state :success, value: 'success'
- state :canceled, value: 'canceled'
end
- delegate :sha, :short_sha, :project, :gl_project,
- to: :commit, prefix: false
-
- def before_sha
- Gitlab::Git::BLANK_SHA
+ def ignored?
+ failed? && allow_failure?
end
def trace_html
@@ -156,22 +114,6 @@ module Ci
html || ''
end
- def started?
- !pending? && !canceled? && started_at
- end
-
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
- end
-
- def ignored?
- failed? && allow_failure?
- end
-
def timeout
project.timeout
end
@@ -180,14 +122,6 @@ module Ci
yaml_variables + project_variables + trigger_variables
end
- def duration
- if started_at && finished_at
- finished_at - started_at
- elsif started_at
- Time.now - started_at
- end
- end
-
def project
commit.project
end
@@ -278,6 +212,25 @@ module Ci
"#{dir_to_trace}/#{id}.log"
end
+ def target_url
+ Gitlab::Application.routes.url_helpers.
+ namespace_project_build_url(gl_project.namespace, gl_project, self)
+ end
+
+ def cancel_url
+ if active?
+ Gitlab::Application.routes.url_helpers.
+ cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url)
+ end
+ end
+
+ def retry_url
+ if commands.present?
+ Gitlab::Application.routes.url_helpers.
+ cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url)
+ end
+ end
+
private
def yaml_variables
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index fde754a92a1..68864edfbbf 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -20,7 +20,8 @@ module Ci
extend Ci::Model
belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id
- has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
+ has_many :statuses, dependent: :destroy, class_name: 'CommitStatus'
+ has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :sha
@@ -47,7 +48,7 @@ module Ci
end
def retry
- builds_without_retry.each do |build|
+ latest_builds.each do |build|
Ci::Build.retry(build)
end
end
@@ -81,12 +82,11 @@ module Ci
end
def stage
- running_or_pending = builds_without_retry.running_or_pending
- running_or_pending.limit(1).pluck(:stage).first
+ running_or_pending = statuses.latest.running_or_pending.ordered
+ running_or_pending.first.try(:stage)
end
def create_builds(ref, tag, user, trigger_request = nil)
- return if skip_ci? && trigger_request.blank?
return unless config_processor
config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
@@ -94,7 +94,6 @@ module Ci
end
def create_next_builds(ref, tag, user, trigger_request)
- return if skip_ci? && trigger_request.blank?
return unless config_processor
stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
@@ -107,61 +106,60 @@ module Ci
end
def refs
- builds.group(:ref).pluck(:ref)
+ statuses.order(:ref).pluck(:ref).uniq
end
- def last_ref
- builds.latest.first.try(:ref)
+ def latest_statuses
+ @latest_statuses ||= statuses.latest.to_a
end
- def builds_without_retry
- builds.latest
+ def latest_builds
+ @latest_builds ||= builds.latest.to_a
end
- def builds_without_retry_for_ref(ref)
- builds.for_ref(ref).latest
+ def latest_builds_for_ref(ref)
+ latest_builds.select { |build| build.ref == ref }
end
- def retried_builds
- @retried_builds ||= (builds.order(id: :desc) - builds_without_retry)
+ def retried
+ @retried ||= (statuses.order(id: :desc) - statuses.latest)
end
def status
- if skip_ci?
- return 'skipped'
- elsif yaml_errors.present?
+ if yaml_errors.present?
return 'failed'
- elsif builds.none?
- return 'skipped'
- elsif success?
- 'success'
- elsif pending?
- 'pending'
- elsif running?
- 'running'
- elsif canceled?
- 'canceled'
- else
- 'failed'
+ end
+
+ @status ||= begin
+ latest = latest_statuses
+ latest.reject! { |status| status.try(&:allow_failure?) }
+
+ if latest.none?
+ 'skipped'
+ elsif latest.all?(&:success?)
+ 'success'
+ elsif latest.all?(&:pending?)
+ 'pending'
+ elsif latest.any?(&:running?) || latest.any?(&:pending?)
+ 'running'
+ elsif latest.all?(&:canceled?)
+ 'canceled'
+ else
+ 'failed'
+ end
end
end
def pending?
- builds_without_retry.all? do |build|
- build.pending?
- end
+ status == 'pending'
end
def running?
- builds_without_retry.any? do |build|
- build.running? || build.pending?
- end
+ status == 'running'
end
def success?
- builds_without_retry.all? do |build|
- build.success? || build.ignored?
- end
+ status == 'success'
end
def failed?
@@ -169,26 +167,21 @@ module Ci
end
def canceled?
- builds_without_retry.all? do |build|
- build.canceled?
- end
+ status == 'canceled'
end
def duration
- @duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
- end
-
- def duration_for_ref(ref)
- builds_without_retry_for_ref(ref).select(&:duration).sum(&:duration).to_i
+ duration_array = latest_statuses.map(&:duration).compact
+ duration_array.reduce(:+).to_i
end
def finished_at
- @finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
+ @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
end
def coverage
if project.coverage_enabled?
- coverage_array = builds_without_retry.map(&:coverage).compact
+ coverage_array = latest_builds.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
@@ -196,7 +189,7 @@ module Ci
end
def matrix_for_ref?(ref)
- builds_without_retry_for_ref(ref).pluck(:id).size > 1
+ latest_builds_for_ref(ref).size > 1
end
def config_processor
@@ -217,7 +210,6 @@ module Ci
end
def skip_ci?
- return false if builds.any?
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index aff329d71fa..d5c50013525 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -184,4 +184,12 @@ class Commit
def parents
@parents ||= Commit.decorate(super, project)
end
+
+ def ci_commit
+ project.ci_commit(sha)
+ end
+
+ def status
+ ci_commit.try(:status) || :not_found
+ end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
new file mode 100644
index 00000000000..b4d91b1b0c3
--- /dev/null
+++ b/app/models/commit_status.rb
@@ -0,0 +1,91 @@
+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
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
new file mode 100644
index 00000000000..fa54e3540d0
--- /dev/null
+++ b/app/models/generic_commit_status.rb
@@ -0,0 +1,15 @@
+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
diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb
index 0e6e97394bc..f17993d9f3b 100644
--- a/app/models/project_services/ci/hip_chat_service.rb
+++ b/app/models/project_services/ci/hip_chat_service.rb
@@ -49,7 +49,7 @@ module Ci
commit = build.commit
return unless commit
- return unless commit.builds_without_retry.include? build
+ return unless commit.latest_builds.include? build
case commit.status.to_sym
when :failed
diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb
index 11a2743f969..fd193301001 100644
--- a/app/models/project_services/ci/mail_service.rb
+++ b/app/models/project_services/ci/mail_service.rb
@@ -48,7 +48,7 @@ module Ci
# it doesn't make sense to send emails for retried builds
commit = build.commit
return unless commit
- return unless commit.builds_without_retry.include?(build)
+ return unless commit.latest_builds.include?(build)
case build.status.to_sym
when :failed
diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb
index 5ac8907ecd0..dc050a3fc59 100644
--- a/app/models/project_services/ci/slack_message.rb
+++ b/app/models/project_services/ci/slack_message.rb
@@ -23,7 +23,7 @@ module Ci
def attachments
fields = []
- commit.builds_without_retry.each do |build|
+ commit.latest_builds.each do |build|
next if build.allow_failure?
next unless build.failed?
fields << {
diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb
index 76db573dc17..ee8e4988826 100644
--- a/app/models/project_services/ci/slack_service.rb
+++ b/app/models/project_services/ci/slack_service.rb
@@ -48,7 +48,7 @@ module Ci
commit = build.commit
return unless commit
- return unless commit.builds_without_retry.include?(build)
+ return unless commit.latest_builds.include?(build)
case commit.status.to_sym
when :failed
diff --git a/app/services/ci/create_commit_service.rb b/app/services/ci/create_commit_service.rb
index fc1ae5774d5..479a2d6defc 100644
--- a/app/services/ci/create_commit_service.rb
+++ b/app/services/ci/create_commit_service.rb
@@ -17,8 +17,10 @@ module Ci
tag = origin_ref.start_with?('refs/tags/')
commit = project.gl_project.ensure_ci_commit(sha)
- commit.update_committed!
- commit.create_builds(ref, tag, user)
+ unless commit.skip_ci?
+ commit.update_committed!
+ commit.create_builds(ref, tag, user)
+ end
commit
end
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index 19d919f9b6a..f98fd9f06ba 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -3,10 +3,9 @@
.gray-content-block
- if current_user
- %ul.nav.nav-pills.event_filter.pull-right
- %li.pull-right
- = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do
- %i.fa.fa-rss
+ .pull-right
+ = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do
+ %i.fa.fa-rss
= render 'shared/event_filter'
.content_list
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index a9ba9d2ba10..dc8e81323a6 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -25,11 +25,9 @@
.hidden-xs
- if current_user
= render "events/event_last_push", event: @last_push
-
- %ul.nav.nav-pills.event_filter.pull-right
- %li
- = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do
- %i.fa.fa-rss
+ .pull-right
+ = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
+ %i.fa.fa-rss
= render 'shared/event_filter'
%hr
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 2468687b56d..1a883e20e89 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -6,7 +6,7 @@
= brand_header_logo
.gitlab-text-container
%h3 GitLab
-
+
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
- elsif current_user
@@ -23,6 +23,7 @@
= current_user.username
.content-wrapper
= render "layouts/flash"
+ = yield :flash_message
%div{ class: container_class }
.content
.clearfix
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 1261f6254d7..c2683bc6219 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,10 +1,9 @@
= render 'projects/last_push'
.gray-content-block.activity-filter-block
- if current_user
- %ul.nav.nav-pills.event_filter.pull-right
- %li
- = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do
- %i.fa.fa-rss
+ .pull-right
+ = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do
+ %i.fa.fa-rss
= render 'shared/event_filter'
.content_list{:"data-href" => activity_project_path(@project)}
diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml
deleted file mode 100644
index 65fd9413b60..00000000000
--- a/app/views/projects/builds/_build.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-- 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
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index b561078e8c7..9c3ae622b72 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -9,7 +9,7 @@
#up-build-trace
- if @commit.matrix_for_ref?(@build.ref)
%ul.center-top-menu.build-top-menu
- - @commit.builds_without_retry_for_ref(@build.ref).each do |build|
+ - @commit.latest_builds_for_ref(@build.ref).each do |build|
%li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do
= ci_icon_for_status(build.status)
@@ -20,7 +20,7 @@
= build.id
- - unless @commit.builds_without_retry_for_ref(@build.ref).include?(@build)
+ - unless @commit.latest_builds_for_ref(@build.ref).include?(@build)
%li.active
%a
Build ##{@build.id}
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
index 4b69a6d7a6f..3bc2daeec4e 100644
--- a/app/views/projects/buttons/_notifications.html.haml
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -1,6 +1,6 @@
- return unless @membership
-= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline-form', id: 'notification-form' do
+= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline', id: 'notification-form' do
= hidden_field_tag :notification_type, 'project'
= hidden_field_tag :notification_id, @membership.id
= hidden_field_tag :notification_level
diff --git a/app/views/projects/commit/ci.html.haml b/app/views/projects/commit/ci.html.haml
index 26ab38445c2..4a1ef378a30 100644
--- a/app/views/projects/commit/ci.html.haml
+++ b/app/views/projects/commit/ci.html.haml
@@ -20,30 +20,31 @@
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
-- @ci_commit.refs.each do |ref|
+.gray-content-block.second-block
+ Latest builds
+ - if @ci_commit.duration > 0
+ %small.pull-right
+ %i.fa.fa-time
+ #{time_interval_in_words @ci_commit.duration}
+
+%table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Ref
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @ci_project && @ci_project.coverage_enabled?
+ %th Coverage
+ %th
+ - @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.any?
.gray-content-block.second-block
- Builds for #{ref}
- - if @ci_commit.duration_for_ref(ref) > 0
- %small.pull-right
- %i.fa.fa-time
- #{time_interval_in_words @ci_commit.duration_for_ref(ref)}
-
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @ci_project && @ci_project.coverage_enabled?
- %th Coverage
- %th
- = render partial: "projects/builds/build", collection: @ci_commit.builds_without_retry.for_ref(ref), controls: true
-
-- if @ci_commit.retried_builds.any?
- %h3
Retried builds
%table.table.builds
@@ -59,4 +60,4 @@
- if @ci_project && @ci_project.coverage_enabled?
%th Coverage
%th
- = render partial: "projects/builds/build", collection: @ci_commit.retried_builds, ref: true
+ = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, coverage: @ci_project.try(:coverage_enabled?)
diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml
new file mode 100644
index 00000000000..e3a17faf0bd
--- /dev/null
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -0,0 +1,51 @@
+%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
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 1b093c8f514..daab2326bc7 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -111,10 +111,10 @@
- if current_user.can_create_group?
.pull-right
- .light.in-line
+ .light.inline
.space-right
Need a group for several dependent projects?
- = link_to new_group_path, class: "btn btn-xs" do
+ = link_to new_group_path, class: "btn" do
Create a group
.save-project-loader.hide
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index efa119edd5a..e95d987d74c 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -2,9 +2,10 @@
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "#{@project.name} activity")
-- if current_user && can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
- = render 'shared/no_password'
+= content_for :flash_message do
+ - if current_user && can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+ = render 'shared/no_password'
- if prefer_readme?
= render 'projects/last_push'
diff --git a/db/migrate/20151008123042_add_type_and_description_to_builds.rb b/db/migrate/20151008123042_add_type_and_description_to_builds.rb
new file mode 100644
index 00000000000..c72b1c611c6
--- /dev/null
+++ b/db/migrate/20151008123042_add_type_and_description_to_builds.rb
@@ -0,0 +1,9 @@
+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
diff --git a/db/migrate/20151008130321_migrate_name_to_description_for_builds.rb b/db/migrate/20151008130321_migrate_name_to_description_for_builds.rb
new file mode 100644
index 00000000000..f5c44babd84
--- /dev/null
+++ b/db/migrate/20151008130321_migrate_name_to_description_for_builds.rb
@@ -0,0 +1,5 @@
+class MigrateNameToDescriptionForBuilds < ActiveRecord::Migration
+ def change
+ execute("UPDATE ci_builds SET type='Ci::Build' WHERE type IS NULL")
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c5c462c2e57..7a11dfca034 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20151007120511) do
+ActiveRecord::Schema.define(version: 20151008130321) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -103,9 +103,14 @@ ActiveRecord::Schema.define(version: 20151007120511) do
t.boolean "tag"
t.string "ref"
t.integer "user_id"
+ t.string "type"
+ t.string "target_url"
+ t.string "description"
end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
+ add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
+ add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
diff --git a/doc/api/commits.md b/doc/api/commits.md
index eb8d6a43592..9f72adc6ed9 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -62,7 +62,8 @@ Parameters:
"authored_date": "2012-09-20T09:06:12+03:00",
"parent_ids": [
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
- ]
+ ],
+ "status": "running"
}
```
@@ -156,3 +157,84 @@ Parameters:
"line_type": "new"
}
```
+
+## Get the status of a commit
+
+Get the statuses of a commit in a project.
+
+```
+GET /projects/:id/repository/commits/:sha/statuses
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `sha` (required) - The commit SHA
+- `ref` (optional) - Filter by ref name, it can be branch or tag
+- `stage` (optional) - Filter by stage
+- `name` (optional) - Filer by status name, eg. jenkins
+- `all` (optional) - The flag to return all statuses, not only latest ones
+
+```json
+[
+ {
+ "id": 13,
+ "sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27",
+ "ref": "test",
+ "status": "success",
+ "name": "ci/jenkins",
+ "target_url": "http://jenkins/project/url",
+ "description": "Jenkins success",
+ "created_at": "2015-10-12T09:47:16.250Z",
+ "started_at": "2015-10-12T09:47:16.250Z"",
+ "finished_at": "2015-10-12T09:47:16.262Z",
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "blocked": false,
+ "created_at": "2012-04-29T08:46:00Z"
+ }
+ }
+]
+```
+
+## Post the status to commit
+
+Adds or updates a status of a commit.
+
+```
+POST /projects/:id/statuses/:sha
+```
+
+- `id` (required) - The ID of a project
+- `sha` (required) - The commit SHA
+- `state` (required) - The state of the status. Can be: pending, running, success, failed, canceled
+- `ref` (optional) - The ref (branch or tag) to which the status refers
+- `name` or `context` (optional) - The label to differentiate this status from the status of other systems. Default: "default"
+- `target_url` (optional) - The target URL to associate with this status
+- `description` (optional) - The short description of the status
+
+```json
+{
+ "id": 13,
+ "sha": "b0b3a907f41409829b307a28b82fdbd552ee5a27",
+ "ref": "test",
+ "status": "success",
+ "name": "ci/jenkins",
+ "target_url": "http://jenkins/project/url",
+ "description": "Jenkins success",
+ "created_at": "2015-10-12T09:47:16.250Z",
+ "started_at": "2015-10-12T09:47:16.250Z"",
+ "finished_at": "2015-10-12T09:47:16.262Z",
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "blocked": false,
+ "created_at": "2012-04-29T08:46:00Z"
+ }
+}
+```
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index ae5f90004e6..a3cb83880e3 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -118,6 +118,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'I see builds list' do
expect(page).to have_content "build: pending"
- expect(page).to have_content "Builds for master"
+ expect(page).to have_content "Latest builds"
end
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index c09488d3547..afc0402f9e1 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -46,6 +46,7 @@ module API
mount Services
mount Files
mount Commits
+ mount CommitStatus
mount Namespaces
mount Branches
mount Labels
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
new file mode 100644
index 00000000000..2c0596c9dfb
--- /dev/null
+++ b/lib/api/commit_statuses.rb
@@ -0,0 +1,80 @@
+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
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 9620d36ac41..519072d0157 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -149,6 +149,7 @@ module API
class RepoCommitDetail < RepoCommit
expose :parent_ids, :committed_date, :authored_date
+ expose :status
end
class ProjectSnippet < Grape::Entity
@@ -228,6 +229,12 @@ module API
expose :created_at
end
+ class CommitStatus < Grape::Entity
+ expose :id, :sha, :ref, :status, :name, :target_url, :description,
+ :created_at, :started_at, :finished_at
+ expose :author, using: Entities::UserBasic
+ end
+
class Event < Grape::Entity
expose :title, :project_id, :action_name
expose :target_id, :target_type, :author_id
diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb
index f47bc1236b8..b80c0b8b273 100644
--- a/lib/ci/api/entities.rb
+++ b/lib/ci/api/entities.rb
@@ -2,7 +2,7 @@ module Ci
module API
module Entities
class Commit < Grape::Entity
- expose :id, :ref, :sha, :project_id, :before_sha, :created_at
+ expose :id, :sha, :project_id, :created_at
expose :status, :finished_at, :duration
expose :git_commit_message, :git_author_name, :git_author_email
end
@@ -12,7 +12,7 @@ module Ci
end
class Build < Grape::Entity
- expose :id, :commands, :ref, :sha, :project_id, :repo_url,
+ expose :id, :commands, :ref, :sha, :status, :project_id, :repo_url,
:before_sha, :allow_git_fetch, :project_name
expose :options do |model|
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 21b582afba4..2fcd70182b9 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -27,6 +27,7 @@
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
+ name 'test'
ref 'master'
tag false
started_at 'Di 29. Okt 09:51:28 CET 2013'
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
new file mode 100644
index 00000000000..52de437052d
--- /dev/null
+++ b/spec/factories/commit_statuses.rb
@@ -0,0 +1,15 @@
+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
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 5da220859e3..cbb6360069b 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -12,6 +12,7 @@ describe "Commits" do
@ci_project = project.ensure_gitlab_ci_project
@commit = FactoryGirl.create :ci_commit, gl_project: project, sha: project.commit.sha
@build = FactoryGirl.create :ci_build, commit: @commit
+ @generic_status = FactoryGirl.create :generic_commit_status, commit: @commit
end
before do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/build_spec.rb
index da56f6e31ae..d875015b991 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -30,17 +30,9 @@ describe Ci::Build do
let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
- subject { build }
- it { is_expected.to belong_to(:commit) }
- it { is_expected.to belong_to(:user) }
- it { is_expected.to validate_presence_of :status }
it { is_expected.to validate_presence_of :ref }
- it { is_expected.to respond_to :success? }
- it { is_expected.to respond_to :failed? }
- it { is_expected.to respond_to :running? }
- it { is_expected.to respond_to :pending? }
it { is_expected.to respond_to :trace_html }
describe :first_pending do
@@ -67,72 +59,6 @@ describe Ci::Build do
end
end
- describe :started? do
- subject { build.started? }
-
- context 'without started_at' do
- before { build.started_at = nil }
-
- it { is_expected.to be_falsey }
- end
-
- %w(running success failed).each do |status|
- context "if build status is #{status}" do
- before { build.status = status }
-
- it { is_expected.to be_truthy }
- end
- end
-
- %w(pending canceled).each do |status|
- context "if build status is #{status}" do
- before { build.status = status }
-
- it { is_expected.to be_falsey }
- end
- end
- end
-
- describe :active? do
- subject { build.active? }
-
- %w(pending running).each do |state|
- context "if build.status is #{state}" do
- before { build.status = state }
-
- it { is_expected.to be_truthy }
- end
- end
-
- %w(success failed canceled).each do |state|
- context "if build.status is #{state}" do
- before { build.status = state }
-
- it { is_expected.to be_falsey }
- end
- end
- end
-
- describe :complete? do
- subject { build.complete? }
-
- %w(success failed canceled).each do |state|
- context "if build.status is #{state}" do
- before { build.status = state }
-
- it { is_expected.to be_truthy }
- end
- end
-
- %w(pending running).each do |state|
- context "if build.status is #{state}" do
- before { build.status = state }
-
- it { is_expected.to be_falsey }
- end
- end
- end
-
describe :ignored? do
subject { build.ignored? }
@@ -200,31 +126,6 @@ describe Ci::Build do
it { is_expected.to eq(commit.project.timeout) }
end
- describe :duration do
- subject { build.duration }
-
- it { is_expected.to eq(120.0) }
-
- context 'if the building process has not started yet' do
- before do
- build.started_at = nil
- build.finished_at = nil
- end
-
- it { is_expected.to be_nil }
- end
-
- context 'if the building process has started' do
- before do
- build.started_at = Time.now - 1.minute
- build.finished_at = nil
- end
-
- it { is_expected.to be_a(Float) }
- it { is_expected.to be > 0.0 }
- end
- end
-
describe :options do
let(:options) do
{
@@ -239,18 +140,6 @@ describe Ci::Build do
it { is_expected.to eq(options) }
end
- describe :sha do
- subject { build.sha }
-
- it { is_expected.to eq(commit.sha) }
- end
-
- describe :short_sha do
- subject { build.short_sha }
-
- it { is_expected.to eq(commit.short_sha) }
- end
-
describe :allow_git_fetch do
subject { build.allow_git_fetch }
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index acff1ddf0fc..330971174fb 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -23,6 +23,8 @@ describe Ci::Commit do
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
it { is_expected.to belong_to(:gl_project) }
+ it { is_expected.to have_many(:statuses) }
+ it { is_expected.to have_many(:trigger_requests) }
it { is_expected.to have_many(:builds) }
it { is_expected.to validate_presence_of :sha }
@@ -47,10 +49,12 @@ describe Ci::Commit do
@second = FactoryGirl.create :ci_build, commit: commit
end
- it "creates new build" do
+ it "creates only a new build" do
expect(commit.builds.count(:all)).to eq 2
+ expect(commit.statuses.count(:all)).to eq 2
commit.retry
expect(commit.builds.count(:all)).to eq 3
+ expect(commit.statuses.count(:all)).to eq 3
end
end
@@ -78,8 +82,8 @@ describe Ci::Commit do
subject { commit.stage }
before do
- @second = FactoryGirl.create :ci_build, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: :pending
- @first = FactoryGirl.create :ci_build, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: :pending
+ @second = FactoryGirl.create :commit_status, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: 'pending'
+ @first = FactoryGirl.create :commit_status, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: 'pending'
end
it 'returns first running stage' do
@@ -88,7 +92,7 @@ describe Ci::Commit do
context 'first build succeeded' do
before do
- @first.update_attributes(status: :success)
+ @first.success
end
it 'returns last running stage' do
@@ -98,8 +102,8 @@ describe Ci::Commit do
context 'all builds succeeded' do
before do
- @first.update_attributes(status: :success)
- @second.update_attributes(status: :success)
+ @first.success
+ @second.success
end
it 'returns nil' do
@@ -111,6 +115,33 @@ describe Ci::Commit do
describe :create_next_builds do
end
+ describe :refs do
+ subject { commit.refs }
+
+ before do
+ FactoryGirl.create :commit_status, commit: commit, name: 'deploy'
+ FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'develop'
+ FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'master'
+ end
+
+ it 'returns all refs' do
+ is_expected.to contain_exactly('master', 'develop', nil)
+ end
+ end
+
+ describe :retried do
+ subject { commit.retried }
+
+ before do
+ @commit1 = FactoryGirl.create :ci_build, commit: commit, name: 'deploy'
+ @commit2 = FactoryGirl.create :ci_build, commit: commit, name: 'deploy'
+ end
+
+ it 'returns old builds' do
+ is_expected.to contain_exactly(@commit1)
+ end
+ end
+
describe :create_builds do
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
@@ -194,9 +225,10 @@ describe Ci::Commit do
it 'rebuilds commit' do
expect(commit.status).to eq('skipped')
expect(create_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
- expect(commit.status).to eq('pending')
+
+ # since everything in Ci::Commit is cached we need to fetch a new object
+ new_commit = Ci::Commit.find_by_id(commit.id)
+ expect(new_commit.status).to eq('pending')
end
end
end
@@ -252,10 +284,10 @@ describe Ci::Commit do
describe :should_create_next_builds? do
before do
- @build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: :success
- @build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: :failed
- @build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: :failed
- @build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: :success
+ @build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: 'success'
+ @build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: 'failed'
+ @build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: 'failed'
+ @build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'success'
end
context 'for success' do
@@ -266,7 +298,7 @@ describe Ci::Commit do
context 'for failed' do
before do
- @build4.update_attributes(status: :failed)
+ @build4.update_attributes(status: 'failed')
end
it 'to not create' do
@@ -286,7 +318,7 @@ describe Ci::Commit do
context 'for running' do
before do
- @build4.update_attributes(status: :running)
+ @build4.update_attributes(status: 'running')
end
it 'to not create' do
@@ -296,7 +328,7 @@ describe Ci::Commit do
context 'for retried' do
before do
- @build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: :failed
+ @build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'failed'
end
it 'to not create' do
diff --git a/spec/models/ci/project_services/mail_service_spec.rb b/spec/models/ci/project_services/mail_service_spec.rb
index 04e870dce7f..d9b3d34ff15 100644
--- a/spec/models/ci/project_services/mail_service_spec.rb
+++ b/spec/models/ci/project_services/mail_service_spec.rb
@@ -35,7 +35,7 @@ describe Ci::MailService do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit, user: user) }
+ let(:build) { FactoryGirl.create(:ci_build, status: 'failed', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
@@ -58,7 +58,7 @@ describe Ci::MailService do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true, email_only_broken_builds: false) }
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
+ let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
@@ -86,7 +86,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
+ let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
@@ -115,7 +115,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
+ let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
@@ -144,7 +144,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit, user: user) }
+ let(:build) { FactoryGirl.create(:ci_build, status: 'success', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
@@ -167,7 +167,7 @@ describe Ci::MailService do
end
let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit, user: user) }
+ let(:build) { FactoryGirl.create(:ci_build, status: 'failed', commit: commit, user: user) }
before do
allow(mail).to receive_messages(
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
new file mode 100644
index 00000000000..c96a606fdaa
--- /dev/null
+++ b/spec/models/commit_status_spec.rb
@@ -0,0 +1,164 @@
+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
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
new file mode 100644
index 00000000000..f442fa5fbe5
--- /dev/null
+++ b/spec/models/generic_commit_status_spec.rb
@@ -0,0 +1,39 @@
+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
diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb
new file mode 100644
index 00000000000..b9e6dfc15a7
--- /dev/null
+++ b/spec/requests/api/commit_status_spec.rb
@@ -0,0 +1,135 @@
+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
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index a1c248c636e..49acc3368f4 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -47,6 +47,19 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/commits/invalid_sha", user)
expect(response.status).to eq(404)
end
+
+ it "should return not_found for CI status" do
+ get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['status']).to eq('not_found')
+ end
+
+ it "should return status for CI" do
+ ci_commit = project.ensure_ci_commit(project.repository.commit.sha)
+ get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['status']).to eq(ci_commit.status)
+ end
end
context "unauthorized user" do