#
1
@@ -53,7 +53,7 @@ class @LineHighlighter
$.scrollTo("#L#{range[0]}", offset: -150)
bindEvents: ->
- $('#tree-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
+ $('#blob-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
# While it may seem odd to bind to the mousedown event and then throw away
# the click event, there is a method to our madness.
@@ -62,7 +62,7 @@ class @LineHighlighter
# active state even when the event is cancelled, resulting in an ugly border
# around the link and/or a persisted underline text decoration.
- $('#tree-content-holder').on 'click', 'a[data-line-number]', (event) ->
+ $('#blob-content-holder').on 'click', 'a[data-line-number]', (event) ->
event.preventDefault()
clickHandler: (event) =>
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 4e56791bde4..3e77ea515f8 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -69,7 +69,7 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
top = $(container + " " + window.location.hash).offset().top
- $('body').scrollTo(top);
+ $('body').scrollTo(top)
# Activate a tab based on the current action
activateTab: (action) ->
@@ -139,13 +139,16 @@ class @MergeRequestTabs
@diffsLoaded = true
@scrollToElement(".diffs")
- toggleLoading: ->
- $('.mr-loading-status .loading').toggle()
+ # Show or hide the loading spinner
+ #
+ # status - Boolean, true to show, false to hide
+ toggleLoading: (status) ->
+ $('.mr-loading-status .loading').toggle(status)
_get: (options) ->
defaults = {
- beforeSend: @toggleLoading
- complete: @toggleLoading
+ beforeSend: => @toggleLoading(true)
+ complete: => @toggleLoading(false)
dataType: 'json'
type: 'GET'
}
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 4b9f0d68912..ea75c656bcc 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -63,12 +63,6 @@ class @Notes
# fetch notes when tab becomes visible
$(document).on "visibilitychange", @visibilityChange
- # Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown.
- $(document).on 'keydown', '.js-note-text', (e) ->
- return if e.originalEvent.repeat
- if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13)
- $(@).closest('form').submit()
-
cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form"
@@ -82,7 +76,6 @@ class @Notes
$(document).off "click", ".js-discussion-reply-button"
$(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange"
- $(document).off "keydown", ".js-note-text"
$(document).off "keyup", ".js-note-text"
$(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close"
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
*/
-
-@import "base/fonts";
-@import "base/variables";
-@import "base/mixins";
-@import "base/layout";
-
-
-/**
- * Customized Twitter bootstrap
+/*
+ * 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/gl_variables';
-@import 'base/gl_bootstrap';
-/**
+/*
+ * GitLab UI framework
+ */
+@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/base/variables.scss b/app/assets/stylesheets/base/variables.scss
deleted file mode 100644
index befd63832d5..00000000000
--- a/app/assets/stylesheets/base/variables.scss
+++ /dev/null
@@ -1,46 +0,0 @@
-$hover: #FFFAF1;
-$gl-text-color: #54565B;
-$gl-text-green: #4A2;
-$gl-text-red: #D12F19;
-$gl-text-orange: #D90;
-$gl-header-color: #4c4e54;
-$gl-link-color: #333c48;
-$md-text-color: #444;
-$md-link-color: #3084bb;
-$nprogress-color: #c0392b;
-$gl-font-size: 15px;
-$list-font-size: 15px;
-$sidebar_collapsed_width: 62px;
-$sidebar_width: 230px;
-$avatar_radius: 50%;
-$code_font_size: 13px;
-$code_line_height: 1.5;
-$border-color: #dce0e6;
-$background-color: #F7F8FA;
-$header-height: 58px;
-$fixed-layout-width: 1200px;
-$gl-gray: #7f8fa4;
-$gl-padding: 16px;
-$gl-avatar-size: 46px;
-
-
-/*
- * State colors:
- */
-$gl-primary: #446e9b;
-$gl-success: #44c679;
-$gl-info: #00aaff;
-$gl-warning: #EB9532;
-$gl-danger: #d9534f;
-
-/*
- * Commit Diff Colors
- */
-$added: #63c363;
-$deleted: #f77;
-
-/*
- * Fonts
- */
-$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
-$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif;
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
similarity index 100%
rename from app/assets/stylesheets/generic/avatar.scss
rename to app/assets/stylesheets/framework/avatar.scss
diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
similarity index 100%
rename from app/assets/stylesheets/generic/blocks.scss
rename to app/assets/stylesheets/framework/blocks.scss
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
new file mode 100644
index 00000000000..e5f0c0ad9ef
--- /dev/null
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -0,0 +1,171 @@
+@mixin btn-default {
+ @include border-radius(2px);
+ border-width: 1px;
+ border-style: solid;
+ text-transform: uppercase;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 18px;
+ padding: 11px $gl-padding;
+ letter-spacing: .4px;
+
+ &:focus,
+ &:active {
+ outline: none;
+ @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+ }
+}
+
+@mixin btn-middle {
+ @include btn-default;
+ @include border-radius(2px);
+ padding: 11px 24px;
+}
+
+@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) {
+ background-color: $light;
+ border-color: $border-light;
+ color: $color;
+
+ &:hover,
+ &:focus {
+ background-color: $normal;
+ border-color: $border-normal;
+ color: $color;
+ }
+
+ &:active {
+ @include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12));
+
+ background-color: $dark;
+ border-color: $border-dark;
+ color: $color;
+ }
+}
+
+@mixin btn-green {
+ @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, #FFFFFF);
+}
+
+@mixin btn-blue {
+ @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF);
+}
+
+@mixin btn-orange {
+ @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF);
+}
+
+@mixin btn-red {
+ @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, #FFFFFF);
+}
+
+@mixin btn-gray {
+ @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, #313236);
+}
+
+@mixin btn-white {
+ @include btn-color($white-light, $border-white-light, $white-normal, $border-white-normal, $white-dark, $border-white-dark, #313236);
+}
+
+.btn {
+ @include btn-default;
+ @include btn-white;
+
+ &.btn-sm {
+ padding: 5px 10px;
+ }
+
+ &.btn-xs {
+ padding: 1px 5px;
+ }
+
+ &.btn-success,
+ &.btn-new,
+ &.btn-create,
+ &.btn-save,
+ &.btn-green {
+ @include btn-green;
+ }
+
+ &.btn-gray {
+ @include btn-gray;
+ }
+
+ &.btn-primary,
+ &.btn-info {
+ @include btn-blue;
+ }
+
+ &.btn-warning {
+ @include btn-orange;
+ }
+
+ &.btn-danger,
+ &.btn-remove,
+ &.btn-red {
+ @include btn-red;
+ }
+
+ &.btn-cancel {
+ float: right;
+ }
+
+ &.btn-close {
+ color: $gl-danger;
+ border-color: $gl-danger;
+ &:hover {
+ color: #B94A48;
+ }
+ }
+
+ &.btn-reopen {
+ color: $gl-success;
+ border-color: $gl-success;
+ &:hover {
+ color: #468847;
+ }
+ }
+
+ &.btn-grouped {
+ margin-right: 7px;
+ float: left;
+ &:last-child {
+ margin-right: 0px;
+ }
+ }
+}
+
+.btn-block {
+ width: 100%;
+ margin: 0;
+ margin-bottom: 15px;
+ &.btn {
+ padding: 6px 0;
+ }
+}
+
+.btn-group {
+ &.btn-grouped {
+ margin-right: 7px;
+ float: left;
+ &:last-child {
+ margin-right: 0px;
+ }
+ }
+}
+
+.btn-group-next {
+ .btn {
+ padding: 9px 0px;
+ font-size: 15px;
+ color: #7f8fa4;
+ border-color: #e7e9ed;
+ width: 140px;
+
+ &.active {
+ border-color: $gl-info;
+ background: $gl-info;
+ color: #fff;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
similarity index 100%
rename from app/assets/stylesheets/generic/calendar.scss
rename to app/assets/stylesheets/framework/calendar.scss
diff --git a/app/assets/stylesheets/generic/callout.scss b/app/assets/stylesheets/framework/callout.scss
similarity index 100%
rename from app/assets/stylesheets/generic/callout.scss
rename to app/assets/stylesheets/framework/callout.scss
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/framework/common.scss
similarity index 99%
rename from app/assets/stylesheets/generic/common.scss
rename to app/assets/stylesheets/framework/common.scss
index 45e284542d2..e1a1793be9c 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -394,3 +394,7 @@ table {
.dropzone .dz-preview .dz-progress .dz-upload {
background: $gl-success !important;
}
+
+.space-right {
+ margin-right: 10px;
+}
diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/framework/files.scss
similarity index 100%
rename from app/assets/stylesheets/generic/files.scss
rename to app/assets/stylesheets/framework/files.scss
diff --git a/app/assets/stylesheets/generic/filters.scss b/app/assets/stylesheets/framework/filters.scss
similarity index 100%
rename from app/assets/stylesheets/generic/filters.scss
rename to app/assets/stylesheets/framework/filters.scss
diff --git a/app/assets/stylesheets/generic/flash.scss b/app/assets/stylesheets/framework/flash.scss
similarity index 100%
rename from app/assets/stylesheets/generic/flash.scss
rename to app/assets/stylesheets/framework/flash.scss
diff --git a/app/assets/stylesheets/base/fonts.scss b/app/assets/stylesheets/framework/fonts.scss
similarity index 100%
rename from app/assets/stylesheets/base/fonts.scss
rename to app/assets/stylesheets/framework/fonts.scss
diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/framework/forms.scss
similarity index 86%
rename from app/assets/stylesheets/generic/forms.scss
rename to app/assets/stylesheets/framework/forms.scss
index 4282832e2bf..0edfe24f195 100644
--- a/app/assets/stylesheets/generic/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -29,12 +29,6 @@ input[type='text'].danger {
border-top: 1px solid $border-color;
}
-@media (min-width: $screen-sm-min) {
- .form-actions {
- padding-left: 17%;
- }
-}
-
label {
&.control-label {
@extend .col-sm-2;
@@ -84,3 +78,17 @@ label {
.wiki-content {
margin-top: 35px;
}
+
+.form-group .control-label {
+ font-weight: normal;
+}
+
+.form-control::-webkit-input-placeholder {
+ color: #7f8fa4;
+}
+
+.input-group {
+ .input-group-addon {
+ background-color: #f7f8fa;
+ }
+}
diff --git a/app/assets/stylesheets/generic/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
similarity index 95%
rename from app/assets/stylesheets/generic/gfm.scss
rename to app/assets/stylesheets/framework/gfm.scss
index bd9200ace23..5ae0520fd7b 100644
--- a/app/assets/stylesheets/generic/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -22,4 +22,5 @@
.gfm-commit, .gfm-commit_range {
font-family: $monospace_font;
+ font-size: 90%;
}
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
similarity index 100%
rename from app/assets/stylesheets/themes/gitlab-theme.scss
rename to app/assets/stylesheets/framework/gitlab-theme.scss
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/framework/header.scss
similarity index 96%
rename from app/assets/stylesheets/generic/header.scss
rename to app/assets/stylesheets/framework/header.scss
index 543ce41ab52..91e6975e269 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -50,15 +50,17 @@ header {
.navbar-toggle {
color: #666;
- margin: 0;
+ margin: 6px 0;
border-radius: 0;
position: absolute;
right: 2px;
- top: 15px;
&:hover {
background-color: #EEE;
}
+ &.active {
+ color: #7f8fa4;
+ }
}
}
}
@@ -87,6 +89,7 @@ header {
.navbar-collapse {
float: right;
+ border-top: none;
}
}
diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
similarity index 100%
rename from app/assets/stylesheets/generic/highlight.scss
rename to app/assets/stylesheets/framework/highlight.scss
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
similarity index 94%
rename from app/assets/stylesheets/generic/issue_box.scss
rename to app/assets/stylesheets/framework/issue_box.scss
index b1fb87a6830..93377e45e70 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -5,7 +5,7 @@
*/
.issue-box {
- @include border-radius(3px);
+ @include border-radius(2px);
display: inline-block;
padding: 10px $gl-padding;
diff --git a/app/assets/stylesheets/generic/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
similarity index 100%
rename from app/assets/stylesheets/generic/jquery.scss
rename to app/assets/stylesheets/framework/jquery.scss
diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/framework/layout.scss
similarity index 88%
rename from app/assets/stylesheets/base/layout.scss
rename to app/assets/stylesheets/framework/layout.scss
index b91c15d8910..c7b3b60e769 100644
--- a/app/assets/stylesheets/base/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -5,6 +5,7 @@ html {
body {
padding-top: $header-height;
+ text-rendering: geometricPrecision;
}
}
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/framework/lists.scss
similarity index 95%
rename from app/assets/stylesheets/generic/lists.scss
rename to 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
similarity index 100%
rename from app/assets/stylesheets/generic/markdown_area.scss
rename to app/assets/stylesheets/framework/markdown_area.scss
diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
similarity index 55%
rename from app/assets/stylesheets/base/mixins.scss
rename to 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
similarity index 91%
rename from app/assets/stylesheets/generic/mobile.scss
rename to 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
similarity index 100%
rename from app/assets/stylesheets/generic/pagination.scss
rename to app/assets/stylesheets/framework/pagination.scss
diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/framework/selects.scss
similarity index 69%
rename from app/assets/stylesheets/generic/selects.scss
rename to app/assets/stylesheets/framework/selects.scss
index f0860de1c49..cba621635b6 100644
--- a/app/assets/stylesheets/generic/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -8,7 +8,7 @@
font-size: $gl-font-size;
line-height: 1.42857143;
- @include border-radius(4px);
+ @include border-radius(2px);
.select2-arrow {
background: #FFF;
@@ -18,8 +18,39 @@
}
}
+.select2-container .select2-choice, .select2-container.select2-drop-above .select2-choice{
+ color: #7f8fa4;
+ border: 1px solid #e7e9ed;
+}
+
+.select2-drop {
+ @include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
+ @include border-radius (0px);
+
+ padding: 16px;
+ border: none !important;
+}
+
+.select2-results .select2-result-label {
+ padding: 16px;
+}
+
+.select2-drop{
+ color: #7f8fa4;
+}
+
+.select2-highlighted {
+ background: #3084bb !important;
+}
+
+.select2-results li.select2-result-with-children > .select2-result-label {
+ font-weight: 600;
+ color: #313236;
+}
+
+
.select2-container-multi .select2-choices {
- @include border-radius(4px);
+ @include border-radius(2px);
border-color: #CCC;
}
@@ -63,7 +94,7 @@
.ajax-users-dropdown, .ajax-project-users-dropdown {
.select2-search {
- padding-top: 4px;
+ padding-top: 2px;
}
}
@@ -97,9 +128,6 @@
}
.user-name {
}
- .user-username {
- color: #999;
- }
}
.namespace-result {
@@ -114,5 +142,5 @@
}
.ajax-users-dropdown {
- min-width: 225px !important;
+ min-width: 250px !important;
}
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
similarity index 100%
rename from app/assets/stylesheets/generic/sidebar.scss
rename to app/assets/stylesheets/framework/sidebar.scss
diff --git a/app/assets/stylesheets/generic/tables.scss b/app/assets/stylesheets/framework/tables.scss
similarity index 54%
rename from app/assets/stylesheets/generic/tables.scss
rename to app/assets/stylesheets/framework/tables.scss
index a66e45577de..789b34020c1 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;
@@ -12,7 +28,7 @@ table {
border-bottom: 1px solid $border-color !important;
}
td {
- border-color: #F1F1F1 !important;
+ border-color: $table-border-color !important;
border-bottom: 1px solid;
}
}
diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
similarity index 93%
rename from app/assets/stylesheets/generic/timeline.scss
rename to app/assets/stylesheets/framework/timeline.scss
index 74bbaabad39..bf21d7fce76 100644
--- a/app/assets/stylesheets/generic/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -10,8 +10,8 @@
margin-left: -$gl-padding;
margin-right: -$gl-padding;
color: $gl-gray;
- border-bottom: 1px solid #f1f2f4;
- border-right: 1px solid #f1f2f4;
+ border-bottom: 1px solid #ECEEF1;
+ border-right: 1px solid #ECEEF1;
&:last-child {
border-bottom: none;
diff --git a/app/assets/stylesheets/base/gl_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
similarity index 92%
rename from app/assets/stylesheets/base/gl_bootstrap.scss
rename to 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
similarity index 97%
rename from app/assets/stylesheets/base/gl_variables.scss
rename to app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 7378d404008..63868a34e2a 100644
--- a/app/assets/stylesheets/base/gl_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -22,8 +22,8 @@ $brand-info: $gl-info;
$brand-warning: $gl-warning;
$brand-danger: $gl-danger;
-$border-radius-base: 3px !default;
-$border-radius-large: 5px !default;
+$border-radius-base: 2px !default;
+$border-radius-large: 2px !default;
$border-radius-small: 2px !default;
@@ -156,3 +156,5 @@ $nav-link-padding: 13px $gl-padding;
$pre-bg: #f8fafc !default;
$pre-color: $gl-gray !default;
$pre-border-color: #e7e9ed;
+
+$table-bg-accent: $background-color;
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/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
new file mode 100644
index 00000000000..91954683c3e
--- /dev/null
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -0,0 +1,99 @@
+$hover: #FFFAF1;
+$gl-text-color: #54565B;
+$gl-text-green: #4A2;
+$gl-text-red: #D12F19;
+$gl-text-orange: #D90;
+$gl-header-color: #4c4e54;
+$gl-link-color: #333c48;
+$md-text-color: #444;
+$md-link-color: #3084bb;
+$nprogress-color: #c0392b;
+$gl-font-size: 15px;
+$list-font-size: 15px;
+$sidebar_collapsed_width: 62px;
+$sidebar_width: 230px;
+$avatar_radius: 50%;
+$code_font_size: 13px;
+$code_line_height: 1.5;
+$border-color: #dce0e6;
+$table-border-color: #eef0f2;
+$background-color: #F7F8FA;
+$header-height: 58px;
+$fixed-layout-width: 1200px;
+$gl-gray: #7f8fa4;
+$gl-padding: 16px;
+$gl-avatar-size: 46px;
+
+/*
+ * Color schema
+ */
+
+$white-light: #FFFFFF;
+$white-normal: #DCE0E5;
+$white-dark: #E4E7ED;
+
+$gray-light: #F0F2F5;
+$gray-normal: #DCE0E5;
+$gray-dark: #E4E7ED;
+
+$green-light: #31AF64;
+$green-normal: #2FAA60;
+$green-dark: #2CA05B;
+
+$blue-light: #2EA8E5;
+$blue-normal: #2D9FD8;
+$blue-dark: #2897CE;
+
+$orange-light: #FC6443;
+$orange-normal: #E75E40;
+$orange-dark: #CE5237;
+
+$red-light: #F43263;
+$red-normal: #E52C5A;
+$red-dark: #D22852;
+
+$border-white-light: #E3E7EC;
+$border-white-normal: #D6DAE2;
+$border-white-dark: #C6CACF;
+
+$border-gray-light: #DCE0E5;
+$border-gray-normal: #D6DAE2;
+$border-gray-dark: #C6CACF;
+
+$border-green-light: #2FAA60;
+$border-green-normal: #2CA05B;
+$border-green-dark: #279654;
+
+$border-blue-light: #2D9FD8;
+$border-blue-normal: #2897CE;
+$border-blue-dark: #258DC1;
+
+$border-orange-light: #ED5C3D;
+$border-orange-normal: #CE5237;
+$border-orange-dark: #C14E35;
+
+$border-red-light: #E52C5A;
+$border-red-normal: #D22852;
+$border-red-dark: #CA264F;
+
+
+/*
+ * State colors:
+ */
+$gl-primary: $blue-normal;
+$gl-success: $green-normal;
+$gl-info: $blue-normal;
+$gl-warning: $orange-normal;
+$gl-danger: $red-normal;
+
+/*
+ * Commit Diff Colors
+ */
+$added: #63c363;
+$deleted: #f77;
+
+/*
+ * Fonts
+ */
+$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
+$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif;
diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/framework/zen.scss
similarity index 100%
rename from app/assets/stylesheets/generic/zen.scss
rename to app/assets/stylesheets/framework/zen.scss
diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss
deleted file mode 100644
index cf76f538e01..00000000000
--- a/app/assets/stylesheets/generic/buttons.scss
+++ /dev/null
@@ -1,228 +0,0 @@
-body {
- text-rendering: geometricPrecision;
-}
-.btn {
- @extend .btn-default;
-
- &.btn-new {
- @extend .btn-success;
- }
-
- &.btn-create {
- @extend .btn-success;
- }
-
- &.btn-save {
- @extend .btn-success;
- }
-
- &.btn-remove {
- @extend .btn-danger;
- }
-
- &.btn-cancel {
- float: right;
- }
-
- &.btn-close {
- color: $gl-danger;
- border-color: $gl-danger;
- &:hover {
- color: #B94A48;
- }
- }
-
- &.btn-reopen {
- color: $gl-success;
- border-color: $gl-success;
- &:hover {
- color: #468847;
- }
- }
-
- &.btn-grouped {
- margin-right: 7px;
- float: left;
- &:last-child {
- margin-right: 0px;
- }
- }
-
- &.btn-save {
- @extend .btn-primary;
- }
-
- &.btn-new, &.btn-create {
- @extend .btn-success;
- }
-}
-
-.btn-block {
- width: 100%;
- margin: 0;
- margin-bottom: 15px;
- &.btn {
- padding: 6px 0;
- }
-}
-
-.btn-group {
- &.btn-grouped {
- margin-right: 7px;
- float: left;
- &:last-child {
- margin-right: 0px;
- }
- }
-}
-
-.btn-group-next {
- .btn {
- padding: 9px 0px;
- font-size: 15px;
- color: #7f8fa4;
- border-color: #e7e9ed;
- width: 140px;
-
- &.active {
- border-color: $gl-info;
- background: $gl-info;
- color: #fff;
- }
- }
-}
-
-@mixin btn-info {
- @include border-radius(2px);
-
- border-width: 1px;
- border-style: solid;
- text-transform: uppercase;
- font-size: 13px;
- font-weight: 600;
- line-height: 18px;
- padding: 11px 16px;
- letter-spacing: .4px;
-
- &:hover {
- border-width: 1px;
- border-style: solid;
- }
-
- &:focus {
- border-width: 1px;
- border-style: solid;
- }
-
- &:active {
- @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
- border-width: 1px;
- border-style: solid;
- }
-}
-
-@mixin btn-middle {
- @include border-radius(2px);
-
- border-width: 1px;
- border-style: solid;
- text-transform: uppercase;
- font-size: 13px;
- font-weight: 600;
- line-height: 18px;
- padding: 11px 24px;
- letter-spacing: .4px;
-
- &:hover {
- border-width: 1px;
- border-style: solid;
- }
-
- &:focus {
- border-width: 1px;
- border-style: solid;
- }
-
- &:active {
- @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
- border-width: 1px;
- border-style: solid;
- }
-}
-
-
-@mixin btn-green {
- background-color: #28b061;
- border: 1px solid #26a65c;
- color: #fff;
-
- &:hover {
- background-color: #26ab5d;
- border: 1px solid #229954;
- color: #fff;
- }
-
- &:focus {
- background-color: #26ab5d;
- border: 1px solid #229954;
- color: #fff;
- }
-
- &:active {
- @include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12));
-
- background-color: #23a158 !important;
- border: 1px solid #229954 !important;
- color: #fff !important;
- }
-}
-
-/*Butons*/
-
-@mixin bnt-project {
- background-color: #f0f2f5;
- border-color: #dce0e5;
- color: #313236;
-
- &:hover {
- border-color:#dce0e5;
- background-color: #ebeef2;
- color: #313236;
- }
-
- &:focus {
- border-color: #dce0e5;
- background-color: #ebeef2;
- color: #313236;
- }
-
- &:active {
- @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
-
- color: #313236 !important;
- border-color: #c6cacf !important;
- background-color: #e4e7ed !important;
- }
-}
-
-@mixin btn-remove {
- background-color: #f72e60;
- border-color: #ee295a;
-
- &:hover {
- background-color: #e82757;
- border-color: #e32555;
- }
-
- &:focus {
- background-color: #e82757;
- border-color: #e32555;
- }
-
- &:active {
- @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
- background-color: #d42450 !important;
- border-color: #e12554 !important;
- }
-
-}
\ No newline at end of file
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
similarity index 99%
rename from app/assets/stylesheets/ci/builds.scss
rename to app/assets/stylesheets/pages/builds.scss
index a27dd0db581..74dc3e321c1 100644
--- a/app/assets/stylesheets/ci/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -73,4 +73,3 @@
margin-bottom: 2px;
}
}
-
diff --git a/app/assets/stylesheets/ci/projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
similarity index 100%
rename from app/assets/stylesheets/ci/projects.scss
rename to app/assets/stylesheets/pages/ci_projects.scss
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index de2ae93df37..4e121b95d13 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -1,5 +1,6 @@
.commits-compare-switch{
- @extend .btn;
+ @include btn-default;
+ @include btn-white;
background: image-url("switch_icon.png") no-repeat center center;
text-indent: -9999px;
float: left;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 5e7e59a6af8..d9ef06dc6b6 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1,3 +1,4 @@
+// Common
.diff-file {
margin-left: -$gl-padding;
margin-right: -$gl-padding;
@@ -12,24 +13,17 @@
color: #555;
z-index: 10;
- > span {
+ .diff-title {
font-family: $monospace_font;
word-break: break-all;
- margin-right: 200px;
display: block;
.file-mode {
- margin-left: 10px;
color: #777;
}
}
- .diff-btn-group {
- float: right;
- position: absolute;
- top: 5px;
- right: 15px;
-
+ .diff-controls {
.btn {
padding: 0px 10px;
font-size: 13px;
@@ -90,12 +84,12 @@
}
}
- tr.line_holder.parallel{
+ tr.line_holder.parallel {
.old_line, .new_line, .diff_line {
min-width: 50px;
}
- td.line_content.parallel{
+ td.line_content.parallel {
width: 50%;
}
}
@@ -105,7 +99,7 @@
padding: 0px;
border: none;
background: $background-color;
- color: rgba(0,0,0,0.3);
+ color: rgba(0, 0, 0, 0.3);
padding: 0px 5px;
border-right: 1px solid $border-color;
text-align: right;
@@ -117,7 +111,7 @@
float: left;
width: 35px;
font-weight: normal;
- color: rgba(0,0,0,0.3);
+ color: rgba(0, 0, 0, 0.3);
&:hover {
text-decoration: underline;
}
@@ -168,7 +162,7 @@
background: #ddd;
text-align: center;
padding: 30px;
- .wrap{
+ .wrap {
display: inline-block;
}
@@ -176,7 +170,7 @@
display: inline-block;
background-color: #fff;
line-height: 0;
- img{
+ img {
border: 1px solid #FFF;
background: image-url('trans_bg.gif');
max-width: 100%;
@@ -189,21 +183,21 @@
border: 1px solid $added;
}
}
- .image-info{
+ .image-info {
font-size: 12px;
margin: 5px 0 0 0;
color: grey;
}
- .view.swipe{
+ .view.swipe {
position: relative;
- .swipe-frame{
+ .swipe-frame {
display: block;
margin: auto;
position: relative;
}
- .swipe-wrap{
+ .swipe-wrap {
overflow: hidden;
border-left: 1px solid #999;
position: absolute;
@@ -211,33 +205,33 @@
top: 13px;
right: 7px;
}
- .frame{
+ .frame {
top: 0;
right: 0;
position: absolute;
- &.deleted{
+ &.deleted {
margin: 0;
display: block;
top: 13px;
right: 7px;
}
}
- .swipe-bar{
+ .swipe-bar {
display: block;
height: 100%;
width: 15px;
z-index: 100;
position: absolute;
cursor: pointer;
- &:hover{
- .top-handle{
+ &:hover {
+ .top-handle {
background-position: -15px 3px;
}
- .bottom-handle{
+ .bottom-handle {
background-position: -15px -11px;
}
- };
- .top-handle{
+ }
+ .top-handle {
display: block;
height: 14px;
width: 15px;
@@ -245,7 +239,7 @@
top: 0px;
background: image-url('swipemode_sprites.gif') 0 3px no-repeat;
}
- .bottom-handle{
+ .bottom-handle {
display: block;
height: 14px;
width: 15px;
@@ -254,9 +248,10 @@
background: image-url('swipemode_sprites.gif') 0 -11px no-repeat;
}
}
- } //.view.swipe
- .view.onion-skin{
- .onion-skin-frame{
+ }
+ //.view.swipe
+ .view.onion-skin {
+ .onion-skin-frame {
display: block;
margin: auto;
position: relative;
@@ -267,7 +262,7 @@
top: 0px;
left: 0px;
}
- .controls{
+ .controls {
display: block;
height: 14px;
width: 300px;
@@ -277,7 +272,7 @@
left: 50%;
margin-left: -150px;
- .drag-track{
+ .drag-track {
display: block;
position: absolute;
left: 12px;
@@ -317,39 +312,40 @@
background: image-url('onion_skin_sprites.gif') -2px -10px no-repeat;
}
}
- } //.view.onion-skin
+ }
+ //.view.onion-skin
}
- .view-modes{
+ .view-modes {
padding: 10px;
text-align: center;
background: #EEE;
- ul, li{
+ ul, li {
list-style: none;
margin: 0;
padding: 0;
display: inline-block;
}
- li{
+ li {
color: grey;
border-left: 1px solid #c1c1c1;
padding: 0 12px 0 16px;
cursor: pointer;
- &:first-child{
+ &:first-child {
border-left: none;
}
- &:hover{
+ &:hover {
text-decoration: underline;
}
- &.active{
- &:hover{
+ &.active {
+ &:hover {
text-decoration: none;
}
cursor: default;
color: #333;
}
- &.disabled{
+ &.disabled {
display: none;
}
}
@@ -373,3 +369,37 @@
float: right;
margin-top: -5px;
}
+
+// Mobile
+@media (max-width: 480px) {
+ .diff-title {
+ margin: 0;
+
+ .file-mode {
+ display: none;
+ }
+ }
+
+ .diff-controls {
+ position: static;
+ text-align: center;
+ }
+}
+
+// Bigger screens
+@media (min-width: 481px) {
+ .diff-title {
+ margin-right: 200px;
+
+ .file-mode {
+ margin-left: 10px;
+ }
+ }
+
+ .diff-controls {
+ float: right;
+ position: absolute;
+ top: 5px;
+ right: 15px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index b5c61f7f91d..9da085a3473 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -54,21 +54,22 @@
margin-top: -15px;
padding: 10px 0;
margin-bottom: 0;
- color: $gl-gray;
+ color: #5c5d5e;
font-size: 16px;
.author {
- color: $gl-gray;
+ color: #5c5d5e;
}
.issue-id {
- font-size: 19px;
- color: $gl-text-color;
+ color: #5c5d5e;
}
}
.issue-title {
margin: 0;
+ font-size: 23px;
+ color: #313236;
}
.description {
diff --git a/app/assets/stylesheets/ci/lint.scss b/app/assets/stylesheets/pages/lint.scss
similarity index 100%
rename from app/assets/stylesheets/ci/lint.scss
rename to app/assets/stylesheets/pages/lint.scss
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index d8c8e5ad0a4..a1a5208c59c 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -3,12 +3,11 @@
*
*/
.mr-state-widget {
- background: #f8fafc;
+ background: #F7F8FA;
margin-bottom: 20px;
color: $gl-gray;
- border: 1px solid #eef0f2;
- @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
- @include border-radius(3px);
+ border: 1px solid #dce0e6;
+ @include border-radius(2px);
form {
margin-bottom: 0;
@@ -77,10 +76,16 @@
padding: 15px;
}
+ .normal {
+ color: #5c5d5e;
+ }
+
.mr-widget-body {
h4 {
- font-weight: bold;
+ font-weight: 600;
+ font-size: 17px;
margin: 5px 0;
+ color: #313236;
}
p:last-child {
@@ -97,14 +102,26 @@
}
}
-.merge-request .merge-request-tabs{
+.merge-request .merge-request-tabs {
@include nav-menu;
margin: -$gl-padding;
padding: $gl-padding;
text-align: center;
- border-top: 1px solid #e7e9ed;
- margin-top: 18px;
- margin-bottom: 3px;
+ margin-bottom: 1px;
+}
+
+// Mobile
+@media (max-width: 480px) {
+ .merge-request .merge-request-tabs {
+ margin: 0;
+ padding: 0;
+
+ li {
+ a {
+ padding: 0;
+ }
+ }
+ }
}
.mr_source_commit,
@@ -120,10 +137,12 @@
}
.label-branch {
- color: #222;
+ color: #313236;
font-family: $monospace_font;
font-weight: bold;
overflow: hidden;
+ font-size: 14px;
+ margin: 0 3px;
}
.mr-list {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index fdc2c3332df..4392f08942b 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -65,19 +65,18 @@
.note-image-attach {
@extend .col-md-4;
- @extend .thumbnail;
margin-left: 45px;
float: none;
}
.common-note-form {
margin: 0;
- background: #f8fafc;
+ background: #F7F8FA;
padding: $gl-padding;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
- border-right: 1px solid #f1f2f4;
- border-top: 1px solid #f1f2f4;
+ border-right: 1px solid #ECEEF1;
+ border-top: 1px solid #ECEEF1;
margin-bottom: -$gl-padding;
}
@@ -168,7 +167,7 @@
.comment-hints {
color: #999;
background: #FFF;
- padding: 5px;
+ padding: 7px;
margin-top: -11px;
border: 1px solid $border-color;
font-size: 13px;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 2a77f065aed..abb03b07f51 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -18,7 +18,7 @@ ul.notes {
font-size: 14px;
padding-top: 10px;
padding-bottom: 10px;
- background: #f8fafc;
+ background: #FDFDFD;
.timeline-icon {
.avatar {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 818aa10aefe..f7a22849003 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -13,11 +13,15 @@
.edit_project {
fieldset.features {
.control-label {
- font-weight: bold;
+ font-weight: normal;
}
}
}
+.project-edit-content {
+ padding: 7px;
+}
+
.project-name-holder {
.help-inline {
vertical-align: top;
@@ -59,6 +63,7 @@
}
p {
+ padding: 0 $gl-padding;
color: #5c5d5e;
}
}
@@ -92,8 +97,7 @@
margin-bottom: 0px;
.btn {
- @include bnt-project;
- @include btn-info;
+ @include btn-gray;
.count {
display: inline-block;
@@ -149,7 +153,7 @@
.input-group-btn {
.btn {
- @include bnt-project;
+ @include btn-gray;
@include btn-middle;
&:hover {
@@ -183,8 +187,8 @@
margin: 0 12px 0 12px;
.btn{
- @include bnt-project;
- @include btn-info;
+ @include btn-gray;
+ @include btn-default;
}
.dropdown-toggle {
@@ -251,18 +255,19 @@
margin-bottom: 10px;
i {
- margin: 0 3px;
+ margin: 2px 0;
font-size: 20px;
}
.option-title {
- font-weight: bold;
+ font-weight: normal;
display: inline-block;
+ color: #313236;
}
.option-descr {
- margin-left: 36px;
- color: $gray;
+ margin-left: 29px;
+ color: #54565b;
}
}
}
@@ -376,8 +381,8 @@ table.table.protected-branches-list tr.no-border {
}
.nav > li > a {
- @include btn-info;
- @include bnt-project;
+ @include btn-default;
+ @include btn-gray;
background-color: transparent;
border: 1px solid #f7f8fa;
@@ -437,7 +442,7 @@ pre.light-well {
.btn-remove {
@include btn-middle;
- @include btn-remove;
+ @include btn-red;
float: left !important;
}
@@ -506,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
similarity index 100%
rename from app/assets/stylesheets/ci/runners.scss
rename to app/assets/stylesheets/pages/runners.scss
diff --git a/app/assets/stylesheets/ci/status.scss b/app/assets/stylesheets/pages/status.scss
similarity index 100%
rename from app/assets/stylesheets/ci/status.scss
rename to app/assets/stylesheets/pages/status.scss
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 271cc547e2b..dadd86e88cc 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,7 +1,7 @@
.tree-holder {
- .tree-content-holder {
- float: left;
- width: 100%;
+ .tree-table-holder {
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
}
.tree_progress {
@@ -13,10 +13,15 @@
}
.tree-table {
- @extend .table;
- @include border-radius(0);
+ margin-bottom: 0;
tr {
+ > td, > th {
+ padding: 10px $gl-padding;
+ line-height: 32px;
+ border-color: $table-border-color !important;
+ }
+
&:hover {
td {
background: $hover;
@@ -27,9 +32,9 @@
}
&.selected {
td {
- background: $background-color;
- border-top: 1px solid #EEE;
- border-bottom: 1px solid #EEE;
+ background: $gray-dark;
+ border-top: 1px solid $border-gray-dark;
+ border-bottom: 1px solid $border-gray-dark;
}
}
}
@@ -85,19 +90,6 @@
margin-right: 15px;
}
-.readme-holder {
- margin: 0 auto;
-
- .readme-file-title {
- font-size: 14px;
- font-weight: bold;
- margin-bottom: 20px;
- color: #777;
- border-bottom: 1px solid #DDD;
- padding: 10px 0;
- }
-}
-
.blob-commit-info {
list-style: none;
margin: 0;
diff --git a/app/assets/stylesheets/ci/xterm.scss b/app/assets/stylesheets/pages/xterm.scss
similarity index 99%
rename from app/assets/stylesheets/ci/xterm.scss
rename to app/assets/stylesheets/pages/xterm.scss
index 532dede0b23..9a50096c0d0 100644
--- a/app/assets/stylesheets/ci/xterm.scss
+++ b/app/assets/stylesheets/pages/xterm.scss
@@ -1,4 +1,4 @@
-.ci-body {
+.build-page {
// color codes are based on http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg
// see also: https://gist.github.com/jasonm23/2868981
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 527c9da0faa..2b2ea3dff16 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -30,7 +30,7 @@ class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound do |exception|
log_exception(exception)
- render "errors/not_found", layout: "errors", status: 404
+ render_404
end
protected
@@ -149,10 +149,6 @@ class ApplicationController < ActionController::Base
render "errors/access_denied", layout: "errors", status: 404
end
- def not_found!
- render "errors/not_found", layout: "errors", status: 404
- end
-
def git_not_found!
render "errors/git_not_found", layout: "errors", status: 404
end
diff --git a/app/controllers/ci/builds_controller.rb b/app/controllers/ci/builds_controller.rb
deleted file mode 100644
index b0b8b62fced..00000000000
--- a/app/controllers/ci/builds_controller.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-module Ci
- class BuildsController < Ci::ApplicationController
- before_action :authenticate_user!, except: [:status]
- before_action :project
- before_action :authorize_access_project!, except: [:status]
- before_action :authorize_manage_project!, except: [:status, :retry, :cancel]
- before_action :authorize_manage_builds!, only: [:retry, :cancel]
- before_action :build
-
- def retry
- if @build.commands.blank?
- return page_404
- end
-
- build = Ci::Build.retry(@build)
-
- if params[:return_to]
- redirect_to URI.parse(params[:return_to]).path
- else
- redirect_to build_path(build)
- end
- end
-
- def status
- render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
- end
-
- def cancel
- @build.cancel
-
- redirect_to build_path(@build)
- end
-
- protected
-
- def project
- @project = Ci::Project.find(params[:project_id])
- end
-
- def build
- @build ||= project.builds.unscoped.find_by!(id: params[:id])
- end
-
- def commit_by_sha
- @project.commits.find_by(sha: params[:id])
- end
-
- def build_path(build)
- namespace_project_build_path(build.gl_project.namespace, build.gl_project, build)
- end
- end
-end
diff --git a/app/controllers/ci/commits_controller.rb b/app/controllers/ci/commits_controller.rb
deleted file mode 100644
index 7e6705c9702..00000000000
--- a/app/controllers/ci/commits_controller.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module Ci
- class CommitsController < Ci::ApplicationController
- before_action :authenticate_user!, except: [:status, :show]
- before_action :authenticate_public_page!, only: :show
- before_action :project
- before_action :authorize_access_project!, except: [:status, :show, :cancel]
- before_action :authorize_manage_builds!, only: [:cancel]
-
- def status
- commit = Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id])
- render json: commit.to_json(only: [:id, :sha], methods: [:status, :coverage])
- rescue ActiveRecord::RecordNotFound
- render json: { status: "not_found" }
- end
-
- def cancel
- commit.builds.running_or_pending.each(&:cancel)
-
- redirect_to namespace_project_commit_path(commit.gl_project.namespace, commit.gl_project, commit.sha)
- end
-
- private
-
- def project
- @project ||= Ci::Project.find(params[:project_id])
- end
-
- def commit
- @commit ||= Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id])
- end
- end
-end
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index adb913a783f..7777aa18031 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -1,27 +1,11 @@
module Ci
class ProjectsController < Ci::ApplicationController
- before_action :authenticate_user!, except: [:build, :badge, :show]
- before_action :authenticate_public_page!, only: :show
- before_action :project, only: [:build, :show, :badge, :toggle_shared_runners, :dumped_yaml]
- before_action :authorize_access_project!, except: [:build, :badge, :show, :new]
+ before_action :project
+ before_action :authenticate_user!, except: [:build, :badge]
+ before_action :authorize_access_project!, except: [:badge]
before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
- before_action :authenticate_token!, only: [:build]
before_action :no_cache, only: [:badge]
- protect_from_forgery except: :build
-
- layout 'ci/project', except: [:index]
-
- def show
- @ref = params[:ref]
-
- @commits = @project.commits.reverse_order
- if @ref
- # unscope is required, because of default_scope defined in Ci::Build
- builds = @project.builds.unscope(:select, :order).where(ref: @ref).select(:commit_id).distinct
- @commits = @commits.where(id: builds)
- end
- @commits = @commits.page(params[:page]).per(20)
- end
+ protect_from_forgery
# Project status badge
# Image with build status for sha or ref
diff --git a/app/controllers/ci/services_controller.rb b/app/controllers/ci/services_controller.rb
deleted file mode 100644
index 52c96a34ce8..00000000000
--- a/app/controllers/ci/services_controller.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-module Ci
- class ServicesController < Ci::ApplicationController
- before_action :authenticate_user!
- before_action :project
- before_action :authorize_access_project!
- before_action :authorize_manage_project!
- before_action :service, only: [:edit, :update, :test]
-
- respond_to :html
-
- layout 'ci/project'
-
- def index
- @project.build_missing_services
- @services = @project.services.reload
- end
-
- def edit
- end
-
- def update
- if @service.update_attributes(service_params)
- redirect_to edit_ci_project_service_path(@project, @service.to_param)
- else
- render 'edit'
- end
- end
-
- def test
- last_build = @project.builds.last
-
- if @service.execute(last_build)
- message = { notice: 'We successfully tested the service' }
- else
- message = { alert: 'We tried to test the service but error occurred' }
- end
-
- redirect_to :back, message
- end
-
- private
-
- def project
- @project = Ci::Project.find(params[:project_id])
- end
-
- def service
- @service ||= @project.services.find { |service| service.to_param == params[:id] }
- end
-
- def service_params
- params.require(:service).permit(
- :type, :active, :webhook, :notify_only_broken_builds,
- :email_recipients, :email_only_broken_builds, :email_add_pusher,
- :hipchat_token, :hipchat_room, :hipchat_server
- )
- end
- end
-end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 524218290c6..40fb15a5b36 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -88,7 +88,7 @@ class GroupsController < Groups::ApplicationController
def destroy
DestroyGroupService.new(@group, current_user).execute
- redirect_to root_path, alert: "Group '#{@group.name} was deleted."
+ redirect_to root_path, alert: "Group '#{@group.name}' was successfully deleted."
end
protected
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index f84f85a7df8..25e58724860 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -62,7 +62,7 @@ class Import::BitbucketController < Import::BaseController
end
def verify_bitbucket_import_enabled
- not_found! unless bitbucket_import_enabled?
+ render_404 unless bitbucket_import_enabled?
end
def bitbucket_auth
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index 849646cd665..18300390851 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -99,6 +99,6 @@ class Import::FogbugzController < Import::BaseController
end
def verify_fogbugz_import_enabled
- not_found! unless fogbugz_import_enabled?
+ render_404 unless fogbugz_import_enabled?
end
end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index f21fbd9ecca..aae77d384c6 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -47,7 +47,7 @@ class Import::GithubController < Import::BaseController
end
def verify_github_import_enabled
- not_found! unless github_import_enabled?
+ render_404 unless github_import_enabled?
end
def github_auth
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 27af19f5f61..23a396e8084 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -44,7 +44,7 @@ class Import::GitlabController < Import::BaseController
end
def verify_gitlab_import_enabled
- not_found! unless gitlab_import_enabled?
+ render_404 unless gitlab_import_enabled?
end
def gitlab_auth
diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb
index f24cdb3709a..eecbe380c9e 100644
--- a/app/controllers/import/gitorious_controller.rb
+++ b/app/controllers/import/gitorious_controller.rb
@@ -42,7 +42,7 @@ class Import::GitoriousController < Import::BaseController
end
def verify_gitorious_import_enabled
- not_found! unless gitorious_import_enabled?
+ render_404 unless gitorious_import_enabled?
end
end
diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb
index 82fadeb7e83..41472a6fe6c 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -106,7 +106,7 @@ class Import::GoogleCodeController < Import::BaseController
end
def verify_google_code_import_enabled
- not_found! unless google_code_import_enabled?
+ render_404 unless google_code_import_enabled?
end
def user_map
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 9c3763d5934..548f1b9ebfe 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -12,7 +12,7 @@ class Projects::AvatarsController < Projects::ApplicationController
filename: @blob.name
)
else
- not_found!
+ render_404
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 8776721d243..8cc2f21d887 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -8,7 +8,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code!
- before_action :authorize_push_code!, only: [:destroy]
+ before_action :authorize_push_code!, only: [:destroy, :create]
before_action :assign_blob_vars
before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create]
@@ -25,7 +25,7 @@ class Projects::BlobController < Projects::ApplicationController
result = Files::CreateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
- flash[:notice] = "Your changes have been successfully committed"
+ flash[:notice] = "The changes have been successfully committed"
respond_to do |format|
format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) }
format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } }
@@ -34,7 +34,7 @@ class Projects::BlobController < Projects::ApplicationController
flash[:alert] = result[:message]
respond_to do |format|
format.html { render :new }
- format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } }
+ format.json { render json: { message: "failed", filePath: namespace_project_blob_path(@project.namespace, @project, @id) } }
end
end
end
@@ -113,14 +113,14 @@ class Projects::BlobController < Projects::ApplicationController
end
end
- return not_found!
+ return render_404
end
end
def commit
@commit = @repository.commit(@ref)
- return not_found! unless @commit
+ return render_404 unless @commit
end
def assign_blob_vars
@@ -128,7 +128,7 @@ class Projects::BlobController < Projects::ApplicationController
@ref, @path = extract_ref(@id)
rescue InvalidPathError
- not_found!
+ render_404
end
def after_edit_path
@@ -154,7 +154,7 @@ class Projects::BlobController < Projects::ApplicationController
def editor_variables
@current_branch = @ref
- @target_branch = (sanitized_new_branch_name || @ref)
+ @target_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
@file_path =
if action_name.to_s == 'create'
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 76c7f31f61b..4e4ac6689d3 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -2,6 +2,8 @@ class Projects::BuildsController < Projects::ApplicationController
before_action :ci_project
before_action :build
+ before_action :authorize_admin_project!, except: [:show, :status]
+
layout "project"
def show
@@ -17,9 +19,37 @@ class Projects::BuildsController < Projects::ApplicationController
end
end
+ def retry
+ if @build.commands.blank?
+ return page_404
+ end
+
+ build = Ci::Build.retry(@build)
+
+ if params[:return_to]
+ redirect_to URI.parse(params[:return_to]).path
+ else
+ redirect_to build_path(build)
+ end
+ end
+
+ def status
+ render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
+ end
+
+ def cancel
+ @build.cancel
+
+ redirect_to build_path(@build)
+ end
+
private
def build
@build ||= ci_project.builds.unscoped.find_by!(id: params[:id])
end
+
+ def build_path(build)
+ namespace_project_build_path(build.gl_project.namespace, build.gl_project, build)
+ end
end
diff --git a/app/controllers/projects/ci_services_controller.rb b/app/controllers/projects/ci_services_controller.rb
new file mode 100644
index 00000000000..6d2756eba3d
--- /dev/null
+++ b/app/controllers/projects/ci_services_controller.rb
@@ -0,0 +1,49 @@
+class Projects::CiServicesController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :authorize_admin_project!
+
+ layout "project_settings"
+
+ def index
+ @ci_project.build_missing_services
+ @services = @ci_project.services.reload
+ end
+
+ def edit
+ service
+ end
+
+ def update
+ if @service.update_attributes(service_params)
+ redirect_to edit_namespace_project_ci_service_path(@project, @project.namespace, @service.to_param)
+ else
+ render 'edit'
+ end
+ end
+
+ def test
+ last_build = @project.builds.last
+
+ if @service.execute(last_build)
+ message = { notice: 'We successfully tested the service' }
+ else
+ message = { alert: 'We tried to test the service but error occurred' }
+ end
+
+ redirect_to :back, message
+ end
+
+ private
+
+ def service
+ @service ||= @ci_project.services.find { |service| service.to_param == params[:id] }
+ end
+
+ def service_params
+ params.require(:service).permit(
+ :type, :active, :webhook, :notify_only_broken_builds,
+ :email_recipients, :email_only_broken_builds, :email_add_pusher,
+ :hipchat_token, :hipchat_room, :hipchat_server
+ )
+ end
+end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 1938c63c10c..7886f3c6deb 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -38,6 +38,14 @@ class Projects::CommitController < Projects::ApplicationController
@ci_project = @project.gitlab_ci_project
end
+ def cancel_builds
+ @ci_commit = @project.ci_commit(@commit.sha)
+ @ci_commit.builds.running_or_pending.each(&:cancel)
+
+ redirect_to ci_namespace_project_commit_path(project.namespace, project, commit.sha)
+ end
+
+
def branches
@branches = @project.repository.branch_names_contains(commit.id)
@tags = @project.repository.tag_names_contains(commit.id)
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 5f6fbce795e..d5ee6ac8663 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -20,7 +20,7 @@ class Projects::RawController < Projects::ApplicationController
disposition: 'inline'
)
else
- not_found!
+ render_404
end
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 92e4bc16d9d..bdcb1a3e297 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -1,13 +1,16 @@
# Controller for viewing a repository's file structure
class Projects::TreeController < Projects::ApplicationController
include ExtractsPath
+ include ActionView::Helpers::SanitizeHelper
before_action :require_non_empty_project, except: [:new, :create]
before_action :assign_ref_vars
+ before_action :assign_dir_vars, only: [:create_dir]
before_action :authorize_download_code!
+ before_action :authorize_push_code!, only: [:create_dir]
def show
- return not_found! unless @repository.commit(@ref)
+ return render_404 unless @repository.commit(@ref)
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
@@ -16,7 +19,7 @@ class Projects::TreeController < Projects::ApplicationController
File.join(@ref, @path))
) and return
elsif @path.present?
- return not_found!
+ return render_404
end
end
@@ -26,4 +29,38 @@ class Projects::TreeController < Projects::ApplicationController
format.js { no_cache_headers }
end
end
+
+ def create_dir
+ return render_404 unless @commit_params.values.all?
+
+ begin
+ result = Files::CreateDirService.new(@project, current_user, @commit_params).execute
+ message = result[:message]
+ rescue => e
+ message = e.to_s
+ end
+
+ if result && result[:status] == :success
+ flash[:notice] = "The directory has been successfully created"
+ respond_to do |format|
+ format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name)) }
+ end
+ else
+ flash[:alert] = message
+ respond_to do |format|
+ format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, @new_branch) }
+ end
+ end
+ end
+
+ def assign_dir_vars
+ @new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref
+ @dir_name = File.join(@path, params[:dir_name])
+ @commit_params = {
+ file_path: @dir_name,
+ current_branch: @ref,
+ target_branch: @new_branch,
+ commit_message: params[:commit_message],
+ }
+ end
end
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index 71ecc20dd95..e1fe7ea2114 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -20,7 +20,7 @@ class Projects::UploadsController < Projects::ApplicationController
end
def show
- return not_found! if uploader.nil? || !uploader.file.exists?
+ return render_404 if uploader.nil? || !uploader.file.exists?
disposition = uploader.image? ? 'inline' : 'attachment'
send_file uploader.file.path, disposition: disposition
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 28536e359e5..868b05929d7 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -10,7 +10,7 @@ class UploadsController < ApplicationController
end
unless uploader.file && uploader.file.exists?
- return not_found!
+ return render_404
end
disposition = uploader.image? ? 'inline' : 'attachment'
@@ -21,7 +21,7 @@ class UploadsController < ApplicationController
def find_model
unless upload_model && upload_mount
- return not_found!
+ return render_404
end
@model = upload_model.find(params[:id])
@@ -44,7 +44,7 @@ class UploadsController < ApplicationController
return if authorized
if current_user
- not_found!
+ render_404
else
authenticate_user!
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 6aa16673d63..97c7e74c294 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -72,11 +72,15 @@ class IssuableFinder
params[:milestone_title].present?
end
+ def no_milestones?
+ milestones? && params[:milestone_title] == Milestone::None.title
+ end
+
def milestones
return @milestones if defined?(@milestones)
@milestones =
- if milestones? && params[:milestone_title] != Milestone::None.title
+ if milestones?
Milestone.where(title: params[:milestone_title])
else
nil
@@ -183,7 +187,11 @@ class IssuableFinder
def by_milestone(items)
if milestones?
- items = items.where(milestone_id: milestones.try(:pluck, :id))
+ if no_milestones?
+ items = items.where(milestone_id: [-1, nil])
+ else
+ items = items.where(milestone_id: milestones.try(:pluck, :id))
+ end
end
items
@@ -207,13 +215,19 @@ class IssuableFinder
def by_label(items)
if params[:label_name].present?
- label_names = params[:label_name].split(",")
+ if params[:label_name] == Label::None.title
+ item_ids = LabelLink.where(target_type: klass.name).pluck(:target_id)
- item_ids = LabelLink.joins(:label).
- where('labels.title in (?)', label_names).
- where(target_type: klass.name).pluck(:target_id)
+ items = items.where('id NOT IN (?)', item_ids)
+ else
+ label_names = params[:label_name].split(",")
- items = items.where(id: item_ids)
+ item_ids = LabelLink.joins(:label).
+ where('labels.title in (?)', label_names).
+ where(target_type: klass.name).pluck(:target_id)
+
+ items = items.where(id: item_ids)
+ end
end
items
diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb
index 9ea342cb26d..81a12403801 100644
--- a/app/finders/trending_projects_finder.rb
+++ b/app/finders/trending_projects_finder.rb
@@ -1,13 +1,6 @@
class TrendingProjectsFinder
- def execute(current_user, start_date = nil)
- start_date ||= Date.today - 1.month
-
- projects = projects_for(current_user)
-
- # Determine trending projects based on comments count
- # for period of time - ex. month
- projects.joins(:notes).where('notes.created_at > ?', start_date).
- group("projects.id").reorder("count(notes.id) DESC")
+ def execute(current_user, start_date = 1.month.ago)
+ projects_for(current_user).trending(start_date)
end
private
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 8036303851b..66b18eea699 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -93,7 +93,10 @@ module LabelsHelper
end
def project_labels_options(project)
- options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name])
+ labels = project.labels.to_a
+ labels.unshift(Label::None)
+ labels.unshift(Label::Any)
+ options_from_collection_for_select(labels, 'name', 'title', params[:label_name])
end
# Required for Gitlab::Markdown::LabelReferenceFilter
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 132a893e532..37a5b58cce8 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -30,7 +30,8 @@ module MilestonesHelper
grouped_milestones = Milestones::GroupService.new(milestones).execute
grouped_milestones.unshift(Milestone::None)
+ grouped_milestones.unshift(Milestone::Any)
- options_from_collection_for_select(grouped_milestones, 'title', 'title', params[:milestone_title])
+ options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title])
end
end
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..cfafbf6786e 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)
+ end
+ end
+
+ def retry_url
+ if commands.present?
+ Gitlab::Application.routes.url_helpers.
+ retry_namespace_project_build_path(gl_project.namespace, gl_project, self)
+ 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 95ac7156bed..23b5e38336c 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/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
new file mode 100644
index 00000000000..fe0cea8465f
--- /dev/null
+++ b/app/models/concerns/case_sensitivity.rb
@@ -0,0 +1,28 @@
+# Concern for querying columns with specific case sensitivity handling.
+module CaseSensitivity
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Queries the given columns regardless of the casing used.
+ #
+ # Unlike other ActiveRecord methods this method only operates on a Hash.
+ def iwhere(params)
+ criteria = self
+ cast_lower = Gitlab::Database.postgresql?
+
+ params.each do |key, value|
+ column = ActiveRecord::Base.connection.quote_table_name(key)
+
+ if cast_lower
+ condition = "LOWER(#{column}) = LOWER(:value)"
+ else
+ condition = "#{column} = :value"
+ end
+
+ criteria = criteria.where(condition, value: value)
+ end
+
+ criteria
+ end
+ 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/group_milestone.rb b/app/models/group_milestone.rb
index ab055f6b80b..1dd2be68ebf 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -1,5 +1,7 @@
class GroupMilestone
+ alias_attribute :name, :title
+
def initialize(title, milestones)
@title = title
@milestones = milestones
diff --git a/app/models/label.rb b/app/models/label.rb
index 4a22bd53400..1bb4b5f55cf 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -12,6 +12,11 @@
class Label < ActiveRecord::Base
include Referable
+ # Represents a "No Label" state used for filtering Issues and Merge
+ # Requests that have no label assigned.
+ LabelStruct = Struct.new(:title, :name)
+ None = LabelStruct.new('No Label', 'No Label')
+ Any = LabelStruct.new('Any', '')
DEFAULT_COLOR = '#428BCA'
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index eb468c6cd53..c83b15c7d39 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -227,7 +227,7 @@ class MergeRequest < ActiveRecord::Base
end
def work_in_progress?
- title =~ /\A\[?WIP\]?:? /i
+ !!(title =~ /\A\[?WIP\]?:? /i)
end
def mergeable?
@@ -275,7 +275,8 @@ class MergeRequest < ActiveRecord::Base
attrs = {
source: source_project.hook_attrs,
target: target_project.hook_attrs,
- last_commit: nil
+ last_commit: nil,
+ work_in_progress: work_in_progress?
}
unless last_commit.nil?
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index d979a35084b..84acba30b6b 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -16,7 +16,9 @@
class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
- None = Struct.new(:title).new('No Milestone')
+ MilestoneStruct = Struct.new(:title, :name)
+ None = MilestoneStruct.new('No Milestone', 'No Milestone')
+ Any = MilestoneStruct.new('Any', '')
include InternalId
include Sortable
@@ -47,6 +49,8 @@ class Milestone < ActiveRecord::Base
state :active
end
+ alias_attribute :name, :title
+
class << self
def search(query)
query = "%#{query}%"
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index bc8525df5a5..5782e649f8b 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -118,6 +118,8 @@ class Namespace < ActiveRecord::Base
gitlab_shell.add_namespace(path_was)
if gitlab_shell.mv_namespace(path_was, path)
+ Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
+
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
diff --git a/app/models/project.rb b/app/models/project.rb
index bb47b9abb03..cd30467fae3 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -40,6 +40,7 @@ class Project < ActiveRecord::Base
include Referable
include Sortable
include AfterCommitQueue
+ include CaseSensitivity
extend Gitlab::ConfigHelper
extend Enumerize
@@ -235,13 +236,18 @@ class Project < ActiveRecord::Base
end
def find_with_namespace(id)
- return nil unless id.include?('/')
+ namespace_path, project_path = id.split('/')
- id = id.split('/')
- namespace = Namespace.by_path(id.first)
- return nil unless namespace
+ return nil if !namespace_path || !project_path
- where(namespace_id: namespace.id).where("LOWER(projects.path) = :path", path: id.second.downcase).first
+ # Use of unscoped ensures we're not secretly adding any ORDER BYs, which
+ # have a negative impact on performance (and aren't needed for this
+ # query).
+ unscoped.
+ joins(:namespace).
+ iwhere('namespaces.path' => namespace_path).
+ iwhere('projects.path' => project_path).
+ take
end
def visibility_levels
@@ -260,6 +266,20 @@ class Project < ActiveRecord::Base
name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
%r{(?
#{name_pattern}/#{name_pattern})}
end
+
+ def trending(since = 1.month.ago)
+ # By counting in the JOIN we don't expose the GROUP BY to the outer query.
+ # This means that calls such as "any?" and "count" just return a number of
+ # the total count, instead of the counts grouped per project as a Hash.
+ join_body = "INNER JOIN (
+ SELECT project_id, COUNT(*) AS amount
+ FROM notes
+ WHERE created_at >= #{sanitize(since)}
+ GROUP BY project_id
+ ) join_note_counts ON projects.id = join_note_counts.project_id"
+
+ joins(join_body).reorder('join_note_counts.amount DESC')
+ end
end
def team
@@ -636,6 +656,8 @@ class Project < ActiveRecord::Base
# db changes in order to prevent out of sync between db and fs
raise Exception.new('repository cannot be renamed')
end
+
+ Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
end
def hook_attrs
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index d8aedbd2ab4..5f5255ab487 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -40,12 +40,19 @@ class BambooService < CiService
attr_accessor :response
after_save :compose_service_hook, if: :activated?
+ before_update :reset_password
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
+ def reset_password
+ if prop_updated?(:bamboo_url)
+ self.password = nil
+ end
+ end
+
def title
'Atlassian Bamboo CI'
end
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/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 3c002a1634b..fb11cad352e 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -37,12 +37,19 @@ class TeamcityService < CiService
attr_accessor :response
after_save :compose_service_hook, if: :activated?
+ before_update :reset_password
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
+ def reset_password
+ if prop_updated?(:teamcity_url)
+ self.password = nil
+ end
+ end
+
def title
'JetBrains TeamCity CI'
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 2c5ab62d22c..8b51602bc23 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -373,11 +373,25 @@ class Repository
@root_ref ||= raw_repository.root_ref
end
- def commit_file(user, path, content, message, branch)
+ def commit_dir(user, path, message, branch)
commit_with_hooks(user, branch) do |ref|
- path[0] = '' if path[0] == '/'
+ committer = user_to_committer(user)
+ options = {}
+ options[:committer] = committer
+ options[:author] = committer
- committer = user_to_comitter(user)
+ options[:commit] = {
+ message: message,
+ branch: ref,
+ }
+
+ raw_repository.mkdir(path, options)
+ end
+ end
+
+ def commit_file(user, path, content, message, branch, update)
+ commit_with_hooks(user, branch) do |ref|
+ committer = user_to_committer(user)
options = {}
options[:committer] = committer
options[:author] = committer
@@ -388,7 +402,8 @@ class Repository
options[:file] = {
content: content,
- path: path
+ path: path,
+ update: update
}
Gitlab::Git::Blob.commit(raw_repository, options)
@@ -397,9 +412,7 @@ class Repository
def remove_file(user, path, message, branch)
commit_with_hooks(user, branch) do |ref|
- path[0] = '' if path[0] == '/'
-
- committer = user_to_comitter(user)
+ committer = user_to_committer(user)
options = {}
options[:committer] = committer
options[:author] = committer
@@ -416,7 +429,7 @@ class Repository
end
end
- def user_to_comitter(user)
+ def user_to_committer(user)
{
email: user.email,
name: user.name,
diff --git a/app/models/service.rb b/app/models/service.rb
index 60fcc9d2857..7e845d565b1 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -117,6 +117,15 @@ class Service < ActiveRecord::Base
end
end
+ # ActiveRecord does not provide a mechanism to track changes in serialized keys.
+ # This is why we need to perform extra query to do it mannually.
+ def prop_updated?(prop_name)
+ relation_name = self.type.underscore
+ previous_value = project.send(relation_name).send(prop_name)
+ return false if previous_value.nil?
+ previous_value != send(prop_name)
+ end
+
def async_execute(data)
return unless supported_events.include?(data[:object_kind])
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/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb
new file mode 100644
index 00000000000..71272fb5707
--- /dev/null
+++ b/app/services/files/create_dir_service.rb
@@ -0,0 +1,9 @@
+require_relative "base_service"
+
+module Files
+ class CreateDirService < Files::BaseService
+ def commit
+ repository.commit_dir(current_user, @file_path, @commit_message, @target_branch)
+ end
+ end
+end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index ffbb5993279..c8e3a910bba 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -3,7 +3,7 @@ require_relative "base_service"
module Files
class CreateService < Files::BaseService
def commit
- repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false)
end
def validate
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index a20903c6f02..1960dc7d949 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -3,7 +3,7 @@ require_relative "base_service"
module Files
class UpdateService < Files::BaseService
def commit
- repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, true)
end
end
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index fcc0f2a6a8d..7963af127e1 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -29,7 +29,7 @@ module MergeRequests
private
def commit
- committer = repository.user_to_comitter(current_user)
+ committer = repository.user_to_committer(current_user)
options = {
message: commit_message,
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index c327c244f0d..64ea6dd42eb 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -27,6 +27,7 @@ module Projects
def transfer(project, new_namespace)
Project.transaction do
old_path = project.path_with_namespace
+ old_namespace = project.namespace
new_path = File.join(new_namespace.try(:path) || '', project.path)
if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
@@ -51,6 +52,9 @@ module Projects
# clear project cached events
project.reset_events_cache
+ # Move uploads
+ Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
+
true
end
end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index 60235b6be2a..9a5fe4af9dd 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -54,6 +54,7 @@ class SystemHooksService
data.merge!({
project_name: model.project.name,
project_path: model.project.path,
+ project_path_with_namespace: model.project.path_with_namespace,
project_id: model.project.id,
user_name: model.user.name,
user_email: model.user.email,
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index f9673abbfe8..e8211585834 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -26,7 +26,7 @@ class FileUploader < CarrierWave::Uploader::Base
end
def secure_url
- File.join(Gitlab.config.gitlab.url, @project.path_with_namespace, "uploads", @secret, file.filename)
+ File.join("/uploads", @secret, file.filename)
end
def file_storage?
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index e3698ac1c46..bc08458312c 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -32,7 +32,7 @@
%hr
= form_tag admin_users_path, method: :get, class: 'form-inline' do
.form-group
- = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control'
+ = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control', spellcheck: false
= hidden_field_tag "filter", params[:filter]
= button_tag class: 'btn btn-primary' do
%i.fa.fa-search
@@ -54,19 +54,19 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to admin_users_path(sort: sort_value_name) do
+ = link_to admin_users_path(sort: sort_value_name, filter: params[:filter]) do
= sort_title_name
- = link_to admin_users_path(sort: sort_value_recently_signin) do
+ = link_to admin_users_path(sort: sort_value_recently_signin, filter: params[:filter]) do
= sort_title_recently_signin
- = link_to admin_users_path(sort: sort_value_oldest_signin) do
+ = link_to admin_users_path(sort: sort_value_oldest_signin, filter: params[:filter]) do
= sort_title_oldest_signin
- = link_to admin_users_path(sort: sort_value_recently_created) do
+ = link_to admin_users_path(sort: sort_value_recently_created, filter: params[:filter]) do
= sort_title_recently_created
- = link_to admin_users_path(sort: sort_value_oldest_created) do
+ = link_to admin_users_path(sort: sort_value_oldest_created, filter: params[:filter]) do
= sort_title_oldest_created
- = link_to admin_users_path(sort: sort_value_recently_updated) do
+ = link_to admin_users_path(sort: sort_value_recently_updated, filter: params[:filter]) do
= sort_title_recently_updated
- = link_to admin_users_path(sort: sort_value_oldest_updated) do
+ = link_to admin_users_path(sort: sort_value_oldest_updated, filter: params[:filter]) do
= sort_title_oldest_updated
= link_to 'New User', new_admin_user_path, class: "btn btn-new btn-sm"
diff --git a/app/views/ci/admin/runners/index.html.haml b/app/views/ci/admin/runners/index.html.haml
index b9d6703ff41..01ce81b4476 100644
--- a/app/views/ci/admin/runners/index.html.haml
+++ b/app/views/ci/admin/runners/index.html.haml
@@ -27,7 +27,7 @@
.pull-left
= form_tag ci_admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
.form-group
- = search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token'
+ = search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token', spellcheck: false
= submit_tag 'Search', class: 'btn'
.pull-right.light
diff --git a/app/views/ci/admin/runners/show.html.haml b/app/views/ci/admin/runners/show.html.haml
index 5bb442cbf92..92787b2e6ac 100644
--- a/app/views/ci/admin/runners/show.html.haml
+++ b/app/views/ci/admin/runners/show.html.haml
@@ -76,7 +76,7 @@
%td
= form_tag ci_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
.form-group
- = search_field_tag :search, params[:search], class: 'form-control'
+ = search_field_tag :search, params[:search], class: 'form-control', spellcheck: false
= submit_tag 'Search', class: 'btn'
%td
diff --git a/app/views/ci/projects/_info.html.haml b/app/views/ci/projects/_info.html.haml
deleted file mode 100644
index 1888e1bde93..00000000000
--- a/app/views/ci/projects/_info.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-- if no_runners_for_project?(@project)
- = render 'no_runners'
diff --git a/app/views/ci/projects/show.html.haml b/app/views/ci/projects/show.html.haml
deleted file mode 100644
index 888b1ea41d5..00000000000
--- a/app/views/ci/projects/show.html.haml
+++ /dev/null
@@ -1,60 +0,0 @@
-= render 'ci/shared/guide' unless @project.setup_finished?
-
-- if current_user && can?(current_user, :manage_project, gl_project) && !@project.any_runners?
- .alert.alert-danger
- Builds for this project wont be served unless you configure runners on
- = link_to "Runners page", runners_path(@project.gl_project)
-
-%ul.nav.nav-tabs.append-bottom-20
- %li{class: ref_tab_class}
- = link_to 'All commits', ci_project_path(@project)
- - @project.tracked_refs.each do |ref|
- %li{class: ref_tab_class(ref)}
- = link_to ref, ci_project_path(@project, ref: ref)
-
- - if @ref && !@project.tracked_refs.include?(@ref)
- %li{class: 'active'}
- = link_to @ref, ci_project_path(@project, ref: @ref)
-
- %li.pull-right
- = link_to 'Go to project', project_path(gl_project), class: 'btn btn-sm'
-
-- if @ref
- %p
- Paste build status image for #{@ref} with next link
- = link_to '#', class: 'badge-codes-toggle btn btn-default btn-xs' do
- Status Badge
- .badge-codes-block.bs-callout.bs-callout-info.hide
- %p
- Status badge for
- %span.label.label-info #{@ref}
- branch
- %div
- %label Markdown:
- = text_field_tag 'badge_md', markdown_badge_code(@project, @ref), readonly: true, class: 'form-control'
- %label Html:
- = text_field_tag 'badge_html', html_badge_code(@project, @ref), readonly: true, class: 'form-control'
-
-
-
-
-%table.table.builds
- %thead
- %tr
- %th Status
- %th Commit
- %th Message
- %th Branch
- %th Total duration
- %th Finished at
- - if @project.coverage_enabled?
- %th Coverage
-
- = render @commits
-
-= paginate @commits
-
-- if @commits.empty?
- .bs-callout
- %h4 No commits yet
-
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/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index e09e032a7f1..81a5909e2d2 100644
--- a/app/views/dashboard/projects/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -1,10 +1,11 @@
.projects-list-holder
.projects-search-form
.input-group
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if current_user.can_create_project?
%span.input-group-btn
= link_to new_project_path, class: 'btn btn-green' do
- New project
+ %i.fa.fa-plus
+ New Project
= render 'shared/projects/list', projects: @projects, ci: true
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 83d4d321c83..fcb07b04083 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -11,7 +11,7 @@
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort
.form-group
- = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search"
+ = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search", spellcheck: false
.form-group
= button_tag 'Search', class: "btn btn-default"
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 5a3d689d1e5..2761272aa8a 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -1,7 +1,7 @@
.pull-left
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
- = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search"
+ = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
.form-group
= button_tag 'Search', class: "btn btn-success"
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 2b27a88794d..11d69977ef9 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -1,10 +1,11 @@
.panel.panel-default.projects-list-holder
.panel-heading.clearfix
.input-group
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if can? current_user, :create_projects, @group
%span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
- New project
+ %i.fa.fa-plus
+ 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
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 3a6d07ebddf..fee4b0052b5 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -12,7 +12,7 @@
.clearfix.js-toggle-container
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
= button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:admin_group_member, @group)
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/help/ui.html.haml b/app/views/help/ui.html.haml
index 7c89457ace3..2169a821fb2 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -14,6 +14,8 @@
= link_to 'Lists', '#lists'
%li
= link_to 'Tables', '#tables'
+ %li
+ = link_to 'Nav', '#nav'
%li
= link_to 'Buttons', '#buttons'
%li
@@ -30,16 +32,31 @@
%h2#blocks Blocks
%h3
- %code .well
+ %code .gray-content-block
- .well
- %h4 Something
+
+ .gray-content-block.middle-block
+ %h4 Normal block inside content
+ = lorem
+
+ .gray-content-block.second-block
+ %h4 Second block
= lorem
%h2#lists Lists
+ %h3
+ %code .content-list
+ %ul.content-list
+ %li
+ One item
+ %li
+ One item
+ %li
+ One item
+
%h3
%code .well-list
%ul.well-list
@@ -102,11 +119,40 @@
%td the Bird
%td @twitter
+ %h2#navs Navigation
+
+ %h3
+ %code .center-top-menu
+ .example
+ %ul.center-top-menu
+ %li.active
+ %a Open
+ %li
+ %a Closed
+
+ %h3
+ %code .btn-group.btn-group-next
+ .example
+ %div.btn-group.btn-group-next
+ %a.btn.active Open
+ %a.btn Closed
+
+
+ %h3
+ %code .nav.nav-tabs
+ .example
+ %ul.nav.nav-tabs
+ %li.active
+ %a Open
+ %li
+ %a Closed
+
%h2#buttons Buttons
.example
%button.btn.btn-default{:type => "button"} Default
+ %button.btn.btn-gray{:type => "button"} Gray
%button.btn.btn-primary{:type => "button"} Primary
%button.btn.btn-success{:type => "button"} Success
%button.btn.btn-info{:type => "button"} Info
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/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index e2d2dec7ab8..ceb64ce3157 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,6 +1,6 @@
.search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
- = search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control"
+ = search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control", spellcheck: false
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
diff --git a/app/views/layouts/ci/_nav_project.html.haml b/app/views/layouts/ci/_nav_project.html.haml
index 545abc23d99..f094edbfa87 100644
--- a/app/views/layouts/ci/_nav_project.html.haml
+++ b/app/views/layouts/ci/_nav_project.html.haml
@@ -5,17 +5,6 @@
%span
Back to project
%li.separate-item
- = nav_link path: ['projects#show', 'commits#show', 'builds#show'] do
- = link_to ci_project_path(@project) do
- = icon('list-alt fw')
- %span
- Commits
- %span.count= @project.commits.count
- = nav_link path: ['services#index', 'services#edit'] do
- = link_to ci_project_services_path(@project) do
- = icon('share fw')
- %span
- Services
= nav_link path: 'events#index' do
= link_to ci_project_events_path(@project) do
= icon('book fw')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index a218ec7486c..e4c285d8023 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -32,7 +32,7 @@
Files
- if project_nav_tab? :commits
- = nav_link(controller: %w(commit commits compare repositories tags branches)) do
+ = nav_link(controller: %w(commit commits compare repositories tags branches builds)) do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
= icon('history fw')
%span
@@ -76,13 +76,6 @@
Merge Requests
%span.count.merge_counter= @project.merge_requests.opened.count
- - if @project.gitlab_ci?
- = nav_link(controller: [:ci, :project]) do
- = link_to ci_project_path(@project.gitlab_ci_project), title: 'Continuous Integration', data: {placement: 'right'} do
- = icon('building fw')
- %span
- Continuous Integration
-
- if project_nav_tab? :settings
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 9279a846623..954dbe5d2b9 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -60,3 +60,13 @@
= icon('building fw')
%span
CI Settings
+ = nav_link controller: 'ci_services' do
+ = link_to namespace_project_ci_services_path(@project.namespace, @project) do
+ = icon('share fw')
+ %span
+ CI Services
+ = nav_link path: 'events#index' do
+ = link_to ci_project_events_path(@project.gitlab_ci_project) do
+ = icon('book fw')
+ %span
+ CI Events
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index e0ae4d9720f..0ca8bd95157 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -18,5 +18,6 @@
%code.key-fingerprint= @key.fingerprint
%pre.well-pre
= @key.key
- .pull-right
- = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
+ .col-md-12
+ .pull-right
+ = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
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/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index b4c7d8b9b71..a1ae1397584 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -19,7 +19,7 @@
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project
-%div#tree-content-holder.tree-content-holder
+%div#blob-content-holder.blob-content-holder
%article.file-holder
.file-title
= blob_icon blob.mode, blob.name
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 9c3e1703c89..f1ad0c3c403 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -11,7 +11,7 @@
- if current_action?(:new) || current_action?(:create)
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
- required: true, class: 'form-control new-file-name'
+ required: true, class: 'form-control new-file-name js-quick-submit'
.pull-right
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
new file mode 100644
index 00000000000..cb1567a2e68
--- /dev/null
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -0,0 +1,25 @@
+#modal-create-new-dir.modal
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3.page-title Create New Directory
+ .modal-body
+ = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, id: 'dir-create-form', class: 'form-horizontal' do
+ .form-group
+ = label_tag :dir_name, 'Directory Name', class: 'control-label'
+ .col-sm-10
+ = text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control'
+ = render 'shared/commit_message_container', params: params, placeholder: ''
+ - unless @project.empty_repo?
+ .form-group
+ = label_tag :branch_name, 'Branch', class: 'control-label'
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ = submit_tag "Create directory", class: 'btn btn-primary btn-create'
+ = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+
+:coffeescript
+ disableButtonIfAnyEmptyField($("#dir-create-form"), ".form-control", ".btn-create");
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 1a1df127703..e27f1707527 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -4,9 +4,6 @@
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title #{title}
- %p.light
- From branch
- %strong= @ref
.modal-body
= form_tag form_path, method: method, class: 'blob-file-upload-form-js form-horizontal' do
.dropzone
@@ -18,6 +15,12 @@
.dropzone-alerts{class: "alert alert-danger data", style: "display:none"}
= render 'shared/commit_message_container', params: params,
placeholder: placeholder
+ - unless @project.empty_repo?
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
.form-group
.col-sm-offset-2.col-sm-10
= button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 1950586b112..7975137c37f 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -2,12 +2,7 @@
= render "header_title"
.gray-content-block.top-block
- Create a new file or
- = link_to 'upload', '#modal-upload-blob',
- { class: 'upload-link', 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'}
- an existing one
-
-= render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
+ Create a new file
.file-editor
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do
@@ -20,7 +15,7 @@
= label_tag 'branch', class: 'control-label' do
Branch
.col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
+ = text_field_tag 'new_branch', @ref, class: "form-control js-quick-submit"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml
deleted file mode 100644
index 21c543b38dd..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_ci_project_build_path(build.project, build, return_to: request.original_url), title: 'Cancel build' do
- %i.fa.fa-remove.cred
- - elsif build.commands.present?
- = link_to retry_ci_project_build_path(build.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 93cd4dcfd93..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}
@@ -72,9 +72,9 @@
- if current_user && can?(current_user, :manage_builds, @project)
.pull-right
- if @build.active?
- = link_to "Cancel", cancel_ci_project_build_path(@ci_project, @build), class: 'btn btn-sm btn-danger'
+ = link_to "Cancel", cancel_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-danger'
- elsif @build.commands.present?
- = link_to "Retry", retry_ci_project_build_path(@ci_project, @build), class: 'btn btn-sm btn-primary', method: :post
+ = link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary', method: :post
- if @build.duration
%p
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/ci/services/_form.html.haml b/app/views/projects/ci_services/_form.html.haml
similarity index 80%
rename from app/views/ci/services/_form.html.haml
rename to app/views/projects/ci_services/_form.html.haml
index 9110aaa0528..397832e56db 100644
--- a/app/views/ci/services/_form.html.haml
+++ b/app/views/projects/ci_services/_form.html.haml
@@ -4,13 +4,10 @@
%p= @service.description
-.back-link
- = link_to ci_project_services_path(@project) do
- ← to services
%hr
-= form_for(@service, as: :service, url: ci_project_service_path(@project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
+= form_for(@service, as: :service, url: namespace_project_ci_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
- if @service.errors.any?
.alert.alert-danger
%ul
@@ -54,4 +51,4 @@
= f.submit 'Save', class: 'btn btn-save'
- if @service.valid? && @service.activated? && @service.can_test?
- = link_to 'Test settings', test_ci_project_service_path(@project, @service.to_param), class: 'btn'
+ = link_to 'Test settings', test_namespace_project_ci_service_path(@project.namespace, @project, @service.to_param), class: 'btn'
diff --git a/app/views/ci/services/edit.html.haml b/app/views/projects/ci_services/edit.html.haml
similarity index 100%
rename from app/views/ci/services/edit.html.haml
rename to app/views/projects/ci_services/edit.html.haml
diff --git a/app/views/ci/services/index.html.haml b/app/views/projects/ci_services/index.html.haml
similarity index 81%
rename from app/views/ci/services/index.html.haml
rename to app/views/projects/ci_services/index.html.haml
index 37e5723b541..c78b21884a3 100644
--- a/app/views/ci/services/index.html.haml
+++ b/app/views/projects/ci_services/index.html.haml
@@ -13,7 +13,7 @@
%td
= boolean_to_icon service.activated?
%td
- = link_to edit_ci_project_service_path(@project, service.to_param) do
+ = link_to edit_namespace_project_ci_service_path(@project.namespace, @project, service.to_param) do
%strong= service.title
%td
= service.description
diff --git a/app/views/projects/ci_settings/_form.html.haml b/app/views/projects/ci_settings/_form.html.haml
index 9f891f557a9..d711413c6b9 100644
--- a/app/views/projects/ci_settings/_form.html.haml
+++ b/app/views/projects/ci_settings/_form.html.haml
@@ -8,6 +8,22 @@
Edit your
#{link_to ".gitlab-ci.yml using web-editor", yaml_web_editor_link(@ci_project)}
+- unless @project.empty_repo?
+ %p
+ Paste build status image for #{@repository.root_ref} with next link
+ = link_to '#', class: 'badge-codes-toggle btn btn-default btn-xs' do
+ Status Badge
+ .badge-codes-block.bs-callout.bs-callout-info.hide
+ %p
+ Status badge for
+ %span.label.label-info #{@ref}
+ branch
+ %div
+ %label Markdown:
+ = text_field_tag 'badge_md', markdown_badge_code(@ci_project, @repository.root_ref), readonly: true, class: 'form-control'
+ %label Html:
+ = text_field_tag 'badge_html', html_badge_code(@ci_project, @repository.root_ref), readonly: true, class: 'form-control'
+
= nested_form_for @ci_project, url: namespace_project_ci_settings_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
- if @ci_project.errors.any?
#error_explanation
diff --git a/app/views/ci/projects/_no_runners.html.haml b/app/views/projects/ci_settings/_no_runners.html.haml
similarity index 87%
rename from app/views/ci/projects/_no_runners.html.haml
rename to app/views/projects/ci_settings/_no_runners.html.haml
index c0a296fb17d..33038c52978 100644
--- a/app/views/ci/projects/_no_runners.html.haml
+++ b/app/views/projects/ci_settings/_no_runners.html.haml
@@ -4,5 +4,5 @@
%br
You can add Specific runner for this project on Runners page
- - if current_user.is_admin
+ - if current_user.admin
or add Shared runner for whole application in admin are.
diff --git a/app/views/projects/ci_settings/edit.html.haml b/app/views/projects/ci_settings/edit.html.haml
index e9040fe4337..eedf484bf00 100644
--- a/app/views/projects/ci_settings/edit.html.haml
+++ b/app/views/projects/ci_settings/edit.html.haml
@@ -6,6 +6,9 @@
yaml file which is based on your old jobs.
Put this file to the root of your project and name it .gitlab-ci.yml
+- if no_runners_for_project?(@ci_project)
+ = render 'no_runners'
+
= render 'form'
- if @ci_project.generated_yaml_config
diff --git a/app/views/projects/commit/ci.html.haml b/app/views/projects/commit/ci.html.haml
index f4382e88046..ca71a91af15 100644
--- a/app/views/projects/commit/ci.html.haml
+++ b/app/views/projects/commit/ci.html.haml
@@ -3,11 +3,6 @@
= render "commit_box"
= render "ci_menu"
-- if @ci_project && current_user && can?(current_user, :manage_builds, @project)
- .pull-right
- - if @ci_commit.builds.running_or_pending.any?
- = link_to "Cancel", cancel_ci_project_commits_path(@ci_project, @ci_commit), class: 'btn btn-sm btn-danger'
-
- if @ci_commit.yaml_errors.present?
.bs-callout.bs-callout-danger
@@ -20,30 +15,39 @@
.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
+
+ .pull-right
+ - if @ci_commit.duration > 0
+ %i.fa.fa-time
+ #{time_interval_in_words @ci_commit.duration}
+
+
+
+ - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
+ - if @ci_commit.builds.running_or_pending.any?
+ = link_to "Cancel all", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger'
+
+%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,
+ locals: { coverage: @ci_project.try(:coverage_enabled?), allow_retry: 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 +63,5 @@
- 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,
+ locals: { 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..7314f8e79d3
--- /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
+ .pull-right
+ - if current_user && can?(current_user, :manage_builds, commit_status.gl_project)
+ - if commit_status.cancel_url
+ = link_to commit_status.cancel_url, title: 'Cancel' do
+ %i.fa.fa-remove.cred
+ - elsif defined?(allow_retry) && allow_retry && 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/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 4617b188150..9698921f6da 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -16,7 +16,7 @@
- if diff_file.mode_changed?
%span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
- .diff-btn-group
+ .diff-controls
- if blob.text?
= link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do
%i.fa.fa-comments
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 90dce739992..1882a82fba5 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -193,13 +193,13 @@
.panel.panel-default.panel.panel-danger
.panel-heading Remove project
.panel-body
- = form_tag(namespace_project_path(@project.namespace, @project), method: :delete, html: { class: 'form-horizontal'}) do
+ = form_tag(namespace_project_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do
%p
Removing the project will delete its repository and all related resources including issues, merge requests etc.
%br
%strong Removed projects cannot be restored!
- = link_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
+ = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
- else
.nothing-here-block Only project owner can remove a project
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index cd5f3a5d39e..f0b0a11c04a 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -1,36 +1,41 @@
- page_title "Fork project"
-%h3.page-title Fork project
-%p.lead
- Click to fork the project to a user or group
-%hr
+- if @namespaces.present?
+ %h3.page-title Fork project
+ %p.lead
+ Click to fork the project to a user or group
+ %hr
-.fork-namespaces
- - @namespaces.in_groups_of(6, false) do |group|
- .row
- - group.each do |namespace|
- .col-md-2.col-sm-3
- - if fork = namespace.find_fork_of(@project)
- .fork-thumbnail
- = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
- = image_tag namespace_icon(namespace, 100)
- .caption
- %strong
- = namespace.human_name
- %div.text-primary
- Already forked
+ .fork-namespaces
+ - @namespaces.in_groups_of(6, false) do |group|
+ .row
+ - group.each do |namespace|
+ .col-md-2.col-sm-3
+ - if fork = namespace.find_fork_of(@project)
+ .fork-thumbnail
+ = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
+ = image_tag namespace_icon(namespace, 100)
+ .caption
+ %strong
+ = namespace.human_name
+ %div.text-primary
+ Already forked
- - else
- .fork-thumbnail
- = link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
- = image_tag namespace_icon(namespace, 100)
- .caption
- %strong
- = namespace.human_name
+ - else
+ .fork-thumbnail
+ = link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
+ = image_tag namespace_icon(namespace, 100)
+ .caption
+ %strong
+ = namespace.human_name
- %p.light
- Fork is a copy of a project repository.
- %br
- Forking a repository allows you to do changes without affecting the original project.
+ %p.light
+ Fork is a copy of a project repository.
+ %br
+ Forking a repository allows you to do changes without affecting the original project.
+- else
+ %h3 No available namespaces to fork the project
+ %p.slead
+ You must have permission to create a project in a namespace before forking.
.save-project-loader.hide
.center
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 534c545329b..4cf13492e99 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -10,7 +10,7 @@
.form-group
= f.label :title, class: 'control-label'
.col-sm-10
- = f.text_field :title, class: "form-control", required: true
+ = f.text_field :title, class: "form-control js-quick-submit", required: true
.form-group
= f.label :color, "Background Color", class: 'control-label'
.col-sm-10
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 0b0f52c653c..e7ac7a0eaa4 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -24,7 +24,7 @@
%ul.dropdown-menu
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
- .light
+ .normal
%span Request to merge
%span.label-branch #{source_branch_with_namespace(@merge_request)}
%span into
@@ -34,7 +34,7 @@
= render "projects/merge_requests/widget/show.html.haml"
- if @merge_request.open? && @merge_request.can_be_merged?
- .light
+ .light.append-bottom-20
You can also accept this merge request manually using the
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 74e9668052d..255ddab479f 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -16,13 +16,13 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control", required: true
+ = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true
%p.hint Required
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit'
.hint
.pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
.pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index bccea21e7a8..daab2326bc7 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -8,7 +8,7 @@
= form_for @project, html: { class: 'new_project form-horizontal js-requires-input' } do |f|
.form-group.project-name-holder
= f.label :path, class: 'control-label' do
- %strong Project path
+ Project path
.col-sm-10
.input-group
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true, required: true
@@ -23,7 +23,6 @@
= f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
- if import_sources_enabled?
- %hr
.project-import.js-toggle-container
.form-group
@@ -35,7 +34,7 @@
%i.fa.fa-github
GitHub
- else
- = link_to '#', class: 'how_to_import_link light btn import_github' do
+ = link_to '#', class: 'how_to_import_link btn import_github' do
%i.fa.fa-github
GitHub
= render 'github_import_modal'
@@ -46,7 +45,7 @@
%i.fa.fa-bitbucket
Bitbucket
- else
- = link_to status_import_bitbucket_path, class: 'how_to_import_link light btn import_bitbucket', "data-no-turbolink" => "true" do
+ = link_to status_import_bitbucket_path, class: 'how_to_import_link btn import_bitbucket', "data-no-turbolink" => "true" do
%i.fa.fa-bitbucket
Bitbucket
= render 'bitbucket_import_modal'
@@ -57,7 +56,7 @@
%i.fa.fa-heart
GitLab.com
- else
- = link_to status_import_gitlab_path, class: 'how_to_import_link light btn import_gitlab' do
+ = link_to status_import_gitlab_path, class: 'how_to_import_link btn import_gitlab' do
%i.fa.fa-heart
GitLab.com
= render 'gitlab_import_modal'
@@ -97,7 +96,7 @@
%li
To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
- %hr.prepend-botton-10
+ .prepend-botton-10
.form-group
= f.label :description, class: 'control-label' do
@@ -112,10 +111,11 @@
- if current_user.can_create_group?
.pull-right
- .light
- Need a group for several dependent projects?
- = link_to new_group_path, class: "btn btn-xs" do
- Create a group
+ .light.inline
+ .space-right
+ Need a group for several dependent projects?
+ = link_to new_group_path, class: "btn" do
+ Create a group
.save-project-loader.hide
.center
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index a0e26f9827e..a21c019986a 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -2,7 +2,7 @@
= form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f|
= note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field js-quick-submit'
= render 'projects/notes/hints'
.note-form-actions
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index d99445da59a..13dfa0a1bb3 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -8,12 +8,12 @@
= f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-quick-submit'
= render 'projects/notes/hints'
.error-alert
.note-form-actions
.buttons.clearfix
- = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button"
+ = f.submit 'Add Comment', class: "btn btn-green comment-btn btn-grouped js-comment-button"
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9a0a824b811..82809bec5b8 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -5,7 +5,7 @@
.clearfix.js-toggle-container
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false }
= button_tag 'Search', class: 'btn'
- if can?(current_user, :admin_project_member, @project)
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index b9486a9b492..07c24950ee2 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -3,10 +3,10 @@
- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn btn-success col-xs-10', rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
- %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
+ %a.col-xs-2.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Archive Format
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/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index f082d711865..7e9af19c8ba 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -1,7 +1,8 @@
-%article.readme-holder#README
- = link_to '#README' do
- %h4.readme-file-title
- %i.fa.fa-file
- = readme.name
- .wiki
+%article.file-holder.readme-holder#README
+ .file-title
+ = link_to '#README' do
+ %strong
+ %i.fa.fa-file
+ = readme.name
+ .file-content.wiki
= render_readme(readme)
diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml
index 367a87927d7..7ff48e32e60 100644
--- a/app/views/projects/tree/_tree.html.haml
+++ b/app/views/projects/tree/_tree.html.haml
@@ -1,51 +1,71 @@
-%ul.breadcrumb.repo-breadcrumb
- %li
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
- = @project.path
- - tree_breadcrumbs(tree, 6) do |title, path|
+.gray-content-block
+ %ul.breadcrumb.repo-breadcrumb
%li
- - if path
- = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- - else
- = link_to title, '#'
- - if current_user && can_push_branch?(@project, @ref)
- %li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'New file', id: 'new-file-link' do
- %small
- %i.fa.fa-plus
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = @project.path
+ - tree_breadcrumbs(tree, 6) do |title, path|
+ %li
+ - if path
+ = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
+ - else
+ = link_to title, '#'
+ - if allowed_tree_edit?
+ %li
+ %span.dropdown
+ %a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+ = icon('plus')
+ %ul.dropdown-menu
+ %li
+ = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
+ = icon('pencil fw')
+ Create file
+ %li
+ = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
+ = icon('file fw')
+ Upload file
+ %li.divider
+ %li
+ = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
+ = icon('folder fw')
+ New directory
-%div#tree-content-holder.tree-content-holder.prepend-top-20
- %table#tree-slider{class: "table_#{@hex_path} tree-table" }
- %thead
- %tr
- %th Name
- %th Last Update
- %th.hidden-xs
- .pull-left Last Commit
- .last-commit.hidden-sm.pull-left
-
- %i.fa.fa-angle-right
-
- %small.light
- = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit)
- –
- = truncate(@commit.title, length: 50)
- = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
+%div#tree-content-holder.tree-content-holder
+ .tree-table-holder
+ %table.table#tree-slider{class: "table_#{@hex_path} tree-table table-striped" }
+ %thead
+ %tr
+ %th Name
+ %th Last Update
+ %th.hidden-xs
+ .pull-left Last Commit
+ .last-commit.hidden-sm.pull-left
+
+ %i.fa.fa-angle-right
+
+ %small.light
+ = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit)
+ –
+ = truncate(@commit.title, length: 50)
+ = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
- - if @path.present?
- %tr.tree-item
- %td.tree-item-file-name
- = link_to "..", namespace_project_tree_path(@project.namespace, @project, up_dir_path), class: 'prepend-left-10'
- %td
- %td.hidden-xs
+ - if @path.present?
+ %tr.tree-item
+ %td.tree-item-file-name
+ = link_to "..", namespace_project_tree_path(@project.namespace, @project, up_dir_path), class: 'prepend-left-10'
+ %td
+ %td.hidden-xs
- = render_tree(tree)
+ = render_tree(tree)
- if tree.readme
= render "projects/tree/readme", readme: tree.readme
%div.tree_progress
+- if allowed_tree_edit?
+ = render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
+ = render 'projects/blob/new_dir'
+
:javascript
// Load last commit log for each file in tree
$('#tree-slider').waitForImages(function() {
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 05d754adbe5..261d4a92d7d 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -22,7 +22,7 @@
= f.label :content, class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :content, classes: 'description form-control js-quick-submit'
.col-sm-12.hint
.pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
.pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 03e6a522b25..d179a1abec1 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -3,6 +3,7 @@
= render 'nav'
.gray-content-block
+ = render 'main_links'
%h3.page-title
All Pages
%ul.content-list
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index 3938c545cad..17b0981f073 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -6,7 +6,7 @@
.search-holder.clearfix
.input-group
- = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true
+ = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true, spellcheck: false
%span.input-group-btn
= button_tag 'Search', class: "btn btn-primary"
- unless params[:snippets].eql? 'true'
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index 5071ff640f1..cc3f1268f8b 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -6,7 +6,7 @@
.max-width-marker
= text_area_tag 'commit_message',
(params[:commit_message] || local_assigns[:text]),
- class: 'form-control', placeholder: local_assigns[:placeholder],
+ class: 'form-control js-quick-submit', placeholder: local_assigns[:placeholder],
required: true, rows: (local_assigns[:rows] || 3)
- if local_assigns[:hint]
%p.hint
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 33ec726e93c..594e54f404c 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -10,7 +10,7 @@
%strong= 'Title *'
.col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
- class: 'form-control pad js-gfm-input', required: true
+ class: 'form-control pad js-gfm-input js-quick-submit', required: true
- if issuable.is_a?(MergeRequest)
%p.help-block
@@ -26,7 +26,7 @@
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control'
+ classes: 'description form-control js-quick-submit'
.col-sm-12.hint
.pull-left
Parsed with
diff --git a/app/views/shared/issuable/_search_form.html.haml b/app/views/shared/issuable/_search_form.html.haml
index 58c3de64b77..3a5ad00aa91 100644
--- a/app/views/shared/issuable/_search_form.html.haml
+++ b/app/views/shared/issuable/_search_form.html.haml
@@ -1,6 +1,6 @@
= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
.append-right-10.hidden-xs.hidden-sm
- = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input' }
+ = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input', spellcheck: false }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 16e1d8421de..357cfd6a370 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -2,11 +2,12 @@
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
- ci = false unless local_assigns[:ci] == true
+- skip_namespace = false unless local_assigns[:skip_namespace] == true
%ul.projects-list
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil
- = render "shared/projects/project", project: project,
+ = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci
- if projects.size > projects_limit
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index e67e5a8a638..aee839b44e7 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -1,6 +1,7 @@
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
- ci = false unless local_assigns[:ci] == true
+- skip_namespace = false unless local_assigns[:skip_namespace] == true
- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" unless project.description.present?
%li.project-row{ class: css_class }
@@ -11,7 +12,7 @@
= project_icon(project, alt: '', class: 'avatar project-avatar s46')
%span.project-full-name
%span.namespace-name
- - if project.namespace
+ - if project.namespace && !skip_namespace
= project.namespace.human_name
\/
%span.project-name.filter-title
diff --git a/config/application.rb b/config/application.rb
index a96e22211e6..bfa2a809dd7 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -74,7 +74,7 @@ module Gitlab
origins '*'
resource '/api/*',
headers: :any,
- methods: [:get, :post, :options, :put, :delete],
+ methods: :any,
expose: ['Link']
end
end
diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example
index 82e1a42058e..bb624e8a187 100644
--- a/config/mail_room.yml.example
+++ b/config/mail_room.yml.example
@@ -9,11 +9,13 @@
# # Whether the IMAP server uses StartTLS
# :start_tls: false
# # Email account username. Usually the full email address.
- # :email: "replies@gitlab.example.com"
+ # :email: "gitlab-incoming@gmail.com"
# # Email account password
# :password: "password"
+
# # The name of the mailbox where incoming mail will end up. Usually "inbox".
# :name: "inbox"
+
# # Always "sidekiq".
# :delivery_method: sidekiq
# # Always true.
@@ -25,5 +27,13 @@
# :namespace: resque:gitlab
# # Always "incoming_email".
# :queue: incoming_email
- # # Always "EmailReceiverWorker"
+ # # Always "EmailReceiverWorker".
# :worker: EmailReceiverWorker
+
+ # # Always "redis".
+ # :arbitration_method: redis
+ # :arbitration_options:
+ # # The URL to the Redis server. Should match the URL in config/resque.yml.
+ # :redis_url: redis://localhost:6379
+ # # Always "mail_room:gitlab".
+ # :namespace: mail_room:gitlab
diff --git a/config/routes.rb b/config/routes.rb
index ccce40589e7..8e6fbf6340c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -22,27 +22,6 @@ Gitlab::Application.routes.draw do
get :dumped_yaml
end
- resources :services, only: [:index, :edit, :update] do
- member do
- get :test
- end
- end
-
- resources :commits, only: [] do
- member do
- get :status
- get :cancel
- end
- end
-
- resources :builds, only: [] do
- member do
- get :cancel
- get :status
- post :retry
- end
- end
-
resources :runner_projects, only: [:create, :destroy]
resources :events, only: [:index]
@@ -463,6 +442,15 @@ Gitlab::Application.routes.draw do
)
end
+ scope do
+ post(
+ '/create_dir/*id',
+ to: 'tree#create_dir',
+ constraints: { id: /.+/ },
+ as: 'create_dir'
+ )
+ end
+
scope do
get(
'/blame/*id',
@@ -486,6 +474,7 @@ Gitlab::Application.routes.draw do
member do
get :branches
get :ci
+ get :cancel_builds
end
end
@@ -590,7 +579,19 @@ Gitlab::Application.routes.draw do
end
end
- resources :builds, only: [:show]
+ resources :ci_services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
+ member do
+ get :test
+ end
+ end
+
+ resources :builds, only: [:show] do
+ member do
+ get :cancel
+ get :status
+ post :retry
+ end
+ end
resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do
member do
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 8f71198e47f..b4639999967 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -73,8 +73,13 @@ Sidekiq::Testing.inline! do
}
project = Projects::CreateService.new(User.first, params).execute
+ # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
+ # hook won't run until after the fixture is loaded. That is too late
+ # since the Sidekiq::Testing block has already exited. Force clearing
+ # the `after_commit` queue to ensure the job is run now.
+ project.send(:_run_after_commit_queue)
- if project.valid?
+ if project.valid? && project.valid_repo?
print '.'
else
puts project.errors.full_messages
diff --git a/db/migrate/20151007120511_namespaces_projects_path_lower_indexes.rb b/db/migrate/20151007120511_namespaces_projects_path_lower_indexes.rb
new file mode 100644
index 00000000000..7f6cd6d5a78
--- /dev/null
+++ b/db/migrate/20151007120511_namespaces_projects_path_lower_indexes.rb
@@ -0,0 +1,17 @@
+class NamespacesProjectsPathLowerIndexes < ActiveRecord::Migration
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab::Database.postgresql?
+
+ execute 'CREATE INDEX CONCURRENTLY index_on_namespaces_lower_path ON namespaces (LOWER(path));'
+ execute 'CREATE INDEX CONCURRENTLY index_on_projects_lower_path ON projects (LOWER(path));'
+ end
+
+ def down
+ return unless Gitlab::Database.postgresql?
+
+ remove_index :namespaces, name: :index_on_namespaces_lower_path
+ remove_index :projects, name: :index_on_projects_lower_path
+ end
+end
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 93202f16111..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: 20151005162154) 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: 20151005162154) 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/doc/api/merge_requests.md b/doc/api/merge_requests.md
index bb551fc67f7..ffa7f2cdf14 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -188,6 +188,7 @@ Parameters:
- `title` (required) - Title of MR
- `description` (optional) - Description of MR
- `target_project_id` (optional) - The target project (numeric id)
+- `labels` (optional) - Labels for MR as a comma-separated list
```json
{
@@ -239,6 +240,7 @@ Parameters:
- `title` - Title of MR
- `description` - Description of MR
- `state_event` - New state (close|reopen|merge)
+- `labels` (optional) - Labels for MR as a comma-separated list
```json
{
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
index 6c141d1fb7a..e993230bb88 100644
--- a/doc/customization/welcome_message.md
+++ b/doc/customization/welcome_message.md
@@ -8,31 +8,5 @@ It is possible to add a markdown-formatted welcome message to your GitLab
sign-in page. Users of GitLab Enterprise Edition should use the [branded login
page feature](/ee/customization/branded_login_page.html) instead.
-## Omnibus-gitlab example
-
-In `/etc/gitlab/gitlab.rb`:
-
-```ruby
-gitlab_rails['extra_sign_in_text'] = <<'EOS'
-# ACME GitLab
-Welcome to the [ACME](http://www.example.com) GitLab server!
-EOS
-```
-
-Run `sudo gitlab-ctl reconfigure` for changes to take effect.
-
-## Installation from source
-
-In `/home/git/gitlab/config/gitlab.yml`:
-
-```yaml
-# snip
-production:
- # snip
- extra:
- sign_in_text: |
- # ACME GitLab
- Welcome to the [ACME](http://www.example.com) GitLab server!
-```
-
-Run `sudo service gitlab reload` for the change to take effect.
+The welcome message (extra_sign_in_text) can now be set/changed in the Admin UI.
+Admin area > Settings
\ No newline at end of file
diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md
index 316746ab54d..aafa2345fab 100644
--- a/doc/incoming_email/README.md
+++ b/doc/incoming_email/README.md
@@ -2,10 +2,6 @@
GitLab can be set up to allow users to comment on issues and merge requests by replying to notification emails.
-**Warning**: Do not enable Reply by email if you have **multiple GitLab application servers**.
-Due to an issue with the way incoming emails are read from the mail server, every incoming reply-by-email email will result in as many comments being created as you have application servers.
-[A fix is being worked on.](https://github.com/tpitale/mail_room/issues/46)
-
## Get a mailbox
Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises.
@@ -16,24 +12,35 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
## Set it up
-In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
-
### Omnibus package installations
1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature, enter the email address including a placeholder for the `key` that references the item being replied to and fill in the details for your specific IMAP server and email account:
```ruby
+ # Postfix mail server, assumes mailbox incoming@gitlab.example.com
+ gitlab_rails['incoming_email_enabled'] = true
+ gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com"
+ gitlab_rails['incoming_email_host'] = "gitlab.example.com" # IMAP server host
+ gitlab_rails['incoming_email_port'] = 143 # IMAP server port
+ gitlab_rails['incoming_email_ssl'] = false # Whether the IMAP server uses SSL
+ gitlab_rails['incoming_email_email'] = "incoming" # Email account username. Usually the full email address.
+ gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password
+ gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox".
+ ```
+
+ ```ruby
+ # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
gitlab_rails['incoming_email_enabled'] = true
gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com"
gitlab_rails['incoming_email_host'] = "imap.gmail.com" # IMAP server host
gitlab_rails['incoming_email_port'] = 993 # IMAP server port
gitlab_rails['incoming_email_ssl'] = true # Whether the IMAP server uses SSL
gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" # Email account username. Usually the full email address.
- gitlab_rails['incoming_email_password'] = "password" # Email account password
+ gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password
gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox".
```
- As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `gitlab-incoming@gmail.com`.
+ As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
1. Reconfigure GitLab for the changes to take effect:
@@ -64,12 +71,20 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
```
```yaml
+ # Postfix mail server, assumes mailbox incoming@gitlab.example.com
+ incoming_email:
+ enabled: true
+ address: "incoming+%{key}@gitlab.example.com"
+ ```
+
+ ```yaml
+ # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email:
enabled: true
address: "gitlab-incoming+%{key}@gmail.com"
```
- As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `gitlab-incoming@gmail.com`.
+ As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`:
@@ -84,6 +99,50 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
```
```yaml
+ # Postfix mail server
+ :mailboxes:
+ -
+ # IMAP server host
+ :host: "gitlab.example.com"
+ # IMAP server port
+ :port: 143
+ # Whether the IMAP server uses SSL
+ :ssl: false
+ # Whether the IMAP server uses StartTLS
+ :start_tls: false
+ # Email account username. Usually the full email address.
+ :email: "incoming"
+ # Email account password
+ :password: "[REDACTED]"
+
+ # The name of the mailbox where incoming mail will end up. Usually "inbox".
+ :name: "inbox"
+
+ # Always "sidekiq".
+ :delivery_method: sidekiq
+ # Always true.
+ :delete_after_delivery: true
+ :delivery_options:
+ # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
+ :redis_url: redis://localhost:6379
+ # Always "resque:gitlab".
+ :namespace: resque:gitlab
+ # Always "incoming_email".
+ :queue: incoming_email
+ # Always "EmailReceiverWorker"
+ :worker: EmailReceiverWorker
+
+ # Always "redis".
+ :arbitration_method: redis
+ :arbitration_options:
+ # The URL to the Redis server. Should match the URL in config/resque.yml.
+ :redis_url: redis://localhost:6379
+ # Always "mail_room:gitlab".
+ :namespace: mail_room:gitlab
+ ```
+
+ ```yaml
+ # Gmail / Google Apps
:mailboxes:
-
# IMAP server host
@@ -98,8 +157,10 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
:email: "gitlab-incoming@gmail.com"
# Email account password
:password: "[REDACTED]"
+
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
+
# Always "sidekiq".
:delivery_method: sidekiq
# Always true.
@@ -113,6 +174,14 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
+
+ # Always "redis".
+ :arbitration_method: redis
+ :arbitration_options:
+ # The URL to the Redis server. Should match the URL in config/resque.yml.
+ :redis_url: redis://localhost:6379
+ # Always "mail_room:gitlab".
+ :namespace: mail_room:gitlab
```
5. Edit the init script configuration at `/etc/default/gitlab` to enable `mail_room`:
@@ -143,6 +212,7 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `key` that references the item being replied to:
```yaml
+ # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email:
enabled: true
address: "gitlab-incoming+%{key}@gmail.com"
@@ -159,6 +229,7 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account:
```yaml
+ # Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
:mailboxes:
-
# IMAP server host
@@ -173,8 +244,10 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
:email: "gitlab-incoming@gmail.com"
# Email account password
:password: "[REDACTED]"
+
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
+
# Always "sidekiq".
:delivery_method: sidekiq
# Always true.
@@ -188,6 +261,14 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
+
+ # Always "redis".
+ :arbitration_method: redis
+ :arbitration_options:
+ # The URL to the Redis server. Should match the URL in config/resque.yml.
+ :redis_url: redis://localhost:6379
+ # Always "mail_room:gitlab".
+ :namespace: mail_room:gitlab
```
4. Uncomment the `mail_room` line in your `Procfile`:
@@ -208,4 +289,4 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
bundle exec rake gitlab:incoming_email:check RAILS_ENV=development
```
-8. Reply by email should now be working.
\ No newline at end of file
+8. Reply by email should now be working.
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index 4fbd20762da..629d38efc53 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -56,3 +56,17 @@ bundle exec rake gitlab:import:all_users_to_all_groups RAILS_ENV=production
```
block_auto_created_users: false
```
+
+## Disable Two-factor Authentication (2FA) for all users
+
+This task will disable 2FA for all users that have it enabled. This can be
+useful if GitLab's `.secret` file has been lost and users are unable to login,
+for example.
+
+```bash
+# omnibus-gitlab
+sudo gitlab-rake gitlab:two_factor:disable_for_all_users
+
+# installation from source
+bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production
+```
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index c56e99a7005..bd8a67d1d85 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -74,20 +74,19 @@ Xth: (1 working day before the 22nd)
- [ ] Update GitLab.com with the latest RC (#LINK)
- [ ] Update ci.gitLab.com with the latest RC (#LINK)
-22nd before 12AM CET:
+22nd before 1200 CET:
-Release before 12AM CET / 3AM PST, to make sure the majority of our users
+Release before 1200 CET / 2AM PST, to make sure the majority of our users
get the new version on the 22nd and there is sufficient time in the European
workday to quickly fix any issues.
- [ ] Merge CE stable into EE stable (#LINK)
- [ ] Create the 'x.y.0' tag with the [release tools](https://dev.gitlab.org/gitlab/release-tools) (#LINK)
- [ ] Create the 'x.y.0' version on version.gitlab.com
-- [ ] Try to do before 11AM CET: Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK)
-- [ ] Try to do before 12AM CET: Publish the release blog post (#LINK)
+- [ ] Try to do before 1100 CET: Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK)
+- [ ] Try to do before 1200 CET: Publish the release blog post (#LINK)
- [ ] Tweet about the release (blog post) (#LINK)
-- [ ] Schedule a second tweet of the release announcement with the same text at 6PM CET / 9AM PST
-
+- [ ] Schedule a second tweet of the release announcement with the same text at 1800 CET / 8AM PST
```
- - -
@@ -223,4 +222,4 @@ Consider creating a post on Hacker News.
## Create a WIP blogpost for the next release
-Create a WIP blogpost using [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md).
\ No newline at end of file
+Create a WIP blogpost using [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md).
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index b0e4613cdef..5cb05b13b3e 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -48,16 +48,17 @@ X-Gitlab-Event: System Hook
```json
{
- "created_at": "2012-07-21T07:30:56Z",
- "event_name": "user_add_to_team",
- "project_access": "Master",
- "project_id": 74,
- "project_name": "StoreCloud",
- "project_path": "storecloud",
- "user_email": "johnsmith@gmail.com",
- "user_name": "John Smith",
- "user_id": 41,
- "project_visibility": "private",
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_add_to_team",
+ "project_access": "Master",
+ "project_id": 74,
+ "project_name": "StoreCloud",
+ "project_path": "storecloud",
+ "project_path_with_namespace": "jsmith/storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+ "user_id": 41,
+ "project_visibility": "private",
}
```
@@ -65,16 +66,17 @@ X-Gitlab-Event: System Hook
```json
{
- "created_at": "2012-07-21T07:30:56Z",
- "event_name": "user_remove_from_team",
- "project_access": "Master",
- "project_id": 74,
- "project_name": "StoreCloud",
- "project_path": "storecloud",
- "user_email": "johnsmith@gmail.com",
- "user_name": "John Smith",
- "user_id": 41,
- "project_visibility": "private",
+ "created_at": "2012-07-21T07:30:56Z",
+ "event_name": "user_remove_from_team",
+ "project_access": "Master",
+ "project_id": 74,
+ "project_name": "StoreCloud",
+ "project_path": "storecloud",
+ "project_path_with_namespace": "jsmith/storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+ "user_id": 41,
+ "project_visibility": "private",
}
```
diff --git a/doc/update/7.14-to-8.0.md b/doc/update/7.14-to-8.0.md
index 0e65c32098c..7ad4935e839 100644
--- a/doc/update/7.14-to-8.0.md
+++ b/doc/update/7.14-to-8.0.md
@@ -175,12 +175,21 @@ Also note that because Apache does not support upstreams behind Unix sockets you
Now, GitLab CE and EE has CI integrated. However, migrations don't happen automatically and you need to do it manually.
Please follow the following guide [to migrate](../migrate_ci_to_ce/README.md) your GitLab CI instance to GitLab CE/EE.
-### 10. Start application
+### 10. Use Redis v2.4.0+
+
+Previous versions of GitLab allowed Redis versions >= 2.0 to be used, but
+Sidekiq jobs could fail due to lack of support for the SREM command. GitLab
+8.0 now checks that Redis >= 2.4.0 is used. You can check your Redis version
+with the following command:
+
+ redis-cli info | grep redis_version
+
+### 11. Start application
sudo service gitlab start
sudo service nginx restart
-### 11. Check application status
+### 12. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index a66a863f6c4..da719229ab6 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -23,9 +23,11 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
cd /home/git/gitlab
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- Gemfile.lock db/schema.rb
-LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
-sudo -u git -H git checkout $LATEST_TAG -b $LATEST_TAG
+sudo -u git -H git checkout LATEST_TAG -b LATEST_TAG
```
+Replace `LATEST_TAG` with the latest GitLab tag you want to update to, for example `v8.0.3`.
+Use `git tag -l 'v*.[0-9]' --sort='v:refname'` to see a list of all tags.
+Make sure to update patch versions only (check your current version with `cat VERSION`)
### 3. Update gitlab-shell to the corresponding version
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index c185ccfcac3..ef99a69f60a 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -314,7 +314,8 @@ X-Gitlab-Event: Note Hook
"name": "John Smith",
"email": "john@example.com"
}
- }
+ },
+ "work_in_progress": false
}
}
```
@@ -500,6 +501,7 @@ X-Gitlab-Event: Merge Request Hook
"email": "gitlabdev@dv6700.(none)"
}
},
+ "work_in_progress": false,
"url": "http://example.com/diaspora/merge_requests/1",
"action": "open"
}
@@ -537,4 +539,4 @@ When you press 'Test Hook' in GitLab, you should see something like this in the
{"before":"077a85dd266e6f3573ef7e9ef8ce3343ad659c4e","after":"95cd4a99e93bc4bbabacfa2cd10e6725b1403c60",}
example.com - - [14/May/2014:07:45:26 EDT] "POST / HTTP/1.1" 200 0
- -> /
-```
\ No newline at end of file
+```
diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature
index bbd82a85e3a..76392068357 100644
--- a/features/dashboard/new_project.feature
+++ b/features/dashboard/new_project.feature
@@ -7,24 +7,24 @@ Background:
And I click "New project" link
@javascript
- Scenario: I should see New projects page
- Then I see "New project" page
+ Scenario: I should see New Projects page
+ Then I see "New Project" page
Then I see all possible import optios
@javascript
Scenario: I should see instructions on how to import from Git URL
- Given I see "New project" page
+ Given I see "New Project" page
When I click on "Any repo by URL"
Then I see instructions on how to import from Git URL
@javascript
Scenario: I should see instructions on how to import from GitHub
- Given I see "New project" page
+ Given I see "New Project" page
When I click on "Import project from GitHub"
Then I see instructions on how to import from GitHub
@javascript
Scenario: I should see Google Code import page
- Given I see "New project" page
+ Given I see "New Project" page
When I click on "Google Code"
Then I redirected to Google Code import page
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 58574166ef3..377c5e1a9a7 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -21,12 +21,12 @@ Feature: Project Source Browse Files
Then I should see raw file content
Scenario: I can create file
- Given I click on "new file" link in repo
+ Given I click on "New file" link in repo
Then I can see new file page
@javascript
Scenario: I can create and commit file
- Given I click on "new file" link in repo
+ Given I click on "New file" link in repo
And I edit code
And I fill the new file name
And I fill the commit message
@@ -36,14 +36,13 @@ Feature: Project Source Browse Files
@javascript
Scenario: I can upload file and commit
- Given I click on "new file" link in repo
- Then I can see new file page
- And I can see "upload an existing one"
- And I click on "upload"
+ Given I click on "Upload file" link in repo
And I upload a new text file
And I fill the upload file commit message
+ And I fill the new branch name
And I click on "Upload file"
Then I can see the new text file
+ And I am redirected to the uploaded file on new branch
And I can see the new commit message
@javascript
@@ -59,7 +58,7 @@ Feature: Project Source Browse Files
@javascript
Scenario: I can create and commit file and specify new branch
- Given I click on "new file" link in repo
+ Given I click on "New file" link in repo
And I edit code
And I fill the new file name
And I fill the commit message
@@ -83,7 +82,7 @@ Feature: Project Source Browse Files
@javascript
Scenario: If I enter an illegal file name I see an error message
- Given I click on "new file" link in repo
+ Given I click on "New file" link in repo
And I fill the new file name with an illegal name
And I edit code
And I fill the commit message
@@ -138,6 +137,24 @@ Feature: Project Source Browse Files
Then I am on the ".gitignore" edit file page
And I see a commit error message
+ @javascript
+ Scenario: I can create directory in repo
+ When I click on "New directory" link in repo
+ And I fill the new directory name
+ And I fill the commit message
+ And I fill the new branch name
+ And I click on "Create directory"
+ Then I am redirected to the new directory
+
+ @javascript
+ Scenario: I attempt to create an existing directory
+ When I click on "New directory" link in repo
+ And I fill an existing directory name
+ And I fill the commit message
+ And I click on "Create directory"
+ Then I see "Unable to create directory"
+ And I am redirected to the root directory
+
@javascript
Scenario: I can see editing preview
Given I click on ".gitignore" file in repo
diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb
index 17233f89f38..5a1cc9aa151 100644
--- a/features/steps/admin/projects.rb
+++ b/features/steps/admin/projects.rb
@@ -41,6 +41,8 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
end
step 'I transfer project to group \'Web\'' do
+ allow_any_instance_of(Projects::TransferService).
+ to receive(:move_uploads_to_new_namespace).and_return(true)
find(:xpath, "//input[@id='new_namespace_id']").set group.id
click_button 'Transfer'
end
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
index 1e09162a5b5..44a4aa9844a 100644
--- a/features/steps/dashboard/new_project.rb
+++ b/features/steps/dashboard/new_project.rb
@@ -3,13 +3,13 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
include SharedPaths
include SharedProject
- step 'I click "New project" link' do
+ step 'I click "New Project" link' do
page.within('.content') do
- click_link "New project"
+ click_link "New Project"
end
end
- step 'I see "New project" page' do
+ step 'I see "New Project" page' do
expect(page).to have_content('Project path')
end
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/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index a1a49dd58a6..cb100ca0f54 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -71,7 +71,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I fill the new branch name' do
- fill_in :new_branch, with: 'new_branch_name'
+ fill_in :new_branch, with: 'new_branch_name', visible: true
end
step 'I fill the new file name with an illegal name' do
@@ -90,6 +90,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
click_button 'Commit Changes'
end
+ step 'I click on "Create directory"' do
+ click_button 'Create directory'
+ end
+
step 'I click on "Remove"' do
click_button 'Remove'
end
@@ -110,8 +114,27 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_css '.line_holder.new'
end
- step 'I click on "new file" link in repo' do
- click_link 'new-file-link'
+ step 'I click on "New file" link in repo' do
+ find('.add-to-tree').click
+ click_link 'Create file'
+ end
+
+ step 'I click on "Upload file" link in repo' do
+ find('.add-to-tree').click
+ click_link 'Upload file'
+ end
+
+ step 'I click on "New directory" link in repo' do
+ find('.add-to-tree').click
+ click_link 'New directory'
+ end
+
+ step 'I fill the new directory name' do
+ fill_in :dir_name, with: new_dir_name
+ end
+
+ step 'I fill an existing directory name' do
+ fill_in :dir_name, with: 'files'
end
step 'I can see new file page' do
@@ -119,14 +142,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_content "Commit message"
end
- step 'I can see "upload an existing one"' do
- expect(page).to have_content "upload an existing one"
- end
-
- step 'I click on "upload"' do
- click_link 'upload'
- end
-
step 'I click on "Upload file"' do
click_button 'Upload file'
end
@@ -228,10 +243,30 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
@project.namespace, @project, 'new_branch_name/' + new_file_name))
end
+ step 'I am redirected to the uploaded file on new branch' do
+ expect(current_path).to eq(namespace_project_blob_path(
+ @project.namespace, @project,
+ 'new_branch_name/' + File.basename(test_text_file)))
+ end
+
+ step 'I am redirected to the new directory' do
+ expect(current_path).to eq(namespace_project_tree_path(
+ @project.namespace, @project, 'new_branch_name/' + new_dir_name))
+ end
+
+ step 'I am redirected to the root directory' do
+ expect(current_path).to eq(namespace_project_tree_path(
+ @project.namespace, @project, 'master/'))
+ end
+
step "I don't see the permalink link" do
expect(page).not_to have_link('permalink')
end
+ step 'I see "Unable to create directory"' do
+ expect(page).to have_content('Directory already exists')
+ end
+
step 'I see a commit error message' do
expect(page).to have_content('Your changes could not be committed')
end
@@ -287,6 +322,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
'not_a_file.md'
end
+ # Constant value that is a valid directory and
+ # not a directory present at root of the seed repository.
+ def new_dir_name
+ 'new_dir/subdir'
+ end
+
def drop_in_dropzone(file_path)
# Generate a fake input selector
page.execute_script <<-JS
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..883a5e14b17 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
@@ -255,6 +262,18 @@ module API
expose :notification_level
end
+ class ProjectService < Grape::Entity
+ expose :id, :title, :created_at, :updated_at, :active
+ expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events
+ # Expose serialized properties
+ expose :properties do |service, options|
+ field_names = service.fields.
+ select { |field| options[:include_passwords] || field[:type] != 'password' }.
+ map { |field| field[:name] }
+ service.properties.slice(*field_names)
+ end
+ end
+
class ProjectWithAccess < Project
expose :permissions do
expose :project_access, using: Entities::ProjectAccess do |project, options|
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 63ea2f05438..f3a59fadf24 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -99,7 +99,7 @@ module API
# id (required) - The ID of a project - this will be the source of the merge request
# source_branch (required) - The source branch
# target_branch (required) - The target branch
- # target_project - The target project of the merge request defaults to the :id of the project
+ # target_project_id - The target project of the merge request defaults to the :id of the project
# assignee_id - Assignee user ID
# title (required) - Title of MR
# description - Description of MR
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 6727e80ac1e..203f04a6259 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -57,7 +57,7 @@ module API
# GET /project/:id/services/gitlab-ci
#
get ':id/services/:service_slug' do
- present project_service
+ present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
end
end
end
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/lib/extracts_path.rb b/lib/extracts_path.rb
index 322aed5e27c..51e46da82cc 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -110,7 +110,7 @@ module ExtractsPath
@project, @ref, @path)
rescue RuntimeError, NoMethodError, InvalidPathError
- not_found!
+ render_404
end
def tree
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 14ee4701e7b..01b8bda05c6 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -4,7 +4,8 @@ module Gitlab
class KeyAdder < Struct.new(:io)
def add_key(id, key)
- io.puts("#{id}\t#{key.strip}")
+ key.gsub!(/[[:space:]]+/, ' ').strip!
+ io.puts("#{id}\t#{key}")
end
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
new file mode 100644
index 00000000000..741a52714ac
--- /dev/null
+++ b/lib/gitlab/database.rb
@@ -0,0 +1,11 @@
+module Gitlab
+ module Database
+ def self.mysql?
+ ActiveRecord::Base.connection.adapter_name.downcase == 'mysql'
+ end
+
+ def self.postgresql?
+ ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql'
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 1ea7751e27d..4be99dd88c2 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -35,7 +35,7 @@ module Gitlab
end
def find_by_email
- ::User.find_by(email: auth_hash.email)
+ ::User.find_by(email: auth_hash.email.downcase)
end
def update_user_attributes
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index dc6751fa1c2..d1f22070cba 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -92,12 +92,14 @@ module Gitlab
autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter'
autoload :TaskListFilter, 'gitlab/markdown/task_list_filter'
autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
+ autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter'
def self.gfm_filters
@gfm_filters ||= [
Gitlab::Markdown::SyntaxHighlightFilter,
Gitlab::Markdown::SanitizationFilter,
+ Gitlab::Markdown::UploadLinkFilter,
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
Gitlab::Markdown::AutolinkFilter,
diff --git a/lib/gitlab/markdown/upload_link_filter.rb b/lib/gitlab/markdown/upload_link_filter.rb
new file mode 100644
index 00000000000..fbada73ab86
--- /dev/null
+++ b/lib/gitlab/markdown/upload_link_filter.rb
@@ -0,0 +1,47 @@
+require 'gitlab/markdown'
+require 'html/pipeline/filter'
+require 'uri'
+
+module Gitlab
+ module Markdown
+ # HTML filter that "fixes" relative upload links to files.
+ # Context options:
+ # :project (required) - Current project
+ #
+ class UploadLinkFilter < HTML::Pipeline::Filter
+ def call
+ doc.search('a').each do |el|
+ process_link_attr el.attribute('href')
+ end
+
+ doc.search('img').each do |el|
+ process_link_attr el.attribute('src')
+ end
+
+ doc
+ end
+
+ protected
+
+ def process_link_attr(html_attr)
+ return if html_attr.blank?
+
+ uri = html_attr.value
+ if uri.starts_with?("/uploads/")
+ html_attr.value = build_url(uri).to_s
+ end
+ end
+
+ def build_url(uri)
+ File.join(Gitlab.config.gitlab.url, context[:project].path_with_namespace, uri)
+ end
+
+ # Ensure that a :project key exists in context
+ #
+ # Note that while the key might exist, its value could be nil!
+ def validate
+ needs :project
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb
new file mode 100644
index 00000000000..be8fcc7b2d2
--- /dev/null
+++ b/lib/gitlab/uploads_transfer.rb
@@ -0,0 +1,35 @@
+module Gitlab
+ class UploadsTransfer
+ def move_project(project_path, namespace_path_was, namespace_path)
+ new_namespace_folder = File.join(root_dir, namespace_path)
+ FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder)
+ from = File.join(root_dir, namespace_path_was, project_path)
+ to = File.join(root_dir, namespace_path, project_path)
+ move(from, to, "")
+ end
+
+ def rename_project(path_was, path, namespace_path)
+ base_dir = File.join(root_dir, namespace_path)
+ move(path_was, path, base_dir)
+ end
+
+ def rename_namespace(path_was, path)
+ move(path_was, path)
+ end
+
+ private
+
+ def move(path_was, path, base_dir = nil)
+ base_dir = root_dir unless base_dir
+ from = File.join(base_dir, path_was)
+ to = File.join(base_dir, path)
+ FileUtils.mv(from, to)
+ rescue Errno::ENOENT
+ false
+ end
+
+ def root_dir
+ File.join(Rails.root, "public", "uploads")
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index 0ac4b0fa8a3..4cbccf2ca89 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -16,6 +16,7 @@ namespace :gitlab do
Rake::Task["db:setup"].invoke
Rake::Task["add_limits_mysql"].invoke
+ Rake::Task["setup_postgresql"].invoke
Rake::Task["db:seed_fu"].invoke
rescue Gitlab::TaskAbortedByUserError
puts "Quitting...".red
diff --git a/lib/tasks/gitlab/two_factor.rake b/lib/tasks/gitlab/two_factor.rake
new file mode 100644
index 00000000000..9196677a017
--- /dev/null
+++ b/lib/tasks/gitlab/two_factor.rake
@@ -0,0 +1,23 @@
+namespace :gitlab do
+ namespace :two_factor do
+ desc "GitLab | Disable Two-factor authentication (2FA) for all users"
+ task disable_for_all_users: :environment do
+ scope = User.with_two_factor
+ count = scope.count
+
+ if count > 0
+ puts "This will disable 2FA for #{count.to_s.red} users..."
+
+ begin
+ ask_to_continue
+ scope.find_each(&:disable_two_factor!)
+ puts "Successfully disabled 2FA for #{count} users.".green
+ rescue Gitlab::TaskAbortedByUserError
+ puts "Quitting...".red
+ end
+ else
+ puts "There are currently no users with 2FA enabled.".yellow
+ end
+ end
+ end
+end
diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake
new file mode 100644
index 00000000000..bf6894a8351
--- /dev/null
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -0,0 +1,6 @@
+require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes')
+
+desc 'GitLab | Sets up PostgreSQL'
+task setup_postgresql: :environment do
+ NamespacesProjectsPathLowerIndexes.new.up
+end
diff --git a/public/robots.txt b/public/robots.txt
index 528f421083e..4f616c7f4c1 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -38,7 +38,8 @@ Disallow: /*/*/edit
Disallow: /*/*/raw
Disallow: /*/*/blame
Disallow: /*/*/commits/*/*
-Disallow: /*/*/commit
+Disallow: /*/*/commit/*.patch
+Disallow: /*/*/commit/*.diff
Disallow: /*/*/compare
Disallow: /*/*/branches/new
Disallow: /*/*/tags/new
diff --git a/public/uploads/.gitkeep b/public/uploads/.gitkeep
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/spec/benchmarks/finders/trending_projects_finder_spec.rb b/spec/benchmarks/finders/trending_projects_finder_spec.rb
new file mode 100644
index 00000000000..551ce21840d
--- /dev/null
+++ b/spec/benchmarks/finders/trending_projects_finder_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe TrendingProjectsFinder, benchmark: true do
+ describe '#execute' do
+ let(:finder) { described_class.new }
+ let(:user) { create(:user) }
+
+ # to_a is used to force actually running the query (instead of just building
+ # it).
+ benchmark_subject { finder.execute(user).non_archived.to_a }
+
+ it { is_expected.to iterate_per_second(500) }
+ end
+end
diff --git a/spec/benchmarks/models/project_spec.rb b/spec/benchmarks/models/project_spec.rb
new file mode 100644
index 00000000000..cee0949edc5
--- /dev/null
+++ b/spec/benchmarks/models/project_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Project, benchmark: true do
+ describe '.trending' do
+ let(:group) { create(:group) }
+ let(:project1) { create(:empty_project, :public, group: group) }
+ let(:project2) { create(:empty_project, :public, group: group) }
+
+ let(:iterations) { 500 }
+
+ before do
+ 2.times do
+ create(:note_on_commit, project: project1)
+ end
+
+ create(:note_on_commit, project: project2)
+ end
+
+ describe 'without an explicit start date' do
+ benchmark_subject { described_class.trending.to_a }
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+
+ describe 'with an explicit start date' do
+ let(:date) { 1.month.ago }
+
+ benchmark_subject { described_class.trending(date).to_a }
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+ end
+
+ describe '.find_with_namespace' do
+ let(:group) { create(:group, name: 'sisinmaru') }
+ let(:project) { create(:project, name: 'maru', namespace: group) }
+
+ describe 'using a capitalized namespace' do
+ benchmark_subject { described_class.find_with_namespace('sisinmaru/MARU') }
+
+ it { is_expected.to iterate_per_second(600) }
+ end
+
+ describe 'using a lowercased namespace' do
+ benchmark_subject { described_class.find_with_namespace('sisinmaru/maru') }
+
+ it { is_expected.to iterate_per_second(600) }
+ end
+ end
+end
diff --git a/spec/controllers/ci/commits_controller_spec.rb b/spec/controllers/ci/commits_controller_spec.rb
deleted file mode 100644
index cc39ce7687c..00000000000
--- a/spec/controllers/ci/commits_controller_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require "spec_helper"
-
-describe Ci::CommitsController do
- describe "GET /status" do
- it "returns status of commit" do
- commit = FactoryGirl.create :ci_commit
- get :status, id: commit.sha, ref_id: commit.ref, project_id: commit.project.id
-
- expect(response).to be_success
- expect(response.code).to eq('200')
- JSON.parse(response.body)["status"] == "pending"
- end
-
- it "returns not_found status" do
- commit = FactoryGirl.create :ci_commit
- get :status, id: commit.sha, ref_id: "deploy", project_id: commit.project.id
-
- expect(response).to be_success
- expect(response.code).to eq('200')
- JSON.parse(response.body)["status"] == "not_found"
- end
- end
-end
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index 53915856357..a474574c6e5 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -88,4 +88,40 @@ describe Projects::TreeController do
end
end
end
+
+ describe '#create_dir' do
+ render_views
+
+ before do
+ post(:create_dir,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: 'master',
+ dir_name: path,
+ new_branch: target_branch,
+ commit_message: 'Test commit message')
+ end
+
+ context 'successful creation' do
+ let(:path) { 'files/new_dir'}
+ let(:target_branch) { 'master-test'}
+
+ it 'redirects to the new directory' do
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/blob/#{target_branch}/#{path}")
+ expect(flash[:notice]).to eq('The directory has been successfully created')
+ end
+ end
+
+ context 'unsuccessful creation' do
+ let(:path) { 'README.md' }
+ let(:target_branch) { 'master'}
+
+ it 'does not allow overwriting of existing files' do
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/blob/master")
+ expect(flash[:alert]).to eq('Directory already exists as a file')
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index f51abfedae5..93c4494c660 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -33,7 +33,7 @@ describe Projects::UploadsController do
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"rails_sample\"'
- expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads"
+ expect(response.body).to match "\"url\":\"/uploads"
expect(response.body).to match '\"is_image\":true'
end
end
@@ -49,7 +49,7 @@ describe Projects::UploadsController do
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
- expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads"
+ expect(response.body).to match "\"url\":\"/uploads"
expect(response.body).to match '\"is_image\":false'
end
end
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/builds_spec.rb b/spec/features/builds_spec.rb
index d0d60491b65..924047a0d8f 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -1,7 +1,6 @@
require 'spec_helper'
describe "Builds" do
-
before do
login_as(:user)
@commit = FactoryGirl.create :ci_commit
@@ -19,4 +18,24 @@ describe "Builds" do
it { expect(page).to have_content @commit.git_commit_message }
it { expect(page).to have_content @commit.git_author_name }
end
+
+ describe "GET /:project/builds/:id/cancel" do
+ before do
+ @build.run!
+ visit cancel_namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
+ end
+
+ it { expect(page).to have_content 'canceled' }
+ it { expect(page).to have_content 'Retry' }
+ end
+
+ describe "POST /:project/builds/:id/retry" do
+ before do
+ visit cancel_namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
+ click_link 'Retry'
+ end
+
+ it { expect(page).to have_content 'pending' }
+ it { expect(page).to have_content 'Cancel' }
+ end
end
diff --git a/spec/features/ci/builds_spec.rb b/spec/features/ci/builds_spec.rb
deleted file mode 100644
index aa0df59c04d..00000000000
--- a/spec/features/ci/builds_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require 'spec_helper'
-
-describe "Builds" do
- before do
- login_as(:user)
- @commit = FactoryGirl.create :ci_commit
- @build = FactoryGirl.create :ci_build, commit: @commit
- @gl_project = @commit.project.gl_project
- @gl_project.team << [@user, :master]
- end
-
- describe "GET /:project/builds/:id/cancel" do
- before do
- @build.run!
- visit cancel_ci_project_build_path(@commit.project, @build)
- end
-
- it { expect(page).to have_content 'canceled' }
- it { expect(page).to have_content 'Retry' }
- end
-
- describe "POST /:project/builds/:id/retry" do
- before do
- visit cancel_ci_project_build_path(@commit.project, @build)
- click_link 'Retry'
- end
-
- it { expect(page).to have_content 'pending' }
- it { expect(page).to have_content 'Cancel' }
- end
-end
diff --git a/spec/features/ci/projects_spec.rb b/spec/features/ci/projects_spec.rb
deleted file mode 100644
index c633acf85fb..00000000000
--- a/spec/features/ci/projects_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe "Projects" do
- let(:user) { create(:user) }
-
- before do
- login_as(user)
- @project = FactoryGirl.create :ci_project, name: "GitLab / gitlab-shell"
- @project.gl_project.team << [user, :master]
- end
-
- describe "GET /ci/projects/:id" do
- before do
- visit ci_project_path(@project)
- end
-
- it { expect(page).to have_content @project.name }
- it { expect(page).to have_content 'All commits' }
- end
-end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 5da220859e3..1adc2cdf70a 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
@@ -28,8 +29,16 @@ describe "Commits" do
it { expect(page).to have_content @commit.git_author_name }
end
- describe "Cancel commit" do
+ describe "Cancel all builds" do
it "cancels commit" do
+ visit ci_status_path(@commit)
+ click_on "Cancel all"
+ expect(page).to have_content "canceled"
+ end
+ end
+
+ describe "Cancel build" do
+ it "cancels build" do
visit ci_status_path(@commit)
click_on "Cancel"
expect(page).to have_content "canceled"
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index a362c54b9ad..aac93b17a38 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -50,7 +50,7 @@ feature 'Project', feature: true do
end
def remove_project
- click_link "Remove project"
+ click_button "Remove project"
fill_in 'confirm_name_input', with: project.path
click_button 'Confirm'
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index db20b23f87d..b1648055462 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -6,9 +6,11 @@ describe IssuesFinder do
let(:project1) { create(:project) }
let(:project2) { create(:project) }
let(:milestone) { create(:milestone, project: project1) }
+ let(:label) { create(:label, project: project2) }
let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) }
let(:issue2) { create(:issue, author: user, assignee: user, project: project2) }
let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) }
+ let!(:label_link) { create(:label_link, label: label, target: issue2) }
before do
project1.team << [user, :master]
@@ -48,6 +50,24 @@ describe IssuesFinder do
expect(issues).to eq([issue1])
end
+ it 'should filter by no milestone id' do
+ params = { scope: "all", milestone_title: Milestone::None.title, state: 'opened' }
+ issues = IssuesFinder.new(user, params).execute
+ expect(issues).to match_array([issue2, issue3])
+ end
+
+ it 'should filter by label name' do
+ params = { scope: "all", label_name: label.title, state: 'opened' }
+ issues = IssuesFinder.new(user, params).execute
+ expect(issues).to eq([issue2])
+ end
+
+ it 'should filter by no label name' do
+ params = { scope: "all", label_name: Label::None.title, state: 'opened' }
+ issues = IssuesFinder.new(user, params).execute
+ expect(issues).to match_array([issue1, issue3])
+ end
+
it 'should be empty for unauthorized user' do
params = { scope: "all", state: 'opened' }
issues = IssuesFinder.new(nil, params).execute
diff --git a/spec/finders/trending_projects_finder_spec.rb b/spec/finders/trending_projects_finder_spec.rb
new file mode 100644
index 00000000000..a49cbfd5160
--- /dev/null
+++ b/spec/finders/trending_projects_finder_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe TrendingProjectsFinder do
+ let(:user) { build(:user) }
+
+ describe '#execute' do
+ describe 'without an explicit start date' do
+ subject { described_class.new }
+
+ it 'returns the trending projects' do
+ relation = double(:ar_relation)
+
+ allow(subject).to receive(:projects_for)
+ .with(user)
+ .and_return(relation)
+
+ allow(relation).to receive(:trending)
+ .with(an_instance_of(ActiveSupport::TimeWithZone))
+ end
+ end
+
+ describe 'with an explicit start date' do
+ let(:date) { 2.months.ago }
+
+ subject { described_class.new }
+
+ it 'returns the trending projects' do
+ relation = double(:ar_relation)
+
+ allow(subject).to receive(:projects_for)
+ .with(user)
+ .and_return(relation)
+
+ allow(relation).to receive(:trending)
+ .with(date)
+ end
+ end
+ end
+end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 0c8d06b7059..fb70a36dc02 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -14,6 +14,11 @@ describe LabelsHelper do
expect(label).not_to receive(:project)
link_to_label(label)
end
+
+ it 'includes option for "No Label"' do
+ result = project_labels_options(project)
+ expect(result).to include('No Label')
+ end
end
context 'without @project set' do
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js.coffee b/spec/javascripts/behaviors/quick_submit_spec.js.coffee
new file mode 100644
index 00000000000..09708c12ed4
--- /dev/null
+++ b/spec/javascripts/behaviors/quick_submit_spec.js.coffee
@@ -0,0 +1,70 @@
+#= require behaviors/quick_submit
+
+describe 'Quick Submit behavior', ->
+ fixture.preload('behaviors/quick_submit.html')
+
+ beforeEach ->
+ fixture.load('behaviors/quick_submit.html')
+
+ # Prevent a form submit from moving us off the testing page
+ $('form').submit (e) -> e.preventDefault()
+
+ @spies = {
+ submit: spyOnEvent('form', 'submit')
+ }
+
+ it 'does not respond to other keyCodes', ->
+ $('input').trigger(keydownEvent(keyCode: 32))
+
+ expect(@spies.submit).not.toHaveBeenTriggered()
+
+ it 'does not respond to Enter alone', ->
+ $('input').trigger(keydownEvent(ctrlKey: false, metaKey: false))
+
+ expect(@spies.submit).not.toHaveBeenTriggered()
+
+ it 'does not respond to repeated events', ->
+ $('input').trigger(keydownEvent(repeat: true))
+
+ expect(@spies.submit).not.toHaveBeenTriggered()
+
+ it 'disables submit buttons', ->
+ $('textarea').trigger(keydownEvent())
+
+ expect($('input[type=submit]')).toBeDisabled()
+ expect($('button[type=submit]')).toBeDisabled()
+
+ # We cannot stub `navigator.userAgent` for CI's `rake teaspoon` task, so we'll
+ # only run the tests that apply to the current platform
+ if navigator.userAgent.match(/Macintosh/)
+ it 'responds to Meta+Enter', ->
+ $('input').trigger(keydownEvent())
+
+ expect(@spies.submit).toHaveBeenTriggered()
+
+ it 'excludes other modifier keys', ->
+ $('input').trigger(keydownEvent(altKey: true))
+ $('input').trigger(keydownEvent(ctrlKey: true))
+ $('input').trigger(keydownEvent(shiftKey: true))
+
+ expect(@spies.submit).not.toHaveBeenTriggered()
+ else
+ it 'responds to Ctrl+Enter', ->
+ $('input').trigger(keydownEvent())
+
+ expect(@spies.submit).toHaveBeenTriggered()
+
+ it 'excludes other modifier keys', ->
+ $('input').trigger(keydownEvent(altKey: true))
+ $('input').trigger(keydownEvent(metaKey: true))
+ $('input').trigger(keydownEvent(shiftKey: true))
+
+ expect(@spies.submit).not.toHaveBeenTriggered()
+
+ keydownEvent = (options) ->
+ if navigator.userAgent.match(/Macintosh/)
+ defaults = { keyCode: 13, metaKey: true }
+ else
+ defaults = { keyCode: 13, ctrlKey: true }
+
+ $.Event('keydown', $.extend({}, defaults, options))
diff --git a/spec/javascripts/fixtures/behaviors/quick_submit.html.haml b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
new file mode 100644
index 00000000000..b80a28a33ea
--- /dev/null
+++ b/spec/javascripts/fixtures/behaviors/quick_submit.html.haml
@@ -0,0 +1,6 @@
+%form{ action: '/foo' }
+ %input.js-quick-submit{ type: 'text' }
+ %textarea.js-quick-submit
+
+ %input{ type: 'submit'} Submit
+ %button.btn{ type: 'submit' } Submit
diff --git a/spec/javascripts/fixtures/line_highlighter.html.haml b/spec/javascripts/fixtures/line_highlighter.html.haml
index da1ebcdb23c..514877340e4 100644
--- a/spec/javascripts/fixtures/line_highlighter.html.haml
+++ b/spec/javascripts/fixtures/line_highlighter.html.haml
@@ -1,4 +1,4 @@
-#tree-content-holder
+#blob-content-holder
.file-content
.line-numbers
- 1.upto(25) do |i|
diff --git a/spec/javascripts/line_highlighter_spec.js.coffee b/spec/javascripts/line_highlighter_spec.js.coffee
index 57453c716a5..a073f21e7bc 100644
--- a/spec/javascripts/line_highlighter_spec.js.coffee
+++ b/spec/javascripts/line_highlighter_spec.js.coffee
@@ -39,7 +39,7 @@ describe 'LineHighlighter', ->
expect(spy).toHaveBeenPrevented()
it 'handles garbage input from the hash', ->
- func = -> new LineHighlighter('#tree-content-holder')
+ func = -> new LineHighlighter('#blob-content-holder')
expect(func).not.toThrow()
describe '#clickHandler', ->
diff --git a/spec/javascripts/spec_helper.coffee b/spec/javascripts/spec_helper.coffee
index 47b41dd2c81..90b02a6aec5 100644
--- a/spec/javascripts/spec_helper.coffee
+++ b/spec/javascripts/spec_helper.coffee
@@ -9,6 +9,7 @@
# require the specific files that are being used in the spec that tests them.
#= require jquery
+#= require jquery.turbolinks
#= require bootstrap
#= require underscore
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index b6d04330599..b60e23454d6 100644
--- a/spec/lib/gitlab/backend/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -15,4 +15,17 @@ describe Gitlab::Shell do
it { is_expected.to respond_to :fork_repository }
it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") }
+
+ describe Gitlab::Shell::KeyAdder do
+ describe '#add_key' do
+ it 'normalizes space characters in the key' do
+ io = spy
+ adder = described_class.new(io)
+
+ adder.add_key('key-42', "sha-rsa foo\tbar\tbaz")
+
+ expect(io).to have_received(:puts).with("key-42\tsha-rsa foo bar baz")
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
new file mode 100644
index 00000000000..7cdebdf209a
--- /dev/null
+++ b/spec/lib/gitlab/database_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Gitlab::Database do
+ # These are just simple smoke tests to check if the methods work (regardless
+ # of what they may return).
+ describe '.mysql?' do
+ subject { described_class.mysql? }
+
+ it { is_expected.to satisfy { |val| val == true || val == false } }
+ end
+
+ describe '.postgresql?' do
+ subject { described_class.postgresql? }
+
+ it { is_expected.to satisfy { |val| val == true || val == false } }
+ end
+end
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index e8208e15e29..8fb432367b6 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -13,7 +13,6 @@ describe Gitlab::Email::AttachmentUploader do
expect(link).not_to be_nil
expect(link[:is_image]).to be_truthy
expect(link[:alt]).to eq("bricks")
- expect(link[:url]).to include("/#{project.path_with_namespace}")
expect(link[:url]).to include("bricks.png")
end
end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index fd2e5f6d0e1..b5b56a34952 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -13,6 +13,17 @@ describe Gitlab::LDAP::User do
let(:auth_hash) do
OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info)
end
+ let(:ldap_user_upper_case) { Gitlab::LDAP::User.new(auth_hash_upper_case) }
+ let(:info_upper_case) do
+ {
+ name: 'John',
+ email: 'John@Example.com', # Email address has upper case chars
+ nickname: 'john'
+ }
+ end
+ let(:auth_hash_upper_case) do
+ OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info_upper_case)
+ end
describe :changed? do
it "marks existing ldap user as changed" do
@@ -57,6 +68,16 @@ describe Gitlab::LDAP::User do
expect(existing_user.id).to eql ldap_user.gl_user.id
end
+ it 'connects to existing ldap user if the extern_uid changes and email address has upper case characters' do
+ existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
+ expect{ ldap_user_upper_case.save }.not_to change{ User.count }
+
+ existing_user.reload
+ expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
+ expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
+ expect(existing_user.id).to eql ldap_user.gl_user.id
+ end
+
it 'maintains an identity per provider' do
existing_user = create(:omniauth_user, email: 'john@example.com', provider: 'twitter')
expect(existing_user.identities.count).to eql(1)
diff --git a/spec/lib/gitlab/markdown/upload_link_filter_spec.rb b/spec/lib/gitlab/markdown/upload_link_filter_spec.rb
new file mode 100644
index 00000000000..9ae45a6f559
--- /dev/null
+++ b/spec/lib/gitlab/markdown/upload_link_filter_spec.rb
@@ -0,0 +1,75 @@
+# encoding: UTF-8
+
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe UploadLinkFilter do
+ def filter(doc, contexts = {})
+ contexts.reverse_merge!({
+ project: project
+ })
+
+ described_class.call(doc, contexts)
+ end
+
+ def image(path)
+ %(
)
+ end
+
+ def link(path)
+ %(#{path})
+ end
+
+ let(:project) { create(:project) }
+
+ shared_examples :preserve_unchanged do
+ it 'does not modify any relative URL in anchor' do
+ doc = filter(link('README.md'))
+ expect(doc.at_css('a')['href']).to eq 'README.md'
+ end
+
+ it 'does not modify any relative URL in image' do
+ doc = filter(image('files/images/logo-black.png'))
+ expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
+ end
+ end
+
+ it 'does not raise an exception on invalid URIs' do
+ act = link("://foo")
+ expect { filter(act) }.not_to raise_error
+ end
+
+ context 'with a valid repository' do
+ it 'rebuilds relative URL for a link' do
+ doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('a')['href']).
+ to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ end
+
+ it 'rebuilds relative URL for an image' do
+ doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('a')['href']).
+ to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+ end
+
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'))
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
+
+ it 'supports Unicode filenames' do
+ path = '/uploads/한글.png'
+ escaped = Addressable::URI.escape(path)
+
+ # Stub these methods so the file doesn't actually need to be in the repo
+ allow_any_instance_of(described_class).
+ to receive(:file_exists?).and_return(true)
+ allow_any_instance_of(described_class).
+ to receive(:image?).with(path).and_return(true)
+
+ doc = filter(image(escaped))
+ expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb
new file mode 100644
index 00000000000..260364a513e
--- /dev/null
+++ b/spec/lib/gitlab/uploads_transfer_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::UploadsTransfer do
+ before do
+ @root_dir = File.join(Rails.root, "public", "uploads")
+ @upload_transfer = Gitlab::UploadsTransfer.new
+
+ @project_path_was = "test_project_was"
+ @project_path = "test_project"
+ @namespace_path_was = "test_namespace_was"
+ @namespace_path = "test_namespace"
+ end
+
+ after do
+ FileUtils.rm_rf([
+ File.join(@root_dir, @namespace_path),
+ File.join(@root_dir, @namespace_path_was)
+ ])
+ end
+
+ describe '#move_project' do
+ it "moves project upload to another namespace" do
+ FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+ @upload_transfer.move_project(@project_path, @namespace_path_was, @namespace_path)
+
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ describe '#rename_project' do
+ it "renames project" do
+ FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was))
+ @upload_transfer.rename_project(@project_path_was, @project_path, @namespace_path)
+
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ describe '#rename_namespace' do
+ it "renames namespace" do
+ FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
+ @upload_transfer.rename_namespace(@namespace_path_was, @namespace_path)
+
+ expected_path = File.join(@root_dir, @namespace_path, @project_path)
+ expect(Dir.exist?(expected_path)).to be_truthy
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/build_spec.rb
similarity index 74%
rename from spec/models/ci/build_spec.rb
rename to 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/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
new file mode 100644
index 00000000000..f7ed30f8198
--- /dev/null
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -0,0 +1,189 @@
+require 'spec_helper'
+
+describe CaseSensitivity do
+ describe '.iwhere' do
+ let(:connection) { ActiveRecord::Base.connection }
+ let(:model) { Class.new { include CaseSensitivity } }
+
+ describe 'using PostgreSQL' do
+ before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ allow(Gitlab::Database).to receive(:mysql?).and_return(false)
+ end
+
+ describe 'with a single column/value pair' do
+ it 'returns the criteria for a column and a value' do
+ criteria = double(:criteria)
+
+ expect(connection).to receive(:quote_table_name).
+ with(:foo).
+ and_return('"foo"')
+
+ expect(model).to receive(:where).
+ with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar').
+ and_return(criteria)
+
+ expect(model.iwhere(foo: 'bar')).to eq(criteria)
+ end
+
+ it 'returns the criteria for a column with a table, and a value' do
+ criteria = double(:criteria)
+
+ expect(connection).to receive(:quote_table_name).
+ with(:'foo.bar').
+ and_return('"foo"."bar"')
+
+ expect(model).to receive(:where).
+ with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
+ and_return(criteria)
+
+ expect(model.iwhere(:'foo.bar' => 'bar')).to eq(criteria)
+ end
+ end
+
+ describe 'with multiple column/value pairs' do
+ it 'returns the criteria for a column and a value' do
+ initial = double(:criteria)
+ final = double(:criteria)
+
+ expect(connection).to receive(:quote_table_name).
+ with(:foo).
+ and_return('"foo"')
+
+ expect(connection).to receive(:quote_table_name).
+ with(:bar).
+ and_return('"bar"')
+
+ expect(model).to receive(:where).
+ with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar').
+ and_return(initial)
+
+ expect(initial).to receive(:where).
+ with(%q{LOWER("bar") = LOWER(:value)}, value: 'baz').
+ and_return(final)
+
+ got = model.iwhere(foo: 'bar', bar: 'baz')
+
+ expect(got).to eq(final)
+ end
+
+ it 'returns the criteria for a column with a table, and a value' do
+ initial = double(:criteria)
+ final = double(:criteria)
+
+ expect(connection).to receive(:quote_table_name).
+ with(:'foo.bar').
+ and_return('"foo"."bar"')
+
+ expect(connection).to receive(:quote_table_name).
+ with(:'foo.baz').
+ and_return('"foo"."baz"')
+
+ expect(model).to receive(:where).
+ with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
+ and_return(initial)
+
+ expect(initial).to receive(:where).
+ with(%q{LOWER("foo"."baz") = LOWER(:value)}, value: 'baz').
+ and_return(final)
+
+ got = model.iwhere(:'foo.bar' => 'bar',
+ :'foo.baz' => 'baz')
+
+ expect(got).to eq(final)
+ end
+ end
+ end
+
+ describe 'using MySQL' do
+ before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ allow(Gitlab::Database).to receive(:mysql?).and_return(true)
+ end
+
+ describe 'with a single column/value pair' do
+ it 'returns the criteria for a column and a value' do
+ criteria = double(:criteria)
+
+ expect(connection).to receive(:quote_table_name).
+ with(:foo).
+ and_return('`foo`')
+
+ expect(model).to receive(:where).
+ with(%q{`foo` = :value}, value: 'bar').
+ and_return(criteria)
+
+ expect(model.iwhere(foo: 'bar')).to eq(criteria)
+ end
+
+ it 'returns the criteria for a column with a table, and a value' do
+ criteria = double(:criteria)
+
+ expect(connection).to receive(:quote_table_name).
+ with(:'foo.bar').
+ and_return('`foo`.`bar`')
+
+ expect(model).to receive(:where).
+ with(%q{`foo`.`bar` = :value}, value: 'bar').
+ and_return(criteria)
+
+ expect(model.iwhere(:'foo.bar' => 'bar')).
+ to eq(criteria)
+ end
+ end
+
+ describe 'with multiple column/value pairs' do
+ it 'returns the criteria for a column and a value' do
+ initial = double(:criteria)
+ final = double(:criteria)
+
+ expect(connection).to receive(:quote_table_name).
+ with(:foo).
+ and_return('`foo`')
+
+ expect(connection).to receive(:quote_table_name).
+ with(:bar).
+ and_return('`bar`')
+
+ expect(model).to receive(:where).
+ with(%q{`foo` = :value}, value: 'bar').
+ and_return(initial)
+
+ expect(initial).to receive(:where).
+ with(%q{`bar` = :value}, value: 'baz').
+ and_return(final)
+
+ got = model.iwhere(foo: 'bar', bar: 'baz')
+
+ expect(got).to eq(final)
+ end
+
+ it 'returns the criteria for a column with a table, and a value' do
+ initial = double(:criteria)
+ final = double(:criteria)
+
+ expect(connection).to receive(:quote_table_name).
+ with(:'foo.bar').
+ and_return('`foo`.`bar`')
+
+ expect(connection).to receive(:quote_table_name).
+ with(:'foo.baz').
+ and_return('`foo`.`baz`')
+
+ expect(model).to receive(:where).
+ with(%q{`foo`.`bar` = :value}, value: 'bar').
+ and_return(initial)
+
+ expect(initial).to receive(:where).
+ with(%q{`foo`.`baz` = :value}, value: 'baz').
+ and_return(final)
+
+ got = model.iwhere(:'foo.bar' => 'bar',
+ :'foo.baz' => 'baz')
+
+ expect(got).to eq(final)
+ end
+ end
+ 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/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 17a49013d25..6aaf1c036b0 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -165,6 +165,17 @@ describe MergeRequest do
end
end
+ describe "#hook_attrs" do
+ it "has all the required keys" do
+ attrs = subject.hook_attrs
+ attrs = attrs.to_h
+ expect(attrs).to include(:source)
+ expect(attrs).to include(:target)
+ expect(attrs).to include(:last_commit)
+ expect(attrs).to include(:work_in_progress)
+ end
+ end
+
it_behaves_like 'an editable mentionable' do
subject { create(:merge_request) }
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
new file mode 100644
index 00000000000..f8a3493f52d
--- /dev/null
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -0,0 +1,56 @@
+# == 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
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
new file mode 100644
index 00000000000..3dbd2346bcc
--- /dev/null
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -0,0 +1,56 @@
+# == 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
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 8b5d2c3a1c1..f93935ebe3b 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -423,4 +423,42 @@ describe Project do
it { expect(project.gitlab_ci?).to be_truthy }
it { expect(project.gitlab_ci_project).to be_a(Ci::Project) }
end
+
+ describe '.trending' do
+ let(:group) { create(:group) }
+ let(:project1) { create(:empty_project, :public, group: group) }
+ let(:project2) { create(:empty_project, :public, group: group) }
+
+ before do
+ 2.times do
+ create(:note_on_commit, project: project1)
+ end
+
+ create(:note_on_commit, project: project2)
+ end
+
+ describe 'without an explicit start date' do
+ subject { described_class.trending.to_a }
+
+ it 'sorts Projects by the amount of notes in descending order' do
+ expect(subject).to eq([project1, project2])
+ end
+ end
+
+ describe 'with an explicit start date' do
+ let(:date) { 2.months.ago }
+
+ subject { described_class.trending(date).to_a }
+
+ before do
+ 2.times do
+ create(:note_on_commit, project: project2, created_at: date)
+ end
+ end
+
+ it 'sorts Projects by the amount of notes in descending order' do
+ expect(subject).to eq([project2, project1])
+ end
+ end
+ end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index a213ffe6c4b..da87ea5b84f 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -103,4 +103,27 @@ describe Service do
end
end
end
+
+ describe "#prop_updated?" do
+ let(:service) do
+ BambooService.create(
+ project: create(:project),
+ properties: {
+ bamboo_url: 'http://gitlab.com',
+ username: 'mic',
+ password: "password"
+ }
+ )
+ end
+
+ it "returns false" do
+ service.username = "key_changed"
+ expect(service.prop_updated?(:bamboo_url)).to be_falsy
+ end
+
+ it "returns true" do
+ service.bamboo_url = "http://other.com"
+ expect(service.prop_updated?(:bamboo_url)).to be_truthy
+ end
+ end
end
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
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 9aa60826f21..c0226605a23 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -3,6 +3,8 @@ require "spec_helper"
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:user2) { create(:user) }
let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
Service.available_services_names.each do |service|
@@ -51,11 +53,40 @@ describe API::API, api: true do
describe "GET /projects/:id/services/#{service.dasherize}" do
include_context service
- it "should get #{service} settings" do
+ # inject some properties into the service
+ before do
+ project.build_missing_services
+ service_object = project.send(service_method)
+ service_object.properties = service_attrs
+ service_object.save
+ end
+
+ it 'should return authentication error when unauthenticated' do
+ get api("/projects/#{project.id}/services/#{dashed_service}")
+ expect(response.status).to eq(401)
+ end
+
+ it "should return all properties of service #{service} when authenticated as admin" do
+ get api("/projects/#{project.id}/services/#{dashed_service}", admin)
+
+ expect(response.status).to eq(200)
+ expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map)
+ end
+
+ it "should return properties of service #{service} other than passwords when authenticated as project owner" do
get api("/projects/#{project.id}/services/#{dashed_service}", user)
expect(response.status).to eq(200)
+ expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords)
end
+
+ it "should return error when authenticated but not a project owner" do
+ project.team << [user2, :developer]
+ get api("/projects/#{project.id}/services/#{dashed_service}", user2)
+
+ expect(response.status).to eq(403)
+ end
+
end
end
end
diff --git a/spec/requests/ci/builds_spec.rb b/spec/requests/ci/builds_spec.rb
deleted file mode 100644
index f68116c52aa..00000000000
--- a/spec/requests/ci/builds_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe "Builds" do
- before do
- @commit = FactoryGirl.create :ci_commit
- @build = FactoryGirl.create :ci_build, commit: @commit
- end
-
- describe "GET /:project/builds/:id/status.json" do
- before do
- get status_ci_project_build_path(@commit.project, @build), format: :json
- end
-
- it { expect(response.status).to eq(200) }
- it { expect(response.body).to include(@build.sha) }
- end
-end
diff --git a/spec/requests/ci/commits_spec.rb b/spec/requests/ci/commits_spec.rb
deleted file mode 100644
index f43a3982d71..00000000000
--- a/spec/requests/ci/commits_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'spec_helper'
-
-describe "Commits" do
- before do
- @commit = FactoryGirl.create :ci_commit
- end
-
- describe "GET /:project/refs/:ref_name/commits/:id/status.json" do
- before do
- get status_ci_project_commits_path(@commit.project, @commit.sha), format: :json
- end
-
- it { expect(response.status).to eq(200) }
- it { expect(response.body).to include(@commit.sha) }
- end
-end
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index f12e09c58c3..ddee2e62dfc 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -37,7 +37,6 @@ describe Projects::DownloadService do
it { expect(@link_to_file).to have_key('url') }
it { expect(@link_to_file).to have_key('is_image') }
it { expect(@link_to_file['is_image']).to be true }
- it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('rails_sample.jpg') }
it { expect(@link_to_file['alt']).to eq('rails_sample') }
end
@@ -52,7 +51,6 @@ describe Projects::DownloadService do
it { expect(@link_to_file).to have_key('url') }
it { expect(@link_to_file).to have_key('is_image') }
it { expect(@link_to_file['is_image']).to be false }
- it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('doc_sample.txt') }
it { expect(@link_to_file['alt']).to eq('doc_sample.txt') }
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index bb7da33b12e..47755bfc990 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -7,6 +7,8 @@ describe Projects::TransferService do
context 'namespace -> namespace' do
before do
+ allow_any_instance_of(Gitlab::UploadsTransfer).
+ to receive(:move_project).and_return(true)
group.add_owner(user)
@result = transfer_project(project, user, group)
end
diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb
index fa4ff6b01ad..1b1a80d1fe7 100644
--- a/spec/services/projects/upload_service_spec.rb
+++ b/spec/services/projects/upload_service_spec.rb
@@ -18,7 +18,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('banana_sample') }
it { expect(@link_to_file[:is_image]).to equal(true) }
- it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('banana_sample.gif') }
end
@@ -34,7 +33,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_value('dk') }
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file[:is_image]).to equal(true) }
- it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('dk.png') }
end
@@ -49,7 +47,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('rails_sample') }
it { expect(@link_to_file[:is_image]).to equal(true) }
- it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
end
@@ -64,7 +61,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('doc_sample.txt') }
it { expect(@link_to_file[:is_image]).to equal(false) }
- it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index 48c49e2f717..a31fc1e4b07 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -13,8 +13,8 @@ describe SystemHooksService do
it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :email, :user_id) }
it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
- it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
+ it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
+ it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
it { expect(event_data(key, :create)).to include(:username, :key, :id) }
it { expect(event_data(key, :destroy)).to include(:username, :key, :id) }
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
index 4d007ae55ee..d1c999cad4d 100644
--- a/spec/support/services_shared_context.rb
+++ b/spec/support/services_shared_context.rb
@@ -3,7 +3,13 @@ Service.available_services_names.each do |service|
let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym }
let(:service_klass) { "#{service}_service".classify.constantize }
- let(:service_attrs_list) { service_klass.new.fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
+ let(:service_fields) { service_klass.new.fields }
+ let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
+ let(:service_attrs_list_without_passwords) do
+ service_fields.
+ select { |field| field[:type] != 'password' }.
+ map { |field| field[:name].to_sym}
+ end
let(:service_attrs) do
service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/