#
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 19a07b6a03..593a8f4213 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -66,6 +66,11 @@ class @MergeRequestTabs
@setCurrentAction(action)
+ scrollToElement: (container) ->
+ if window.location.hash
+ $el = $("#{container} #{window.location.hash}")
+ $('body').scrollTo($el.offset().top) if $el.length
+
# Activate a tab based on the current action
activateTab: (action) ->
action = 'notes' if action == 'show'
@@ -122,6 +127,7 @@ class @MergeRequestTabs
document.getElementById('commits').innerHTML = data.html
$('.js-timeago').timeago()
@commitsLoaded = true
+ @scrollToElement("#commits")
loadDiff: (source) ->
return if @diffsLoaded
@@ -131,14 +137,18 @@ class @MergeRequestTabs
success: (data) =>
document.getElementById('diffs').innerHTML = data.html
@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/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 995a2f2409..3176e5a896 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -15,11 +15,12 @@ class @MergeRequestWidget
type: 'GET'
url: $('.merge-request').data('url')
success: (data) =>
- switch data.state
- when 'merged'
- location.reload()
- else
- setTimeout(merge_request_widget.mergeInProgress, 2000)
+ if data.state == "merged"
+ location.reload()
+ else if data.merge_error
+ $('.mr-widget-body').html("
" + data.merge_error + "
")
+ else
+ setTimeout(merge_request_widget.mergeInProgress, 2000)
dataType: 'json'
getMergeStatus: ->
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index ce638c2641..ea75c656bc 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"
@@ -277,13 +270,15 @@ class @Notes
Updates the current note field.
###
- updateNote: (xhr, note, status) =>
- note_li = $(".note-row-" + note.id)
- note_li.replaceWith(note.html)
- note_li.find('.note-edit-form').hide()
- note_li.find('.note-body > .note-text').show()
- note_li.find('js-task-list-container').taskList('enable')
- @enableTaskList()
+ updateNote: (_xhr, note, _status) =>
+ # Convert returned HTML to a jQuery object so we can modify it further
+ $html = $(note.html)
+ $html.syntaxHighlight()
+ $html.find('.js-task-list-container').taskList('enable')
+
+ # Find the note's `li` element by ID and replace it with the updated HTML
+ $note_li = $("#note_#{note.id}")
+ $note_li.replaceWith($html)
###
Called in response to clicking the edit note link
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
index 5b6f9e7e3f..8decaedd87 100644
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ b/app/assets/javascripts/shortcuts_navigation.coffee
@@ -7,6 +7,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
+ Mousetrap.bind('g b', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'))
diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee
index d428db5b42..de8eebcd0b 100644
--- a/app/assets/javascripts/tree.js.coffee
+++ b/app/assets/javascripts/tree.js.coffee
@@ -16,6 +16,9 @@ class @TreeView
li = $("tr.tree-item")
liSelected = null
$('body').keydown (e) ->
+ if $("input:focus").length > 0 && (e.which == 38 || e.which == 40)
+ return false
+
if e.which is 40
if liSelected
next = liSelected.next()
@@ -38,4 +41,4 @@ class @TreeView
$(liSelected).focus()
else if e.which is 13
path = $('.tree-item.selected .tree-item-file-name a').attr('href')
- Turbolinks.visit(path)
+ if path then Turbolinks.visit(path)
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index d9ede63794..7b060ce485 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 2fc7bf1720..0000000000
--- a/app/assets/stylesheets/base/variables.scss
+++ /dev/null
@@ -1,43 +0,0 @@
-$hover: #FFFAF1;
-$gl-text-color: #54565b;
-$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: #E7E9ED;
-$background-color: #F8FAFC;
-$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/ci/runners.scss b/app/assets/stylesheets/ci/runners.scss
deleted file mode 100644
index 2b15ab8312..0000000000
--- a/app/assets/stylesheets/ci/runners.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-.ci-body {
- .runner-state {
- padding: 6px 12px;
- margin-right: 10px;
- color: #FFF;
-
- &.runner-state-shared {
- background: #32b186;
- }
- &.runner-state-specific {
- background: #3498db;
- }
- }
-
- .runner-status-online {
- color: green;
- }
-
- .runner-status-offline {
- color: gray;
- }
-
- .runner-status-paused {
- color: red;
- }
-
- .runner {
- .btn {
- padding: 1px 6px;
- }
-
- h4 {
- font-weight: normal;
- }
- }
-}
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
new file mode 100644
index 0000000000..1ec9d2fd84
--- /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 91%
rename from app/assets/stylesheets/generic/avatar.scss
rename to app/assets/stylesheets/framework/avatar.scss
index 221cb6a04a..36e582d485 100644
--- a/app/assets/stylesheets/generic/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -28,6 +28,7 @@
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
+ &.s110 { width: 110px; height: 110px; margin-right: 15px; }
&.s140 { width: 140px; height: 140px; margin-right: 20px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; }
}
@@ -42,6 +43,7 @@
&.s32 { font-size: 22px; line-height: 32px; }
&.s60 { font-size: 32px; line-height: 60px; }
&.s90 { font-size: 36px; line-height: 90px; }
+ &.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
&.s140 { font-size: 72px; line-height: 140px; }
&.s160 { font-size: 96px; line-height: 160px; }
}
diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
similarity index 55%
rename from app/assets/stylesheets/generic/blocks.scss
rename to app/assets/stylesheets/framework/blocks.scss
index 6ce34b5c3e..5949a0fd5a 100644
--- a/app/assets/stylesheets/generic/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -18,6 +18,7 @@
line-height: 36px;
}
+.content-block,
.gray-content-block {
margin: -$gl-padding;
background-color: $background-color;
@@ -27,6 +28,10 @@
border-bottom: 1px solid $border-color;
color: $gl-gray;
+ &.white {
+ background-color: white;
+ }
+
&.top-block {
border-top: none;
}
@@ -60,3 +65,48 @@
line-height: 42px;
}
}
+
+.cover-block {
+ text-align: center;
+ background: #f7f8fa;
+ margin: -$gl-padding;
+ margin-bottom: 0;
+ padding: 44px $gl-padding;
+ border-bottom: 1px solid $border-color;
+ position: relative;
+
+ .avatar-holder {
+ margin-bottom: 16px;
+
+ .avatar, .identicon {
+ margin: 0 auto;
+ float: none;
+ }
+
+ .identicon {
+ @include border-radius(50%);
+ }
+ }
+
+ .cover-title {
+ color: $gl-header-color;
+ margin: 0;
+ font-size: 23px;
+ font-weight: normal;
+ margin: 16px 0 5px 0;
+ color: #4c4e54;
+ font-size: 23px;
+ line-height: 1.1;
+ }
+
+ .cover-desc {
+ padding: 0 $gl-padding;
+ color: $gl-text-color;
+ }
+
+ .cover-controls {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
new file mode 100644
index 0000000000..0402441958
--- /dev/null
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -0,0 +1,182 @@
+@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;
+
+ .badge {
+ font-weight: normal;
+ background-color: #eee;
+ color: #78a;
+ }
+
+ &.active {
+ border-color: $gl-info;
+ background: $gl-info;
+ color: #fff;
+
+ .badge {
+ color: $gl-info;
+ background-color: white;
+ }
+ }
+ }
+}
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 97%
rename from app/assets/stylesheets/generic/common.scss
rename to app/assets/stylesheets/framework/common.scss
index b659717b4e..e1a1793be9 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -1,8 +1,8 @@
/** COLORS **/
.cgray { color: $gl-gray; }
.clgray { color: #BBB }
-.cred { color: #D12F19 }
-.cgreen { color: #4a2 }
+.cred { color: $gl-text-red; }
+.cgreen { color: $gl-text-green; }
.cdark { color: #444 }
/** COMMON CLASSES **/
@@ -313,7 +313,7 @@ table {
}
.wiki .highlight, .note-body .highlight {
- margin-bottom: 9px;
+ margin: 12px 0 12px 0;
}
.wiki .code {
@@ -381,6 +381,10 @@ table {
&.no-bottom {
margin-bottom: 0;
}
+
+ &.no-top {
+ margin-top: 0;
+ }
}
.dropzone .dz-preview .dz-progress {
@@ -390,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 98%
rename from app/assets/stylesheets/generic/files.scss
rename to app/assets/stylesheets/framework/files.scss
index 9dd7774788..35db00281e 100644
--- a/app/assets/stylesheets/generic/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -10,6 +10,10 @@
border-bottom: 1px solid #E7E9EE;
margin-bottom: 1em;
+ &.readme-holder {
+ border-bottom: 0;
+ }
+
table {
@extend .table;
}
@@ -94,7 +98,6 @@
border-right: none;
}
background: #fff;
- padding: 10px $gl-padding;
}
.lines {
pre {
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 4282832e2b..0edfe24f19 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 bd9200ace2..5ae0520fd7 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 b758a526fb..91e6975e26 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -26,7 +26,6 @@ header {
min-height: $header-height;
background-color: #fff;
border: none;
- border-bottom: 1px solid #EEE;
.container-fluid {
width: 100% !important;
@@ -51,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;
+ }
}
}
}
@@ -88,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 b1fb87a683..93377e45e7 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 b91c15d891..c7b3b60e76 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 93%
rename from app/assets/stylesheets/generic/lists.scss
rename to app/assets/stylesheets/framework/lists.scss
index 3bfed8de77..f6942db581 100644
--- a/app/assets/stylesheets/generic/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -107,7 +107,7 @@ ul.content-list {
> li {
padding: $gl-padding;
- border-color: #f1f2f4;
+ border-color: $table-border-color;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
color: $gl-gray;
@@ -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 54%
rename from app/assets/stylesheets/base/mixins.scss
rename to app/assets/stylesheets/framework/mixins.scss
index 421588f64d..fe078d016d 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -54,146 +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;
- }
-
- 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;
@@ -287,7 +147,6 @@
.badge {
font-weight: normal;
- background-color: #fff;
background-color: #eee;
color: #78a;
}
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 36ae126f86..cea47fba19 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 f0860de1c4..78fff58d23 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: 9px;
+}
+
+.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;
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
similarity index 96%
rename from app/assets/stylesheets/generic/sidebar.scss
rename to app/assets/stylesheets/framework/sidebar.scss
index 3d055f0e66..985ea16457 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -21,12 +21,11 @@
min-height: 100vh;
width: 100%;
padding: 20px;
- background: #f1f4f8;
+ background: #EAEBEC;
.container-fluid {
background: #FFF;
padding: $gl-padding;
- border: 1px solid #e7e9ed;
min-height: 90vh;
&.container-blank {
@@ -243,6 +242,9 @@
img {
width: 36px;
height: 36px;
+ }
+
+ #tanuki-logo, img {
float: left;
}
@@ -266,3 +268,13 @@
}
}
}
+
+
+.tanuki-shape {
+ transition: all 0.8s;
+
+ &:hover {
+ fill: rgb(255, 255, 255);
+ transition: all 0.1s;
+ }
+}
diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss
new file mode 100644
index 0000000000..66e16e8df7
--- /dev/null
+++ b/app/assets/stylesheets/framework/tables.scss
@@ -0,0 +1,44 @@
+.table-holder {
+ margin: -$gl-padding;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+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: 10px $gl-padding;
+ line-height: 20px;
+ vertical-align: middle;
+ }
+
+ th {
+ font-weight: normal;
+ font-size: 15px;
+ border-bottom: 1px solid $border-color !important;
+ }
+
+ td {
+ 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 85%
rename from app/assets/stylesheets/generic/timeline.scss
rename to app/assets/stylesheets/framework/timeline.scss
index 74bbaabad3..eb53c4153d 100644
--- a/app/assets/stylesheets/generic/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -6,12 +6,16 @@
.timeline-entry {
padding: $gl-padding;
- border-color: #f1f2f4;
+ border-color: $table-border-color;
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;
+
+ &:target {
+ background: $hover;
+ }
&: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 eb8d23d645..99d028d122 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 7378d40400..63868a34e2 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 0000000000..e6558a2385
--- /dev/null
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -0,0 +1,259 @@
+@mixin md-typography {
+ color: $md-text-color;
+ word-wrap: break-word;
+
+ 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;
+ }
+
+ 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 {
+ color: #7f8fa4;
+ font-size: inherit;
+ padding: 8px 21px;
+ margin: 12px 0 12px;
+ border-left: 3px solid #e7e9ed;
+ }
+
+ blockquote p {
+ color: #7f8fa4 !important;
+ font-size: inherit;
+ 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;
+ font-size: 13px !important;
+ color: #5b6169;
+ line-height: 1.6em !important;
+ @include border-radius(2px);
+ }
+
+ p > code {
+ font-weight: inherit;
+ }
+
+ ul, ol {
+ padding: 0;
+ margin: 6px 0 6px 18px !important;
+ }
+
+ 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;
+ }
+ }
+
+ /* 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;
+ }
+ }
+}
+
+
+/**
+ * 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;
+}
+
+/**
+ * Apply Markdown typography
+ *
+ */
+.wiki {
+ @include md-typography;
+}
+
+.md {
+ @include md-typography;
+}
+
+/**
+ * Textareas intended for GFM
+ *
+ */
+textarea.js-gfm-input {
+ font-family: $monospace_font;
+ color: $gl-text-color;
+}
+
+.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 0000000000..91954683c3
--- /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 46ef595ddf..0000000000
--- a/app/assets/stylesheets/generic/buttons.scss
+++ /dev/null
@@ -1,90 +0,0 @@
-.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;
- }
- }
-}
diff --git a/app/assets/stylesheets/generic/tables.scss b/app/assets/stylesheets/generic/tables.scss
deleted file mode 100644
index a66e45577d..0000000000
--- a/app/assets/stylesheets/generic/tables.scss
+++ /dev/null
@@ -1,20 +0,0 @@
-table {
- &.table {
- tr {
- td, th {
- padding: 8px 10px;
- line-height: 20px;
- vertical-align: middle;
- }
- th {
- font-weight: normal;
- font-size: 15px;
- border-bottom: 1px solid $border-color !important;
- }
- td {
- border-color: #F1F1F1 !important;
- border-bottom: 1px solid;
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
deleted file mode 100644
index 551db31db1..0000000000
--- a/app/assets/stylesheets/generic/typography.scss
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * Headers
- *
- */
-.page-title {
- margin-top: 0px;
- line-height: 1.5;
- font-weight: normal;
- margin-bottom: 5px;
-}
-
-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/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 8323a8598e..6a2b25ddc6 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,11 +1,10 @@
/* https://github.com/MozMorris/tomorrow-pygments */
-pre.code.highlight.dark,
.code.dark {
- background-color: #1d1f21;
- color: #c5c8c6;
+ background-color: #1d1f21 !important;
+ color: #c5c8c6 !important;
- pre.code,
+ pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #1d1f21 !important;
@@ -23,8 +22,8 @@ pre.code.highlight.dark,
// Search result highlight
span.highlight_word {
- background: #ffe792;
- color: #000000;
+ background-color: #ffe792 !important;
+ color: #000000 !important;
}
.hll { background-color: #373b41 }
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index e838167433..8560c3c490 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -1,15 +1,14 @@
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */
-pre.code.monokai,
.code.monokai {
- background: #272822;
- color: #f8f8f2;
+ background-color: #272822 !important;
+ color: #f8f8f2 !important;
pre.highlight,
.line-numbers,
.line-numbers a {
- background:#272822 !important;
- color:#f8f8f2 !important;
+ background-color :#272822 !important;
+ color: #f8f8f2 !important;
}
pre.code {
@@ -23,8 +22,8 @@ pre.code.monokai,
// Search result highlight
span.highlight_word {
- background: #ffe792;
- color: #000000;
+ background-color: #ffe792 !important;
+ color: #000000 !important;
}
.hll { background-color: #49483e }
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index bd41480aef..7d489a9666 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -1,11 +1,10 @@
/* https://gist.github.com/qguv/7936275 */
-pre.code.highlight.solarized-dark,
.code.solarized-dark {
- background-color: #002b36;
- color: #93a1a1;
+ background-color: #002b36 !important;
+ color: #93a1a1 !important;
- pre.code,
+ pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #002b36 !important;
@@ -23,7 +22,7 @@ pre.code.highlight.solarized-dark,
// Search result highlight
span.highlight_word {
- background: #094554;
+ background-color: #094554 !important;
}
/* Solarized Dark
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 4cc6286387..200ed34644 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -1,11 +1,10 @@
/* https://gist.github.com/qguv/7936275 */
-pre.code.highlight.solarized-light,
.code.solarized-light {
- background-color: #fdf6e3;
- color: #586e75;
+ background-color: #fdf6e3 !important;
+ color: #586e75 !important;
- pre.code,
+ pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #fdf6e3 !important;
@@ -23,7 +22,7 @@ pre.code.highlight.solarized-light,
// Search result highlight
span.highlight_word {
- background: #eee8d5;
+ background-color: #eee8d5 !important;
}
/* Solarized Light
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 5de589109b..e2626da787 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,23 +1,20 @@
/* https://github.com/aahan/pygments-github-style */
-pre.code.highlight.white,
.code.white {
- background-color: #fff;
- color: #333;
+ background-color: #f8fafc !important;
+ color: #5b6169 !important;
+ pre.highlight,
.line-numbers,
.line-numbers a {
background-color: $background-color !important;
color: $gl-gray !important;
}
- pre.highlight {
- background-color: #fff !important;
- color: #333 !important;
- }
-
pre.code {
border-left: 1px solid $border-color;
+ background-color: #fff !important;
+ color: #333 !important;
}
// highlight line via anchor
@@ -27,7 +24,7 @@ pre.code.highlight.white,
// Search result highlight
span.highlight_word {
- background: #fafe3d;
+ background-color: #fafe3d !important;
}
.hll { background-color: #f8f8f8 }
diff --git a/app/assets/stylesheets/ci/builds.scss b/app/assets/stylesheets/pages/builds.scss
similarity index 93%
rename from app/assets/stylesheets/ci/builds.scss
rename to app/assets/stylesheets/pages/builds.scss
index a11a935b54..74dc3e321c 100644
--- a/app/assets/stylesheets/ci/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -1,4 +1,4 @@
-.ci-body {
+.build-page {
pre.trace {
background: #111111;
color: #fff;
@@ -67,4 +67,9 @@
color: #3084bb !important;
}
}
+
+ .build-top-menu {
+ margin-top: 0;
+ margin-bottom: 2px;
+ }
}
diff --git a/app/assets/stylesheets/ci/projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
similarity index 89%
rename from app/assets/stylesheets/ci/projects.scss
rename to app/assets/stylesheets/pages/ci_projects.scss
index 8c5273abcd..2a7b5cfc7f 100644
--- a/app/assets/stylesheets/ci/projects.scss
+++ b/app/assets/stylesheets/pages/ci_projects.scss
@@ -6,11 +6,6 @@
line-height: 1.5;
}
- .wide-table-holder {
- margin-left: -$gl-padding;
- margin-right: -$gl-padding;
- }
-
.builds,
.projects-table {
.light {
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index 051ca3792c..fbd7c363de 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -49,30 +49,33 @@
}
.file-stats {
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 10px 0;
+
+ li {
+ padding: 3px 0px;
+ }
+ }
.new-file {
a {
- color: #090;
- }
- i {
- color: #1BCF00;
+ color: $gl-text-green;
}
}
.renamed-file {
- i {
- color: #FE9300;
+ a {
+ color: $gl-text-orange;
}
}
.deleted-file {
a {
- color: #B00;
- }
- i {
- color: #EE0000;
+ color: $gl-text-red;
}
}
.edit-file{
- i{
- color: #555;
+ a {
+ color: $gl-text-color;
}
}
}
@@ -104,3 +107,16 @@
z-index: 2;
}
}
+
+.commit-ci-menu {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ margin-top: 5px;
+ height: 56px;
+ margin: -16px;
+ padding: 16px;
+ text-align: center;
+ margin-top: 0px;
+ margin-bottom: 2px;
+}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index de2ae93df3..4e121b95d1 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 5e7e59a6af..d9ef06dc6b 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/events.scss b/app/assets/stylesheets/pages/events.scss
index ca2ee45542..dfb901652b 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -7,7 +7,7 @@
padding: $gl-padding;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
- border-bottom: 1px solid #f1f2f4;
+ border-bottom: 1px solid $table-border-color;
color: #7f8fa4;
&.event-inline {
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 2b1b747139..07a38a19fa 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -10,3 +10,9 @@
.milestone-row {
@include str-truncated(90%);
}
+
+.dashboard .side .panel .panel-heading .input-group {
+ .form-control {
+ height: 42px;
+ }
+}
\ No newline at end of file
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 6da7a2511a..bd224705f0 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -68,3 +68,7 @@ body.modal-open {
.modal .modal-dialog {
width: 860px;
}
+
+.documentation {
+ padding: 7px;
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index b5c61f7f91..c60aa5c7fe 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 {
@@ -79,3 +80,11 @@
}
}
}
+
+.issuable-filter-count {
+ span {
+ display: block;
+ margin-bottom: -16px;
+ padding: 13px 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 4bf58cb4a5..41c069f0ad 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -132,6 +132,11 @@ form.edit-issue {
}
}
+.issue-closed-by-widget {
+ padding: 16px 0;
+ margin: 0px;
+}
+
.issue-form .select2-container {
width: 250px !important;
}
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 d8c8e5ad0a..a1a5208c59 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 fdc2c3332d..4392f08942 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 2a77f065ae..1980fe0d45 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 {
@@ -30,7 +30,6 @@ ul.notes {
.discussion-header,
.note-header {
@extend .cgray;
- padding-bottom: 15px;
a:hover {
text-decoration: none;
@@ -75,6 +74,10 @@ ul.notes {
}
}
+ .discussion-body {
+ padding-top: 15px;
+ }
+
.discussion {
overflow: hidden;
display: block;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 8e4f0eb2b2..b7391e5303 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -47,3 +47,9 @@
}
}
}
+
+.calendar-hint {
+ margin-top: -12px;
+ float: right;
+ font-size: 12px;
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 8d75f3aad4..6eb659dae1 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -1,12 +1,27 @@
+.alert_holder {
+ margin: -16px;
+
+ .alert-link {
+ font-weight: normal;
+ }
+}
+.no-ssh-key-message {
+ background-color: #f28d35;
+ margin-bottom: 16px;
+}
.new_project,
.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;
@@ -19,10 +34,10 @@
background: #f7f8fa;
margin: -$gl-padding;
padding: $gl-padding;
- padding-top: 40px;
+ padding: 44px 0 17px 0;
.project-identicon-holder {
- margin-bottom: 15px;
+ margin-bottom: 16px;
.avatar, .identicon {
margin: 0 auto;
@@ -35,28 +50,42 @@
}
.project-home-dropdown {
- margin: 11px 3px 0;
+ margin: 13px 0px 0;
+ }
+
+ .notifications-btn {
+ .fa-bell {
+ margin-right: 6px;
+ }
+
+ .fa-angle-down {
+ margin-left: 6px;
+ }
}
.project-home-desc {
h1 {
+ color: #313236;
margin: 0;
- margin-bottom: 10px;
+ margin-bottom: 6px;
font-size: 23px;
font-weight: normal;
}
p {
- color: #7f8fa4;
+ padding: 0 $gl-padding;
+ color: #5c5d5e;
}
}
.git-clone-holder {
- max-width: 600px;
- margin: 20px auto;
+ max-width: 498px;
.form-control {
background: #FFF;
+ font-size: 14px;
+ height: 42px;
+ margin-left: -1px;
}
}
@@ -67,29 +96,36 @@
}
}
+ .input-group {
+ display: inline-table;
+ position: relative;
+ top: 17px;
+ margin-bottom: 44px;
+ }
+
.project-repo-buttons {
- margin-top: $gl-padding;
- margin-bottom: 25px;
+ margin-top: 12px;
+ margin-bottom: 0px;
.btn {
- @extend .btn-info;
-
- text-transform: uppercase;
- font-size: 15px;
- line-height: 20px;
- padding: 8px 14px;
- border-radius: 3px;
- margin-left: 10px;
+ @include btn-gray;
.count {
- padding-left: 7px;
display: inline-block;
- margin-left: 7px;
}
}
}
}
+.split-one {
+ display: inline-table;
+ margin-right: 12px;
+
+ a {
+ margin: -1px !important;
+ }
+}
+
.git-clone-holder {
.project-home-dropdown + & {
margin-right: 45px;
@@ -99,11 +135,11 @@
cursor: auto;
@extend .monospace;
background: #FAFAFA;
- width: 100%;
+ width: 101%;
}
.input-group-addon {
- background: #FAFAFA;
+ background: #f7f8fa;
&.git-protocols {
padding: 0;
@@ -111,28 +147,126 @@
.input-group-btn:last-child > .btn {
@include border-radius-right(0);
+
+ border-left: 1px solid #c6cacf;
+ margin-left: -2px !important;
}
}
}
}
+.projects-search-form {
+
+ .input-group .form-control {
+ height: 42px;
+ }
+}
+
+.input-group-btn {
+ .btn {
+ @include btn-gray;
+ @include btn-middle;
+
+ &:hover {
+ outline: none;
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ &:active {
+ outline: none;
+ }
+ }
+
+ .active {
+ @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+
+ border: 1px solid #c6cacf !important;
+ background-color: #e4e7ed !important;
+ }
+
+ .btn-green {
+ @include btn-green
+ }
+
+}
+
+.split-repo-buttons {
+ display: inline-table;
+ margin: 0 12px 0 12px;
+
+ .btn{
+ @include btn-gray;
+ @include btn-default;
+ }
+
+ .dropdown-toggle {
+ margin: -5px;
+ }
+}
+
+#notification-form {
+ margin-left: 5px;
+}
+
+.dropdown-new {
+ margin-left: -5px;
+}
+
+.open > .dropdown-new.btn {
+ @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+
+ border: 1px solid #c6cacf !important;
+ background-color: #e4e7ed !important;
+ text-transform: uppercase;
+ color: #313236 !important;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.dropdown-menu {
+ @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);
+
+ border: none;
+ padding: 16px 0;
+ font-size: 14px;
+ font-weight: 100;
+
+ li a {
+ color: #5f697a;
+ line-height: 30px;
+
+ &:hover {
+ background-color: #3084bb !important;
+ }
+ }
+
+ i {
+ margin-right: 8px;
+ }
+}
+
.project-visibility-level-holder {
.radio {
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;
}
}
}
@@ -232,15 +366,28 @@ table.table.protected-branches-list tr.no-border {
.project-stats {
text-align: center;
- margin-top: 0;
+ margin-top: 15px;
margin-bottom: 0;
- padding-top: 5px;
- padding-bottom: 0;
+ padding-top: 10px;
+ padding-bottom: 4px;
ul.nav-pills {
display:inline-block;
}
+ .nav-pills li {
+ display:inline;
+ }
+
+ .nav > li > a {
+ @include btn-default;
+ @include btn-gray;
+
+ background-color: transparent;
+ border: 1px solid #f7f8fa;
+ margin-left: 12px;
+ }
+
li {
display:inline;
}
@@ -251,11 +398,11 @@ table.table.protected-branches-list tr.no-border {
}
li.missing a {
- color: #bbb;
- border: 1px dashed #ccc;
+ color: #5a6069;
+ border: 1px dashed #dce0e5;
&:hover {
- background-color: #FAFAFA;
+ background-color: #f0f2f5;
}
}
}
@@ -273,15 +420,43 @@ pre.light-well {
border-bottom: 1px solid #e7e9ed;
}
+.git-empty {
+ margin: 0 7px 0 7px;
+
+ h5 {
+ color: #5c5d5e;
+ }
+
+ .light-well {
+ @include border-radius (2px);
+
+ color: #5b6169;
+ font-size: 13px;
+ line-height: 1.6em;
+ }
+}
+
+.project-footer {
+ margin-top: 20px;
+
+ .btn-remove {
+ @include btn-middle;
+ @include btn-red;
+
+ float: left !important;
+ }
+}
+
/*
* Projects list rendered on dashboard and user page
*/
+
.projects-list {
@include basic-list;
.project-row {
padding: $gl-padding;
- border-color: #f1f2f4;
+ border-color: $table-border-color;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
@@ -336,6 +511,45 @@ pre.light-well {
}
}
-.inline-form {
- display: inline-block;
+.project-last-commit {
+ margin: 0 7px;
+
+ .ci-status {
+ margin-right: 16px;
+ }
+
+ .commit-row-message {
+ color: $gl-gray;
+ }
+
+ .commit_short_id {
+ margin-right: 5px;
+ color: $gl-link-color;
+ font-weight: 600;
+ }
+
+ .commit-author-link {
+ margin-left: 7px;
+ text-decoration: none;
+ .avatar {
+ float: none;
+ margin-right: 4px;
+ }
+
+ .commit-author-name {
+ font-weight: 600;
+ }
+ }
+}
+
+.project-show-readme .readme-holder {
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ padding: ($gl-padding + 7px);
+ border-top: 0;
+
+ .edit-project-readme {
+ z-index: 100;
+ position: relative;
+ }
}
diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss
new file mode 100644
index 0000000000..a9111a7388
--- /dev/null
+++ b/app/assets/stylesheets/pages/runners.scss
@@ -0,0 +1,34 @@
+.runner-state {
+ padding: 6px 12px;
+ margin-right: 10px;
+ color: #FFF;
+
+ &.runner-state-shared {
+ background: #32b186;
+ }
+ &.runner-state-specific {
+ background: #3498db;
+ }
+}
+
+.runner-status-online {
+ color: green;
+}
+
+.runner-status-offline {
+ color: gray;
+}
+
+.runner-status-paused {
+ color: red;
+}
+
+.runner {
+ .btn {
+ padding: 1px 6px;
+ }
+
+ h4 {
+ font-weight: normal;
+ }
+}
diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
index a3d7aba054..242783a7b7 100644
--- a/app/assets/stylesheets/pages/snippets.scss
+++ b/app/assets/stylesheets/pages/snippets.scss
@@ -1,8 +1,3 @@
-.my-snippets li:first-child {
- h4 { margin-top: 0; }
- padding-top: 0;
-}
-
.snippet-form-holder .file-holder .file-title {
padding: 2px;
}
@@ -30,3 +25,58 @@
}
}
}
+
+.snippet-holder {
+ .snippet-details {
+ .page-title {
+ margin-top: -15px;
+ padding: 10px 0;
+ margin-bottom: 0;
+ color: #5c5d5e;
+ font-size: 16px;
+
+ .author {
+ color: #5c5d5e;
+ }
+
+ .snippet-id {
+ color: #5c5d5e;
+ }
+ }
+
+ .snippet-title {
+ margin: 0;
+ font-size: 23px;
+ color: #313236;
+ }
+
+ @media (max-width: $screen-md-max) {
+ .new-snippet-link {
+ display: none;
+ }
+ }
+
+ @media (max-width: $screen-sm-max) {
+ .creator,
+ .page-title .btn-close {
+ display: none;
+ }
+ }
+ }
+
+ .file-holder {
+ border-top: 0;
+ }
+}
+
+
+.snippet-box {
+ @include border-radius(2px);
+
+ display: inline-block;
+ padding: 10px $gl-padding;
+ font-weight: normal;
+ margin-right: 10px;
+ font-size: $gl-font-size;
+ border: 1px solid;
+}
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 271cc547e2..d4ab6967cc 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,22 +1,13 @@
.tree-holder {
- .tree-content-holder {
- float: left;
- width: 100%;
- }
-
- .tree_progress {
- display: none;
- margin: 20px;
- &.loading {
- display: block;
- }
- }
.tree-table {
- @extend .table;
- @include border-radius(0);
+ margin-bottom: 0;
tr {
+ > td, > th {
+ line-height: 28px;
+ }
+
&:hover {
td {
background: $hover;
@@ -27,9 +18,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 +76,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 532dede0b2..9a50096c0d 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/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 65dbd5ef55..2f4054eaa1 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -9,6 +9,10 @@ class AbuseReportsController < ApplicationController
@abuse_report.reporter = current_user
if @abuse_report.save
+ if current_application_settings.admin_notification_email.present?
+ AbuseReportMailer.delay.notify(@abuse_report.id)
+ end
+
message = "Thank you for your report. A GitLab administrator will look into it shortly."
redirect_to root_path, notice: message
else
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 5f70582cbb..039f18f23e 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -55,8 +55,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_snippet_visibility,
:restricted_signup_domains_raw,
:version_check_enabled,
+ :admin_notification_email,
:user_oauth_applications,
- :ci_enabled,
restricted_visibility_levels: [],
import_sources: []
)
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index 0808024fc3..497c34f8f4 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
BroadcastMessage.find(params[:id]).destroy
respond_to do |format|
- format.html { redirect_to :back }
+ format.html { redirect_back_or_default(default: { action: 'index' }) }
format.js { render nothing: true }
end
end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index d670386f8c..0bd19c49d8 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -35,7 +35,7 @@ class Admin::HooksController < Admin::ApplicationController
}
@hook.execute(data, 'system_hooks')
- redirect_to :back
+ redirect_back_or_default
end
def hook_params
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index a62170662e..4613358833 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController
end
def application_services_params
- params.permit(:id,
+ application_services_params = params.permit(:id,
service: Projects::ServicesController::ALLOWED_PARAMS)
+ if application_services_params[:service].is_a?(Hash)
+ Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param|
+ application_services_params[:service].delete(param) if application_services_params[:service][param].blank?
+ end
+ end
+ application_services_params
end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index a19b1abee2..c63d0793e3 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -33,36 +33,42 @@ class Admin::UsersController < Admin::ApplicationController
def block
if user.block
- redirect_to :back, notice: "Successfully blocked"
+ redirect_back_or_admin_user(notice: "Successfully blocked")
else
- redirect_to :back, alert: "Error occurred. User was not blocked"
+ redirect_back_or_admin_user(alert: "Error occurred. User was not blocked")
end
end
def unblock
if user.activate
- redirect_to :back, notice: "Successfully unblocked"
+ redirect_back_or_admin_user(notice: "Successfully unblocked")
else
- redirect_to :back, alert: "Error occurred. User was not unblocked"
+ redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked")
end
end
def unlock
if user.unlock_access!
- redirect_to :back, alert: "Successfully unlocked"
+ redirect_back_or_admin_user(alert: "Successfully unlocked")
else
- redirect_to :back, alert: "Error occurred. User was not unlocked"
+ redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked")
end
end
def confirm
if user.confirm
- redirect_to :back, notice: "Successfully confirmed"
+ redirect_back_or_admin_user(notice: "Successfully confirmed")
else
- redirect_to :back, alert: "Error occurred. User was not confirmed"
+ redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed")
end
end
+ def login_as
+ sign_in(user)
+ flash[:alert] = "Logged in as #{user.username}"
+ redirect_to root_path
+ end
+
def disable_two_factor
user.disable_two_factor!
redirect_to admin_user_path(user),
@@ -132,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController
user.update_secondary_emails!
respond_to do |format|
- format.html { redirect_to :back, notice: "Successfully removed email." }
+ format.html { redirect_back_or_admin_user(notice: "Successfully removed email.") }
format.js { render nothing: true }
end
end
@@ -151,4 +157,12 @@ class Admin::UsersController < Admin::ApplicationController
:projects_limit, :can_create_group, :admin, :key_id
)
end
+
+ def redirect_back_or_admin_user(options = {})
+ redirect_back_or_default(default: default_route, options: options)
+ end
+
+ def default_route
+ [:admin, @user]
+ end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 9b6472a7b1..1b0609e279 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -30,7 +30,11 @@ class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound do |exception|
log_exception(exception)
- render "errors/not_found", layout: "errors", status: 404
+ render_404
+ end
+
+ def redirect_back_or_default(default: root_path, options: {})
+ redirect_to request.referer.present? ? :back : default, options
end
protected
@@ -117,9 +121,13 @@ class ApplicationController < ActionController::Base
redirect_to request.original_url.gsub(/\.git\Z/, '') and return
end
- @project = Project.find_with_namespace("#{namespace}/#{id}")
+ project_path = "#{namespace}/#{id}"
+ @project = Project.find_with_namespace(project_path)
if @project and can?(current_user, :read_project, @project)
+ if @project.path_with_namespace != project_path
+ redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return
+ end
@project
elsif current_user.nil?
@project = nil
@@ -144,12 +152,8 @@ 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
+ render html: "errors/git_not_found", layout: "errors", status: 404
end
def method_missing(method_sym, *arguments, &block)
diff --git a/app/controllers/ci/admin/runners_controller.rb b/app/controllers/ci/admin/runners_controller.rb
index dc3508b49d..110954a612 100644
--- a/app/controllers/ci/admin/runners_controller.rb
+++ b/app/controllers/ci/admin/runners_controller.rb
@@ -6,13 +6,16 @@ module Ci
@runners = Ci::Runner.order('id DESC')
@runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30)
- @active_runners_cnt = Ci::Runner.where("contacted_at > ?", 1.minutes.ago).count
+ @active_runners_cnt = Ci::Runner.online.count
end
def show
@builds = @runner.builds.order('id DESC').first(30)
@projects = Ci::Project.all
- @projects = @projects.search(params[:search]) if params[:search].present?
+ if params[:search].present?
+ @gl_projects = ::Project.search(params[:search])
+ @projects = @projects.where(gitlab_id: @gl_projects.select(:id))
+ end
@projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any?
@projects = @projects.page(params[:page]).per(30)
end
@@ -63,7 +66,7 @@ module Ci
end
def runner_params
- params.require(:runner).permit(:token, :description, :tag_list, :contacted_at, :active)
+ params.require(:runner).permit(:token, :description, :tag_list, :active)
end
end
end
diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb
index d8227e632e..9be470660e 100644
--- a/app/controllers/ci/application_controller.rb
+++ b/app/controllers/ci/application_controller.rb
@@ -1,7 +1,5 @@
module Ci
class ApplicationController < ::ApplicationController
- before_action :check_enable_flag!
-
def self.railtie_helpers_paths
"app/helpers/ci"
end
@@ -10,13 +8,6 @@ module Ci
private
- def check_enable_flag!
- unless current_application_settings.ci_enabled
- redirect_to(disabled_ci_projects_path)
- return
- end
- end
-
def authenticate_public_page!
unless project.public
authenticate_user!
diff --git a/app/controllers/ci/builds_controller.rb b/app/controllers/ci/builds_controller.rb
deleted file mode 100644
index 80ee866679..0000000000
--- a/app/controllers/ci/builds_controller.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-module Ci
- class BuildsController < 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]
- before_action :authorize_manage_project!, except: [:status, :show, :retry, :cancel]
- before_action :authorize_manage_builds!, only: [:retry, :cancel]
- before_action :build, except: [:show]
- layout 'ci/build'
-
- def show
- if params[:id] =~ /\A\d+\Z/
- @build = build
- else
- # try to find commit by sha
- commit = commit_by_sha
-
- if commit
- # Redirect to commit page
- redirect_to ci_project_ref_commit_path(@project, @build.commit.ref, @build.commit.sha)
- return
- end
- end
-
- raise ActiveRecord::RecordNotFound unless @build
-
- @builds = @project.commits.find_by_sha(@build.sha).builds.order('id DESC')
- @builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20)
- @commit = @build.commit
-
- respond_to do |format|
- format.html
- format.json do
- render json: @build.to_json(methods: :trace_html)
- end
- 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 ci_project_build_path(project, build)
- end
- end
-
- def status
- render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
- end
-
- def cancel
- @build.cancel
-
- redirect_to ci_project_build_path(@project, @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
- end
-end
diff --git a/app/controllers/ci/commits_controller.rb b/app/controllers/ci/commits_controller.rb
deleted file mode 100644
index 7a0a500fbe..0000000000
--- a/app/controllers/ci/commits_controller.rb
+++ /dev/null
@@ -1,38 +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]
- before_action :commit, only: :show
- layout 'ci/commit'
-
- def show
- @builds = @commit.builds
- end
-
- def status
- commit = Ci::Project.find(params[:project_id]).commits.find_by_sha_and_ref!(params[:id], params[:ref_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 ci_project_ref_commits_path(project, commit.ref, 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_and_ref!(params[:id], params[:ref_id])
- end
- end
-end
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index a81e4e319f..24dd1b5c93 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -18,7 +18,7 @@ module Ci
rescue Ci::GitlabCiYamlProcessor::ValidationError => e
@error = e.message
@status = false
- rescue Exception => e
+ rescue Exception
@error = "Undefined error"
@status = false
end
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index 6111753a7f..809b44387b 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -1,48 +1,15 @@
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, :edit, :update, :destroy, :toggle_shared_runners, :dumped_yaml]
- before_action :authorize_access_project!, except: [:build, :badge, :show, :new, :disabled]
- before_action :authorize_manage_project!, only: [:edit, :update, :destroy, :toggle_shared_runners, :dumped_yaml]
- before_action :authenticate_token!, only: [:build]
+ before_action :project, except: [:index]
+ before_action :authenticate_user!, except: [:index, :build, :badge]
+ before_action :authorize_access_project!, except: [:index, :badge]
+ before_action :authorize_manage_project!, only: [:toggle_shared_runners, :dumped_yaml]
before_action :no_cache, only: [:badge]
- skip_before_action :check_enable_flag!, only: [:disabled]
- protect_from_forgery except: :build
-
- layout 'ci/project', except: [:index, :disabled]
-
- def disabled
- end
+ protect_from_forgery
def show
- @ref = params[:ref]
-
- @commits = @project.commits.reverse_order
- @commits = @commits.where(ref: @ref) if @ref
- @commits = @commits.page(params[:page]).per(20)
- end
-
- def edit
- end
-
- def update
- if project.update_attributes(project_params)
- Ci::EventService.new.change_project_settings(current_user, project)
-
- redirect_to :back, notice: 'Project was successfully updated.'
- else
- render action: "edit"
- end
- end
-
- def destroy
- project.gl_project.gitlab_ci_service.update_attributes(active: false)
- project.destroy
-
- Ci::EventService.new.remove_project(current_user, project)
-
- redirect_to ci_projects_url
+ # Temporary compatibility with CI badges pointing to CI project page
+ redirect_to namespace_project_path(project.gl_project.namespace, project.gl_project)
end
# Project status badge
@@ -55,7 +22,8 @@ module Ci
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
- redirect_to :back
+
+ redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project)
end
def dumped_yaml
@@ -73,12 +41,5 @@ module Ci
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
-
- def project_params
- params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
- :polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients,
- :email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
- { variables_attributes: [:id, :key, :value, :_destroy] })
- end
end
end
diff --git a/app/controllers/ci/runner_projects_controller.rb b/app/controllers/ci/runner_projects_controller.rb
index a8bdd5bb36..97f01d40af 100644
--- a/app/controllers/ci/runner_projects_controller.rb
+++ b/app/controllers/ci/runner_projects_controller.rb
@@ -11,10 +11,12 @@ module Ci
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
+ path = runners_path(@project.gl_project)
+
if @runner.assign_to(project, current_user)
- redirect_to ci_project_runners_path(project)
+ redirect_to path
else
- redirect_to ci_project_runners_path(project), alert: 'Failed adding runner to project'
+ redirect_to path, alert: 'Failed adding runner to project'
end
end
@@ -22,7 +24,7 @@ module Ci
runner_project = project.runner_projects.find(params[:id])
runner_project.destroy
- redirect_to ci_project_runners_path(project)
+ redirect_to runners_path(@project.gl_project)
end
private
diff --git a/app/controllers/ci/runners_controller.rb b/app/controllers/ci/runners_controller.rb
deleted file mode 100644
index a672370302..0000000000
--- a/app/controllers/ci/runners_controller.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-module Ci
- class RunnersController < Ci::ApplicationController
- before_action :authenticate_user!
- before_action :project
- before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
- before_action :authorize_access_project!
- before_action :authorize_manage_project!
-
- layout 'ci/project'
-
- def index
- @runners = @project.runners.order('id DESC')
- @specific_runners =
- Ci::Runner.specific.includes(:runner_projects).
- where(Ci::RunnerProject.table_name => { project_id: current_user.authorized_projects } ).
- where.not(id: @runners).order("#{Ci::Runner.table_name}.id DESC").page(params[:page]).per(20)
- @shared_runners = Ci::Runner.shared.active
- @shared_runners_count = @shared_runners.count(:all)
- end
-
- def edit
- end
-
- def update
- if @runner.update_attributes(runner_params)
- redirect_to edit_ci_project_runner_path(@project, @runner), notice: 'Runner was successfully updated.'
- else
- redirect_to edit_ci_project_runner_path(@project, @runner), alert: 'Runner was not updated.'
- end
- end
-
- def destroy
- if @runner.only_for?(@project)
- @runner.destroy
- end
-
- redirect_to ci_project_runners_path(@project)
- end
-
- def resume
- if @runner.update_attributes(active: true)
- redirect_to ci_project_runners_path(@project, @runner), notice: 'Runner was successfully updated.'
- else
- redirect_to ci_project_runners_path(@project, @runner), alert: 'Runner was not updated.'
- end
- end
-
- def pause
- if @runner.update_attributes(active: false)
- redirect_to ci_project_runners_path(@project, @runner), notice: 'Runner was successfully updated.'
- else
- redirect_to ci_project_runners_path(@project, @runner), alert: 'Runner was not updated.'
- end
- end
-
- def show
- end
-
- protected
-
- def project
- @project = Ci::Project.find(params[:project_id])
- end
-
- def set_runner
- @runner ||= @project.runners.find(params[:id])
- end
-
- def runner_params
- params.require(:runner).permit(:description, :tag_list, :contacted_at, :active)
- end
- end
-end
diff --git a/app/controllers/ci/services_controller.rb b/app/controllers/ci/services_controller.rb
deleted file mode 100644
index 52c96a34ce..0000000000
--- 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/ci/triggers_controller.rb b/app/controllers/ci/triggers_controller.rb
deleted file mode 100644
index a39cc5d3a5..0000000000
--- a/app/controllers/ci/triggers_controller.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-module Ci
- class TriggersController < Ci::ApplicationController
- before_action :authenticate_user!
- before_action :project
- before_action :authorize_access_project!
- before_action :authorize_manage_project!
-
- layout 'ci/project'
-
- def index
- @triggers = @project.triggers
- @trigger = Ci::Trigger.new
- end
-
- def create
- @trigger = @project.triggers.new
- @trigger.save
-
- if @trigger.valid?
- redirect_to ci_project_triggers_path(@project)
- else
- @triggers = @project.triggers.select(&:persisted?)
- render :index
- end
- end
-
- def destroy
- trigger.destroy
-
- redirect_to ci_project_triggers_path(@project)
- end
-
- private
-
- def trigger
- @trigger ||= @project.triggers.find(params[:id])
- end
-
- def project
- @project = Ci::Project.find(params[:project_id])
- end
- end
-end
diff --git a/app/controllers/ci/variables_controller.rb b/app/controllers/ci/variables_controller.rb
deleted file mode 100644
index 9c6c775fde..0000000000
--- a/app/controllers/ci/variables_controller.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module Ci
- class VariablesController < Ci::ApplicationController
- before_action :authenticate_user!
- before_action :project
- before_action :authorize_access_project!
- before_action :authorize_manage_project!
-
- layout 'ci/project'
-
- def show
- end
-
- def update
- if project.update_attributes(project_params)
- Ci::EventService.new.change_project_settings(current_user, project)
-
- redirect_to ci_project_variables_path(project), notice: 'Variables were successfully updated.'
- else
- render action: 'show'
- end
- end
-
- private
-
- def project
- @project ||= Ci::Project.find(params[:project_id])
- end
-
- def project_params
- params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] })
- end
- end
-end
diff --git a/app/controllers/ci/web_hooks_controller.rb b/app/controllers/ci/web_hooks_controller.rb
deleted file mode 100644
index 24074a6d9a..0000000000
--- a/app/controllers/ci/web_hooks_controller.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-module Ci
- class WebHooksController < Ci::ApplicationController
- before_action :authenticate_user!
- before_action :project
- before_action :authorize_access_project!
- before_action :authorize_manage_project!
-
- layout 'ci/project'
-
- def index
- @web_hooks = @project.web_hooks
- @web_hook = Ci::WebHook.new
- end
-
- def create
- @web_hook = @project.web_hooks.new(web_hook_params)
- @web_hook.save
-
- if @web_hook.valid?
- redirect_to ci_project_web_hooks_path(@project)
- else
- @web_hooks = @project.web_hooks.select(&:persisted?)
- render :index
- end
- end
-
- def test
- Ci::TestHookService.new.execute(hook, current_user)
-
- redirect_to :back
- end
-
- def destroy
- hook.destroy
-
- redirect_to ci_project_web_hooks_path(@project)
- end
-
- private
-
- def hook
- @web_hook ||= @project.web_hooks.find(params[:id])
- end
-
- def project
- @project = Ci::Project.find(params[:project_id])
- end
-
- def web_hook_params
- params.require(:web_hook).permit(:url)
- end
- end
-end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 467d0f81ac..58e9049f15 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -20,6 +20,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = current_user.starred_projects
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
+ @last_push = current_user.recent_push
@groups = []
respond_to do |format|
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 524218290c..40fb15a5b3 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 f84f85a7df..25e5872486 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 849646cd66..1830039085 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 f21fbd9ecc..67bf4190e7 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -11,10 +11,6 @@ class Import::GithubController < Import::BaseController
def status
@repos = client.repos
- client.orgs.each do |org|
- @repos += client.org_repos(org.login)
- end
-
@already_added_projects = current_user.created_projects.where(import_type: "github")
already_added_projects_names = @already_added_projects.pluck(:import_source)
@@ -47,7 +43,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 27af19f5f6..23a396e808 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 f24cdb3709..eecbe380c9 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 82fadeb7e8..e0de31f225 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -10,18 +10,18 @@ class Import::GoogleCodeController < Import::BaseController
dump_file = params[:dump_file]
unless dump_file.respond_to?(:read)
- return redirect_to :back, alert: "You need to upload a Google Takeout archive."
+ return redirect_back_or_default(options: { alert: "You need to upload a Google Takeout archive." })
end
begin
dump = JSON.parse(dump_file.read)
rescue
- return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
+ return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
end
client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid?
- return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive."
+ return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
end
session[:google_code_dump] = dump
@@ -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/invites_controller.rb b/app/controllers/invites_controller.rb
index 8ef10a17f5..94bb108c5f 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -14,7 +14,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}."
else
- redirect_to :back, alert: "The invitation could not be accepted."
+ redirect_back_or_default(options: { alert: "The invitation could not be accepted." })
end
end
@@ -31,7 +31,7 @@ class InvitesController < ApplicationController
redirect_to path, notice: "You have declined the invitation to join #{label}."
else
- redirect_to :back, alert: "The invitation could not be declined."
+ redirect_back_or_default(options: { alert: "The invitation could not be declined." })
end
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 523264b8ea..f809fa7500 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -71,7 +71,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
- rescue Gitlab::OAuth::SignupDisabledError => e
+ rescue Gitlab::OAuth::SignupDisabledError
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
@@ -80,7 +80,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
flash[:notice] = message
-
+
redirect_to new_user_session_path
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 8450ba3102..2025158d06 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -1,41 +1,7 @@
class PasswordsController < Devise::PasswordsController
-
- def create
- email = resource_params[:email]
- resource_found = resource_class.find_by_email(email)
- if resource_found && resource_found.ldap_user?
- flash[:alert] = "Cannot reset password for LDAP user."
- respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) and return
- end
-
- self.resource = resource_class.send_reset_password_instructions(resource_params)
- if successfully_sent?(resource)
- respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
- else
- respond_with(resource)
- end
- end
-
- # After a user resets their password, prompt for 2FA code if enabled instead
- # of signing in automatically
- #
- # See http://git.io/vURrI
- def update
- super do |resource|
- # TODO (rspeicher): In Devise master (> 3.4.1), we can set
- # `Devise.sign_in_after_reset_password = false` and avoid this mess.
- if resource.errors.empty? && resource.try(:two_factor_enabled?)
- resource.unlock_access! if unlockable?(resource)
-
- # Since we are not signing this user in, we use the :updated_not_active
- # message which only contains "Your password was changed successfully."
- set_flash_message(:notice, :updated_not_active) if is_flashing_format?
-
- # Redirect to sign in so they can enter 2FA code
- respond_with(resource, location: new_session_path(resource)) and return
- end
- end
- end
+ before_action :resource_from_email, only: [:create]
+ before_action :prevent_ldap_reset, only: [:create]
+ before_action :throttle_reset, only: [:create]
def edit
super
@@ -56,4 +22,25 @@ class PasswordsController < Devise::PasswordsController
end
end
end
+
+ protected
+
+ def resource_from_email
+ email = resource_params[:email]
+ self.resource = resource_class.find_by_email(email)
+ end
+
+ def prevent_ldap_reset
+ return unless resource && resource.ldap_user?
+
+ redirect_to after_sending_reset_password_instructions_path_for(resource_name),
+ alert: "Cannot reset password for LDAP user."
+ end
+
+ def throttle_reset
+ return unless resource && resource.recently_sent_password_reset?
+
+ redirect_to new_password_path(resource_name),
+ alert: I18n.t('devise.passwords.recently_reset')
+ end
end
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index 22423651c1..1fd1d6882d 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -29,7 +29,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController
flash[:alert] = "Failed to save new settings"
end
- redirect_to :back
+ redirect_back_or_default(default: profile_notifications_path)
end
format.js
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index f83b4abd1e..a9a06ecc80 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -31,6 +31,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
def preferences_params
params.require(:user).permit(
:color_scheme_id,
+ :layout,
:dashboard,
:project_view,
:theme_id
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 26a4de1546..8da7b4d50e 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -26,7 +26,7 @@ class ProfilesController < Profiles::ApplicationController
end
respond_to do |format|
- format.html { redirect_to :back }
+ format.html { redirect_back_or_default(default: { action: 'show' }) }
end
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 48c922f450..519d6d6127 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -31,4 +31,8 @@ class Projects::ApplicationController < ApplicationController
def ci_enabled
return render_404 unless @project.gitlab_ci?
end
+
+ def ci_project
+ @ci_project ||= @project.ensure_gitlab_ci_project
+ end
end
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 9c3763d593..548f1b9ebf 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 8776721d24..8cc2f21d88 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
new file mode 100644
index 0000000000..816012762c
--- /dev/null
+++ b/app/controllers/projects/builds_controller.rb
@@ -0,0 +1,76 @@
+class Projects::BuildsController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :build, except: [:index, :cancel_all]
+
+ before_action :authorize_admin_project!, except: [:index, :show, :status]
+
+ layout "project"
+
+ def index
+ @scope = params[:scope]
+ @all_builds = project.ci_builds
+ @builds =
+ case @scope
+ when 'all'
+ @all_builds
+ when 'finished'
+ @all_builds.finished
+ else
+ @all_builds.running_or_pending
+ end
+ @builds = @builds.order('created_at DESC').page(params[:page]).per(30)
+ end
+
+ def cancel_all
+ @project.ci_builds.running_or_pending.each(&:cancel)
+
+ redirect_to namespace_project_builds_path(project.namespace, project)
+ end
+
+ def show
+ @builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC')
+ @builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20)
+ @commit = @build.commit
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: @build.to_json(methods: :trace_html)
+ end
+ 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 0000000000..550a019e8e
--- /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.namespace, @project, service.to_param)
+ else
+ render 'edit'
+ end
+ end
+
+ def test
+ last_build = @project.ci_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_back_or_default(options: 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/ci_settings_controller.rb b/app/controllers/projects/ci_settings_controller.rb
new file mode 100644
index 0000000000..a263242a85
--- /dev/null
+++ b/app/controllers/projects/ci_settings_controller.rb
@@ -0,0 +1,36 @@
+class Projects::CiSettingsController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :authorize_admin_project!
+
+ layout "project_settings"
+
+ def edit
+ end
+
+ def update
+ if ci_project.update_attributes(project_params)
+ Ci::EventService.new.change_project_settings(current_user, ci_project)
+
+ redirect_to edit_namespace_project_ci_settings_path(project.namespace, project), notice: 'Project was successfully updated.'
+ else
+ render action: "edit"
+ end
+ end
+
+ def destroy
+ ci_project.destroy
+ Ci::EventService.new.remove_project(current_user, ci_project)
+ project.gitlab_ci_service.update_attributes(active: false)
+
+ redirect_to project_path(project), notice: "CI was disabled for this project"
+ end
+
+ protected
+
+ def project_params
+ params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
+ :polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients,
+ :email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
+ { variables_attributes: [:id, :key, :value, :_destroy] })
+ end
+end
diff --git a/app/controllers/projects/ci_web_hooks_controller.rb b/app/controllers/projects/ci_web_hooks_controller.rb
new file mode 100644
index 0000000000..a2d470d4a6
--- /dev/null
+++ b/app/controllers/projects/ci_web_hooks_controller.rb
@@ -0,0 +1,45 @@
+class Projects::CiWebHooksController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :authorize_admin_project!
+
+ layout "project_settings"
+
+ def index
+ @web_hooks = @ci_project.web_hooks
+ @web_hook = Ci::WebHook.new
+ end
+
+ def create
+ @web_hook = @ci_project.web_hooks.new(web_hook_params)
+ @web_hook.save
+
+ if @web_hook.valid?
+ redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
+ else
+ @web_hooks = @ci_project.web_hooks.select(&:persisted?)
+ render :index
+ end
+ end
+
+ def test
+ Ci::TestHookService.new.execute(hook, current_user)
+
+ redirect_back_or_default(default: { action: 'index' })
+ end
+
+ def destroy
+ hook.destroy
+
+ redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
+ end
+
+ private
+
+ def hook
+ @web_hook ||= @ci_project.web_hooks.find(params[:id])
+ end
+
+ def web_hook_params
+ params.require(:web_hook).permit(:url)
+ end
+end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 2fae505713..7886f3c6de 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -31,6 +31,21 @@ class Projects::CommitController < Projects::ApplicationController
end
end
+ def ci
+ @ci_commit = @project.ci_commit(@commit.sha)
+ @builds = @ci_commit.builds if @ci_commit
+ @notes_count = @commit.notes.count
+ @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/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index d15004f93a..71aaad1fad 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -17,9 +17,10 @@ class Projects::CompareController < Projects::ApplicationController
execute(@project, head_ref, @project, base_ref)
if compare_result
- @commits = compare_result.commits
+ @commits = Commit.decorate(compare_result.commits, @project)
@diffs = compare_result.diffs
@commit = @commits.last
+ @first_commit = @commits.first
@line_notes = []
end
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 40e2b37912..7d09288bc8 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -46,7 +46,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def disable
@project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
- redirect_to :back
+ redirect_back_or_default(default: { action: 'index' })
end
protected
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 4e5b4125f5..c756954189 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -37,7 +37,7 @@ class Projects::HooksController < Projects::ApplicationController
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end
- redirect_to :back
+ redirect_back_or_default(default: { action: 'index' })
end
def destroy
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 0f89f2e88c..e767efbdc0 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -14,6 +14,9 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update]
+ # Cross-reference merge requests
+ before_action :closed_by_merge_requests, only: [:show]
+
respond_to :html
def index
@@ -55,9 +58,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
- @participants = @issue.participants(current_user, @project)
+ @participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.inc_author.fresh
+ @notes = @issue.notes.with_associations.fresh
@noteable = @issue
respond_with(@issue)
@@ -103,7 +106,7 @@ class Projects::IssuesController < Projects::ApplicationController
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
- redirect_to :back, notice: "#{result[:count]} issues updated"
+ redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end
def toggle_subscription
@@ -112,6 +115,10 @@ class Projects::IssuesController < Projects::ApplicationController
render nothing: true
end
+ def closed_by_merge_requests
+ @closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
+ end
+
protected
def issue
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 7574842cd4..16c4238662 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -56,6 +56,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs
@commit = @merge_request.last_commit
+ @first_commit = @merge_request.first_commit
+
@comments_allowed = @reply_allowed = true
@comments_target = {
noteable_type: 'MergeRequest',
@@ -89,7 +91,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project
@source_project = merge_request.source_project
@commits = @merge_request.compare_commits
- @commit = @merge_request.compare_commits.last
+ @commit = @merge_request.last_commit
+ @first_commit = @merge_request.first_commit
@diffs = @merge_request.compare_diffs
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
@@ -150,6 +153,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
if @merge_request.mergeable?
+ @merge_request.update(merge_error: nil)
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = true
else
@@ -245,7 +249,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_show_vars
- @participants = @merge_request.participants(current_user, @project)
+ @participants = @merge_request.participants(current_user)
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@@ -258,7 +262,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff
-
+
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 86f4a02a6e..15506bd677 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -75,11 +75,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def sort_issues
- @issues = @milestone.issues.where(id: params['sortable_issue'])
- @issues.each do |issue|
- issue.position = params['sortable_issue'].index(issue.id.to_s) + 1
- issue.save
- end
+ @milestone.sort_issues(params['sortable_issue'].map(&:to_i))
render json: { saved: true }
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 0f5d82ce13..41cd08c93c 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -25,7 +25,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format|
format.json { render_note_json(@note) }
- format.html { redirect_to :back }
+ format.html { redirect_back_or_default }
end
end
@@ -34,7 +34,7 @@ class Projects::NotesController < Projects::ApplicationController
respond_to do |format|
format.json { render_note_json(@note) }
- format.html { redirect_to :back }
+ format.html { redirect_back_or_default }
end
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index cf73bc01c8..9de5269cd2 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -72,7 +72,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def leave
if @project.namespace == current_user.namespace
- return redirect_to(:back, alert: 'You can not leave your own project. Transfer or delete the project.')
+ message = 'You can not leave your own project. Transfer or delete the project.'
+ return redirect_back_or_default(default: { action: 'index' }, options: { alert: message })
end
@project.project_members.find_by(user_id: current_user).destroy
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 5f6fbce795..d5ee6ac866 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/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 6080c849c8..c4e18c1707 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -3,6 +3,7 @@ class Projects::RefsController < Projects::ApplicationController
include TreeHelper
before_action :require_non_empty_project
+ before_action :validate_ref_id
before_action :assign_ref_vars
before_action :authorize_download_code!
@@ -71,4 +72,10 @@ class Projects::RefsController < Projects::ApplicationController
format.js
end
end
+
+ private
+
+ def validate_ref_id
+ return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex
+ end
end
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index c4a5e2d635..ba9aea1c16 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -11,18 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- begin
- file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
- rescue
- return head :not_found
- end
-
- if file_path
- # Send file to user
- response.headers["Content-Length"] = File.open(file_path).size.to_s
- send_file file_path
- else
- redirect_to request.fullpath
- end
+ render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
+ rescue => ex
+ logger.error("#{self.class.name}: #{ex}")
+ return git_not_found!
end
end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
new file mode 100644
index 0000000000..deb07a2141
--- /dev/null
+++ b/app/controllers/projects/runners_controller.rb
@@ -0,0 +1,65 @@
+class Projects::RunnersController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
+ before_action :authorize_admin_project!
+
+ layout 'project_settings'
+
+ def index
+ @runners = @ci_project.runners.order('id DESC')
+ @specific_runners =
+ Ci::Runner.specific.includes(:runner_projects).
+ where(Ci::RunnerProject.table_name => { project_id: current_user.authorized_projects } ).
+ where.not(id: @runners).order("#{Ci::Runner.table_name}.id DESC").page(params[:page]).per(20)
+ @shared_runners = Ci::Runner.shared.active
+ @shared_runners_count = @shared_runners.count(:all)
+ end
+
+ def edit
+ end
+
+ def update
+ if @runner.update_attributes(runner_params)
+ redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
+ else
+ redirect_to runner_path(@runner), alert: 'Runner was not updated.'
+ end
+ end
+
+ def destroy
+ if @runner.only_for?(@ci_project)
+ @runner.destroy
+ end
+
+ redirect_to runners_path(@project)
+ end
+
+ def resume
+ if @runner.update_attributes(active: true)
+ redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
+ else
+ redirect_to runner_path(@runner), alert: 'Runner was not updated.'
+ end
+ end
+
+ def pause
+ if @runner.update_attributes(active: false)
+ redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
+ else
+ redirect_to runner_path(@runner), alert: 'Runner was not updated.'
+ end
+ end
+
+ def show
+ end
+
+ protected
+
+ def set_runner
+ @runner ||= @ci_project.runners.find(params[:id])
+ end
+
+ def runner_params
+ params.require(:runner).permit(:description, :tag_list, :active)
+ end
+end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 3a22ed832a..42dbb497e0 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
+
+ # Parameters to ignore if no value is specified
+ FILTER_BLANK_PARAMS = [:password]
+
# Authorize
before_action :authorize_admin_project!
before_action :service, only: [:edit, :update, :test]
@@ -48,7 +52,7 @@ class Projects::ServicesController < Projects::ApplicationController
message = { alert: error_message }
end
- redirect_to :back, message
+ redirect_back_or_default(options: message)
end
private
@@ -58,6 +62,10 @@ class Projects::ServicesController < Projects::ApplicationController
end
def service_params
- params.require(:service).permit(ALLOWED_PARAMS)
+ service_params = params.require(:service).permit(ALLOWED_PARAMS)
+ FILTER_BLANK_PARAMS.each do |param|
+ service_params.delete(param) if service_params[param].blank?
+ end
+ service_params
end
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index b07a2a8db2..2104c7a7a7 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -21,6 +21,7 @@ class Projects::SnippetsController < Projects::ApplicationController
filter: :by_project,
project: @project
})
+ @snippets = @snippets.page(params[:page]).per(PER_PAGE)
end
def new
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 92e4bc16d9..bdcb1a3e29 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/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
new file mode 100644
index 0000000000..782ebd01b0
--- /dev/null
+++ b/app/controllers/projects/triggers_controller.rb
@@ -0,0 +1,35 @@
+class Projects::TriggersController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :authorize_admin_project!
+
+ layout 'project_settings'
+
+ def index
+ @triggers = @ci_project.triggers
+ @trigger = Ci::Trigger.new
+ end
+
+ def create
+ @trigger = @ci_project.triggers.new
+ @trigger.save
+
+ if @trigger.valid?
+ redirect_to namespace_project_triggers_path(@project.namespace, @project)
+ else
+ @triggers = @ci_project.triggers.select(&:persisted?)
+ render :index
+ end
+ end
+
+ def destroy
+ trigger.destroy
+
+ redirect_to namespace_project_triggers_path(@project.namespace, @project)
+ end
+
+ private
+
+ def trigger
+ @trigger ||= @ci_project.triggers.find(params[:id])
+ end
+end
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index 71ecc20dd9..e1fe7ea211 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/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
new file mode 100644
index 0000000000..d6561a45a7
--- /dev/null
+++ b/app/controllers/projects/variables_controller.rb
@@ -0,0 +1,25 @@
+class Projects::VariablesController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :authorize_admin_project!
+
+ layout 'project_settings'
+
+ def show
+ end
+
+ def update
+ if ci_project.update_attributes(project_params)
+ Ci::EventService.new.change_project_settings(current_user, ci_project)
+
+ redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
+ else
+ render action: 'show'
+ end
+ end
+
+ private
+
+ def project_params
+ params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] })
+ end
+end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 51c26a6a46..88fccfed50 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -98,7 +98,7 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki
- rescue ProjectWiki::CouldNotCreateWikiError => ex
+ rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project)
return false
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 213c2a7173..82119022cf 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,11 +1,14 @@
class ProjectsController < ApplicationController
+ include ExtractsPath
+
prepend_before_filter :render_go_import, only: [:show]
skip_before_action :authenticate_user!, only: [:show, :activity]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
+ before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
# Authorize
- before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
+ before_action :authorize_admin_project!, only: [:edit, :update]
before_action :event_filter, only: [:show, :activity]
layout :determine_layout
@@ -56,6 +59,8 @@ class ProjectsController < ApplicationController
end
def transfer
+ return access_denied! unless can?(current_user, :change_namespace, @project)
+
namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(project, current_user).execute(namespace)
@@ -64,6 +69,15 @@ class ProjectsController < ApplicationController
end
end
+ def remove_fork
+ return access_denied! unless can?(current_user, :remove_fork_project, @project)
+
+ if @project.forked?
+ @project.forked_project_link.destroy
+ flash[:notice] = 'The fork relationship has been removed.'
+ end
+ end
+
def activity
respond_to do |format|
format.html
@@ -87,7 +101,7 @@ class ProjectsController < ApplicationController
render 'projects/empty'
else
if current_user
- @membership = @project.project_member_by_id(current_user.id)
+ @membership = @project.team.find_member(current_user.id)
end
render :show
@@ -139,6 +153,7 @@ class ProjectsController < ApplicationController
def archive
return access_denied! unless can?(current_user, :archive_project, @project)
+
@project.archive!
respond_to do |format|
@@ -148,6 +163,7 @@ class ProjectsController < ApplicationController
def unarchive
return access_denied! unless can?(current_user, :archive_project, @project)
+
@project.unarchive!
respond_to do |format|
@@ -225,4 +241,14 @@ class ProjectsController < ApplicationController
render "go_import", layout: false
end
+
+ def repo_exists?
+ project.repository_exists? && !project.empty_repo?
+ end
+
+ # Override get_id from ExtractsPath, which returns the branch and file path
+ # for the blob/tree, which in this case is just the root of the default branch.
+ def get_id
+ project.repository.root_ref
+ end
end
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 54171ff67c..ad04c646e1 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -22,6 +22,10 @@ class RootController < Dashboard::ProjectsController
when 'stars'
flash.keep
redirect_to starred_dashboard_projects_path
+ when 'project_activity'
+ redirect_to activity_dashboard_path
+ when 'starred_project_activity'
+ redirect_to activity_dashboard_path(filter: 'starred')
else
return
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 28536e359e..868b05929d 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 ab89aa2c53..c407dfc163 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -39,7 +39,7 @@ class IssuableFinder
items = by_assignee(items)
items = by_author(items)
items = by_label(items)
- items = sort(items)
+ sort(items)
end
def group
@@ -53,15 +53,36 @@ class IssuableFinder
end
end
+ def project?
+ params[:project_id].present?
+ end
+
def project
return @project if defined?(@project)
- @project =
- if params[:project_id].present?
- Project.find(params[:project_id])
- else
- nil
- end
+ if project?
+ @project = Project.find(params[:project_id])
+
+ unless Ability.abilities.allowed?(current_user, :read_project, @project)
+ @project = nil
+ end
+ else
+ @project = nil
+ end
+
+ @project
+ end
+
+ def projects
+ return @projects if defined?(@projects)
+
+ if project?
+ project
+ elsif current_user && params[:authorized_only].presence && !current_user_related?
+ current_user.authorized_projects
+ else
+ ProjectsFinder.new.execute(current_user)
+ end
end
def search
@@ -72,17 +93,31 @@ class IssuableFinder
params[:milestone_title].present?
end
+ def filter_by_no_milestone?
+ milestones? && params[:milestone_title] == Milestone::None.title
+ end
+
def milestones
return @milestones if defined?(@milestones)
@milestones =
- if milestones? && params[:milestone_title] != Milestone::None.title
- Milestone.where(title: params[:milestone_title])
+ if milestones?
+ scope = Milestone.where(project_id: projects)
+
+ scope.where(title: params[:milestone_title])
else
nil
end
end
+ def labels?
+ params[:label_name].present?
+ end
+
+ def filter_by_no_label?
+ labels? && params[:label_name] == Label::None.title
+ end
+
def assignee?
params[:assignee_id].present?
end
@@ -116,19 +151,7 @@ class IssuableFinder
private
def init_collection
- table_name = klass.table_name
-
- if project
- if Ability.abilities.allowed?(current_user, :read_project, project)
- project.send(table_name)
- else
- []
- end
- elsif current_user && params[:authorized_only].presence && !current_user_related?
- klass.of_projects(current_user.authorized_projects).references(:project)
- else
- klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
- end
+ klass.all
end
def by_scope(items)
@@ -166,7 +189,12 @@ class IssuableFinder
end
def by_project(items)
- items = items.of_projects(project.id) if project
+ items =
+ if projects
+ items.of_projects(projects).references(:project)
+ else
+ items.none
+ end
items
end
@@ -181,14 +209,6 @@ class IssuableFinder
items.sort(params[:sort])
end
- def by_milestone(items)
- if milestones?
- items = items.where(milestone_id: milestones.try(:pluck, :id))
- end
-
- items
- end
-
def by_assignee(items)
if assignee?
items = items.where(assignee_id: assignee.try(:id))
@@ -205,15 +225,37 @@ class IssuableFinder
items
end
+ def by_milestone(items)
+ if milestones?
+ if filter_by_no_milestone?
+ items = items.where(milestone_id: [-1, nil])
+ else
+ items = items.joins(:milestone).where(milestones: { title: params[:milestone_title] })
+
+ if projects
+ items = items.where(milestones: { project_id: projects })
+ end
+ end
+ end
+
+ items
+ end
+
def by_label(items)
- if params[:label_name].present?
- label_names = params[:label_name].split(",")
+ if labels?
+ if filter_by_no_label?
+ items = items.
+ joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
+ where(label_links: { id: nil })
+ else
+ label_names = params[:label_name].split(",")
- item_ids = LabelLink.joins(:label).
- where('labels.title in (?)', label_names).
- where(target_type: klass.name).pluck(:target_id)
+ items = items.joins(:labels).where(labels: { title: label_names })
- items = items.where(id: item_ids)
+ if projects
+ items = items.where(labels: { project_id: projects })
+ end
+ end
end
items
diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb
index 9ea342cb26..81a1240380 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/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 14df8d4cbd..c5820bf4c5 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -16,6 +16,6 @@ module AppearancesHelper
end
def brand_header_logo
- image_tag 'logo.svg'
+ render 'shared/logo.svg'
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 056ffd278f..596a47938d 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -35,7 +35,7 @@ module ApplicationHelper
def project_icon(project_id, options = {})
project =
if project_id.is_a?(Project)
- project = project_id
+ project_id
else
Project.find_with_namespace(project_id)
end
@@ -68,13 +68,17 @@ module ApplicationHelper
end
end
- def avatar_icon(user_email = '', size = nil, scale = 2)
- user = User.find_by(email: user_email)
+ def avatar_icon(user_email = nil, size = nil, scale = 2)
+ if user_or_email.is_a?(User)
+ user = user_or_email
+ else
+ user = User.find_by(email: user_or_email)
+ end
if user
user.avatar_url(size) || default_avatar
else
- gravatar_icon(user_email, size, scale)
+ gravatar_icon(user_or_email, size, scales)
end
end
@@ -314,4 +318,8 @@ module ApplicationHelper
html.html_safe
end
+
+ def truncate_first_line(message, length = 50)
+ truncate(message.each_line.first.chomp, length: length) if message
+ end
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index ce7e9b1db8..cd99a23240 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -1,6 +1,6 @@
module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze
- FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos', 'crowd'].freeze
+ FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
def ldap_enabled?
Gitlab.config.ldap.enabled
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
new file mode 100644
index 0000000000..1b5a2c31d7
--- /dev/null
+++ b/app/helpers/builds_helper.rb
@@ -0,0 +1,13 @@
+module BuildsHelper
+ def build_ref_link build
+ gitlab_ref_link build.project, build.ref
+ end
+
+ def build_commit_link build
+ gitlab_commit_link build.project, build.short_sha
+ end
+
+ def build_url(build)
+ namespace_project_build_path(build.gl_project, build.project, build)
+ end
+end
diff --git a/app/helpers/ci/application_helper.rb b/app/helpers/ci/application_helper.rb
deleted file mode 100644
index 9fe6282bb8..0000000000
--- a/app/helpers/ci/application_helper.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-module Ci
- module ApplicationHelper
- def loader_html
- image_tag 'ci/loader.gif', alt: 'Loading'
- end
-
- def date_from_to(from, to)
- "#{from.to_s(:short)} - #{to.to_s(:short)}"
- end
-
- def duration_in_words(finished_at, started_at)
- if finished_at && started_at
- interval_in_seconds = finished_at.to_i - started_at.to_i
- elsif started_at
- interval_in_seconds = Time.now.to_i - started_at.to_i
- end
-
- time_interval_in_words(interval_in_seconds)
- end
-
- def time_interval_in_words(interval_in_seconds)
- minutes = interval_in_seconds / 60
- seconds = interval_in_seconds - minutes * 60
-
- if minutes >= 1
- "#{pluralize(minutes, "minute")} #{pluralize(seconds, "second")}"
- else
- "#{pluralize(seconds, "second")}"
- end
- end
-
- def ci_icon_for_status(status)
- icon_name =
- case status
- when 'success'
- 'check-square'
- when 'failed'
- 'close'
- when 'running', 'pending'
- 'clock-o'
- else
- 'circle'
- end
-
- icon(icon_name)
- end
-
- def ci_status_with_icon(status)
- content_tag :span, class: "ci-status ci-#{status}" do
- ci_icon_for_status(status) + ' '.html_safe + status
- end
- end
- end
-end
diff --git a/app/helpers/ci/builds_helper.rb b/app/helpers/ci/builds_helper.rb
deleted file mode 100644
index 5d6e785d95..0000000000
--- a/app/helpers/ci/builds_helper.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Ci
- module BuildsHelper
- def build_ref_link build
- gitlab_ref_link build.project, build.ref
- end
-
- def build_compare_link build
- gitlab_compare_link build.project, build.commit.short_before_sha, build.short_sha
- end
-
- def build_commit_link build
- gitlab_commit_link build.project, build.short_sha
- end
-
- def build_url(build)
- ci_project_build_url(build.project, build)
- end
- end
-end
diff --git a/app/helpers/ci/commits_helper.rb b/app/helpers/ci/commits_helper.rb
deleted file mode 100644
index 9069aed5b4..0000000000
--- a/app/helpers/ci/commits_helper.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module Ci
- module CommitsHelper
- def ci_commit_path(commit)
- ci_project_ref_commits_path(commit.project, commit.ref, commit.sha)
- end
-
- def commit_link(commit)
- link_to(commit.short_sha, ci_commit_path(commit))
- end
-
- def truncate_first_line(message, length = 50)
- truncate(message.each_line.first.chomp, length: length) if message
- end
-
- def ci_commit_title(commit)
- content_tag :span do
- link_to(
- simple_sanitize(commit.project.name), ci_project_path(commit.project)
- ) + ' @ ' +
- gitlab_commit_link(@project, @commit.sha)
- end
- end
- end
-end
diff --git a/app/helpers/ci/gitlab_helper.rb b/app/helpers/ci/gitlab_helper.rb
index 2b89a0ce93..baddbc806f 100644
--- a/app/helpers/ci/gitlab_helper.rb
+++ b/app/helpers/ci/gitlab_helper.rb
@@ -26,10 +26,10 @@ module Ci
def yaml_web_editor_link(project)
commits = project.commits
- if commits.any? && commits.last.push_data[:ci_yaml_file]
- "#{@project.gitlab_url}/edit/master/.gitlab-ci.yml"
+ if commits.any? && commits.last.ci_yaml_file
+ "#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
else
- "#{@project.gitlab_url}/new/master"
+ "#{project.gitlab_url}/new/master"
end
end
end
diff --git a/app/helpers/ci/icons_helper.rb b/app/helpers/ci/icons_helper.rb
deleted file mode 100644
index be40f79e88..0000000000
--- a/app/helpers/ci/icons_helper.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module Ci
- module IconsHelper
- def boolean_to_icon(value)
- if value.to_s == "true"
- content_tag :i, nil, class: 'fa fa-circle cgreen'
- else
- content_tag :i, nil, class: 'fa fa-power-off clgray'
- end
- end
- end
-end
diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb
deleted file mode 100644
index 03c9914641..0000000000
--- a/app/helpers/ci/runners_helper.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module Ci
- module RunnersHelper
- def runner_status_icon(runner)
- unless runner.contacted_at
- return content_tag :i, nil,
- class: "fa fa-warning-sign",
- title: "New runner. Has not connected yet"
- end
-
- status =
- if runner.active?
- runner.contacted_at > 3.hour.ago ? :online : :offline
- else
- :paused
- end
-
- content_tag :i, nil,
- class: "fa fa-circle runner-status-#{status}",
- title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
- end
- end
-end
diff --git a/app/helpers/ci/triggers_helper.rb b/app/helpers/ci/triggers_helper.rb
deleted file mode 100644
index 0d2438928c..0000000000
--- a/app/helpers/ci/triggers_helper.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Ci
- module TriggersHelper
- def ci_build_trigger_url(project_id, ref_name)
- "#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger"
- end
- end
-end
diff --git a/app/helpers/ci/user_helper.rb b/app/helpers/ci/user_helper.rb
deleted file mode 100644
index c332d6ed9c..0000000000
--- a/app/helpers/ci/user_helper.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module Ci
- module UserHelper
- def user_avatar_url(user = nil, size = nil, default = 'identicon')
- size = 40 if size.nil? || size <= 0
-
- if user.blank? || user.avatar_url.blank?
- 'ci/no_avatar.png'
- elsif /^(http(s?):\/\/(www|secure)\.gravatar\.com\/avatar\/(\w*))/ =~ user.avatar_url
- Regexp.last_match[0] + "?s=#{size}&d=#{default}"
- else
- user.avatar_url
- end
- end
- end
-end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 18c30ddb28..dbd1e26fa7 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -1,22 +1,11 @@
module CiStatusHelper
def ci_status_path(ci_commit)
- ci_project_ref_commits_path(ci_commit.project, ci_commit.ref, ci_commit)
+ project = ci_commit.gl_project
+ ci_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end
def ci_status_icon(ci_commit)
- icon_name =
- case ci_commit.status
- when 'success'
- 'check'
- when 'failed'
- 'close'
- when 'running', 'pending'
- 'clock-o'
- else
- 'circle'
- end
-
- icon(icon_name)
+ ci_icon_for_status(ci_commit.status)
end
def ci_status_color(ci_commit)
@@ -31,4 +20,26 @@ module CiStatusHelper
'gray'
end
end
+
+ def ci_status_with_icon(status)
+ content_tag :span, class: "ci-status ci-#{status}" do
+ ci_icon_for_status(status) + ' '.html_safe + status
+ end
+ end
+
+ def ci_icon_for_status(status)
+ icon_name =
+ case status
+ when 'success'
+ 'check'
+ when 'failed'
+ 'close'
+ when 'running', 'pending'
+ 'clock-o'
+ else
+ 'circle'
+ end
+
+ icon(icon_name)
+ end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 6ffa1a7121..e65e37211c 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -167,4 +167,24 @@ module DiffHelper
content_tag(:span, commit_id, class: 'monospace'),
].join(' ').html_safe
end
+
+ def commit_for_diff(diff)
+ if diff.deleted_file
+ first_commit = @first_commit || @commit
+ first_commit.parent
+ else
+ @commit
+ end
+ end
+
+ def diff_file_html_data(project, diff_commit, diff_file)
+ {
+ blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
+ tree_join(diff_commit.id, diff_file.file_path))
+ }
+ end
+
+ def editable_diff?(diff)
+ !diff.deleted_file && @merge_request && @merge_request.source_project
+ end
end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 153a44870f..6581348212 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -19,9 +19,10 @@ module GitlabMarkdownHelper
escape_once(body)
end
- gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: current_user)
+ user = current_user if defined?(current_user)
+ gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
- fragment = Nokogiri::XML::DocumentFragment.parse(gfm_body)
+ fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
# Fragment has only one node, and it's a link generated by `gfm`.
# Replace it with our requested link.
@@ -45,29 +46,39 @@ module GitlabMarkdownHelper
end
def markdown(text, context = {})
+ return "" unless text.present?
+
context.reverse_merge!(
- current_user: current_user,
path: @path,
+ pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
- Gitlab::Markdown.render(text, context)
+ user = current_user if defined?(current_user)
+
+ html = Gitlab::Markdown.render(text, context)
+ Gitlab::Markdown.post_process(html, pipeline: context[:pipeline], project: @project, user: user)
end
# TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered
def gfm(text, options = {})
+ return "" unless text.present?
+
options.reverse_merge!(
- current_user: current_user,
path: @path,
+ pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
- Gitlab::Markdown.gfm(text, options)
+ user = current_user if defined?(current_user)
+
+ html = Gitlab::Markdown.gfm(text, options)
+ Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
end
def asciidoc(text)
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index e0816f4e71..b0b536d464 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end
+ def project_builds_path(project, *args)
+ namespace_project_builds_path(project.namespace, project, *args)
+ end
+
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
@@ -33,6 +37,14 @@ module GitlabRoutingHelper
edit_namespace_project_path(project.namespace, project, *args)
end
+ def runners_path(project, *args)
+ namespace_project_runners_path(project.namespace, project, *args)
+ end
+
+ def runner_path(runner, *args)
+ namespace_project_runner_path(@project.namespace, @project, runner, *args)
+ end
+
def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 6ddb37cd0d..fda18e7b31 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -83,6 +83,10 @@ module IssuesHelper
end
end
+ def merge_requests_sentence(merge_requests)
+ merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
+ end
+
# Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 8036303851..ee04ace35d 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -92,8 +92,19 @@ module LabelsHelper
end
end
- def project_labels_options(project)
- options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name])
+ def projects_labels_options
+ labels =
+ if @project
+ @project.labels
+ else
+ Label.where(project_id: @projects)
+ end
+
+ grouped_labels = Labels::GroupService.new(labels).execute
+ grouped_labels.unshift(Label::None)
+ grouped_labels.unshift(Label::Any)
+
+ options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
end
# Required for Gitlab::Markdown::LabelReferenceFilter
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index f8169b4f28..728d877ace 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -47,7 +47,7 @@ module MergeRequestsHelper
end
def issues_sentence(issues)
- issues.map { |i| "##{i.iid}" }.to_sentence
+ issues.map(&:to_reference).to_sentence
end
def mr_change_branches_path(merge_request)
@@ -71,4 +71,17 @@ module MergeRequestsHelper
merge_request.source_branch
end
end
+
+ def format_mr_branch_names(merge_request)
+ source_path = merge_request.source_project_path
+ target_path = merge_request.target_project_path
+ source_branch = merge_request.source_branch
+ target_branch = merge_request.target_branch
+
+ if source_path == target_path
+ [source_branch, target_branch]
+ else
+ ["#{source_path}:#{source_branch}", "#{target_path}:#{target_branch}"]
+ end
+ end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 132a893e53..37a5b58cce 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/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index df37be51ce..775cf5a3dd 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -26,7 +26,7 @@ module PageLayoutHelper
def fluid_layout(enabled = false)
if @fluid_layout.nil?
- @fluid_layout = enabled
+ @fluid_layout = (current_user && current_user.layout == "fluid") || enabled
else
@fluid_layout
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 7f1b6a6992..c73cb3028e 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -1,9 +1,18 @@
# Helper methods for per-User preferences
module PreferencesHelper
+ def layout_choices
+ [
+ ['Fixed', :fixed],
+ ['Fluid', :fluid]
+ ]
+ end
+
# Maps `dashboard` values to more user-friendly option text
DASHBOARD_CHOICES = {
projects: 'Your Projects (default)',
- stars: 'Starred Projects'
+ stars: 'Starred Projects',
+ project_activity: "Your Projects' Activity",
+ starred_project_activity: "Starred Projects' Activity"
}.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text
@@ -25,7 +34,8 @@ module PreferencesHelper
def project_view_choices
[
['Readme (default)', :readme],
- ['Activity view', :activity]
+ ['Activity view', :activity],
+ ['Files view', :files]
]
end
@@ -37,8 +47,7 @@ module PreferencesHelper
Gitlab::ColorSchemes.for_user(current_user).css_class
end
- def prefer_readme?
- !current_user ||
- current_user.project_view == 'readme'
+ def default_project_view
+ current_user ? current_user.project_view : 'readme'
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 7b4747ce3d..5301c2ccf7 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -29,7 +29,7 @@ module ProjectsHelper
author_html = ""
# Build avatar image tag
- author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
+ author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
# Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
@@ -70,6 +70,10 @@ module ProjectsHelper
"You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
end
+ def remove_fork_project_message(project)
+ "You are going to remove the fork relationship to source project #{@project.forked_from_project.name_with_namespace}. Are you ABSOLUTELY sure?"
+ end
+
def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user)
end
@@ -113,6 +117,10 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
+ if project.gitlab_ci? && can?(current_user, :read_build, project)
+ nav_tabs << :builds
+ end
+
if can?(current_user, :admin_project, project)
nav_tabs << :settings
end
@@ -296,7 +304,7 @@ module ProjectsHelper
def readme_cache_key
sha = @project.commit.try(:sha) || 'nil'
- [@project.id, sha, "readme"].join('-')
+ [@project.path_with_namespace, sha, "readme"].join('-')
end
def round_commit_count(project)
diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb
new file mode 100644
index 0000000000..46eb82a354
--- /dev/null
+++ b/app/helpers/runners_helper.rb
@@ -0,0 +1,29 @@
+module RunnersHelper
+ def runner_status_icon(runner)
+ status = runner.status
+ case status
+ when :not_connected
+ content_tag :i, nil,
+ class: "fa fa-warning",
+ title: "New runner. Has not connected yet"
+
+ when :online, :offline, :paused
+ content_tag :i, nil,
+ class: "fa fa-circle runner-status-#{status}",
+ title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
+ end
+ end
+
+ def runner_link(runner)
+ display_name = truncate(runner.display_name, length: 15)
+ id = "\##{runner.id}"
+
+ if current_user && current_user.admin
+ link_to ci_admin_runner_path(runner) do
+ display_name + id
+ end
+ else
+ display_name + id
+ end
+ end
+end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 0e7d8065ac..04e53fe7c6 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -110,22 +110,4 @@ module TabHelper
'active'
end
end
-
- # Use nav_tab for save controller/action but different params
- def nav_tab(key, value, &block)
- o = {}
- o[:class] = ""
-
- if value.nil?
- o[:class] << " active" if params[key].blank?
- else
- o[:class] << " active" if params[key] == value
- end
-
- if block_given?
- content_tag(:li, capture(&block), o)
- else
- content_tag(:li, nil, o)
- end
- end
end
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
new file mode 100644
index 0000000000..8142f733e7
--- /dev/null
+++ b/app/helpers/time_helper.rb
@@ -0,0 +1,27 @@
+module TimeHelper
+ def duration_in_words(finished_at, started_at)
+ if finished_at && started_at
+ interval_in_seconds = finished_at.to_i - started_at.to_i
+ elsif started_at
+ interval_in_seconds = Time.now.to_i - started_at.to_i
+ end
+
+ time_interval_in_words(interval_in_seconds)
+ end
+
+ def time_interval_in_words(interval_in_seconds)
+ minutes = interval_in_seconds / 60
+ seconds = interval_in_seconds - minutes * 60
+
+ if minutes >= 1
+ "#{pluralize(minutes, "minute")} #{pluralize(seconds, "second")}"
+ else
+ "#{pluralize(seconds, "second")}"
+ end
+ end
+
+
+ def date_from_to(from, to)
+ "#{from.to_s(:short)} - #{to.to_s(:short)}"
+ end
+end
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb
new file mode 100644
index 0000000000..2a3a7e80fc
--- /dev/null
+++ b/app/helpers/triggers_helper.rb
@@ -0,0 +1,5 @@
+module TriggersHelper
+ def ci_build_trigger_url(project_id, ref_name)
+ "#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger"
+ end
+end
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
new file mode 100644
index 0000000000..f0c41f69a5
--- /dev/null
+++ b/app/mailers/abuse_report_mailer.rb
@@ -0,0 +1,12 @@
+class AbuseReportMailer < BaseMailer
+ include Gitlab::CurrentSettings
+
+ def notify(abuse_report_id)
+ @abuse_report = AbuseReport.find(abuse_report_id)
+
+ mail(
+ to: current_application_settings.admin_notification_email,
+ subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse"
+ )
+ end
+end
diff --git a/app/mailers/ci/notify.rb b/app/mailers/ci/notify.rb
index 4462da0d7d..404842cf21 100644
--- a/app/mailers/ci/notify.rb
+++ b/app/mailers/ci/notify.rb
@@ -2,7 +2,6 @@ module Ci
class Notify < ActionMailer::Base
include Ci::Emails::Builds
- add_template_helper Ci::ApplicationHelper
add_template_helper Ci::GitlabHelper
default_url_options[:host] = Gitlab.config.gitlab.host
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 4a6e18e6a7..caba63006d 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -50,10 +50,11 @@ module Emails
subject: subject("Invitation declined"))
end
- def project_was_moved_email(project_id, user_id)
+ def project_was_moved_email(project_id, user_id, old_path_with_namespace)
@current_user = @user = User.find user_id
@project = Project.find project_id
@target_url = namespace_project_url(@project.namespace, @project)
+ @old_path_with_namespace = old_path_with_namespace
mail(to: @user.notification_email,
subject: subject("Project was moved"))
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index db2f9654e1..50a409c375 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -140,7 +140,7 @@ class Notify < BaseMailer
# * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID'
#
def mail_answer_thread(model, headers = {})
- headers['Message-ID'] = SecureRandom.hex
+ headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>"
headers['In-Reply-To'] = message_id(model)
headers['References'] = message_id(model)
diff --git a/app/models/ability.rb b/app/models/ability.rb
index a020b24a55..b72178fa12 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -41,6 +41,7 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
+ :read_build,
:download_code
]
@@ -127,6 +128,7 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
+ :read_build,
:create_project,
:create_issue,
:create_note
@@ -135,6 +137,8 @@ class Ability
def project_report_rules
project_guest_rules + [
+ :create_commit_status,
+ :read_commit_statuses,
:download_code,
:fork_project,
:create_project_snippet,
@@ -185,7 +189,8 @@ class Ability
:change_visibility_level,
:rename_project,
:remove_project,
- :archive_project
+ :archive_project,
+ :remove_fork_project
]
end
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 07c87a7fe8..89b3116b9f 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -11,11 +11,11 @@
#
class AbuseReport < ActiveRecord::Base
- belongs_to :reporter, class_name: "User"
+ belongs_to :reporter, class_name: 'User'
belongs_to :user
validates :reporter, presence: true
validates :user, presence: true
validates :message, presence: true
- validates :user_id, uniqueness: { scope: :reporter_id }
+ validates :user_id, uniqueness: true
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 784f5c96a0..05430c2ee1 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base
allow_blank: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+ validates :admin_notification_email,
+ allow_blank: true,
+ email: true
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
@@ -83,8 +87,7 @@ class ApplicationSetting < ActiveRecord::Base
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
- import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
- ci_enabled: Settings.gitlab_ci['enabled']
+ import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
)
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 8096d4fa5a..b19e2ac136 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -24,28 +24,20 @@
#
module Ci
- class Build < ActiveRecord::Base
- extend Ci::Model
-
+ class Build < CommitStatus
LAZY_ATTRIBUTES = ['trace']
- belongs_to :commit, class_name: 'Ci::Commit'
- belongs_to :project, class_name: 'Ci::Project'
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
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 :ignore_failures, ->() { where(allow_failure: false) }
+ scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
acts_as_taggable
@@ -69,21 +61,24 @@ 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
new_build.commands = build.commands
new_build.tag_list = build.tag_list
new_build.commit_id = build.commit_id
- new_build.project_id = build.project_id
new_build.name = build.name
new_build.allow_failure = build.allow_failure
new_build.stage = build.stage
+ new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request
new_build.save
new_build
@@ -91,96 +86,37 @@ 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?
Ci::WebHookService.new.build_end(build)
end
- if build.commit.success?
- build.commit.create_next_builds(build.trigger_request)
- end
-
+ build.commit.create_next_builds(build)
project.execute_services(build)
if project.coverage_enabled?
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, :before_sha, :ref,
- to: :commit, prefix: false
-
- def trace_html
- html = Ci::Ansi2html::convert(trace) if trace.present?
- html ||= ''
- end
-
- def trace
- if project && read_attribute(:trace).present?
- read_attribute(:trace).gsub(project.token, 'xxxxxx')
- end
- end
-
- def started?
- !pending? && !canceled? && started_at
- end
-
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
end
def ignored?
failed? && allow_failure?
end
+ def trace_html
+ html = Ci::Ansi2html::convert(trace) if trace.present?
+ html || ''
+ end
+
def timeout
project.timeout
end
def variables
- 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
+ predefined_variables + yaml_variables + project_variables + trigger_variables
end
def project
@@ -188,13 +124,23 @@ module Ci
end
def project_id
- commit.project_id
+ commit.project.id
end
def project_name
project.name
end
+ def project_recipients
+ recipients = project.email_recipients.split(' ')
+
+ if project.email_add_pusher? && user.present? && user.notification_email.present?
+ recipients << user.notification_email
+ end
+
+ recipients.uniq
+ end
+
def repo_url
project.repo_url_with_auth
end
@@ -219,13 +165,13 @@ module Ci
if coverage.present?
coverage.to_f
end
- rescue => ex
+ rescue
# if bad regex or something goes wrong we dont want to interrupt transition
# so we just silentrly ignore error for now
end
end
- def trace
+ def raw_trace
if File.exist?(path_to_trace)
File.read(path_to_trace)
else
@@ -234,6 +180,15 @@ module Ci
end
end
+ def trace
+ trace = raw_trace
+ if project && trace.present?
+ trace.gsub(project.token, 'xxxxxx')
+ else
+ trace
+ end
+ end
+
def trace=(trace)
unless Dir.exists? dir_to_trace
FileUtils.mkdir_p dir_to_trace
@@ -254,6 +209,37 @@ 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
+
+ def can_be_served?(runner)
+ (tag_list - runner.tag_list).empty?
+ end
+
+ def any_runners_online?
+ project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
+ end
+
+ def show_warning?
+ pending? && !any_runners_online?
+ end
+
private
def yaml_variables
@@ -281,5 +267,14 @@ module Ci
[]
end
end
+
+ def predefined_variables
+ variables = []
+ variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag?
+ variables << { key: :CI_BUILD_NAME, value: name, public: true }
+ variables << { key: :CI_BUILD_STAGE, value: stage, public: true }
+ variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
+ variables
+ end
end
end
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index f102d0a767..13437b2483 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -18,14 +18,15 @@
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
-
- belongs_to :project, class_name: 'Ci::Project'
- has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
+
+ belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id
+ 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'
- serialize :push_data
+ scope :ordered, -> { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }
- validates_presence_of :ref, :sha, :before_sha, :push_data
+ validates_presence_of :sha
validate :valid_commit_sha
def self.truncate_sha(sha)
@@ -36,12 +37,20 @@ module Ci
sha
end
+ def project
+ @project ||= gl_project.ensure_gitlab_ci_project
+ end
+
+ def project_id
+ project.id
+ end
+
def last_build
builds.order(:id).last
end
def retry
- builds_without_retry.each do |build|
+ latest_builds.each do |build|
Ci::Build.retry(build)
end
end
@@ -52,28 +61,16 @@ module Ci
end
end
- def new_branch?
- before_sha == Ci::Git::BLANK_SHA
- end
-
- def compare?
- !new_branch?
- end
-
def git_author_name
- commit_data[:author][:name] if commit_data && commit_data[:author]
+ commit_data.author_name if commit_data
end
def git_author_email
- commit_data[:author][:email] if commit_data && commit_data[:author]
+ commit_data.author_email if commit_data
end
def git_commit_message
- commit_data[:message] if commit_data && commit_data[:message]
- end
-
- def short_before_sha
- Ci::Commit.truncate_sha(before_sha)
+ commit_data.message if commit_data
end
def short_sha
@@ -81,127 +78,82 @@ module Ci
end
def commit_data
- push_data[:commits].find do |commit|
- commit[:id] == sha
- end
+ @commit ||= gl_project.commit(sha)
rescue
nil
end
- def project_recipients
- recipients = project.email_recipients.split(' ')
-
- if project.email_add_pusher? && push_data[:user_email].present?
- recipients << push_data[:user_email]
- end
-
- recipients.uniq
- end
-
def stage
- return unless config_processor
- stages = builds_without_retry.select(&:active?).map(&:stage)
- config_processor.stages.find { |stage| stages.include? stage }
+ running_or_pending = statuses.latest.running_or_pending.ordered
+ running_or_pending.first.try(:stage)
end
- def create_builds_for_stage(stage, trigger_request)
- return if skip_ci? && trigger_request.blank?
+ def create_builds(ref, tag, user, trigger_request = nil)
return unless config_processor
-
- builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
- builds_attrs.map do |build_attrs|
- builds.create!({
- project: project,
- name: build_attrs[:name],
- commands: build_attrs[:script],
- tag_list: build_attrs[:tags],
- options: build_attrs[:options],
- allow_failure: build_attrs[:allow_failure],
- stage: build_attrs[:stage],
- trigger_request: trigger_request,
- })
- end
- end
-
- def create_next_builds(trigger_request)
- return if skip_ci? && trigger_request.blank?
- return unless config_processor
-
- stages = builds.where(trigger_request: trigger_request).group_by(&:stage)
-
config_processor.stages.any? do |stage|
- !stages.include?(stage) && create_builds_for_stage(stage, trigger_request).present?
+ CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
end
end
- def create_builds(trigger_request = nil)
- return if skip_ci? && trigger_request.blank?
+ def create_next_builds(build)
return unless config_processor
- config_processor.stages.any? do |stage|
- create_builds_for_stage(stage, trigger_request).present?
+ # don't create other builds if this one is retried
+ latest_builds = builds.similar(build).latest
+ return unless latest_builds.exists?(build.id)
+
+ # get list of stages after this build
+ next_stages = config_processor.stages.drop_while { |stage| stage != build.stage }
+ next_stages.delete(build.stage)
+
+ # get status for all prior builds
+ prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) }
+ status = Ci::Status.get_status(prior_builds)
+
+ # create builds for next stages based
+ next_stages.any? do |stage|
+ CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
end
end
- def builds_without_retry
- @builds_without_retry ||=
- begin
- grouped_builds = builds.group_by(&:name)
- grouped_builds.map do |name, builds|
- builds.sort_by(&:id).last
- end
- end
+ def refs
+ statuses.order(:ref).pluck(:ref).uniq
end
- def builds_without_retry_sorted
- return builds_without_retry unless config_processor
-
- stages = config_processor.stages
- builds_without_retry.sort_by do |build|
- [stages.index(build.stage) || -1, build.name || ""]
- end
+ def latest_statuses
+ @latest_statuses ||= statuses.latest.to_a
end
- def retried_builds
- @retried_builds ||= (builds.order(id: :desc) - builds_without_retry)
+ def latest_builds
+ @latest_builds ||= builds.latest.to_a
+ end
+
+ def latest_builds_for_ref(ref)
+ latest_builds.select { |build| build.ref == ref }
+ end
+
+ 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 ||= Ci::Status.get_status(latest_statuses)
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?
@@ -209,34 +161,33 @@ 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
+ 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
end
end
- def matrix?
- builds_without_retry.size > 1
+ def matrix_for_ref?(ref)
+ latest_builds_for_ref(ref).size > 1
end
def config_processor
- @config_processor ||= Ci::GitlabCiYamlProcessor.new(push_data[:ci_yaml_file])
+ @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file)
rescue Ci::GitlabCiYamlProcessor::ValidationError => e
save_yaml_error(e.message)
nil
@@ -246,10 +197,14 @@ module Ci
nil
end
+ def ci_yaml_file
+ gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
+ rescue
+ nil
+ end
+
def skip_ci?
- return false if builds.any?
- commits = push_data[:commits]
- commits.present? && commits.last[:message] =~ /(\[ci skip\])/
+ git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end
def update_committed!
diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb
index a52e28615f..eb65c77357 100644
--- a/app/models/ci/project.rb
+++ b/app/models/ci/project.rb
@@ -33,15 +33,12 @@ module Ci
belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id
- has_many :commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit'
- has_many :builds, through: :commits, dependent: :destroy, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, class_name: 'Ci::Runner'
has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook'
has_many :events, dependent: :destroy, class_name: 'Ci::Event'
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable'
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger'
- has_one :last_commit, -> { order 'ci_commits.created_at DESC' }, class_name: 'Ci::Commit'
# Project services
has_many :services, dependent: :destroy, class_name: 'Ci::Service'
@@ -51,19 +48,18 @@ module Ci
accepts_nested_attributes_for :variables, allow_destroy: true
+ delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project
+
#
# Validations
#
- validates_presence_of :name, :timeout, :token, :default_ref,
- :path, :ssh_url_to_repo, :gitlab_id
+ validates_presence_of :timeout, :token, :default_ref, :gitlab_id
validates_uniqueness_of :gitlab_id
validates :polling_interval,
- presence: true,
- if: ->(project) { project.always_build.present? }
-
- scope :public_only, ->() { where(public: true) }
+ presence: true,
+ if: ->(project) { project.always_build.present? }
before_validation :set_default_values
@@ -79,11 +75,8 @@ module Ci
def parse(project)
params = {
- name: project.name_with_namespace,
gitlab_id: project.id,
- path: project.path_with_namespace,
default_ref: project.default_branch || 'master',
- ssh_url_to_repo: project.ssh_url_to_repo,
email_add_pusher: current_application_settings.add_pusher,
email_only_broken_builds: current_application_settings.all_broken_builds,
}
@@ -100,35 +93,43 @@ module Ci
def unassigned(runner)
joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \
"AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}").
- where('#{Ci::RunnerProject.table_name}.project_id' => nil)
+ where("#{Ci::RunnerProject.table_name}.project_id" => nil)
end
def ordered_by_last_commit_date
- last_commit_subquery = "(SELECT project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY project_id)"
- joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.id = last_commit.project_id").
+ last_commit_subquery = "(SELECT gl_project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY gl_project_id)"
+ joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id").
order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
end
-
- def search(query)
- where("LOWER(#{Ci::Project.table_name}.name) LIKE :query",
- query: "%#{query.try(:downcase)}%")
- end
end
- def any_runners?
- if runners.active.any?
+ def name
+ name_with_namespace
+ end
+
+ def path
+ path_with_namespace
+ end
+
+ def gitlab_url
+ web_url
+ end
+
+ def any_runners?(&block)
+ if runners.active.any?(&block)
return true
end
- shared_runners_enabled && Ci::Runner.shared.active.any?
+ shared_runners_enabled && Ci::Runner.shared.active.any?(&block)
end
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
+ self.default_ref ||= 'master'
end
def tracked_refs
- @tracked_refs ||= default_ref.split(",").map{|ref| ref.strip}
+ @tracked_refs ||= default_ref.split(",").map { |ref| ref.strip }
end
def valid_token? token
@@ -168,8 +169,7 @@ module Ci
# using http and basic auth
def repo_url_with_auth
auth = "gitlab-ci-token:#{token}@"
- url = gitlab_url + ".git"
- url.sub(/^https?:\/\//) do |prefix|
+ http_url_to_repo.sub(/^https?:\/\//) do |prefix|
prefix + auth
end
end
@@ -184,7 +184,7 @@ module Ci
# If service is available but missing in db
# we should create an instance. Ex `create_gitlab_ci_service`
- service = self.send :"create_#{service_name}_service" if service.nil?
+ self.send :"create_#{service_name}_service" if service.nil?
end
end
@@ -200,12 +200,16 @@ module Ci
end
end
- def gitlab_url
- File.join(Gitlab.config.gitlab.url, path)
- end
-
def setup_finished?
commits.any?
end
+
+ def commits
+ gl_project.ci_commits.ordered
+ end
+
+ def builds
+ gl_project.ci_builds
+ end
end
end
diff --git a/app/models/ci/project_status.rb b/app/models/ci/project_status.rb
index 6d5cafe81a..b66f1212f2 100644
--- a/app/models/ci/project_status.rb
+++ b/app/models/ci/project_status.rb
@@ -28,18 +28,6 @@ module Ci
status
end
- # only check for toggling build status within same ref.
- def last_commit_changed_status?
- ref = last_commit.ref
- last_commits = commits.where(ref: ref).last(2)
-
- if last_commits.size < 2
- false
- else
- last_commits[0].status != last_commits[1].status
- end
- end
-
def last_commit_for_ref(ref)
commits.where(ref: ref).last
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 1e9f78a374..1b3669f1b7 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -20,6 +20,8 @@
module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
+
+ LAST_CONTACT_TIME = 5.minutes.ago
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@@ -33,6 +35,7 @@ module Ci
scope :shared, ->() { where(is_shared: true) }
scope :active, ->() { where(active: true) }
scope :paused, ->() { where(active: false) }
+ scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
acts_as_taggable
@@ -41,6 +44,10 @@ module Ci
query: "%#{query.try(:downcase)}%")
end
+ def gl_projects_ids
+ projects.select(:gitlab_id)
+ end
+
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
@@ -52,7 +59,7 @@ module Ci
end
def display_name
- return token unless !description.blank?
+ return short_sha unless !description.blank?
description
end
@@ -61,6 +68,20 @@ module Ci
is_shared
end
+ def online?
+ contacted_at && contacted_at > LAST_CONTACT_TIME
+ end
+
+ def status
+ if contacted_at.nil?
+ :not_connected
+ elsif active?
+ online? ? :online : :offline
+ else
+ :paused
+ end
+ end
+
def belongs_to_one_project?
runner_projects.count == 1
end
@@ -74,7 +95,7 @@ module Ci
end
def short_sha
- token[0...10]
+ token[0...8] if token
end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index aff329d71f..492f6be1ce 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -2,13 +2,13 @@ class Commit
extend ActiveModel::Naming
include ActiveModel::Conversion
- include Mentionable
include Participable
+ include Mentionable
include Referable
include StaticModel
attr_mentionable :safe_message
- participant :author, :committer, :notes, :mentioned_users
+ participant :author, :committer, :notes
attr_accessor :project
@@ -164,6 +164,14 @@ class Commit
@committer ||= User.find_by_any_email(committer_email)
end
+ def parents
+ @parents ||= parent_ids.map { |id| project.commit(id) }
+ end
+
+ def parent
+ @parent ||= project.commit(self.parent_id) if self.parent_id
+ end
+
def notes
project.notes.for_commit_id(self.id)
end
@@ -181,7 +189,11 @@ class Commit
@raw.short_id(7)
end
- def parents
- @parents ||= Commit.decorate(super, project)
+ 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 0000000000..8188ba3a28
--- /dev/null
+++ b/app/models/commit_status.rb
@@ -0,0 +1,96 @@
+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 :finished, -> { where(status:[:success, :failed, :canceled]) }
+ 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 [:pending, :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
+
+ def show_warning?
+ false
+ end
+end
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
new file mode 100644
index 0000000000..fe0cea8465
--- /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/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 4db4ffb2e7..5e964f04ef 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -6,8 +6,8 @@
#
module Issuable
extend ActiveSupport::Concern
- include Mentionable
include Participable
+ include Mentionable
included do
belongs_to :author, class_name: "User"
@@ -47,7 +47,7 @@ module Issuable
prefix: true
attr_mentionable :title, :description
- participant :author, :assignee, :notes, :mentioned_users
+ participant :author, :assignee, :notes_with_associations
end
module ClassMethods
@@ -85,6 +85,10 @@ module Issuable
assignee_id_changed?
end
+ def open?
+ opened? || reopened?
+ end
+
#
# Votes
#
@@ -176,6 +180,10 @@ module Issuable
self.class.to_s.underscore
end
+ def notes_with_associations
+ notes.includes(:author, :project)
+ end
+
private
def filter_superceded_votes(votes, notes)
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 5b0ae41164..193c91f174 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -20,6 +20,12 @@ module Mentionable
end
end
+ included do
+ if self < Participable
+ participant ->(current_user) { mentioned_users(current_user, load_lazy_references: false) }
+ end
+ end
+
# Returns the text used as the body of a Note when this object is referenced
#
# By default this will be the class name and the result of calling
@@ -41,55 +47,49 @@ module Mentionable
self
end
- # Determine whether or not a cross-reference Note has already been created between this Mentionable and
- # the specified target.
- def has_mentioned?(target)
- SystemNoteService.cross_reference_exists?(target, local_reference)
+ def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
+ ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
+ ext.analyze(text)
+ ext
end
- def mentioned_users(current_user = nil)
- return [] if mentionable_text.blank?
-
- ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
- ext.analyze(mentionable_text)
- ext.users.uniq
+ def mentioned_users(current_user = nil, load_lazy_references: true)
+ all_references(current_user, load_lazy_references: load_lazy_references).users
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
- def references(p = project, current_user = self.author, text = mentionable_text)
+ def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
return [] if text.blank?
- ext = Gitlab::ReferenceExtractor.new(p, current_user)
- ext.analyze(text)
-
- (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference]
+ refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
+ (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
- def create_cross_references!(p = project, a = author, without = [])
- refs = references(p)
-
+ def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
+ refs = referenced_mentionables(author, text)
+
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
- refs.reject! { |ref| without.include?(ref) }
+ refs.reject! { |ref| without.include?(ref) || cross_reference_exists?(ref) }
refs.each do |ref|
- SystemNoteService.cross_reference(ref, local_reference, a)
+ SystemNoteService.cross_reference(ref, local_reference, author)
end
end
# When a mentionable field is changed, creates cross-reference notes that
# don't already exist
- def create_new_cross_references!(p = project, a = author)
+ def create_new_cross_references!(author = self.author)
changes = detect_mentionable_changes
return if changes.empty?
original_text = changes.collect { |_, vals| vals.first }.join(' ')
- preexisting = references(p, self.author, original_text)
- create_cross_references!(p, a, preexisting)
+ preexisting = referenced_mentionables(author, original_text)
+ create_cross_references!(author, preexisting)
end
private
@@ -111,4 +111,10 @@ module Mentionable
# Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) }
end
+
+ # Determine whether or not a cross-reference Note has already been created between this Mentionable and
+ # the specified target.
+ def cross_reference_exists?(target)
+ SystemNoteService.cross_reference_exists?(target, local_reference)
+ end
end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 7c9597333d..85367f89f4 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -12,7 +12,7 @@
#
# # ...
#
-# participant :author, :assignee, :mentioned_users, :notes
+# participant :author, :assignee, :notes, ->(current_user) { mentioned_users(current_user) }
# end
#
# issue = Issue.last
@@ -27,7 +27,7 @@ module Participable
module ClassMethods
def participant(*attrs)
- participant_attrs.concat(attrs.map(&:to_s))
+ participant_attrs.concat(attrs)
end
def participant_attrs
@@ -37,21 +37,21 @@ module Participable
# Be aware that this method makes a lot of sql queries.
# Save result into variable if you are going to reuse it inside same request
- def participants(current_user = self.author, project = self.project)
+ def participants(current_user = self.author, load_lazy_references: true)
participants = self.class.participant_attrs.flat_map do |attr|
- meth = method(attr)
-
value =
- if meth.arity == 1 || meth.arity == -1
- meth.call(current_user)
+ if attr.respond_to?(:call)
+ instance_exec(current_user, &attr)
else
- meth.call
+ send(attr)
end
- participants_for(value, current_user, project)
+ participants_for(value, current_user)
end.compact.uniq
- if project
+ if load_lazy_references
+ participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
+
participants.select! do |user|
user.can?(:read_project, project)
end
@@ -62,14 +62,14 @@ module Participable
private
- def participants_for(value, current_user = nil, project = nil)
+ def participants_for(value, current_user = nil)
case value
- when User
+ when User, Gitlab::Markdown::ReferenceFilter::LazyReference
[value]
when Enumerable, ActiveRecord::Relation
- value.flat_map { |v| participants_for(v, current_user, project) }
+ value.flat_map { |v| participants_for(v, current_user) }
when Participable
- value.participants(current_user, project)
+ value.participants(current_user, load_lazy_references: false)
end
end
end
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
new file mode 100644
index 0000000000..fa54e3540d
--- /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.rb b/app/models/group.rb
index 9cd146bb73..465c22d23a 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -64,7 +64,7 @@ class Group < Namespace
end
def owners
- @owners ||= group_members.owners.map(&:user)
+ @owners ||= group_members.owners.includes(:user).map(&:user)
end
def add_users(user_ids, access_level, current_user = nil)
diff --git a/app/models/group_label.rb b/app/models/group_label.rb
new file mode 100644
index 0000000000..0fc39cb877
--- /dev/null
+++ b/app/models/group_label.rb
@@ -0,0 +1,9 @@
+class GroupLabel
+ attr_accessor :title, :labels
+ alias_attribute :name, :title
+
+ def initialize(title, labels)
+ @title = title
+ @labels = labels
+ end
+end
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index ab055f6b80..91844da62e 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -1,22 +1,16 @@
class GroupMilestone
+ attr_accessor :title, :milestones
+ alias_attribute :name, :title
def initialize(title, milestones)
@title = title
@milestones = milestones
end
- def title
- @title
- end
-
def safe_title
@title.parameterize
end
-
- def milestones
- @milestones
- end
-
+
def projects
milestones.map { |milestone| milestone.project }
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index fc7e9abe29..7218310803 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -95,4 +95,14 @@ class Issue < ActiveRecord::Base
def source_project
project
end
+
+ # From all notes on this issue, we'll select the system notes about linked
+ # merge requests. Of those, the MRs closing `self` are returned.
+ def closed_by_merge_requests(current_user = nil)
+ return [] unless open?
+
+ notes.system.flat_map do |note|
+ note.all_references(current_user).merge_requests
+ end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) }
+ end
end
diff --git a/app/models/label.rb b/app/models/label.rb
index 4a22bd5340..1bb4b5f55c 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 eb468c6cd5..21861a46a8 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base
after_create :create_merge_request_diff
after_update :update_merge_request_diff
- delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
+ delegate :commits, :diffs, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
@@ -157,6 +157,18 @@ class MergeRequest < ActiveRecord::Base
reference
end
+ def last_commit
+ merge_request_diff ? merge_request_diff.last_commit : compare_commits.last
+ end
+
+ def first_commit
+ merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
+ end
+
+ def last_commit_short_sha
+ last_commit.short_id
+ end
+
def validate_branches
if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target"
@@ -222,12 +234,8 @@ class MergeRequest < ActiveRecord::Base
self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end
- def open?
- opened? || reopened?
- end
-
def work_in_progress?
- title =~ /\A\[?WIP\]?:? /i
+ !!(title =~ /\A\[?WIP\]?:? /i)
end
def mergeable?
@@ -275,7 +283,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?
@@ -293,6 +302,10 @@ class MergeRequest < ActiveRecord::Base
target_project
end
+ def closes_issue?(issue)
+ closes_issues.include?(issue)
+ end
+
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index f75f999b0d..6575d0bc81 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -55,6 +55,10 @@ class MergeRequestDiff < ActiveRecord::Base
commits.first
end
+ def first_commit
+ commits.last
+ end
+
def last_commit_short_sha
@last_commit_short_sha ||= last_commit.short_id
end
@@ -144,12 +148,10 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Diff objects
# between target and source branches
def unmerged_diffs
- diffs = compare_result.diffs
- diffs ||= []
- diffs
- rescue Gitlab::Git::Diff::TimeoutError => ex
+ compare_result.diffs || []
+ rescue Gitlab::Git::Diff::TimeoutError
self.state = :timeout
- diffs = []
+ []
end
def repository
@@ -165,7 +167,8 @@ class MergeRequestDiff < ActiveRecord::Base
merge_request.fetch_ref
# Get latest sha of branch from source project
- source_sha = merge_request.source_project.commit(source_branch).sha
+ source_commit = merge_request.source_project.commit(source_branch)
+ source_sha = source_commit.try(:sha)
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index d979a35084..2ff16e2825 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}%"
@@ -101,4 +105,36 @@ class Milestone < ActiveRecord::Base
def author_id
nil
end
+
+ # Sorts the issues for the given IDs.
+ #
+ # This method runs a single SQL query using a CASE statement to update the
+ # position of all issues in the current milestone (scoped to the list of IDs).
+ #
+ # Given the ids [10, 20, 30] this method produces a SQL query something like
+ # the following:
+ #
+ # UPDATE issues
+ # SET position = CASE
+ # WHEN id = 10 THEN 1
+ # WHEN id = 20 THEN 2
+ # WHEN id = 30 THEN 3
+ # ELSE position
+ # END
+ # WHERE id IN (10, 20, 30);
+ #
+ # This method expects that the IDs given in `ids` are already Fixnums.
+ def sort_issues(ids)
+ pairs = []
+
+ ids.each_with_index do |id, index|
+ pairs << id
+ pairs << index + 1
+ end
+
+ conditions = 'WHEN id = ? THEN ? ' * ids.length
+
+ issues.where(id: ids).
+ update_all(["position = CASE #{conditions} ELSE position END", *pairs])
+ end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 161a16ca61..5782e649f8 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
@@ -137,7 +139,9 @@ class Namespace < ActiveRecord::Base
end
def send_update_instructions
- projects.each(&:send_move_instructions)
+ projects.each do |project|
+ project.send_move_instructions("#{path_was}/#{project.path}")
+ end
end
def kind
diff --git a/app/models/note.rb b/app/models/note.rb
index 89d81ab1de..0b3aa30abd 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -22,14 +22,14 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Note < ActiveRecord::Base
- include Mentionable
include Gitlab::CurrentSettings
include Participable
+ include Mentionable
default_value_for :system, false
attr_mentionable :note
- participant :author, :mentioned_users
+ participant :author
belongs_to :project
belongs_to :noteable, polymorphic: true
@@ -60,9 +60,13 @@ class Note < ActiveRecord::Base
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
+ scope :with_associations, -> do
+ includes(:author, :noteable, :updated_by,
+ project: [:project_members, { group: [:group_members] }])
+ end
+
serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? }
- after_update :set_references
class << self
def discussions_from_notes(notes)
@@ -333,15 +337,13 @@ class Note < ActiveRecord::Base
end
def noteable_type_name
- if noteable_type.present?
- noteable_type.downcase
- end
+ noteable_type.downcase if noteable_type.present?
end
# FIXME: Hack for polymorphic associations with STI
# For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
- def noteable_type=(sType)
- super(sType.to_s.classify.constantize.base_class.to_s)
+ def noteable_type=(noteable_type)
+ super(noteable_type.to_s.classify.constantize.base_class.to_s)
end
# Reset notes events cache
@@ -357,15 +359,11 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self)
end
- def set_references
- create_new_cross_references!(project, author)
- end
-
def system?
read_attribute(:system)
end
def editable?
- !read_attribute(:system)
+ !system?
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index e912c48467..74b89aad49 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
@@ -118,6 +119,8 @@ class Project < ActiveRecord::Base
has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user
+ has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
+ has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id
@@ -233,13 +236,19 @@ 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.find_by(path: id.first)
- return nil unless namespace
+ return nil if !namespace_path || !project_path
- where(namespace_id: namespace.id).find_by(path: id.second)
+ # 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).
+ projects = unscoped.
+ joins(:namespace).
+ iwhere('namespaces.path' => namespace_path)
+
+ projects.where('projects.path' => project_path).take ||
+ projects.iwhere('projects.path' => project_path).take
end
def visibility_levels
@@ -258,6 +267,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
@@ -411,7 +434,7 @@ class Project < ActiveRecord::Base
if template.nil?
# If no template, we should create an instance. Ex `create_gitlab_ci_service`
- service = self.send :"create_#{service_name}_service"
+ self.send :"create_#{service_name}_service"
else
Service.create_from_template(self.id, template)
end
@@ -479,8 +502,8 @@ class Project < ActiveRecord::Base
end
end
- def send_move_instructions
- NotificationService.new.project_was_moved(self)
+ def send_move_instructions(old_path_with_namespace)
+ NotificationService.new.project_was_moved(self, old_path_with_namespace)
end
def owner
@@ -545,7 +568,7 @@ class Project < ActiveRecord::Base
end
def empty_repo?
- !repository.exists? || repository.empty?
+ !repository.exists? || !repository.has_visible_content?
end
def repo
@@ -622,7 +645,7 @@ class Project < ActiveRecord::Base
# So we basically we mute exceptions in next actions
begin
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
- send_move_instructions
+ send_move_instructions(old_path_with_namespace)
reset_events_cache
rescue
# Returning false does not rollback after_* transaction but gives
@@ -634,6 +657,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
@@ -736,22 +761,26 @@ class Project < ActiveRecord::Base
def create_wiki
ProjectWiki.new(self, self.owner).wiki
true
- rescue ProjectWiki::CouldNotCreateWikiError => ex
+ rescue ProjectWiki::CouldNotCreateWikiError
errors.add(:base, 'Failed create wiki')
false
end
def ci_commit(sha)
- gitlab_ci_project.commits.find_by(sha: sha) if gitlab_ci?
+ ci_commits.find_by(sha: sha)
end
- def enable_ci(user)
- # Enable service
+ def ensure_ci_commit(sha)
+ ci_commit(sha) || ci_commits.create(sha: sha)
+ end
+
+ def ensure_gitlab_ci_project
+ gitlab_ci_project || create_gitlab_ci_project
+ end
+
+ def enable_ci
service = gitlab_ci_service || create_gitlab_ci_service
service.active = true
service.save
-
- # Create Ci::Project
- Ci::CreateProjectService.new.execute(user, self)
end
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index d8aedbd2ab..d31b12f539 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 bamboo_url_changed? && !password_touched?
+ self.password = nil
+ end
+ end
+
def title
'Atlassian Bamboo CI'
end
diff --git a/app/models/project_services/ci/hip_chat_message.rb b/app/models/project_services/ci/hip_chat_message.rb
index 25c72033ea..cbf325cc52 100644
--- a/app/models/project_services/ci/hip_chat_message.rb
+++ b/app/models/project_services/ci/hip_chat_message.rb
@@ -11,14 +11,7 @@ module Ci
def to_s
lines = Array.new
lines.push("#{project.name} - ")
-
- if commit.matrix?
- lines.push("Commit ##{commit.id}")
- else
- first_build = commit.builds_without_retry.first
- lines.push("Build '#{first_build.name}' ##{first_build.id}")
- end
-
+ lines.push("Commit ##{commit.id}")
lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}")
lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).")
lines.join('')
diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb
index 0e6e97394b..f17993d9f3 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 1bd2f33612..fd19330100 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
@@ -61,7 +61,7 @@ module Ci
end
def execute(build)
- build.commit.project_recipients.each do |recipient|
+ build.project_recipients.each do |recipient|
case build.status.to_sym
when :success
mailer.build_success_email(build.id, recipient)
diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb
index 757b196114..dc050a3fc5 100644
--- a/app/models/project_services/ci/slack_message.rb
+++ b/app/models/project_services/ci/slack_message.rb
@@ -23,15 +23,13 @@ module Ci
def attachments
fields = []
- if commit.matrix?
- commit.builds_without_retry.each do |build|
- next if build.allow_failure?
- next unless build.failed?
- fields << {
- title: build.name,
- value: "Build <#{ci_project_build_url(project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
- }
- end
+ commit.latest_builds.each do |build|
+ next if build.allow_failure?
+ next unless build.failed?
+ fields << {
+ title: build.name,
+ value: "Build <#{namespace_project_build_url(build.gl_project.namespace, build.gl_project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
+ }
end
[{
@@ -47,12 +45,7 @@ module Ci
def attachment_message
out = "<#{ci_project_url(project)}|#{project_name}>: "
- if commit.matrix?
- out << "Commit <#{ci_project_ref_commits_url(project, commit.ref, commit.sha)}|\##{commit.id}> "
- else
- build = commit.builds_without_retry.first
- out << "Build <#{ci_project_build_url(project, build)}|\##{build.id}> "
- end
+ out << "Commit <#{ci_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}|\##{commit.id}> "
out << "(<#{commit_sha_link}|#{commit.short_sha}>) "
out << "of <#{commit_ref_link}|#{commit.ref}> "
out << "by #{commit.git_author_name} " if commit.git_author_name
diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb
index 76db573dc1..ee8e498882 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/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 23ab206efb..4dcd16ede3 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -22,12 +22,17 @@ class GitlabCiService < CiService
include Gitlab::Application.routes.url_helpers
after_save :compose_service_hook, if: :activated?
+ after_save :ensure_gitlab_ci_project, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
+ def ensure_gitlab_ci_project
+ project.ensure_gitlab_ci_project
+ end
+
def supported_events
%w(push tag_push)
end
@@ -35,19 +40,10 @@ class GitlabCiService < CiService
def execute(data)
return unless supported_events.include?(data[:object_kind])
- sha = data[:checkout_sha]
-
- if sha.present?
- file = ci_yaml_file(sha)
-
- if file && file.data
- data.merge!(ci_yaml_file: file.data)
- end
- end
-
- ci_project = Ci::Project.find_by(gitlab_id: project.id)
+ ci_project = project.gitlab_ci_project
if ci_project
- Ci::CreateCommitService.new.execute(ci_project, data)
+ current_user = User.find_by(id: data[:user_id])
+ Ci::CreateCommitService.new.execute(ci_project, current_user, data)
end
end
@@ -58,7 +54,7 @@ class GitlabCiService < CiService
end
def get_ci_commit(sha, ref)
- Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha_and_ref!(sha, ref)
+ Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha)
end
def commit_status(sha, ref)
@@ -67,25 +63,6 @@ class GitlabCiService < CiService
:error
end
- def fork_registration(new_project, current_user)
- params = OpenStruct.new({
- id: new_project.id,
- name_with_namespace: new_project.name_with_namespace,
- path_with_namespace: new_project.path_with_namespace,
- web_url: new_project.web_url,
- default_branch: new_project.default_branch,
- ssh_url_to_repo: new_project.ssh_url_to_repo
- })
-
- ci_project = Ci::Project.find_by!(gitlab_id: project.id)
-
- Ci::CreateProjectService.new.execute(
- current_user,
- params,
- ci_project
- )
- end
-
def commit_coverage(sha, ref)
get_ci_commit(sha, ref).coverage
rescue ActiveRecord::RecordNotFound
@@ -94,7 +71,7 @@ class GitlabCiService < CiService
def build_page(sha, ref)
if project.gitlab_ci_project.present?
- ci_project_ref_commits_url(project.gitlab_ci_project, ref, sha)
+ ci_namespace_project_commit_url(project.namespace, project, sha)
end
end
@@ -113,14 +90,4 @@ class GitlabCiService < CiService
def fields
[]
end
-
- private
-
- def ci_yaml_file(sha)
- repository.blob_at(sha, '.gitlab-ci.yml')
- end
-
- def repository
- project.repository
- end
end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 7a15a861ab..af2840a57f 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -85,17 +85,16 @@ class HipchatService < Service
def create_message(data)
object_kind = data[:object_kind]
- message = \
- case object_kind
- when "push", "tag_push"
- create_push_message(data)
- when "issue"
- create_issue_message(data) unless is_update?(data)
- when "merge_request"
- create_merge_request_message(data) unless is_update?(data)
- when "note"
- create_note_message(data)
- end
+ case object_kind
+ when "push", "tag_push"
+ create_push_message(data)
+ when "issue"
+ create_issue_message(data) unless is_update?(data)
+ when "merge_request"
+ create_merge_request_message(data) unless is_update?(data)
+ when "note"
+ create_note_message(data)
+ end
end
def create_push_message(push)
@@ -167,8 +166,6 @@ class HipchatService < Service
obj_attr = data[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr)
merge_request_id = obj_attr[:iid]
- source_branch = obj_attr[:source_branch]
- target_branch = obj_attr[:target_branch]
state = obj_attr[:state]
description = obj_attr[:description]
title = obj_attr[:title]
@@ -194,8 +191,6 @@ class HipchatService < Service
data = HashWithIndifferentAccess.new(data)
user_name = data[:user][:name]
- repo_attr = HashWithIndifferentAccess.new(data[:repository])
-
obj_attr = HashWithIndifferentAccess.new(data[:object_attributes])
note = obj_attr[:note]
note_url = obj_attr[:url]
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 3c002a1634..0b02246125 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 teamcity_url_changed? && !password_touched?
+ self.password = nil
+ end
+ end
+
def title
'JetBrains TeamCity CI'
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 56e49af232..9f380a382c 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -135,15 +135,32 @@ class ProjectTeam
!!find_member(user_id)
end
+ def human_max_access(user_id)
+ Gitlab::Access.options.key max_member_access(user_id)
+ end
+
+ # This method assumes project and group members are eager loaded for optimal
+ # performance.
def max_member_access(user_id)
access = []
- access << project.project_members.find_by(user_id: user_id).try(:access_field)
- if group
- access << group.group_members.find_by(user_id: user_id).try(:access_field)
+ project.project_members.each do |member|
+ if member.user_id == user_id
+ access << member.access_field if member.access_field
+ break
+ end
end
- access.compact.max
+ if group
+ group.group_members.each do |member|
+ if member.user_id == user_id
+ access << member.access_field if member.access_field
+ break
+ end
+ end
+ end
+
+ access.max
end
private
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 79b48ebfed..88d3d73a40 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -8,6 +8,14 @@ class Repository
attr_accessor :raw_repository, :path_with_namespace, :project
+ def self.clean_old_archives
+ repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
+
+ return unless File.directory?(repository_downloads_path)
+
+ Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
+ end
+
def initialize(path_with_namespace, default_branch = nil, project = nil)
@path_with_namespace = path_with_namespace
@project = project
@@ -36,6 +44,19 @@ class Repository
raw_repository.empty?
end
+ #
+ # Git repository can contains some hidden refs like:
+ # /refs/notes/*
+ # /refs/git-as-svn/*
+ # /refs/pulls/*
+ # This refs by default not visible in project page and not cloned to client side.
+ #
+ # This method return true if repository contains some content visible in project page.
+ #
+ def has_visible_content?
+ !raw_repository.branches.empty?
+ end
+
def commit(id = 'HEAD')
return nil unless raw_repository
commit = Gitlab::Git::Commit.find(raw_repository, id)
@@ -269,14 +290,6 @@ class Repository
end
# Remove archives older than 2 hours
- def clean_old_archives
- repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path
-
- return unless File.directory?(repository_downloads_path)
-
- Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
- end
-
def branches_sorted_by(value)
case value
when 'recently_updated'
@@ -312,13 +325,7 @@ class Repository
end
def blob_for_diff(commit, diff)
- file = blob_at(commit.id, diff.new_path)
-
- unless file
- file = prev_blob_for_diff(commit, diff)
- end
-
- file
+ blob_at(commit.id, diff.file_path)
end
def prev_blob_for_diff(commit, diff)
@@ -373,11 +380,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 +409,8 @@ class Repository
options[:file] = {
content: content,
- path: path
+ path: path,
+ update: update
}
Gitlab::Git::Blob.commit(raw_repository, options)
@@ -397,9 +419,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 +436,7 @@ class Repository
end
end
- def user_to_comitter(user)
+ def user_to_committer(user)
{
email: user.email,
name: user.name,
@@ -467,6 +487,10 @@ class Repository
end
end
+ def merge_base(first_commit_id, second_commit_id)
+ rugged.merge_base(first_commit_id, second_commit_id)
+ end
+
def search_files(query, ref)
offset = 2
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
@@ -549,7 +573,7 @@ class Repository
# Run GitLab post receive hook
post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
- status = post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
+ post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
else
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
diff --git a/app/models/service.rb b/app/models/service.rb
index 60fcc9d285..d610abd168 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -33,6 +33,8 @@ class Service < ActiveRecord::Base
after_initialize :initialize_properties
+ after_commit :reset_updated_properties
+
belongs_to :project
has_one :service_hook
@@ -103,6 +105,7 @@ class Service < ActiveRecord::Base
# Provide convenient accessor methods
# for each serialized property.
+ # Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.prop_accessor(*args)
args.each do |arg|
class_eval %{
@@ -111,12 +114,39 @@ class Service < ActiveRecord::Base
end
def #{arg}=(value)
+ updated_properties['#{arg}'] = #{arg} unless #{arg}_changed?
self.properties['#{arg}'] = value
end
+
+ def #{arg}_changed?
+ #{arg}_touched? && #{arg} != #{arg}_was
+ end
+
+ def #{arg}_touched?
+ updated_properties.include?('#{arg}')
+ end
+
+ def #{arg}_was
+ updated_properties['#{arg}']
+ end
}
end
end
+ # Returns a hash of the properties that have been assigned a new value since last save,
+ # indicating their original values (attr => original value).
+ # ActiveRecord does not provide a mechanism to track changes in serialized keys,
+ # so we need a specific implementation for service properties.
+ # This allows to track changes to properties set with the accessor methods,
+ # but not direct manipulation of properties hash.
+ def updated_properties
+ @updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new
+ end
+
+ def reset_updated_properties
+ @updated_properties = nil
+ end
+
def async_execute(data)
return unless supported_events.include?(data[:object_kind])
diff --git a/app/models/user.rb b/app/models/user.rb
index 55c095d7d5..81a47c7bb6 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -54,6 +54,7 @@
# public_email :string(255) default(""), not null
# dashboard :integer default(0)
# project_view :integer default(0)
+# layout :integer default(0)
#
require 'carrierwave/orm/activerecord'
@@ -67,6 +68,7 @@ class User < ActiveRecord::Base
include Referable
include Sortable
include TokenAuthenticatable
+ include CaseSensitivity
default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group
@@ -129,6 +131,8 @@ class User < ActiveRecord::Base
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
+ has_one :abuse_report, dependent: :destroy
+ has_many :ci_builds, dependent: :nullify, class_name: 'Ci::Build'
#
@@ -170,13 +174,16 @@ class User < ActiveRecord::Base
after_create :post_create_hook
after_destroy :post_destroy_hook
+ # User's Layout preference
+ enum layout: [:fixed, :fluid]
+
# User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array.
- enum dashboard: [:projects, :stars]
+ enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity]
# User's Project preference
# Note: When adding an option, it MUST go on the end of the array.
- enum project_view: [:readme, :activity]
+ enum project_view: [:readme, :activity, :files]
alias_attribute :private_token, :authentication_token
@@ -267,8 +274,13 @@ class User < ActiveRecord::Base
end
def by_login(login)
- where('lower(username) = :value OR lower(email) = :value',
- value: login.to_s.downcase).first
+ return nil unless login
+
+ if login.include?('@'.freeze)
+ unscoped.iwhere(email: login).take
+ else
+ unscoped.iwhere(username: login).take
+ end
end
def find_by_username!(username)
@@ -327,6 +339,10 @@ class User < ActiveRecord::Base
@reset_token
end
+ def recently_sent_password_reset?
+ reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
+ end
+
def disable_two_factor!
update_attributes(
two_factor_enabled: false,
@@ -690,23 +706,20 @@ class User < ActiveRecord::Base
end
def toggle_star(project)
- user_star_project = users_star_projects.
- where(project: project, user: self).take
- if user_star_project
- user_star_project.destroy
- else
- UsersStarProject.create!(project: project, user: self)
+ UsersStarProject.transaction do
+ user_star_project = users_star_projects.
+ where(project: project, user: self).lock(true).first
+
+ if user_star_project
+ user_star_project.destroy
+ else
+ UsersStarProject.create!(project: project, user: self)
+ end
end
end
def manageable_namespaces
- @manageable_namespaces ||=
- begin
- namespaces = []
- namespaces << namespace
- namespaces += owned_groups
- namespaces += masters_groups
- end
+ @manageable_namespaces ||= [namespace] + owned_groups + masters_groups
end
def namespaces
diff --git a/app/services/archive_repository_service.rb b/app/services/archive_repository_service.rb
index e1b41527d8..2160bf13e6 100644
--- a/app/services/archive_repository_service.rb
+++ b/app/services/archive_repository_service.rb
@@ -7,19 +7,12 @@ class ArchiveRepositoryService
end
def execute(options = {})
- project.repository.clean_old_archives
+ RepositoryArchiveCacheWorker.perform_async
- raise "No archive file path" unless file_path
+ metadata = project.repository.archive_metadata(ref, storage_path, format)
+ raise "Repository or ref not found" if metadata.empty?
- return file_path if archived?
-
- unless archiving?
- RepositoryArchiveWorker.perform_async(project.id, ref, format)
- end
-
- archived = wait_until_archived(options[:timeout] || 5.0)
-
- file_path if archived
+ metadata
end
private
@@ -27,36 +20,4 @@ class ArchiveRepositoryService
def storage_path
Gitlab.config.gitlab.repository_downloads_path
end
-
- def file_path
- @file_path ||= project.repository.archive_file_path(ref, storage_path, format)
- end
-
- def pid_file_path
- @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
- end
-
- def archived?
- File.exist?(file_path)
- end
-
- def archiving?
- File.exist?(pid_file_path)
- end
-
- def wait_until_archived(timeout = 5.0)
- return archived? if timeout == 0.0
-
- t1 = Time.now
-
- begin
- sleep 0.1
-
- success = archived?
-
- t2 = Time.now
- end until success || t2 - t1 >= timeout
-
- success
- end
end
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
new file mode 100644
index 0000000000..912eb6258a
--- /dev/null
+++ b/app/services/ci/create_builds_service.rb
@@ -0,0 +1,39 @@
+module Ci
+ class CreateBuildsService
+ def execute(commit, stage, ref, tag, user, trigger_request, status)
+ builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag)
+
+ # check when to create next build
+ builds_attrs = builds_attrs.select do |build_attrs|
+ case build_attrs[:when]
+ when 'on_success'
+ status == 'success'
+ when 'on_failure'
+ status == 'failed'
+ when 'always'
+ %w(success failed).include?(status)
+ end
+ end
+
+ builds_attrs.map do |build_attrs|
+ # don't create the same build twice
+ unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
+ build_attrs.slice!(:name,
+ :commands,
+ :tag_list,
+ :options,
+ :allow_failure,
+ :stage,
+ :stage_idx)
+
+ build_attrs.merge!(ref: ref,
+ tag: tag,
+ trigger_request: trigger_request,
+ user: user)
+
+ commit.builds.create!(build_attrs)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/ci/create_commit_service.rb b/app/services/ci/create_commit_service.rb
index 0a1abf89a9..479a2d6def 100644
--- a/app/services/ci/create_commit_service.rb
+++ b/app/services/ci/create_commit_service.rb
@@ -1,7 +1,6 @@
module Ci
class CreateCommitService
- def execute(project, params)
- before_sha = params[:before]
+ def execute(project, user, params)
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
@@ -16,34 +15,13 @@ module Ci
return false
end
- commit = project.commits.find_by_sha_and_ref(sha, ref)
-
- # Create commit if not exists yet
- unless commit
- data = {
- ref: ref,
- sha: sha,
- tag: origin_ref.start_with?('refs/tags/'),
- before_sha: before_sha,
- push_data: {
- before: before_sha,
- after: sha,
- ref: ref,
- user_name: params[:user_name],
- user_email: params[:user_email],
- repository: params[:repository],
- commits: params[:commits],
- total_commits_count: params[:total_commits_count],
- ci_yaml_file: params[:ci_yaml_file]
- }
- }
-
- commit = project.commits.create(data)
+ tag = origin_ref.start_with?('refs/tags/')
+ commit = project.gl_project.ensure_ci_commit(sha)
+ unless commit.skip_ci?
+ commit.update_committed!
+ commit.create_builds(ref, tag, user)
end
- commit.update_committed!
- commit.create_builds unless commit.builds.any?
-
commit
end
end
diff --git a/app/services/ci/create_project_service.rb b/app/services/ci/create_project_service.rb
deleted file mode 100644
index f42babd238..0000000000
--- a/app/services/ci/create_project_service.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module Ci
- class CreateProjectService
- include Gitlab::Application.routes.url_helpers
-
- def execute(current_user, params, forked_project = nil)
- @project = Ci::Project.parse(params)
-
- Ci::Project.transaction do
- @project.save!
-
- gl_project = ::Project.find(@project.gitlab_id)
- gl_project.build_missing_services
- gl_project.gitlab_ci_service.update_attributes(active: true)
- end
-
- if forked_project
- # Copy settings
- settings = forked_project.attributes.select do |attr_name, value|
- ["public", "shared_runners_enabled", "allow_git_fetch"].include? attr_name
- end
-
- @project.update(settings)
- end
-
- Ci::EventService.new.create_project(current_user, @project)
-
- @project
- end
- end
-end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index 9bad09f2f5..4b86cb0a1f 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -1,15 +1,20 @@
module Ci
class CreateTriggerRequestService
def execute(project, trigger, ref, variables = nil)
- commit = project.commits.where(ref: ref).last
+ commit = project.gl_project.commit(ref)
return unless commit
+ # check if ref is tag
+ tag = project.gl_project.repository.find_tag(ref).present?
+
+ ci_commit = project.gl_project.ensure_ci_commit(commit.sha)
+
trigger_request = trigger.trigger_requests.create!(
- commit: commit,
- variables: variables
+ variables: variables,
+ commit: ci_commit,
)
- if commit.create_builds(trigger_request)
+ if ci_commit.create_builds(ref, tag, nil, trigger_request)
trigger_request
end
end
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 33f1c1e918..7beb098659 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -8,18 +8,18 @@ module Ci
builds =
if current_runner.shared?
# don't run projects which have not enables shared runners
- builds.includes(:project).where(ci_projects: { shared_runners_enabled: true })
+ builds.joins(commit: { gl_project: :gitlab_ci_project }).where(ci_projects: { shared_runners_enabled: true })
else
# do run projects which are only assigned to this runner
- builds.where(project_id: current_runner.projects)
+ builds.joins(:commit).where(ci_commits: { gl_project_id: current_runner.gl_projects_ids })
end
builds = builds.order('created_at ASC')
build = builds.find do |build|
- (build.tag_list - current_runner.tag_list).empty?
+ build.can_be_served?(current_runner)
end
-
+
if build
# In case when 2 runners try to assign the same build, second runner will be declined
diff --git a/app/services/ci/web_hook_service.rb b/app/services/ci/web_hook_service.rb
index 87984b20fa..92e6df442b 100644
--- a/app/services/ci/web_hook_service.rb
+++ b/app/services/ci/web_hook_service.rb
@@ -27,9 +27,8 @@ module Ci
project_name: project.name,
gitlab_url: project.gitlab_url,
ref: build.ref,
- sha: build.sha,
before_sha: build.before_sha,
- push_data: build.commit.push_data
+ sha: build.sha,
})
end
end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 7aecee217d..008833eed8 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -21,7 +21,7 @@ module Files
create_target_branch
end
- if sha = commit
+ if commit
success
else
error("Something went wrong. Your changes were not committed")
diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb
new file mode 100644
index 0000000000..71272fb570
--- /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 ffbb599327..c8e3a910bb 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 a20903c6f0..1960dc7d94 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/git_push_service.rb b/app/services/git_push_service.rb
index 8193b6e192..3de7bb9dca 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -49,16 +49,19 @@ class GitPushService
elsif push_to_existing_branch?(ref, oldrev)
# Collect data for this git push
@push_commits = project.repository.commits_between(oldrev, newrev)
- project.update_merge_requests(oldrev, newrev, ref, @user)
process_commit_messages(ref)
end
+ # Update merge requests that may be affected by this push. A new branch
+ # could cause the last commit of a merge request to change.
+ project.update_merge_requests(oldrev, newrev, ref, @user)
+
@push_data = build_push_data(oldrev, newrev, ref)
# If CI was disabled but .gitlab-ci.yml file was pushed
# we enable CI automatically
if !project.gitlab_ci? && gitlab_ci_yaml?(newrev)
- project.enable_ci(user)
+ project.enable_ci
end
EventCreateService.new.push(project, user, @push_data)
@@ -74,48 +77,30 @@ class GitPushService
def process_commit_messages(ref)
is_default_branch = is_default_branch?(ref)
+ authors = Hash.new do |hash, commit|
+ email = commit.author_email
+ next hash[email] if hash.has_key?(email)
+
+ hash[email] = commit_user(commit)
+ end
+
@push_commits.each do |commit|
- # Close issues if these commits were pushed to the project's default branch and the commit message matches the
- # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
- # a different branch.
- issues_to_close = commit.closes_issues(user)
-
- # Load commit author only if needed.
- # For push with 1k commits it prevents 900+ requests in database
- author = nil
-
# Keep track of the issues that will be actually closed because they are on a default branch.
# Hence, when creating cross-reference notes, the not-closed issues (on non-default branches)
# will also have cross-reference.
- actually_closed_issues = []
+ closed_issues = []
- if issues_to_close.present? && is_default_branch
- author ||= commit_user(commit)
- actually_closed_issues = issues_to_close
- issues_to_close.each do |issue|
- Issues::CloseService.new(project, author, {}).execute(issue, commit)
+ if is_default_branch
+ # Close issues if these commits were pushed to the project's default branch and the commit message matches the
+ # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
+ # a different branch.
+ closed_issues = commit.closes_issues(user)
+ closed_issues.each do |issue|
+ Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
end
end
- if project.default_issues_tracker?
- create_cross_reference_notes(commit, actually_closed_issues)
- end
- end
- end
-
- def create_cross_reference_notes(commit, issues_to_close)
- # Create cross-reference notes for any other references than those given in issues_to_close.
- # Omit any issues that were referenced in an issue-closing phrase, or have already been
- # mentioned from this commit (probably from this commit being pushed to a different branch).
- refs = commit.references(project, user) - issues_to_close
- refs.reject! { |r| commit.has_mentioned?(r) }
-
- if refs.present?
- author ||= commit_user(commit)
-
- refs.each do |r|
- SystemNoteService.cross_reference(r, commit, author)
- end
+ commit.create_cross_references!(authors[commit], closed_issues)
end
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 1ea4b72216..bcb380d321 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -10,7 +10,7 @@ module Issues
issue.update_attributes(label_ids: label_params)
notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
- issue.create_cross_references!(issue.project, current_user)
+ issue.create_cross_references!(current_user)
execute_hooks(issue, 'open')
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 2fc6ef7f35..2b5426ad45 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -35,7 +35,7 @@ module Issues
create_title_change_note(issue, issue.previous_changes['title'].first)
end
- issue.create_new_cross_references!(issue.project, current_user)
+ issue.create_new_cross_references!
execute_hooks(issue, 'update')
end
diff --git a/app/services/labels/group_service.rb b/app/services/labels/group_service.rb
new file mode 100644
index 0000000000..b26cee24d5
--- /dev/null
+++ b/app/services/labels/group_service.rb
@@ -0,0 +1,26 @@
+module Labels
+ class GroupService < ::BaseService
+ def initialize(project_labels)
+ @project_labels = project_labels.group_by(&:title)
+ end
+
+ def execute
+ build(@project_labels)
+ end
+
+ def label(title)
+ if title
+ group_label = @project_labels[title].group_by(&:title)
+ build(group_label).first
+ else
+ nil
+ end
+ end
+
+ private
+
+ def build(label)
+ label.map { |title, labels| GroupLabel.new(title, labels) }
+ end
+ end
+end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 9651b16462..009d5a6867 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -18,7 +18,7 @@ module MergeRequests
merge_request.update_attributes(label_ids: label_params)
event_service.open_mr(merge_request, current_user)
notification_service.new_merge_request(merge_request, current_user)
- merge_request.create_cross_references!(merge_request.project, current_user)
+ merge_request.create_cross_references!(current_user)
execute_hooks(merge_request)
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 98a67c0bc9..7963af127e 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,
@@ -38,6 +38,10 @@ module MergeRequests
}
repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
+ rescue Exception => e
+ merge_request.update(merge_error: "Something went wrong during merge")
+ Rails.logger.error(e.message)
+ return false
end
def after_merge
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index aceb8cb902..8f25c5e249 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -6,6 +6,7 @@ module MergeRequests
#
class PostMergeService < MergeRequests::BaseService
def execute(merge_request)
+ close_issues(merge_request)
merge_request.mark_as_merged
create_merge_event(merge_request, current_user)
create_note(merge_request)
@@ -15,6 +16,15 @@ module MergeRequests
private
+ def close_issues(merge_request)
+ return unless merge_request.target_branch == project.default_branch
+
+ closed_issues = merge_request.closes_issues(current_user)
+ closed_issues.each do |issue|
+ Issues::CloseService.new(project, current_user, {}).execute(issue, merge_request)
+ end
+ end
+
def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user)
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index e903e48e3c..d68bc79ecc 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -5,13 +5,20 @@ module MergeRequests
@oldrev, @newrev = oldrev, newrev
@branch_name = Gitlab::Git.ref_name(ref)
- @fork_merge_requests = @project.fork_merge_requests.opened
- @commits = @project.repository.commits_between(oldrev, newrev)
- close_merge_requests
+ find_new_commits
reload_merge_requests
+
+ # Leave a system note if a branch was deleted/added
+ if branch_added? || branch_removed?
+ comment_mr_branch_presence_changed
+ comment_mr_with_commits
+ else
+ comment_mr_with_commits
+ close_merge_requests
+ end
+
execute_mr_web_hooks
- comment_mr_with_commits
true
end
@@ -31,7 +38,6 @@ module MergeRequests
commit_ids.include?(merge_request.last_commit.id)
end
-
merge_requests.uniq.select(&:source_project).each do |merge_request|
MergeRequests::PostMergeService.
new(merge_request.target_project, @current_user).
@@ -47,7 +53,7 @@ module MergeRequests
# Note: we should update merge requests from forks too
def reload_merge_requests
merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a
- merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a
+ merge_requests += fork_merge_requests.by_branch(@branch_name).to_a
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
@@ -70,13 +76,48 @@ module MergeRequests
end
end
+ def find_new_commits
+ if branch_added?
+ @commits = []
+
+ merge_request = merge_requests_for_source_branch.first
+ return unless merge_request
+
+ last_commit = merge_request.last_commit
+
+ begin
+ # Since any number of commits could have been made to the restored branch,
+ # find the common root to see what has been added.
+ common_ref = @project.repository.merge_base(last_commit.id, @newrev)
+ # If the a commit no longer exists in this repo, gitlab_git throws
+ # a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52
+ @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref
+ rescue
+ end
+ elsif branch_removed?
+ # No commits for a deleted branch.
+ @commits = []
+ else
+ @commits = @project.repository.commits_between(@oldrev, @newrev)
+ end
+ end
+
+ # Add comment about branches being deleted or added to merge requests
+ def comment_mr_branch_presence_changed
+ presence = branch_added? ? :add : :delete
+
+ merge_requests_for_source_branch.each do |merge_request|
+ SystemNoteService.change_branch_presence(
+ merge_request, merge_request.project, @current_user,
+ :source, @branch_name, presence)
+ end
+ end
+
# Add comment about pushing new commits to merge requests
def comment_mr_with_commits
- merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
- merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
- merge_requests = filter_merge_requests(merge_requests)
+ return unless @commits.present?
- merge_requests.each do |merge_request|
+ merge_requests_for_source_branch.each do |merge_request|
mr_commit_ids = Set.new(merge_request.commits.map(&:id))
new_commits, existing_commits = @commits.partition do |commit|
@@ -91,14 +132,7 @@ module MergeRequests
# Call merge request webhook with update branches
def execute_mr_web_hooks
- merge_requests = @project.origin_merge_requests.opened
- .where(source_branch: @branch_name)
- .to_a
- merge_requests += @fork_merge_requests.where(source_branch: @branch_name)
- .to_a
- merge_requests = filter_merge_requests(merge_requests)
-
- merge_requests.each do |merge_request|
+ merge_requests_for_source_branch.each do |merge_request|
execute_hooks(merge_request, 'update')
end
end
@@ -106,5 +140,25 @@ module MergeRequests
def filter_merge_requests(merge_requests)
merge_requests.uniq.select(&:source_project)
end
+
+ def merge_requests_for_source_branch
+ @source_merge_requests ||= begin
+ merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
+ merge_requests += fork_merge_requests.where(source_branch: @branch_name).to_a
+ filter_merge_requests(merge_requests)
+ end
+ end
+
+ def fork_merge_requests
+ @fork_merge_requests ||= @project.fork_merge_requests.opened
+ end
+
+ def branch_added?
+ Gitlab::Git.blank_ref?(@oldrev)
+ end
+
+ def branch_removed?
+ Gitlab::Git.blank_ref?(@newrev)
+ end
end
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 25d79e22e3..ebbe0af803 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -59,7 +59,7 @@ module MergeRequests
merge_request.mark_as_unchecked
end
- merge_request.create_new_cross_references!(merge_request.project, current_user)
+ merge_request.create_new_cross_references!
execute_hooks(merge_request, 'update')
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 482c044404..2001dc89c3 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -11,13 +11,7 @@ module Notes
# Skip system notes, like status changes and cross-references.
unless note.system
event_service.leave_note(note, note.author)
-
- # Create a cross-reference note if this Note contains GFM that names an
- # issue, merge request, or commit.
- note.references.each do |mentioned|
- SystemNoteService.cross_reference(mentioned, note.noteable, note.author)
- end
-
+ note.create_cross_references!
execute_hooks(note)
end
end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index c22a9333ef..6c2f08e596 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -4,7 +4,7 @@ module Notes
return note unless note.editable?
note.update_attributes(params.merge(updated_by: current_user))
-
+ note.create_new_cross_references!
note.reset_events_cache
note
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index e294b23bc2..a6b2234865 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -183,12 +183,12 @@ class NotificationService
mailer.group_access_granted_email(group_member.id)
end
- def project_was_moved(project)
+ def project_was_moved(project, old_path_with_namespace)
recipients = project.team.members
recipients = reject_muted_users(recipients, project)
recipients.each do |recipient|
- mailer.project_was_moved_email(project.id, recipient.id)
+ mailer.project_was_moved_email(project.id, recipient.id, old_path_with_namespace)
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index e54a13ed6c..faf1ee008e 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -62,7 +62,7 @@ module Projects
after_create_actions if @project.persisted?
@project
- rescue => ex
+ rescue
@project.errors.add(:base, "Can't save project. Please try again later")
@project
end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 2e995d6fd5..46374a3909 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -18,7 +18,13 @@ module Projects
if new_project.persisted?
if @project.gitlab_ci?
- @project.gitlab_ci_service.fork_registration(new_project, @current_user)
+ new_project.enable_ci
+
+ settings = @project.gitlab_ci_project.attributes.select do |attr_name, value|
+ ["public", "shared_runners_enabled", "allow_git_fetch"].include? attr_name
+ end
+
+ new_project.gitlab_ci_project.update(settings)
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 550ed6897d..64ea6dd42e 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?
@@ -38,7 +39,7 @@ module Projects
project.save!
# Notifications
- project.send_move_instructions
+ project.send_move_instructions(old_path)
# Move main repository
unless gitlab_shell.mv_repository(old_path, new_path)
@@ -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 60235b6be2..9a5fe4af9d 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/services/system_note_service.rb b/app/services/system_note_service.rb
index 8253c1f780..708c2f0048 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -168,6 +168,31 @@ class SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
+ # Called when a branch in Noteable is added or deleted
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # branch_type - :source or :target
+ # branch - branch name
+ # presence - :add or :delete
+ #
+ # Example Note text:
+ #
+ # "Restored target branch `feature`"
+ #
+ # Returns the created Note object
+ def self.change_branch_presence(noteable, project, author, branch_type, branch, presence)
+ verb =
+ if presence == :add
+ 'restored'
+ else
+ 'deleted'
+ end
+ body = "#{verb} #{branch_type.to_s} branch `#{branch}`".capitalize
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
# Called when a Mentionable references a Noteable
#
# noteable - Noteable object being referenced
@@ -302,7 +327,7 @@ class SystemNoteService
commit_ids = if count == 1
existing_commits.first.short_id
else
- if oldrev
+ if oldrev && !Gitlab::Git.blank_ref?(oldrev)
"#{Commit.truncate_sha(oldrev)}...#{existing_commits.last.short_id}"
else
"#{existing_commits.first.short_id}..#{existing_commits.last.short_id}"
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index f9673abbfe..e821158583 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/abuse_report_mailer/notify.html.haml b/app/views/abuse_report_mailer/notify.html.haml
new file mode 100644
index 0000000000..619533e09a
--- /dev/null
+++ b/app/views/abuse_report_mailer/notify.html.haml
@@ -0,0 +1,11 @@
+%p
+ #{link_to @abuse_report.user.name, user_url(@abuse_report.user)}
+ (@#{@abuse_report.user.username}) was reported for abuse by
+ #{link_to @abuse_report.reporter.name, user_url(@abuse_report.reporter)}
+ (@#{@abuse_report.reporter.username}).
+
+%blockquote
+ = @abuse_report.message
+
+%p
+ = link_to "View details", abuse_reports_url
diff --git a/app/views/abuse_report_mailer/notify.text.haml b/app/views/abuse_report_mailer/notify.text.haml
new file mode 100644
index 0000000000..7dacf85703
--- /dev/null
+++ b/app/views/abuse_report_mailer/notify.text.haml
@@ -0,0 +1,5 @@
+#{@abuse_report.user.name} (@#{@abuse_report.user.username}) was reported for abuse by #{@abuse_report.reporter.name} (@#{@abuse_report.reporter.username}).
+\
+> #{@abuse_report.message}
+\
+View details: #{admin_abuse_reports_url}
diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml
index 2e8746146d..40a5fe4628 100644
--- a/app/views/admin/abuse_reports/index.html.haml
+++ b/app/views/admin/abuse_reports/index.html.haml
@@ -2,16 +2,17 @@
%h3.page-title Abuse Reports
%hr
- if @abuse_reports.present?
- %table.table
- %thead
- %tr
- %th Reported by
- %th Reported at
- %th Message
- %th User
- %th Primary action
- %th
- = render @abuse_reports
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Reported by
+ %th Reported at
+ %th Message
+ %th User
+ %th Primary action
+ %th
+ = render @abuse_reports
= paginate @abuse_reports
- else
%h4 There are no abuse reports
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 143cd10c54..7a78526e09 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -47,6 +47,12 @@
= f.label :version_check_enabled do
= f.check_box :version_check_enabled
Version check enabled
+ .form-group
+ = f.label :admin_notification_email, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :admin_notification_email, class: 'form-control'
+ .help-block
+ Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
%fieldset
%legend Account and Limit Settings
@@ -124,14 +130,5 @@
= f.text_area :help_page_text, class: 'form-control', rows: 4
.help-block Markdown enabled
- %fieldset
- %legend Continuous Integration
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :ci_enabled do
- = f.check_box :ci_enabled
- Disable to prevent CI usage until rake ci:migrate is run (8.0 only)
-
.form-actions
= f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml
index fc921a966f..f8cd98f0ec 100644
--- a/app/views/admin/applications/index.html.haml
+++ b/app/views/admin/applications/index.html.haml
@@ -2,7 +2,7 @@
%h3.page-title
System OAuth applications
%p.light
- System OAuth application does not belong to certain user and can be managed only by admins
+ System OAuth applications don't belong to any user and can only be managed by admins
%hr
%p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success'
%table.table.table-striped
diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml
index 0ea2ffeda9..3eb9d61972 100644
--- a/app/views/admin/applications/show.html.haml
+++ b/app/views/admin/applications/show.html.haml
@@ -3,25 +3,26 @@
Application: #{@application.name}
-%table.table
- %tr
- %td
- Application Id
- %td
- %code#application_id= @application.uid
- %tr
- %td
- Secret:
- %td
- %code#secret= @application.secret
+.table-holder
+ %table.table
+ %tr
+ %td
+ Application Id
+ %td
+ %code#application_id= @application.uid
+ %tr
+ %td
+ Secret:
+ %td
+ %code#secret= @application.secret
- %tr
- %td
- Callback url
- %td
- - @application.redirect_uri.split.each do |uri|
- %div
- %span.monospace= uri
+ %tr
+ %td
+ Callback url
+ %td
+ - @application.redirect_uri.split.each do |uri|
+ %div
+ %span.monospace= uri
.form-actions
= link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide pull-left'
= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 3a01e11510..de5bc050cf 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -12,24 +12,25 @@
%i.fa.fa-exclamation-triangle
There are no running sidekiq processes. Please restart GitLab
- else
- %table.table
- %thead
- %th USER
- %th PID
- %th CPU
- %th MEM
- %th STATE
- %th START
- %th COMMAND
- %tbody
- - @sidekiq_processes.each do |process|
- - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- - data = process.strip.split(' ')
- %tr
- %td= gitlab_config.user
- - 5.times do
- %td= data.shift
- %td= data.join(' ')
+ .table-holder
+ %table.table
+ %thead
+ %th USER
+ %th PID
+ %th CPU
+ %th MEM
+ %th STATE
+ %th START
+ %th COMMAND
+ %tbody
+ - @sidekiq_processes.each do |process|
+ - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
+ - data = process.strip.split(' ')
+ %tr
+ %td= gitlab_config.user
+ - 5.times do
+ %td= data.shift
+ %td= data.join(' ')
.clearfix
%p
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 2bf1689cbc..841e6971fb 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -5,22 +5,23 @@
.panel-head-actions
= link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm"
- if @deploy_keys.any?
- %table.table
- %thead.panel-heading
- %tr
- %th Title
- %th Fingerprint
- %th Added at
- %th
- %tbody
- - @deploy_keys.each do |deploy_key|
+ .table-holder
+ %table.table
+ %thead.panel-heading
%tr
- %td
- %strong= deploy_key.title
- %td
- %code.key-fingerprint= deploy_key.fingerprint
- %td
- %span.cgray
- added #{time_ago_with_tooltip(deploy_key.created_at)}
- %td
- = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
+ %th Title
+ %th Fingerprint
+ %th Added at
+ %th
+ %tbody
+ - @deploy_keys.each do |deploy_key|
+ %tr
+ %td
+ %strong= deploy_key.title
+ %td
+ %code.key-fingerprint= deploy_key.fingerprint
+ %td
+ %span.cgray
+ added #{time_ago_with_tooltip(deploy_key.created_at)}
+ %td
+ = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml
index ae57e3adc4..8358a14445 100644
--- a/app/views/admin/identities/index.html.haml
+++ b/app/views/admin/identities/index.html.haml
@@ -2,12 +2,13 @@
= render 'admin/users/head'
- if @identities.present?
- %table.table
- %thead
- %tr
- %th Provider
- %th Identifier
- %th
- = render @identities
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Provider
+ %th Identifier
+ %th
+ = render @identities
- else
%h4 This user has no identities
diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml
index 8b11c28c56..d67454c03e 100644
--- a/app/views/admin/labels/index.html.haml
+++ b/app/views/admin/labels/index.html.haml
@@ -12,5 +12,5 @@
= paginate @labels, theme: 'gitlab'
- else
.light-well
- .nothing-here-block There are no any labels yet
-
\ No newline at end of file
+ .nothing-here-block There are no labels yet
+
diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml
index e237729114..6a5986f496 100644
--- a/app/views/admin/services/index.html.haml
+++ b/app/views/admin/services/index.html.haml
@@ -2,22 +2,23 @@
%h3.page-title Service templates
%p.light Service template allows you to set default values for project services
-%table.table
- %thead
- %tr
- %th
- %th Service
- %th Description
- %th Last edit
- - @services.sort_by(&:title).each do |service|
- %tr
- %td
- = icon("copy", class: 'clgray')
- %td
- = link_to edit_admin_application_settings_service_path(service.id) do
- %strong= service.title
- %td
- = service.description
- %td.light
- = time_ago_in_words service.updated_at
- ago
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th
+ %th Service
+ %th Description
+ %th Last edit
+ - @services.sort_by(&:title).each do |service|
+ %tr
+ %td
+ = icon("copy", class: 'clgray')
+ %td
+ = link_to edit_admin_application_settings_service_path(service.id) do
+ %strong= service.title
+ %td
+ = service.description
+ %td.light
+ = time_ago_in_words service.updated_at
+ ago
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index 9d5e934c8b..4245d0f1ed 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -6,6 +6,8 @@
%span.cred (Admin)
.pull-right
+ - unless @user == current_user
+ = link_to 'Log in as this user', login_as_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info"
= link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
diff --git a/app/views/users/_profile.html.haml b/app/views/admin/users/_profile.html.haml
similarity index 100%
rename from app/views/users/_profile.html.haml
rename to app/views/admin/users/_profile.html.haml
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index e3698ac1c4..bc08458312 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/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index a383ea5738..0848504b7a 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -8,13 +8,13 @@
= @user.name
%ul.well-list
%li
- = image_tag avatar_icon(@user.email, 60), class: "avatar s60"
+ = image_tag avatar_icon(@user, 60), class: "avatar s60"
%li
%span.light Profile page:
%strong
= link_to user_path(@user) do
= @user.username
- = render 'users/profile', user: @user
+ = render 'admin/users/profile', user: @user
.panel.panel-default
.panel-heading
diff --git a/app/views/ci/admin/builds/_build.html.haml b/app/views/ci/admin/builds/_build.html.haml
index 778d51d03b..2df5871321 100644
--- a/app/views/ci/admin/builds/_build.html.haml
+++ b/app/views/ci/admin/builds/_build.html.haml
@@ -1,14 +1,16 @@
+- gl_project = build.project.gl_project
- if build.commit && build.project
%tr.build
%td.build-link
- = link_to ci_project_build_url(build.project, build) do
+ = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
%strong #{build.id}
%td.status
= ci_status_with_icon(build.status)
%td.commit-link
- = commit_link(build.commit)
+ = link_to ci_status_path(build.commit) do
+ %strong #{build.commit.short_sha}
%td.runner
- if build.runner
diff --git a/app/views/ci/admin/events/index.html.haml b/app/views/ci/admin/events/index.html.haml
index f9ab099430..5a5b4dc7c3 100644
--- a/app/views/ci/admin/events/index.html.haml
+++ b/app/views/ci/admin/events/index.html.haml
@@ -1,17 +1,18 @@
-%table.table
- %thead
- %tr
- %th User ID
- %th Description
- %th When
- - @events.each do |event|
- %tr
- %td
- = event.user_id
- %td
- = event.description
- %td.light
- = time_ago_in_words event.updated_at
- ago
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th User ID
+ %th Description
+ %th When
+ - @events.each do |event|
+ %tr
+ %td
+ = event.user_id
+ %td
+ = event.description
+ %td.light
+ = time_ago_in_words event.updated_at
+ ago
-= paginate @events
\ No newline at end of file
+= paginate @events
diff --git a/app/views/ci/admin/projects/_project.html.haml b/app/views/ci/admin/projects/_project.html.haml
index c461206c72..a342d6e1cf 100644
--- a/app/views/ci/admin/projects/_project.html.haml
+++ b/app/views/ci/admin/projects/_project.html.haml
@@ -1,4 +1,4 @@
-- last_commit = project.last_commit
+- last_commit = project.commits.last
%tr
%td
= project.id
diff --git a/app/views/ci/admin/projects/index.html.haml b/app/views/ci/admin/projects/index.html.haml
index dc7b041473..0da8547924 100644
--- a/app/views/ci/admin/projects/index.html.haml
+++ b/app/views/ci/admin/projects/index.html.haml
@@ -1,15 +1,16 @@
-%table.table
- %thead
- %tr
- %th ID
- %th Name
- %th Last build
- %th Access
- %th Builds
- %th
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th ID
+ %th Name
+ %th Last build
+ %th Access
+ %th Builds
+ %th
- - @projects.each do |project|
- = render "ci/admin/projects/project", project: project
+ - @projects.each do |project|
+ = render "ci/admin/projects/project", project: project
= paginate @projects
diff --git a/app/views/ci/admin/runners/index.html.haml b/app/views/ci/admin/runners/index.html.haml
index b9d6703ff4..bb213fbffc 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
@@ -35,18 +35,19 @@
%br
-%table.table
- %thead
- %tr
- %th Type
- %th Runner token
- %th Description
- %th Projects
- %th Builds
- %th Tags
- %th Last contact
- %th
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Type
+ %th Runner token
+ %th Description
+ %th Projects
+ %th Builds
+ %th Tags
+ %th Last contact
+ %th
- - @runners.each do |runner|
- = render "ci/admin/runners/runner", runner: runner
+ - @runners.each do |runner|
+ = render "ci/admin/runners/runner", runner: runner
= paginate @runners
diff --git a/app/views/ci/admin/runners/show.html.haml b/app/views/ci/admin/runners/show.html.haml
index 09905e0eb4..92787b2e6a 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
@@ -96,6 +96,7 @@
%table.builds.runner-builds
%thead
%tr
+ %th Build ID
%th Status
%th Project
%th Commit
@@ -103,6 +104,11 @@
- @builds.each do |build|
%tr.build
+ %td.id
+ - gl_project = build.project.gl_project
+ = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
+ = build.id
+
%td.status
= ci_status_with_icon(build.status)
@@ -110,8 +116,8 @@
= build.project.name
%td.build-link
- = link_to ci_project_build_path(build.project, build) do
- %strong #{build.short_sha}
+ = link_to ci_status_path(build.commit) do
+ %strong #{build.commit.short_sha}
%td.timestamp
- if build.finished_at
diff --git a/app/views/ci/builds/_build.html.haml b/app/views/ci/builds/_build.html.haml
deleted file mode 100644
index 515b862e99..0000000000
--- a/app/views/ci/builds/_build.html.haml
+++ /dev/null
@@ -1,45 +0,0 @@
-%tr.build
- %td.status
- = ci_status_with_icon(build.status)
-
- %td.build-link
- = link_to ci_project_build_path(build.project, build) do
- %strong Build ##{build.id}
-
- %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/ci/builds/show.html.haml b/app/views/ci/builds/show.html.haml
deleted file mode 100644
index 839dbf5c55..0000000000
--- a/app/views/ci/builds/show.html.haml
+++ /dev/null
@@ -1,170 +0,0 @@
-#up-build-trace
-- if @commit.matrix?
- %ul.center-top-menu
- - @commit.builds_without_retry_sorted.each do |build|
- %li{class: ('active' if build == @build) }
- = link_to ci_project_build_url(@project, build) do
- = ci_icon_for_status(build.status)
- %span
- - if build.name
- = build.name
- - else
- = build.id
-
-
- - unless @commit.builds_without_retry.include?(@build)
- %li.active
- %a
- Build ##{@build.id}
- ·
- %i.fa.fa-warning-sign
- This build was retried.
-
-.gray-content-block
- .build-head
- %h4
- - if @build.commit.tag?
- Build for tag
- %code #{@build.ref}
- - else
- Build for commit
- %strong.monospace= commit_link(@build.commit)
- from
-
- = link_to ci_project_path(@build.project, ref: @build.ref) do
- %strong.monospace= "#{@build.ref}"
-
- - if @build.duration
- .pull-right
- %span
- %i.fa.fa-time
- #{duration_in_words(@build.finished_at, @build.started_at)}
-
- .clearfix
- = ci_status_with_icon(@build.status)
- .pull-right
- = @build.updated_at.stamp('19:00 Aug 27')
-
-.row.prepend-top-default
- .col-md-9
- .clearfix
- - if @build.active?
- .autoscroll-container
- %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
- .clearfix
- .scroll-controls
- = link_to '#up-build-trace', class: 'btn' do
- %i.fa.fa-angle-up
- = link_to '#down-build-trace', class: 'btn' do
- %i.fa.fa-angle-down
-
- %pre.trace#build-trace
- %code.bash
- = preserve do
- = raw @build.trace_html
- %div#down-build-trace
-
- .col-md-3
- - if @build.coverage
- .build-widget
- %h4.title
- Test coverage
- %h1 #{@build.coverage}%
-
-
- .build-widget
- %h4.title
- Build
- - if current_user && can?(current_user, :manage_builds, gl_project)
- .pull-right
- - if @build.active?
- = link_to "Cancel", cancel_ci_project_build_path(@project, @build), class: 'btn btn-sm btn-danger'
- - elsif @build.commands.present?
- = link_to "Retry", retry_ci_project_build_path(@project, @build), class: 'btn btn-sm btn-primary', method: :post
-
- - if @build.duration
- %p
- %span.attr-name Duration:
- #{duration_in_words(@build.finished_at, @build.started_at)}
- %p
- %span.attr-name Created:
- #{time_ago_in_words(@build.created_at)} ago
- - if @build.finished_at
- %p
- %span.attr-name Finished:
- #{time_ago_in_words(@build.finished_at)} ago
- %p
- %span.attr-name Runner:
- - if @build.runner && current_user && current_user.admin
- \#{link_to "##{@build.runner.id}", ci_admin_runner_path(@build.runner.id)}
- - elsif @build.runner
- \##{@build.runner.id}
-
- - if @build.trigger_request
- .build-widget
- %h4.title
- Trigger
-
- %p
- %span.attr-name Token:
- #{@build.trigger_request.trigger.short_token}
-
- - if @build.trigger_request.variables
- %p
- %span.attr-name Variables:
-
- %code
- - @build.trigger_request.variables.each do |key, value|
- #{key}=#{value}
-
- .build-widget
- %h4.title
- Commit
- .pull-right
- %small #{build_commit_link @build}
-
- - if @build.commit.compare?
- %p
- %span.attr-name Compare:
- #{build_compare_link @build}
- %p
- %span.attr-name Branch:
- #{build_ref_link @build}
- %p
- %span.attr-name Author:
- #{@build.commit.git_author_name}
- %p
- %span.attr-name Message:
- #{@build.commit.git_commit_message}
-
- - if @build.tags.any?
- .build-widget
- %h4.title
- Tags
- - @build.tag_list.each do |tag|
- %span.label.label-primary
- = tag
-
- - if @builds.present?
- .build-widget
- %h4.title #{pluralize(@builds.count, "other build")} for #{@build.short_sha}:
- %table.builds
- - @builds.each_with_index do |build, i|
- %tr.build
- %td
- = ci_icon_for_status(build.status)
- %td
- = link_to ci_project_build_url(@project, build) do
- - if build.name
- = build.name
- - else
- %span ##{build.id}
-
- %td.status= build.status
-
-
- = paginate @builds
-
-
-:javascript
- new CiBuild("#{ci_project_build_url(@project, @build)}", "#{@build.status}")
diff --git a/app/views/ci/commits/_commit.html.haml b/app/views/ci/commits/_commit.html.haml
index 1eacfca944..b24a3b826c 100644
--- a/app/views/ci/commits/_commit.html.haml
+++ b/app/views/ci/commits/_commit.html.haml
@@ -7,7 +7,7 @@
%td.build-link
- = link_to ci_project_ref_commits_path(commit.project, commit.ref, commit.sha) do
+ = link_to ci_status_path(commit) do
%strong #{commit.short_sha}
%td.build-message
@@ -16,7 +16,8 @@
%td.build-branch
- unless @ref
%span
- = link_to truncate(commit.ref, length: 25), ci_project_path(@project, ref: commit.ref)
+ - commit.refs.each do |ref|
+ = link_to truncate(ref, length: 25), ci_project_path(@project, ref: ref)
%td.duration
- if commit.duration > 0
diff --git a/app/views/ci/commits/show.html.haml b/app/views/ci/commits/show.html.haml
deleted file mode 100644
index 8f38aa8467..0000000000
--- a/app/views/ci/commits/show.html.haml
+++ /dev/null
@@ -1,87 +0,0 @@
-.commit-info
- .append-bottom-20
- = ci_status_with_icon(@commit.status)
-
- .gray-content-block.middle-block
- %pre.commit-message
- #{@commit.git_commit_message}
-
- .gray-content-block.second-block
- .row
- .col-sm-6
- - if @commit.compare?
- %p
- %span.attr-name Compare:
- #{gitlab_compare_link(@project, @commit.short_before_sha, @commit.short_sha)}
- - else
- %p
- %span.attr-name Commit:
- #{gitlab_commit_link(@project, @commit.sha)}
-
- %p
- %span.attr-name Branch:
- #{gitlab_ref_link(@project, @commit.ref)}
- .col-sm-6
- %p
- %span.attr-name Author:
- #{@commit.git_author_name} (#{@commit.git_author_email})
- - if @commit.created_at
- %p
- %span.attr-name Created at:
- #{@commit.created_at.to_s(:short)}
-
-- if current_user && can?(current_user, :manage_builds, gl_project)
- .pull-right
- - if @commit.builds.running_or_pending.any?
- = link_to "Cancel", cancel_ci_project_ref_commits_path(@project, @commit.ref, @commit.sha), class: 'btn btn-sm btn-danger'
-
-
-- if @commit.yaml_errors.present?
- .bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
- %ul
- - @commit.yaml_errors.split(",").each do |error|
- %li= error
-
-- unless @commit.push_data[:ci_yaml_file]
- .bs-callout.bs-callout-warning
- \.gitlab-ci.yml not found in this commit
-
-%h3
- Builds
- - if @commit.duration > 0
- %small.pull-right
- %i.fa.fa-time
- #{time_interval_in_words @commit.duration}
-
-%table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @project.coverage_enabled?
- %th Coverage
- %th
- = render @commit.builds_without_retry_sorted, controls: true
-
-- if @commit.retried_builds.any?
- %h3
- Retried builds
-
- %table.table.builds
- %thead
- %tr
- %th Status
- %th Build ID
- %th Stage
- %th Name
- %th Duration
- %th Finished at
- - if @project.coverage_enabled?
- %th Coverage
- %th
- = render @commit.retried_builds
diff --git a/app/views/ci/events/index.html.haml b/app/views/ci/events/index.html.haml
index 779f49b3d3..9824e85b1a 100644
--- a/app/views/ci/events/index.html.haml
+++ b/app/views/ci/events/index.html.haml
@@ -1,19 +1,20 @@
%h3.page-title Events
-%table.table
- %thead
- %tr
- %th User ID
- %th Description
- %th When
- - @events.each do |event|
- %tr
- %td
- = event.user_id
- %td
- = event.description
- %td.light
- = time_ago_in_words event.updated_at
- ago
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th User ID
+ %th Description
+ %th When
+ - @events.each do |event|
+ %tr
+ %td
+ = event.user_id
+ %td
+ = event.description
+ %td.light
+ = time_ago_in_words event.updated_at
+ ago
-= paginate @events
\ No newline at end of file
+= paginate @events
diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml
index e2179e60f3..f45cd05aec 100644
--- a/app/views/ci/lints/_create.html.haml
+++ b/app/views/ci/lints/_create.html.haml
@@ -4,29 +4,30 @@
syntax is correct
%i.fa.fa-ok.correct-syntax
- %table.table.table-bordered
- %thead
- %tr
- %th Parameter
- %th Value
- %tbody
- - @stages.each do |stage|
- - @builds.select { |build| build[:stage] == stage }.each do |build|
- %tr
- %td #{stage.capitalize} Job - #{build[:name]}
- %td
- %pre
- = simple_format build[:script]
+ .table-holder
+ %table.table.table-bordered
+ %thead
+ %tr
+ %th Parameter
+ %th Value
+ %tbody
+ - @stages.each do |stage|
+ - @builds.select { |build| build[:stage] == stage }.each do |build|
+ %tr
+ %td #{stage.capitalize} Job - #{build[:name]}
+ %td
+ %pre
+ = simple_format build[:script]
- %br
- %b Tag list:
- = build[:tags]
- %br
- %b Refs only:
- = build[:only] && build[:only].join(", ")
- %br
- %b Refs except:
- = build[:except] && build[:except].join(", ")
+ %br
+ %b Tag list:
+ = build[:tags]
+ %br
+ %b Refs only:
+ = build[:only] && build[:only].join(", ")
+ %br
+ %b Refs except:
+ = build[:except] && build[:except].join(", ")
-else
%p
diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/ci/notify/build_fail_email.html.haml
index d818e8b675..69689a7502 100644
--- a/app/views/ci/notify/build_fail_email.html.haml
+++ b/app/views/ci/notify/build_fail_email.html.haml
@@ -11,9 +11,9 @@
%p
Author: #{@build.commit.git_author_name}
%p
- Branch: #{@build.commit.ref}
+ Branch: #{@build.ref}
%p
Message: #{@build.commit.git_commit_message}
%p
- Url: #{link_to @build.short_sha, ci_project_build_url(@project, @build)}
+ Url: #{link_to @build.short_sha, namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)}
diff --git a/app/views/ci/notify/build_fail_email.text.erb b/app/views/ci/notify/build_fail_email.text.erb
index 1add215a1c..6de5dc10f1 100644
--- a/app/views/ci/notify/build_fail_email.text.erb
+++ b/app/views/ci/notify/build_fail_email.text.erb
@@ -3,7 +3,7 @@ Build failed for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.commit.short_sha %>
Author: <%= @build.commit.git_author_name %>
-Branch: <%= @build.commit.ref %>
+Branch: <%= @build.ref %>
Message: <%= @build.commit.git_commit_message %>
-Url: <%= ci_project_build_url(@build.project, @build) %>
+Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %>
diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/ci/notify/build_success_email.html.haml
index a20dcaee24..4e3015a356 100644
--- a/app/views/ci/notify/build_success_email.html.haml
+++ b/app/views/ci/notify/build_success_email.html.haml
@@ -12,9 +12,9 @@
%p
Author: #{@build.commit.git_author_name}
%p
- Branch: #{@build.commit.ref}
+ Branch: #{@build.ref}
%p
Message: #{@build.commit.git_commit_message}
%p
- Url: #{link_to @build.short_sha, ci_project_build_url(@project, @build)}
+ Url: #{link_to @build.short_sha, namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)}
diff --git a/app/views/ci/notify/build_success_email.text.erb b/app/views/ci/notify/build_success_email.text.erb
index 7ebd17e727..d0a43ae1c1 100644
--- a/app/views/ci/notify/build_success_email.text.erb
+++ b/app/views/ci/notify/build_success_email.text.erb
@@ -3,7 +3,7 @@ Build successful for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.commit.short_sha %>
Author: <%= @build.commit.git_author_name %>
-Branch: <%= @build.commit.ref %>
+Branch: <%= @build.ref %>
Message: <%= @build.commit.git_commit_message %>
-Url: <%= ci_project_build_url(@build.project, @build) %>
+Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %>
diff --git a/app/views/ci/projects/_info.html.haml b/app/views/ci/projects/_info.html.haml
deleted file mode 100644
index 1888e1bde9..0000000000
--- 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/disabled.html.haml b/app/views/ci/projects/disabled.html.haml
deleted file mode 100644
index 83b0d8329e..0000000000
--- a/app/views/ci/projects/disabled.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-Continuous Integration has been disabled for time of the migration.
diff --git a/app/views/ci/projects/index.html.haml b/app/views/ci/projects/index.html.haml
new file mode 100644
index 0000000000..9c2290bc4a
--- /dev/null
+++ b/app/views/ci/projects/index.html.haml
@@ -0,0 +1,20 @@
+.wiki
+ %h1
+ GitLab CI is now integrated in GitLab UI
+ %h2 For existing projects
+
+ %p
+ Check the following pages to find the CI status you're looking for:
+
+ %ul
+ %li Projects page - shows CI status for each project.
+ %li Project commits page - show CI status for each commit.
+
+
+
+ %h2 For new projects
+
+ %p
+ If you want to enable CI for a new project it is easy as adding
+ = link_to ".gitlab-ci.yml", "http://doc.gitlab.com/ce/ci/yaml/README.html"
+ file to your repository
diff --git a/app/views/ci/projects/show.html.haml b/app/views/ci/projects/show.html.haml
deleted file mode 100644
index 6443378af9..0000000000
--- 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", ci_project_runners_path(@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 'View on GitLab', @project.gitlab_url, no_turbolink.merge( 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/ci/runners/show.html.haml b/app/views/ci/runners/show.html.haml
deleted file mode 100644
index ffec495f85..0000000000
--- a/app/views/ci/runners/show.html.haml
+++ /dev/null
@@ -1,64 +0,0 @@
-= content_for :title do
- %h3.project-title
- Runner ##{@runner.id}
- .pull-right
- - if @runner.shared?
- %span.runner-state.runner-state-shared
- Shared
- - else
- %span.runner-state.runner-state-specific
- Specific
-
-%table.table
- %thead
- %tr
- %th Property Name
- %th Value
- %tr
- %td
- Tags
- %td
- - @runner.tag_list.each do |tag|
- %span.label.label-primary
- = tag
- %tr
- %td
- Name
- %td
- = @runner.name
- %tr
- %td
- Version
- %td
- = @runner.version
- %tr
- %td
- Revision
- %td
- = @runner.revision
- %tr
- %td
- Platform
- %td
- = @runner.platform
- %tr
- %td
- Architecture
- %td
- = @runner.architecture
- %tr
- %td
- Description
- %td
- = @runner.description
- %tr
- %td
- Last contact
- %td
- - if @runner.contacted_at
- #{time_ago_in_words(@runner.contacted_at)} ago
- - else
- Never
-
-
-
diff --git a/app/views/ci/shared/_guide.html.haml b/app/views/ci/shared/_guide.html.haml
index 8a42f29b77..db2d7f2f4b 100644
--- a/app/views/ci/shared/_guide.html.haml
+++ b/app/views/ci/shared/_guide.html.haml
@@ -4,7 +4,7 @@
%ol
%li
Add at least one runner to the project.
- Go to #{link_to 'Runners page', ci_project_runners_path(@project), target: :blank} for instructions.
+ Go to #{link_to 'Runners page', runners_path(@project.gl_project), target: :blank} for instructions.
%li
Put the .gitlab-ci.yml in the root of your repository. Examples can be found in #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index 19d919f9b6..f98fd9f06b 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/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml
index f689b9698e..1408ebdd5d 100644
--- a/app/views/dashboard/milestones/_issue.html.haml
+++ b/app/views/dashboard/milestones/_issue.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml
index 8f5c4cce52..77c46de030 100644
--- a/app/views/dashboard/milestones/_merge_request.html.haml
+++ b/app/views/dashboard/milestones/_merge_request.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16"
+ = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 0d204ced7e..2fe14c6388 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -13,26 +13,28 @@
%span All issues for this milestone are closed. You may close the milestone now.
.description
-%table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - @dashboard_milestone.milestones.each do |milestone|
- %tr
- %td
- = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %td
- = milestone.issues.opened.count
- %td
- - if milestone.closed?
- Closed
- - else
- Open
- %td
- = milestone.expires_at
+
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - @dashboard_milestone.milestones.each do |milestone|
+ %tr
+ %td
+ = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
+ %td
+ = milestone.issues.opened.count
+ %td
+ - if milestone.closed?
+ Closed
+ - else
+ Open
+ %td
+ = milestone.expires_at
.context
%p.lead
@@ -79,7 +81,7 @@
- @dashboard_milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index c8c315c73d..81a5909e2d 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-success' do
- New project
+ = link_to new_project_path, class: 'btn btn-green' do
+ %i.fa.fa-plus
+ New Project
= render 'shared/projects/list', projects: @projects, ci: true
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 339362701d..f75f2e0a32 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -3,6 +3,9 @@
= render 'dashboard/projects_head'
+- if @last_push
+ = render "events/event_last_push", event: @last_push
+
- if @projects.any?
= render 'projects'
- else
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index d3908062f4..07b6d57932 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -6,33 +6,29 @@
.gray-content-block
.pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
- Add new snippet
+ = icon('plus')
+ New Snippet
- .oneline
- Share code pastes with others out of git repository
-
-%ul.nav.nav-tabs.prepend-top-20
- = nav_tab :scope, nil do
- = link_to dashboard_snippets_path do
+ .btn-group.btn-group-next.snippet-scope-menu
+ = link_to dashboard_snippets_path, class: "btn btn-default #{"active" unless params[:scope]}" do
All
%span.badge
= current_user.snippets.count
- = nav_tab :scope, 'are_private' do
- = link_to dashboard_snippets_path(scope: 'are_private') do
+
+ = link_to dashboard_snippets_path(scope: 'are_private'), class: "btn btn-default #{"active" if params[:scope] == "are_private"}" do
Private
%span.badge
= current_user.snippets.are_private.count
- = nav_tab :scope, 'are_internal' do
- = link_to dashboard_snippets_path(scope: 'are_internal') do
+
+ = link_to dashboard_snippets_path(scope: 'are_internal'), class: "btn btn-default #{"active" if params[:scope] == "are_internal"}" do
Internal
%span.badge
= current_user.snippets.are_internal.count
- = nav_tab :scope, 'are_public' do
- = link_to dashboard_snippets_path(scope: 'are_public') do
+
+ = link_to dashboard_snippets_path(scope: 'are_public'), class: "btn btn-default #{"active" if params[:scope] == "are_public"}" do
Public
%span.badge
= current_user.snippets.are_public.count
-.my-snippets
- = render 'snippets/snippets'
+= render 'snippets/snippets'
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index 29ffe8a8be..535e85869e 100644
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -6,7 +6,7 @@
.devise-errors
= devise_error_messages!
.clearfix.append-bottom-20
- = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email]
+ = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email], autofocus: true
.clearfix
= f.submit "Reset password", class: "btn-primary btn"
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 3b0b19107c..ba4c5b86ef 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -1,17 +1,19 @@
- page_title "Applications"
%h3.page-title Your applications
%p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
-%table.table.table-striped
- %thead
- %tr
- %th Name
- %th Callback URL
- %th
- %th
- %tbody
- - @applications.each do |application|
- %tr{:id => "application_#{application.id}"}
- %td= link_to application.name, oauth_application_path(application)
- %td= application.redirect_uri
- %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link'
- %td= render 'delete_form', application: application
+
+.table-holder
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td= application.redirect_uri
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link'
+ %td= render 'delete_form', application: application
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
index 80340aca54..47442b78d4 100644
--- a/app/views/doorkeeper/applications/show.html.haml
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -2,26 +2,26 @@
%h3.page-title
Application: #{@application.name}
+.table-holder
+ %table.table
+ %tr
+ %td
+ Application Id
+ %td
+ %code#application_id= @application.uid
+ %tr
+ %td
+ Secret:
+ %td
+ %code#secret= @application.secret
-%table.table
- %tr
- %td
- Application Id
- %td
- %code#application_id= @application.uid
- %tr
- %td
- Secret:
- %td
- %code#secret= @application.secret
-
- %tr
- %td
- Callback url
- %td
- - @application.redirect_uri.split.each do |uri|
- %div
- %span.monospace= uri
+ %tr
+ %td
+ Callback url
+ %td
+ - @application.redirect_uri.split.each do |uri|
+ %div
+ %span.monospace= uri
.form-actions
= link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left'
= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml
index 814cdc987e..b184b9c01d 100644
--- a/app/views/doorkeeper/authorized_applications/index.html.haml
+++ b/app/views/doorkeeper/authorized_applications/index.html.haml
@@ -1,16 +1,17 @@
%header.page-header
%h1 Your authorized applications
%main{:role => "main"}
- %table.table.table-striped
- %thead
- %tr
- %th Application
- %th Created At
- %th
- %th
- %tbody
- - @applications.each do |application|
+ .table-holder
+ %table.table.table-striped
+ %thead
%tr
- %td= application.name
- %td= application.created_at.strftime('%Y-%m-%d %H:%M:%S')
- %td= render 'delete_form', application: application
\ No newline at end of file
+ %th Application
+ %th Created At
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr
+ %td= application.name
+ %td= application.created_at.strftime('%Y-%m-%d %H:%M:%S')
+ %td= render 'delete_form', application: application
diff --git a/app/views/events/_event_issue.atom.haml b/app/views/events/_event_issue.atom.haml
index 4259f64c19..fad6531002 100644
--- a/app/views/events/_event_issue.atom.haml
+++ b/app/views/events/_event_issue.atom.haml
@@ -1,3 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- - if issue.description.present?
- = markdown(issue.description, xhtml: true, reference_only_path: false, project: issue.project)
+ = markdown(issue.description, pipeline: :atom, project: issue.project)
diff --git a/app/views/events/_event_merge_request.atom.haml b/app/views/events/_event_merge_request.atom.haml
index e8ed13df78..19bdc7b9ca 100644
--- a/app/views/events/_event_merge_request.atom.haml
+++ b/app/views/events/_event_merge_request.atom.haml
@@ -1,3 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- - if merge_request.description.present?
- = markdown(merge_request.description, xhtml: true, reference_only_path: false, project: merge_request.project)
+ = markdown(merge_request.description, pipeline: :atom, project: merge_request.project)
diff --git a/app/views/events/_event_note.atom.haml b/app/views/events/_event_note.atom.haml
index cfbfba5020..b730ebbd5f 100644
--- a/app/views/events/_event_note.atom.haml
+++ b/app/views/events/_event_note.atom.haml
@@ -1,2 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- = markdown(note.note, xhtml: true, reference_only_path: false, project: note.project)
+ = markdown(note.note, pipeline: :atom, project: note.project)
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index 3625cb49d8..b271b9daff 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -6,7 +6,7 @@
%i
at
= commit[:timestamp].to_time.to_s(:short)
- %blockquote= markdown(escape_once(commit[:message]), xhtml: true, reference_only_path: false, project: event.project)
+ %blockquote= markdown(escape_once(commit[:message]), pipeline: :atom, project: event.project)
- if event.commits_count > 15
%p
%i
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 83d4d321c8..fcb07b0408 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 5a3d689d1e..2761272aa8 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/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index 7e4fa7d487..0f100c39ff 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -10,7 +10,8 @@
- if current_user
.pull-right
= link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
- Add new snippet
+ = icon('plus')
+ New Snippet
.oneline
Public snippets created by you and other users are listed here
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 9ac56b1e5f..11d69977ef 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-success' do
- New project
+ = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
+ %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/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index b5f359279d..3c19381321 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -5,7 +5,7 @@
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)}
- if member.user
- = image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 3a6d07ebdd..fee4b0052b 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/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml
index 09f9b4b896..9b85d83d6d 100644
--- a/app/views/groups/milestones/_issue.html.haml
+++ b/app/views/groups/milestones/_issue.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml
index d0d1426762..e3aa4aad19 100644
--- a/app/views/groups/milestones/_merge_request.html.haml
+++ b/app/views/groups/milestones/_merge_request.html.haml
@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 0c213f4218..a92ad5d751 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -21,26 +21,28 @@
%span All issues for this milestone are closed. You may close the milestone now.
.description
-%table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - @group_milestone.milestones.each do |milestone|
- %tr
- %td
- = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %td
- = milestone.issues.opened.count
- %td
- - if milestone.closed?
- Closed
- - else
- Open
- %td
- = milestone.expires_at
+
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - @group_milestone.milestones.each do |milestone|
+ %tr
+ %td
+ = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
+ %td
+ = milestone.issues.opened.count
+ %td
+ - if milestone.closed?
+ Closed
+ - else
+ Open
+ %td
+ = milestone.expires_at
.context
%p.lead
@@ -87,7 +89,7 @@
- @group_milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index a9ba9d2ba1..dc8e81323a 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/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index e809d99ba7..67349fcbd7 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -99,6 +99,12 @@
.key c
%td
Go to commits
+ %tr
+ %td.shortcut
+ .key g
+ .key b
+ %td
+ Go to builds
%tr
%td.shortcut
.key g
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 7c89457ace..2169a821fb 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/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 777eb48271..30bcdb8682 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -14,45 +14,46 @@
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From Bitbucket
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From Bitbucket
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
- %td
- = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
- %td.import-target
- = "#{repo["owner"]}/#{repo["slug"]}"
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
- - @incompatible_repos.each do |repo|
- %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
- %td
- = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
- %td.import-target
- %td.import-actions-job-status
- = label_tag "Incompatible Project", nil, class: "label label-danger"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
+ %td
+ = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
+ %td.import-target
+ = "#{repo["owner"]}/#{repo["slug"]}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+ - @incompatible_repos.each do |repo|
+ %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
+ %td
+ = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
+ %td.import-target
+ %td.import-actions-job-status
+ = label_tag "Incompatible Project", nil, class: "label label-danger"
- if @incompatible_repos.any?
%p
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index 25cebfb366..a701e49ac5 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -25,22 +25,23 @@
of issues and comments (e.g. "By @johnsmith"). It will also
associate and/or assign these issues and comments with the selected user.
- %table.table
- %thead
- %tr
- %th ID
- %th Name
- %th Email
- %th GitLab User
- %tbody
- - @user_map.each do |id, user|
+ .table-holder
+ %table.table
+ %thead
%tr
- %td= id
- %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
- %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
- %td
- = users_select_tag("users[#{id}][gitlab_user]", class: 'custom-form-control',
- scope: :all, email_user: true, selected: user[:gitlab_user])
+ %th ID
+ %th Name
+ %th Email
+ %th GitLab User
+ %tbody
+ - @user_map.each do |id, user|
+ %tr
+ %td= id
+ %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
+ %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
+ %td
+ = users_select_tag("users[#{id}][gitlab_user]", class: 'custom-form-control',
+ scope: :all, email_user: true, selected: user[:gitlab_user])
.form-actions
= submit_tag 'Continue to the next step', class: 'btn btn-create'
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index f179ece402..beca6ab142 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -14,38 +14,39 @@
%p
= button_tag 'Import all projects', class: 'btn btn-success js-import-all'
-%table.table.import-jobs
- %thead
- %tr
- %th From FogBugz
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = project.import_source
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From FogBugz
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = project.import_source
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = repo.name
- %td.import-target
- = "#{current_user.username}/#{repo.name}"
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = repo.name
+ %td.import-target
+ = "#{current_user.username}/#{repo.name}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
:coffeescript
new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}")
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index ef55249823..0669b05adc 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -9,38 +9,39 @@
%p
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From GitHub
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From GitHub
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
- %td.import-target
- = repo.full_name
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
+ %td.import-target
+ = repo.full_name
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
:coffeescript
new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}")
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index 727f3c7e7f..3bc85059e7 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -9,38 +9,39 @@
%p
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From GitLab.com
- %th To this GitLab instance
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From GitLab.com
+ %th To this GitLab instance
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo["id"]}"}
- %td
- = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
- %td.import-target
- = repo["path_with_namespace"]
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo["id"]}"}
+ %td
+ = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
+ %td.import-target
+ = repo["path_with_namespace"]
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
:coffeescript
new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}")
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index bff7ee7c85..2e3a535737 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -9,38 +9,39 @@
%p
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From Gitorious.org
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From Gitorious.org
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank"
- %td.import-target
- = repo.full_name
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank"
+ %td.import-target
+ = repo.full_name
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
:coffeescript
new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}")
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index e8ec79e72f..c5af06edf8 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -17,45 +17,46 @@
- else
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
-%table.table.import-jobs
- %thead
- %tr
- %th From Google Code
- %th To GitLab
- %th Status
- %tbody
- - @already_added_projects.each do |project|
- %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
- %td
- = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
- %td
- %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
- %td.job-status
- - if project.import_status == 'finished'
- %span
- %i.fa.fa-check
- done
- - elsif project.import_status == 'started'
- %i.fa.fa-spinner.fa-spin
- started
- - else
- = project.human_import_status_name
+.table-holder
+ %table.table.import-jobs
+ %thead
+ %tr
+ %th From Google Code
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
- - @repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
- %td.import-target
- = "#{current_user.username}/#{repo.name}"
- %td.import-actions.job-status
- = button_tag "Import", class: "btn js-add-to-import"
- - @incompatible_repos.each do |repo|
- %tr{id: "repo_#{repo.id}"}
- %td
- = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
- %td.import-target
- %td.import-actions-job-status
- = label_tag "Incompatible Project", nil, class: "label label-danger"
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
+ %td.import-target
+ = "#{current_user.username}/#{repo.name}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+ - @incompatible_repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
+ %td.import-target
+ %td.import-actions-job-status
+ = label_tag "Incompatible Project", nil, class: "label label-danger"
- if @incompatible_repos.any?
%p
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index c3b137e3dd..74174a72f5 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -3,7 +3,7 @@
%meta{charset: "utf-8"}
%meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'}
%meta{content: "GitLab Community Edition", name: "description"}
- %meta{name: 'referrer', content: 'origin'}
+ %meta{name: 'referrer', content: 'origin-when-cross-origin'}
%title= page_title
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 3c58f10e75..035fe0056d 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,3 +1,4 @@
+- project = @target_project || @project
:javascript
- GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(@project.namespace, @project, type: @noteable.class, type_id: params[:id])}"
+ GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: @noteable.class, type_id: params[:id])}"
GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 2468687b56..352b8040cf 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
@@ -18,11 +18,12 @@
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
.username
= 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 e2d2dec7ab..ceb64ce315 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 7daf9342e4..f094edbfa8 100644
--- a/app/views/layouts/ci/_nav_project.html.haml
+++ b/app/views/layouts/ci/_nav_project.html.haml
@@ -5,45 +5,8 @@
%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: ['runners#index', 'runners#show', 'runners#edit'] do
- = link_to ci_project_runners_path(@project) do
- = icon('cog fw')
- %span
- Runners
- = nav_link path: 'variables#show' do
- = link_to ci_project_variables_path(@project) do
- = icon('code fw')
- %span
- Variables
- = nav_link path: 'web_hooks#index' do
- = link_to ci_project_web_hooks_path(@project) do
- = icon('link fw')
- %span
- Web Hooks
- = nav_link path: 'triggers#index' do
- = link_to ci_project_triggers_path(@project) do
- = icon('retweet fw')
- %span
- Triggers
- = 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')
%span
Events
- %li.separate-item
- = nav_link path: 'projects#edit' do
- = link_to edit_ci_project_path(@project) do
- = icon('cogs fw')
- %span
- Settings
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
index bb5ec727bf..ab3e29c3f4 100644
--- a/app/views/layouts/ci/_page.html.haml
+++ b/app/views/layouts/ci/_page.html.haml
@@ -15,7 +15,7 @@
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
+ = image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
diff --git a/app/views/layouts/ci/build.html.haml b/app/views/layouts/ci/build.html.haml
deleted file mode 100644
index a1356f0dc2..0000000000
--- a/app/views/layouts/ci/build.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-!!! 5
-%html{ lang: "en"}
- = render 'layouts/head'
- %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
- - header_title ci_commit_title(@commit)
- - if current_user
- = render "layouts/header/default", title: header_title
- - else
- = render "layouts/header/public", title: header_title
-
- = render 'layouts/ci/page', sidebar: 'nav_project'
diff --git a/app/views/layouts/ci/commit.html.haml b/app/views/layouts/ci/commit.html.haml
deleted file mode 100644
index a1356f0dc2..0000000000
--- a/app/views/layouts/ci/commit.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-!!! 5
-%html{ lang: "en"}
- = render 'layouts/head'
- %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
- - header_title ci_commit_title(@commit)
- - if current_user
- = render "layouts/header/default", title: header_title
- - else
- = render "layouts/header/public", title: header_title
-
- = render 'layouts/ci/page', sidebar: 'nav_project'
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 21d8655131..b1a1d53184 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
- = link_to root_path, title: 'Projects', data: {placement: 'right'} do
+ = link_to dashboard_projects_path, title: 'Projects', data: {placement: 'right'} do
= icon('home fw')
%span
Projects
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index a218ec7486..53a913fe8f 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -38,6 +38,14 @@
%span
Commits
+ - if project_nav_tab? :builds
+ = nav_link(controller: %w(builds)) do
+ = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds', data: {placement: 'right'} do
+ = icon('cubes fw')
+ %span
+ Builds
+ %span.count.builds_counter= @project.ci_builds.running_or_pending.count(:all)
+
- if project_nav_tab? :network
= nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do
@@ -76,13 +84,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 857fb19995..954dbe5d2b 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -34,3 +34,39 @@
%span
Protected Branches
+ - if @project.gitlab_ci?
+ = nav_link(controller: :runners) do
+ = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do
+ = icon('cog fw')
+ %span
+ Runners
+ = nav_link(controller: :variables) do
+ = link_to namespace_project_variables_path(@project.namespace, @project) do
+ = icon('code fw')
+ %span
+ Variables
+ = nav_link path: 'triggers#index' do
+ = link_to namespace_project_triggers_path(@project.namespace, @project) do
+ = icon('retweet fw')
+ %span
+ Triggers
+ = nav_link path: 'ci_web_hooks#index' do
+ = link_to namespace_project_ci_web_hooks_path(@project.namespace, @project) do
+ = icon('link fw')
+ %span
+ CI Web Hooks
+ = nav_link path: 'ci_settings#edit' do
+ = link_to edit_namespace_project_ci_settings_path(@project.namespace, @project) do
+ = 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/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index ec209c38ee..854cda57c3 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -41,6 +41,8 @@
#{link_to "view it on GitLab", @target_url}.
- else
#{link_to "View it on GitLab", @target_url}
+ %br
+ You're receiving this email because of your account on #{link_to Gitlab.config.gitlab.host, root_url}.
+ If you'd like to receive fewer emails, you can adjust your notification settings.
+
= email_action @target_url
- - if @project && !@disable_footer
- You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team.
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 78dafcd8bf..abf73bcc70 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -3,10 +3,11 @@
- sidebar "project" unless sidebar
- content_for :scripts_body_top do
+ - project = @target_project || @project
- if current_user
:javascript
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
- window.markdown_preview_path = "#{markdown_preview_namespace_project_path(@project.namespace, @project)}";
+ window.project_uploads_path = "#{namespace_project_uploads_path project.namespace,project}";
+ window.markdown_preview_path = "#{markdown_preview_namespace_project_path(project.namespace, project)}";
- content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 3fd4b04ac8..00cb4aa24c 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -1,2 +1,2 @@
%div
- = markdown(@note.note, reference_only_path: false)
+ = markdown(@note.note, pipeline: :email)
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 9db75bdb19..34dbc60e19 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,6 +1,6 @@
= "Merge Request ##{@merge_request.iid} was merged"
-Merge Request Url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
+Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index 53a068be52..d3b799fca2 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,5 +1,5 @@
-if @issue.description
- = markdown(@issue.description, reference_only_path: false)
+ = markdown(@issue.description, pipeline: :email)
- if @issue.assignee_id.present?
%p
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index 5b7dd117c1..90ebdfc3fe 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -6,4 +6,4 @@
Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
-if @merge_request.description
- = markdown(@merge_request.description, reference_only_path: false)
+ = markdown(@merge_request.description, pipeline: :email)
diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml
index 3cd759f1f5..87b3ff7f0b 100644
--- a/app/views/notify/project_was_moved_email.html.haml
+++ b/app/views/notify/project_was_moved_email.html.haml
@@ -1,5 +1,5 @@
%p
- Project was moved to another location
+ Project #{@old_path_with_namespace} was moved to another location
%p
The project is now located under
= link_to namespace_project_url(@project.namespace, @project) do
diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb
index b3f18b35a4..d8a23dabf4 100644
--- a/app/views/notify/project_was_moved_email.text.erb
+++ b/app/views/notify/project_was_moved_email.text.erb
@@ -1,4 +1,4 @@
-Project was moved to another location
+Project #{@old_path_with_namespace} was moved to another location
The project is now located under
<%= namespace_project_url(@project.namespace, @project) %>
diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml
index c19ac429d5..58af79716a 100644
--- a/app/views/profiles/_event_table.html.haml
+++ b/app/views/profiles/_event_table.html.haml
@@ -1,16 +1,17 @@
-%table.table#audits
- %thead
- %tr
- %th Action
- %th When
-
- %tbody
- - events.each do |event|
+.table-holder
+ %table.table#audits
+ %thead
%tr
- %td
- %span
- Signed in with
- %b= event.details[:with]
- authentication
- %td #{time_ago_in_words event.created_at} ago
+ %th Action
+ %th When
+
+ %tbody
+ - events.each do |event|
+ %tr
+ %td
+ %span
+ Signed in with
+ %b= event.details[:with]
+ authentication
+ %td #{time_ago_in_words event.created_at} ago
= paginate events, theme: "gitlab"
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index e0ae4d9720..0ca8bd9515 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/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 60289bfe7c..cc41d7dd81 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -32,6 +32,13 @@
.panel-heading
Behavior
.panel-body
+ .form-group
+ = f.label :layout, class: 'control-label' do
+ Layout width
+ .col-sm-10
+ = f.select :layout, layout_choices, {}, class: 'form-control'
+ .help-block
+ Choose between fixed (max. 1200px) and fluid (100%) application layout.
.form-group
= f.label :dashboard, class: 'control-label' do
Default Dashboard
@@ -45,6 +52,6 @@
.col-sm-10
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.help-block
- Choose what content you want to see when visit project page
+ Choose what content you want to see on a project's home page.
.panel-footer
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb
index 6c4b0ce757..4433cab778 100644
--- a/app/views/profiles/preferences/update.js.erb
+++ b/app/views/profiles/preferences/update.js.erb
@@ -2,6 +2,13 @@
$('body').removeClass('<%= Gitlab::Themes.body_classes %>')
$('body').addClass('<%= user_application_theme %>')
+// Toggle container-fluid class
+if ('<%= current_user.layout %>' === 'fluid') {
+ $('.content-wrapper').find('.container-fluid').removeClass('container-limited')
+} else {
+ $('.content-wrapper').find('.container-fluid').addClass('container-limited')
+}
+
// Re-enable the "Save" button
$('input[type=submit]').enable()
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 47412e2ef0..ac7355dde1 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -68,7 +68,7 @@
.col-md-5
.light-well
- = image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160'
+ = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
.clearfix
.profile-avatar-form-option
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 1261f6254d..012858f70b 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,10 +1,8 @@
-= 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/_files.html.haml b/app/views/projects/_files.html.haml
new file mode 100644
index 0000000000..fa978325dd
--- /dev/null
+++ b/app/views/projects/_files.html.haml
@@ -0,0 +1,6 @@
+#tree-holder.tree-holder.clearfix
+ .gray-content-block.second-block
+ = render 'projects/tree/tree_header', tree: @tree
+
+ = render 'projects/tree/tree_content', tree: @tree
+
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 6e53f55b0a..8c0980369f 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -16,18 +16,19 @@
.project-repo-buttons
- = render 'projects/buttons/star'
-
- - unless empty_repo
- = render 'projects/buttons/fork'
-
- - if can? current_user, :download_code, @project
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
- = icon('download fw')
- Download
+ .split-one
+ = render 'projects/buttons/star'
+ - unless empty_repo
+ = render 'projects/buttons/fork'
+
+ = render "shared/clone_panel"
+ .split-repo-buttons
+ - unless empty_repo
+ - if can? current_user, :download_code, @project
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
+ = icon('download fw')
+
+ = render 'projects/buttons/dropdown'
= render 'projects/buttons/notifications'
-
- = render 'projects/buttons/dropdown'
-
- = render "shared/clone_panel"
+
diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml
new file mode 100644
index 0000000000..d7b20bfc6b
--- /dev/null
+++ b/app/views/projects/_last_commit.html.haml
@@ -0,0 +1,12 @@
+.project-last-commit
+ - ci_commit = project.ci_commit(commit.sha)
+ - if ci_commit
+ = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
+ = ci_status_icon(ci_commit)
+ = ci_commit.status
+
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+ ·
+ #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by
+ = commit_author_link(commit, avatar: true, size: 24)
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 507757f6a2..7b21095ea3 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -2,10 +2,10 @@
.md-header.clearfix
%ul.center-top-menu
%li.active
- = link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do
+ %a.js-md-write-button(href="#md-write-holder" tabindex="-1")
Write
%li
- = link_to '#md-preview-holder', class: 'js-md-preview-button', tabindex: '-1' do
+ %a.js-md-preview-button(href="md-preview-holder" tabindex="-1")
Preview
- if defined?(referenced_users) && referenced_users
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index 5038edb95e..b5ef0aca54 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -1,12 +1,9 @@
- if readme = @repository.readme
- %article.readme-holder#README
- .clearfix
- .pull-right
-
- - if can?(current_user, :push_code, @project)
- = link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
- %i.fa.fa-pencil
- .wiki
+ %article.readme-holder
+ .pull-right
+ - if can?(current_user, :push_code, @project)
+ = link_to icon('pencil'), namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light edit-project-readme'
+ .file-content.wiki
= cache(readme_cache_key) do
= render_readme(readme)
- else
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index 6a41cdbc90..63ebfc9381 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -1,10 +1,10 @@
.zennable
- %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
+ %input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
.zen-backdrop
- classes << ' js-gfm-input markdown-area'
= f.text_area attr, class: classes, placeholder: ''
- = link_to nil, class: 'zen-enter-link', tabindex: '-1' do
+ %a.zen-enter-link(tabindex="-1" href="#")
%i.fa.fa-expand
Edit in fullscreen
- = link_to nil, class: 'zen-leave-link' do
+ %a.zen-leave-link(href="#")
%i.fa.fa-compress
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index 555ed76426..69fa4ad37c 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -1,4 +1,6 @@
- page_title "Activity"
- header_title project_title(@project, "Activity", activity_project_path(@project))
+= render 'projects/last_push'
+
= render 'projects/activity'
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index b4c7d8b9b7..42f632b38e 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -1,25 +1,28 @@
-%ul.breadcrumb.repo-breadcrumb
- %li
- %i.fa.fa-angle-right
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
- = @project.path
- - tree_breadcrumbs(@tree, 6) do |title, path|
+.gray-content-block.top-block
+ .tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'blob', path: @path
+
+ %ul.breadcrumb.repo-breadcrumb
%li
- - if path
- - if path.end_with?(@path)
- = link_to namespace_project_blob_path(@project.namespace, @project, path) do
- %strong
- = truncate(title, length: 40)
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = @project.path
+ - tree_breadcrumbs(@tree, 6) do |title, path|
+ %li
+ - if path
+ - if path.end_with?(@path)
+ = link_to namespace_project_blob_path(@project.namespace, @project, path) do
+ %strong
+ = truncate(title, length: 40)
+ - else
+ = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
- = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- - else
- = link_to title, '#'
+ = link_to title, '#'
%ul.blob-commit-info.hidden-xs
- 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 9c3e1703c8..f1ad0c3c40 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 0000000000..cb1567a2e6
--- /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 1a1df12770..e27f170752 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 1950586b11..7975137c37 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/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index fa4be4a1bc..f52b89f692 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -3,9 +3,6 @@
= render 'projects/last_push'
-%div.tree-ref-holder
- = render 'shared/ref_switcher', destination: 'blob', path: @path
-
%div#tree-holder.tree-holder
= render 'blob', blob: @blob
diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml
new file mode 100644
index 0000000000..4ce4ed63b4
--- /dev/null
+++ b/app/views/projects/builds/_build.html.haml
@@ -0,0 +1,53 @@
+%tr.build
+ %td.status
+ = ci_status_with_icon(build.status)
+
+ %td.commit_status-link
+ - if build.target_url
+ = link_to build.target_url do
+ %strong Build ##{build.id}
+ - else
+ %strong Build ##{build.id}
+
+ - if build.show_warning?
+ %i.fa.fa-warning.text-warning
+
+ %td
+ = link_to build.short_sha, namespace_project_commit_path(@project.namespace, @project, build.sha)
+
+ %td
+ = link_to build.ref, namespace_project_commits_path(@project.namespace, @project, build.ref)
+
+ %td
+ - if build.runner
+ = runner_link(build.runner)
+ - else
+ .light none
+
+ %td
+ = build.name
+
+ .pull-right
+ - if build.tags.any?
+ - build.tags.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
+
+ %td
+ .pull-right
+ - if current_user && can?(current_user, :manage_builds, @project)
+ - if build.cancel_url
+ = link_to build.cancel_url, title: 'Cancel' do
+ %i.fa.fa-remove.cred
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
new file mode 100644
index 0000000000..e08556673e
--- /dev/null
+++ b/app/views/projects/builds/index.html.haml
@@ -0,0 +1,53 @@
+- page_title "Builds"
+- header_title project_title(@project, "Builds", project_builds_path(@project))
+
+.project-issuable-filter
+ .controls
+ - if @ci_project && current_user && can?(current_user, :manage_builds, @project)
+ .pull-left.hidden-xs
+ - if @all_builds.running_or_pending.any?
+ = link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger'
+
+ %ul.center-top-menu
+ %li{class: ('active' if @scope.nil?)}
+ = link_to project_builds_path(@project) do
+ Running
+ %span.badge.js-running-count= @all_builds.running_or_pending.count(:id)
+
+ %li{class: ('active' if @scope == 'finished')}
+ = link_to project_builds_path(@project, scope: :finished) do
+ Finished
+ %span.badge.js-running-count= @all_builds.finished.count(:id)
+
+ %li{class: ('active' if @scope == 'all')}
+ = link_to project_builds_path(@project, scope: :all) do
+ All
+ %span.badge.js-totalbuilds-count= @all_builds.count(:id)
+
+.gray-content-block
+ List of #{@scope || 'running'} builds from this project
+
+%ul.content-list
+ - if @builds.blank?
+ %li
+ .nothing-here-block No builds to show
+ - else
+ .table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Commit
+ %th Ref
+ %th Runner
+ %th Name
+ %th Duration
+ %th Finished at
+ %th
+
+ - @builds.each do |build|
+ = render 'projects/builds/build', build: build
+
+ = paginate @builds
+
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
new file mode 100644
index 0000000000..3a8172dc8e
--- /dev/null
+++ b/app/views/projects/builds/show.html.haml
@@ -0,0 +1,178 @@
+.build-page
+ .gray-content-block
+ Build for commit
+ %strong.monospace
+ = link_to @build.commit.short_sha, ci_status_path(@build.commit)
+ from
+ %code #{@build.ref}
+
+ #up-build-trace
+ - if @commit.matrix_for_ref?(@build.ref)
+ %ul.center-top-menu.build-top-menu
+ - @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)
+ %span
+ - if build.name
+ = build.name
+ - else
+ = build.id
+
+
+ - unless @commit.latest_builds_for_ref(@build.ref).include?(@build)
+ %li.active
+ %a
+ Build ##{@build.id}
+ ·
+ %i.fa.fa-warning
+ This build was retried.
+
+ .gray-content-block.second-block
+ .build-head
+ .clearfix
+ = ci_status_with_icon(@build.status)
+ - if @build.duration
+ %span
+ %i.fa.fa-time
+ #{duration_in_words(@build.finished_at, @build.started_at)}
+ .pull-right
+ = @build.updated_at.stamp('19:00 Aug 27')
+
+ - if @build.show_warning?
+ - unless @build.any_runners_online?
+ .bs-callout.bs-callout-warning
+ %p
+ - if no_runners_for_project?(@build.project)
+ This build is stuck, because the project doesn't have any runners online assigned to it.
+ - elsif @build.tags.any?
+ This build is stuck, because you don't have any active runners online with any of these tags assigned to them:
+ - @build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - else
+ This build is stuck, because you don't have any active runners that can run this build.
+
+ %br
+ Go to
+ = link_to namespace_project_runners_path(@build.gl_project.namespace, @build.gl_project) do
+ Runners page
+
+ .row.prepend-top-default
+ .col-md-9
+ .clearfix
+ - if @build.active?
+ .autoscroll-container
+ %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
+ .clearfix
+ .scroll-controls
+ = link_to '#up-build-trace', class: 'btn' do
+ %i.fa.fa-angle-up
+ = link_to '#down-build-trace', class: 'btn' do
+ %i.fa.fa-angle-down
+
+ %pre.trace#build-trace
+ %code.bash
+ = preserve do
+ = raw @build.trace_html
+ %div#down-build-trace
+
+ .col-md-3
+ - if @build.coverage
+ .build-widget
+ %h4.title
+ Test coverage
+ %h1 #{@build.coverage}%
+
+
+ .build-widget
+ %h4.title
+ Build
+ - if current_user && can?(current_user, :manage_builds, @project)
+ .pull-right
+ - if @build.active?
+ = 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_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-primary', method: :post
+
+ - if @build.duration
+ %p
+ %span.attr-name Duration:
+ #{duration_in_words(@build.finished_at, @build.started_at)}
+ %p
+ %span.attr-name Created:
+ #{time_ago_in_words(@build.created_at)} ago
+ - if @build.finished_at
+ %p
+ %span.attr-name Finished:
+ #{time_ago_in_words(@build.finished_at)} ago
+ %p
+ %span.attr-name Runner:
+ - if @build.runner && current_user && current_user.admin
+ \#{link_to "##{@build.runner.id}", ci_admin_runner_path(@build.runner.id)}
+ - elsif @build.runner
+ \##{@build.runner.id}
+
+ - if @build.trigger_request
+ .build-widget
+ %h4.title
+ Trigger
+
+ %p
+ %span.attr-name Token:
+ #{@build.trigger_request.trigger.short_token}
+
+ - if @build.trigger_request.variables
+ %p
+ %span.attr-name Variables:
+
+ %code
+ - @build.trigger_request.variables.each do |key, value|
+ #{key}=#{value}
+
+ .build-widget
+ %h4.title
+ Commit
+ .pull-right
+ %small #{build_commit_link @build}
+ %p
+ %span.attr-name Branch:
+ #{build_ref_link @build}
+ %p
+ %span.attr-name Author:
+ #{@build.commit.git_author_name}
+ %p
+ %span.attr-name Message:
+ #{@build.commit.git_commit_message}
+
+ - if @build.tags.any?
+ .build-widget
+ %h4.title
+ Tags
+ - @build.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
+
+ - if @builds.present?
+ .build-widget
+ %h4.title #{pluralize(@builds.count, "other build")} for #{@build.short_sha}:
+ %table.table.builds
+ - @builds.each_with_index do |build, i|
+ %tr.build
+ %td
+ = ci_icon_for_status(build.status)
+ %td
+ = link_to namespace_project_build_path(@project.namespace, @project, @build) do
+ - if build.name
+ = build.name
+ - else
+ %span ##{build.id}
+
+ %td.status= build.status
+
+
+ = paginate @builds
+
+
+ :javascript
+ new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}")
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index bc7625e898..4580c91269 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -1,6 +1,6 @@
- if current_user
%span.dropdown
- %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
+ %a.dropdown-new.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- if can?(current_user, :create_issue, @project)
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 854c154824..8f2f631eb7 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -8,6 +8,5 @@
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do
= icon('code-fork fw')
- Fork
%span.count
= @project.forks_count
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
index 57f764178d..3e83ec3912 100644
--- a/app/views/projects/buttons/_notifications.html.haml
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -1,14 +1,20 @@
-- return unless @membership
+- case @membership
+- when ProjectMember
+ = 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
+ %span.dropdown
+ %a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"}
+ = icon('bell')
+ = notification_label(@membership)
+ = icon('angle-down')
+ %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
+ - Notification.project_notification_levels.each do |level|
+ = notification_list_item(level, @membership)
-= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline-form', id: 'notification-form' do
- = hidden_field_tag :notification_type, 'project'
- = hidden_field_tag :notification_id, @membership.id
- = hidden_field_tag :notification_level
- %span.dropdown
- %a.dropdown-toggle.btn.btn-new#notifications-button{href: '#', "data-toggle" => "dropdown"}
- = icon('bell')
- = notification_label(@membership)
- = icon('angle-down')
- %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- - Notification.project_notification_levels.each do |level|
- = notification_list_item(level, @membership)
+- when GroupMember
+ .btn.disabled.notifications-btn.has_tooltip{title: "To change the notification level, you need to be a member of the project itself, not only its group."}
+ = icon('bell')
+ = notification_label(@membership)
+ = icon('angle-down')
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 5d7df5ae09..3501dddefb 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,10 +1,6 @@
- if current_user
= link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do
= icon('star fw')
- - if current_user.starred?(@project)
- Unstar
- - else
- Star
%span.count
= @project.star_count
@@ -17,6 +13,5 @@
- else
= link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star fw')
- Star
%span.count
= @project.star_count
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 9110aaa052..397832e56d 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 78%
rename from app/views/ci/services/index.html.haml
rename to app/views/projects/ci_services/index.html.haml
index 37e5723b54..c164b2d4bc 100644
--- a/app/views/ci/services/index.html.haml
+++ b/app/views/projects/ci_services/index.html.haml
@@ -6,14 +6,14 @@
%tr
%th
%th Service
- %th Desription
+ %th Description
%th Last edit
- @services.sort_by(&:title).each do |service|
%tr
%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/ci/projects/_form.html.haml b/app/views/projects/ci_settings/_form.html.haml
similarity index 71%
rename from app/views/ci/projects/_form.html.haml
rename to app/views/projects/ci_settings/_form.html.haml
index e782fd8a0f..d711413c6b 100644
--- a/app/views/ci/projects/_form.html.haml
+++ b/app/views/projects/ci_settings/_form.html.haml
@@ -1,17 +1,36 @@
+%h3.page-title
+ CI settings
+%hr
.bs-callout.help-callout
%p
If you want to test your .gitlab-ci.yml, you can use special tool - #{link_to "Lint", ci_lint_path}
%p
- Edit your
- #{link_to ".gitlab-ci.yml using web-editor", yaml_web_editor_link(@project)}
+ Edit your
+ #{link_to ".gitlab-ci.yml using web-editor", yaml_web_editor_link(@ci_project)}
-= nested_form_for [:ci, @project], html: { class: 'form-horizontal' } do |f|
- - if @project.errors.any?
+- 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
- %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:"
+ %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:"
.alert.alert-error
%ul
- - @project.errors.full_messages.each do |msg|
+ - @ci_project.errors.full_messages.each do |msg|
%li= msg
%fieldset
@@ -93,8 +112,8 @@
= f.label :token, "CI token", class: 'control-label'
.col-sm-10
= f.text_field :token, class: 'form-control', placeholder: 'xEeFCaDAB89'
-
+
.form-actions
= f.submit 'Save changes', class: 'btn btn-save'
- - unless @project.new_record?
- = link_to 'Remove Project', ci_project_path(@project), method: :delete, data: { confirm: 'Project will be removed. Are you sure?' }, class: 'btn btn-danger pull-right'
+ - unless @ci_project.new_record?
+ = link_to 'Remove Project', ci_project_path(@ci_project), method: :delete, data: { confirm: 'Project will be removed. Are you sure?' }, class: 'btn btn-danger pull-right'
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 c0a296fb17..33038c5297 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/ci/projects/edit.html.haml b/app/views/projects/ci_settings/edit.html.haml
similarity index 72%
rename from app/views/ci/projects/edit.html.haml
rename to app/views/projects/ci_settings/edit.html.haml
index 876ae5182d..eedf484bf0 100644
--- a/app/views/ci/projects/edit.html.haml
+++ b/app/views/projects/ci_settings/edit.html.haml
@@ -1,14 +1,17 @@
-- if @project.generated_yaml_config
+- if @ci_project.generated_yaml_config
%p.alert.alert-danger
- CI Jobs are deprecated now, you can #{link_to "download", dumped_yaml_ci_project_path(@project)}
+ CI Jobs are deprecated now, you can #{link_to "download", dumped_yaml_ci_project_path(@ci_project)}
or
%a.preview-yml{:href => "#yaml-content", "data-toggle" => "modal"} preview
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 @project.generated_yaml_config
+- if @ci_project.generated_yaml_config
#yaml-content.modal.fade{"aria-hidden" => "true", "aria-labelledby" => ".gitlab-ci.yml", :role => "dialog", :tabindex => "-1"}
.modal-dialog
.modal-content
@@ -16,6 +19,6 @@
%button.close{"aria-hidden" => "true", "data-dismiss" => "modal", :type => "button"} ×
%h4.modal-title Content of .gitlab-ci.yml
.modal-body
- = text_area_tag :yaml, @project.generated_yaml_config, size: "70x25", class: "form-control"
+ = text_area_tag :yaml, @ci_project.generated_yaml_config, size: "70x25", class: "form-control"
.modal-footer
%button.btn.btn-default{"data-dismiss" => "modal", :type => "button"} Close
diff --git a/app/views/ci/web_hooks/index.html.haml b/app/views/projects/ci_web_hooks/index.html.haml
similarity index 79%
rename from app/views/ci/web_hooks/index.html.haml
rename to app/views/projects/ci_web_hooks/index.html.haml
index 78e8203b25..369086b39e 100644
--- a/app/views/ci/web_hooks/index.html.haml
+++ b/app/views/projects/ci_web_hooks/index.html.haml
@@ -1,12 +1,12 @@
%h3.page-title
- Web hooks
+ CI Web hooks
%p.light
Web Hooks can be used for binding events when build completed.
%hr.clearfix
-= form_for [:ci, @project, @web_hook], html: { class: 'form-horizontal' } do |f|
+= form_for @web_hook, url: namespace_project_ci_web_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
-if @web_hook.errors.any?
.alert.alert-danger
- @web_hook.errors.full_messages.each do |msg|
@@ -20,17 +20,18 @@
-if @web_hooks.any?
%h4 Activated web hooks (#{@web_hooks.count})
- %table.table
- - @web_hooks.each do |hook|
- %tr
- %td
- .clearfix
- %span.monospace= hook.url
- %td
- .pull-right
- - if @project.commits.any?
- = link_to 'Test Hook', test_ci_project_web_hook_path(@project, hook), class: "btn btn-sm btn-grouped"
- = link_to 'Remove', ci_project_web_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
+ .table-holder
+ %table.table
+ - @web_hooks.each do |hook|
+ %tr
+ %td
+ .clearfix
+ %span.monospace= hook.url
+ %td
+ .pull-right
+ - if @ci_project.commits.any?
+ = link_to 'Test Hook', test_namespace_project_ci_web_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
+ = link_to 'Remove', namespace_project_ci_web_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
%h4 Web Hook data example
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
new file mode 100644
index 0000000000..a634ae5dfd
--- /dev/null
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -0,0 +1,7 @@
+%ul.center-top-menu.commit-ci-menu
+ = nav_link(path: 'commit#show') do
+ = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+ Changes
+ = nav_link(path: 'commit#ci') do
+ = link_to ci_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+ Builds
diff --git a/app/views/projects/commit/ci.html.haml b/app/views/projects/commit/ci.html.haml
new file mode 100644
index 0000000000..43033cad24
--- /dev/null
+++ b/app/views/projects/commit/ci.html.haml
@@ -0,0 +1,69 @@
+- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
+= render "projects/commits/header_title"
+= render "commit_box"
+= render "ci_menu"
+
+
+- if @ci_commit.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - @ci_commit.yaml_errors.split(",").each do |error|
+ %li= error
+
+- unless @ci_commit.ci_yaml_file
+ .bs-callout.bs-callout-warning
+ \.gitlab-ci.yml not found in this commit
+
+.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-holder
+ %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
+ Retried builds
+
+ .table-holder
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Ref
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @ci_project && @ci_project.coverage_enabled?
+ %th Coverage
+ %th
+ = 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/show.html.haml b/app/views/projects/commit/show.html.haml
index f8681024d1..30a3973828 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,5 +1,6 @@
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
= render "projects/commits/header_title"
= render "commit_box"
+= render "ci_menu" if @ci_commit
= render "projects/diffs/diffs", diffs: @diffs, project: @project
= render "projects/notes/notes_with_form", view: params[:view]
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 0000000000..637154f56a
--- /dev/null
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -0,0 +1,54 @@
+%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}
+
+ - if commit_status.show_warning?
+ %i.fa.fa-warning.text-warning
+
+ %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/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index efad4cb147..cddd5aa3a8 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -5,7 +5,7 @@
- note_count = notes.user.count
- ci_commit = project.ci_commit(commit.sha)
-- cache_key = [project.id, commit.id, note_count]
+- cache_key = [project.path_with_namespace, commit.id, note_count]
- cache_key.push(ci_commit.status) if ci_commit
= cache(cache_key) do
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 2f24dc7c90..56b51f038b 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,21 +1,26 @@
- if params[:view] == 'parallel'
- fluid_layout true
+- diff_files = safe_diff_files(diffs)
+
.gray-content-block.second-block
.inline-parallel-buttons
.btn-group
= inline_diff_btn
= parallel_diff_btn
- = render 'projects/diffs/stats', diffs: diffs
-
-- diff_files = safe_diff_files(diffs)
+ = render 'projects/diffs/stats', diff_files: diff_files
- if diff_files.count < diffs.size
= render 'projects/diffs/warning', diffs: diffs, shown_files_count: diff_files.count
.files
- diff_files.each_with_index do |diff_file, index|
- = render 'projects/diffs/file', diff_file: diff_file, i: index, project: project
+ - diff_commit = commit_for_diff(diff_file)
+ - blob = project.repository.blob_for_diff(diff_commit, diff_file)
+ - next unless blob
+
+ = render 'projects/diffs/file', i: index, project: project,
+ diff_file: diff_file, diff_commit: diff_commit, blob: blob
- if @diff_timeout
.alert.alert-danger
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 99ee23a1dd..410ff6abb4 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,39 +1,33 @@
-- blob = project.repository.blob_for_diff(@commit, diff_file.diff)
-- return unless blob
-- blob_diff_path = namespace_project_blob_diff_path(project.namespace, project, tree_join(@commit.id, diff_file.file_path))
-.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }}
- .diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"}
- - if diff_file.deleted_file
- %span="#{diff_file.old_path} deleted"
-
- .diff-btn-group
- - if @commit.parent_ids.present?
- = view_file_btn(@commit.parent_id, diff_file, project)
- - elsif diff_file.diff.submodule?
+.diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
+ .diff-header{id: "file-path-#{hexdigest(diff_file.file_path)}"}
+ - if diff_file.diff.submodule?
%span
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
= submodule_link(submodule_item, @commit.id, project.repository)
- else
%span
- - if diff_file.renamed_file
+ - if diff_file.deleted_file
+ = "#{diff_file.old_path} deleted"
+ - elsif diff_file.renamed_file
= "#{diff_file.old_path} renamed to #{diff_file.new_path}"
- else
= diff_file.new_path
+
- 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
- - if @merge_request && @merge_request.source_project
+ - if editable_diff?(diff_file)
= edit_blob_link(@merge_request.source_project,
@merge_request.source_branch, diff_file.new_path,
after: ' ', from_merge_request_id: @merge_request.id)
- = view_file_btn(@commit.id, diff_file, project)
+ = view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs
@@ -44,7 +38,7 @@
- else
= render "projects/diffs/text_file", diff_file: diff_file, index: i
- elsif blob.image?
- - old_file = project.repository.prev_blob_for_diff(@commit, diff_file)
+ - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
= render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i
- else
.nothing-here-block No preview for this file type
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index c4d7f26430..ea2a3e0127 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -2,37 +2,35 @@
.commit-stat-summary
Showing
= link_to '#', class: 'js-toggle-button' do
- %strong #{pluralize(diffs.count, "changed file")}
- - if current_controller?(:commit)
- - unless @commit.has_zero_stats?
- with
- %strong.cgreen #{@commit.stats.additions} additions
- and
- %strong.cred #{@commit.stats.deletions} deletions
+ %strong #{pluralize(diff_files.count, "changed file")}
+ with
+ %strong.cgreen #{diff_files.sum(&:added_lines)} additions
+ and
+ %strong.cred #{diff_files.sum(&:removed_lines)} deletions
.file-stats.js-toggle-content.hide
%ul
- - diffs.each_with_index do |diff, i|
+ - diff_files.each_with_index do |diff_file, i|
%li
- - if diff.deleted_file
+ - if diff_file.deleted_file
%span.deleted-file
%a{href: "#diff-#{i}"}
%i.fa.fa-minus
- = diff.old_path
- - elsif diff.renamed_file
+ = diff_file.old_path
+ - elsif diff_file.renamed_file
%span.renamed-file
%a{href: "#diff-#{i}"}
%i.fa.fa-minus
- = diff.old_path
+ = diff_file.old_path
→
- = diff.new_path
- - elsif diff.new_file
+ = diff_file.new_path
+ - elsif diff_file.new_file
%span.new-file
%a{href: "#diff-#{i}"}
%i.fa.fa-plus
- = diff.new_path
+ = diff_file.new_path
- else
%span.edit-file
%a{href: "#diff-#{i}"}
%i.fa.fa-adjust
- = diff.new_path
+ = diff_file.new_path
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 90dce73999..afbf88b550 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -189,19 +189,35 @@
- else
.nothing-here-block Only the project owner can transfer a project
+ - if @project.forked?
+ - if can?(current_user, :remove_fork_project, @project)
+ = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
+ .panel.panel-default.panel.panel-danger
+ .panel-heading Remove fork relationship
+ .panel-body
+ %p
+ This will remove the fork relationship to source project
+ #{link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)}.
+ %br
+ %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
+ = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+ - else
+ .nothing-here-block Only the project owner can remove the fork relationship.
+
- if can?(current_user, :remove_project, @project)
.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
+ .nothing-here-block Only the project owner can remove a project.
+
.save-project-loader.hide
.center
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 185ebf2393..c3858e78ca 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,52 +1,57 @@
-- if current_user && can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
- = render 'shared/no_password'
+.alert_holder
+ - if current_user && can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+ = render 'shared/no_password'
= render "home_panel"
.gray-content-block.center
%h3.page-title
The repository for this project is empty
- %p
- If you already have files you can push them using command line instructions below.
- %br
- Otherwise you can start with
- = link_to "adding README", new_readme_path, class: 'underlined-link'
- file to this project.
+ - if can?(current_user, :download_code, @project)
+ %p
+ If you already have files you can push them using command line instructions below.
+ %br
+ - if can?(current_user, :push_code, @project)
+ Otherwise you can start with
+ = link_to "adding README", new_readme_path, class: 'underlined-link'
+ file to this project.
-.prepend-top-20
-%h3.page-title
- Command line instructions
-%div.git-empty
- %fieldset
- %h5 Git global setup
- %pre.light-well
- :preserve
- git config --global user.name "#{h git_user_name}"
- git config --global user.email "#{h git_user_email}"
-
- %fieldset
- %h5 Create a new repository
- %pre.light-well
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- cd #{h @project.path}
- touch README.md
- git add README.md
- git commit -m "add README"
- git push -u origin master
-
- %fieldset
- %h5 Existing folder or Git repository
- %pre.light-well
- :preserve
- cd existing_folder
- git init
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- git add .
- git commit
- git push -u origin master
-
-- if can? current_user, :remove_project, @project
+- if can?(current_user, :download_code, @project)
.prepend-top-20
- = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
+ .empty_wrapper
+ %h3.page-title-empty
+ Command line instructions
+ %div.git-empty
+ %fieldset
+ %h5 Git global setup
+ %pre.light-well
+ :preserve
+ git config --global user.name "#{h git_user_name}"
+ git config --global user.email "#{h git_user_email}"
+
+ %fieldset
+ %h5 Create a new repository
+ %pre.light-well
+ :preserve
+ git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ cd #{h @project.path}
+ touch README.md
+ git add README.md
+ git commit -m "add README"
+ git push -u origin master
+
+ %fieldset
+ %h5 Existing folder or Git repository
+ %pre.light-well
+ :preserve
+ cd existing_folder
+ git init
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git add .
+ git commit
+ git push -u origin master
+
+ - if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index cd5f3a5d39..f0b0a11c04 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/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index f8f2e192e2..92a87690c5 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -17,6 +17,6 @@
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
- For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
+ For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}
.form-actions
= f.submit 'Start import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml
new file mode 100644
index 0000000000..aef352029d
--- /dev/null
+++ b/app/views/projects/issues/_closed_by_box.html.haml
@@ -0,0 +1,3 @@
+.issue-closed-by-widget
+ = icon('check')
+ This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index a3399c57aa..ca5b1a8386 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -5,8 +5,9 @@
.nothing-here-block No issues to show
- if @issues.present?
- .pull-right
- %span.issue_counter #{@issues.total_count}
- issues for this filter
+ .issuable-filter-count
+ %span.pull-right
+ = @issues.total_count
+ issues for this filter
= paginate @issues, theme: "gitlab"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 5cb814c9ea..f01bf2505d 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -46,6 +46,7 @@
= markdown(@issue.description)
%textarea.hidden.js-task-list-field
= @issue.description
-
+ - if @closed_by_merge_requests.present?
+ = render 'projects/issues/closed_by_box'
.issue-discussion
= render 'projects/issues/discussion'
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 534c545329..4cf13492e9 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/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index d86707b3d9..0af970e4b9 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -5,8 +5,10 @@
.nothing-here-block No merge requests to show
- if @merge_requests.present?
- .pull-right
- %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
+ .issuable-filter-count
+ %span.pull-right
+ = @merge_requests.total_count
+ merge requests for this filter
= paginate @merge_requests, theme: "gitlab"
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 46aeecd873..6244d3ba0b 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -1,10 +1,11 @@
%h3.page-title
New merge request
%p.slead
+ - source_title, target_title = format_mr_branch_names(@merge_request)
From
- %strong.label-branch #{@merge_request.source_project_namespace}:#{@merge_request.source_branch}
+ %strong.label-branch #{source_title}
%span into
- %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
+ %strong.label-branch #{target_title}
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 0b0f52c653..eeaa72ed21 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,9 +34,10 @@
= 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"
+ = succeed '.' do
+ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if @commits.present?
%ul.merge-request-tabs
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 10640f746f..10efb81193 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,4 +1,17 @@
-- if @merge_request.has_ci?
+- ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha)
+- if ci_commit
+ - status = ci_commit.status
+ .mr-widget-heading
+ .ci_widget{class: "ci-#{status}"}
+ = ci_status_icon(ci_commit)
+ %span CI build #{status}
+ for #{@merge_request.last_commit_short_sha}.
+ %span.ci-coverage
+ = link_to "View build details", ci_status_path(ci_commit)
+
+- elsif @merge_request.has_ci?
+ - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
+ - # Remove in later versions when services like Jenkins will set CI status via Commit status API
.mr-widget-heading
- [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"}
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 74e9668052..255ddab479 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/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml
index 88fccfe498..133d802aac 100644
--- a/app/views/projects/milestones/_issue.html.haml
+++ b/app/views/projects/milestones/_issue.html.haml
@@ -1,7 +1,7 @@
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
.pull-right.assignee-icon
- if issue.assignee
- = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
%span
= link_to [@project.namespace.becomes(Namespace), @project, issue] do
%span.cgray ##{issue.iid}
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
index 0d7a118569..a1033607c5 100644
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ b/app/views/projects/milestones/_merge_request.html.haml
@@ -5,4 +5,4 @@
= link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
- = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 4eeb0621e5..3a898dfbcf 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -104,7 +104,7 @@
- @users.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ = image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index bccea21e7a..daab2326bc 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 a0e26f9827..a21c019986 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 d99445da59..13dfa0a1bb 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/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 9bfbde02ca..5d18473079 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,8 +1,8 @@
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
.timeline-entry-inner
.timeline-icon
- = link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
+ %a{href: user_path(note.author)}
+ %img.avatar.s40{src: avatar_icon(note.author), alt: ''}
.timeline-content
.note-header
- if note_editable?(note)
@@ -14,10 +14,10 @@
= icon('trash-o')
- unless note.system
- - member = note.project.team.find_member(note.author.id)
- - if member
+ - access = note.project.team.human_max_access(note.author.id)
+ - if access
%span.note-role.label
- = member.human_access
+ = access
= link_to_member(note.project, note.author, avatar: false)
@@ -25,7 +25,7 @@
= '@' + note.author.username
%span.note-last-update
- = link_to "##{dom_id(note)}", name: dom_id(note), title: "Link here" do
+ %a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
- if note.updated_at != note.created_at
%span
@@ -59,7 +59,9 @@
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
- = render 'projects/notes/edit_form', note: note
+ - unless note.system?
+ -# System notes can't be edited
+ = render 'projects/notes/edit_form', note: note
- if note.attachment.url
.note-attachment
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index 860a997cff..76c46d1d80 100644
--- a/app/views/projects/project_members/_project_member.html.haml
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -4,7 +4,7 @@
%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
%span.list-item-name
- if member.user
- = image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: ''
+ = image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 9a0a824b81..82809bec5b 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/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index bb49f4de87..f68449b186 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,34 +1,35 @@
- unless @branches.empty?
%br
%h4 Already Protected:
- %table.table.protected-branches-list
- %thead
- %tr.no-border
- %th Branch
- %th Developers can push
- %th Last commit
- %th
+ .table-holder
+ %table.table.protected-branches-list
+ %thead
+ %tr.no-border
+ %th Branch
+ %th Developers can push
+ %th Last commit
+ %th
- %tbody
- - @branches.each do |branch|
- - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
- %tr
- %td
- = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do
- %strong= branch.name
- - if @project.root_ref?(branch.name)
- %span.label.label-info default
+ %tbody
+ - @branches.each do |branch|
+ - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
+ %tr
%td
- = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
- %td
- - if commit = branch.commit
- = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- ·
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
- %td
- .pull-right
- - if can? current_user, :admin_project, @project
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
+ = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do
+ %strong= branch.name
+ - if @project.root_ref?(branch.name)
+ %span.label.label-info default
+ %td
+ = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
+ %td
+ - if commit = branch.commit
+ = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do
+ = commit.short_id
+ ·
+ #{time_ago_with_tooltip(commit.committed_date)}
+ - else
+ (branch was removed from repository)
+ %td
+ .pull-right
+ - if can? current_user, :admin_project, @project
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
diff --git a/app/views/projects/remove_fork.js.haml b/app/views/projects/remove_fork.js.haml
new file mode 100644
index 0000000000..17b9fecfeb
--- /dev/null
+++ b/app/views/projects/remove_fork.js.haml
@@ -0,0 +1,2 @@
+:plain
+ location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index b9486a9b49..07c24950ee 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/ci/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
similarity index 54%
rename from app/views/ci/runners/_runner.html.haml
rename to app/views/projects/runners/_runner.html.haml
index ef8622e280..e6b8a2e6fe 100644
--- a/app/views/ci/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -3,9 +3,9 @@
= runner_status_icon(runner)
%span.monospace
- if @runners.include?(runner)
- = link_to runner.short_sha, ci_project_runner_path(@project, runner)
+ = link_to runner.short_sha, runner_path(runner)
%small
- =link_to edit_ci_project_runner_path(@project, runner) do
+ =link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
%i.fa.fa-edit.btn
- else
= runner.short_sha
@@ -13,12 +13,12 @@
.pull-right
- if @runners.include?(runner)
- if runner.belongs_to_one_project?
- = link_to 'Remove runner', [:ci, @project, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+ = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- else
- - runner_project = @project.runner_projects.find_by(runner_id: runner)
- = link_to 'Disable for this project', [:ci, @project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+ - runner_project = @ci_project.runner_projects.find_by(runner_id: runner)
+ = link_to 'Disable for this project', [:ci, @ci_project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- elsif runner.specific?
- = form_for [:ci, @project, @project.runner_projects.new] do |f|
+ = form_for [:ci, @ci_project, @ci_project.runner_projects.new] do |f|
= f.hidden_field :runner_id, value: runner.id
= f.submit 'Enable for this project', class: 'btn btn-sm'
.pull-right
@@ -32,4 +32,3 @@
- runner.tag_list.each do |tag|
%span.label.label-primary
= tag
-
diff --git a/app/views/ci/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
similarity index 68%
rename from app/views/ci/runners/_shared_runners.html.haml
rename to app/views/projects/runners/_shared_runners.html.haml
index 944b3fd930..316ea747b1 100644
--- a/app/views/ci/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -3,11 +3,11 @@
.bs-callout.bs-callout-warning
GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
%hr
- - if @project.shared_runners_enabled
- = link_to toggle_shared_runners_ci_project_path(@project), class: 'btn btn-warning', method: :post do
+ - if @ci_project.shared_runners_enabled
+ = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-warning', method: :post do
Disable shared runners
- else
- = link_to toggle_shared_runners_ci_project_path(@project), class: 'btn btn-success', method: :post do
+ = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-success', method: :post do
Enable shared runners
for this project
@@ -17,7 +17,7 @@
- else
%h4.underlined-title Available shared runners - #{@shared_runners_count}
%ul.bordered-list.available-shared-runners
- = render @shared_runners.first(10)
+ = render partial: 'runner', collection: @shared_runners, as: :runner
- if @shared_runners_count > 10
.light
and #{@shared_runners_count - 10} more...
diff --git a/app/views/ci/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
similarity index 81%
rename from app/views/ci/runners/_specific_runners.html.haml
rename to app/views/projects/runners/_specific_runners.html.haml
index 0604e7a46c..c13625c7e4 100644
--- a/app/views/ci/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -12,7 +12,7 @@
%code #{ci_root_url(only_path: false)}
%li
Use the following registration token during setup:
- %code #{@project.token}
+ %code #{@ci_project.token}
%li
Start runner!
@@ -20,10 +20,10 @@
- if @runners.any?
%h4.underlined-title Runners activated for this project
%ul.bordered-list.activated-specific-runners
- = render @runners
+ = render partial: 'runner', collection: @runners, as: :runner
- if @specific_runners.any?
%h4.underlined-title Available specific runners
%ul.bordered-list.available-specific-runners
- = render @specific_runners
+ = render partial: 'runner', collection: @specific_runners, as: :runner
= paginate @specific_runners
diff --git a/app/views/ci/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml
similarity index 90%
rename from app/views/ci/runners/edit.html.haml
rename to app/views/projects/runners/edit.html.haml
index 81c8e58ae2..66851d3831 100644
--- a/app/views/ci/runners/edit.html.haml
+++ b/app/views/projects/runners/edit.html.haml
@@ -1,6 +1,6 @@
%h4 Runner ##{@runner.id}
%hr
-= form_for [:ci, @project, @runner], html: { class: 'form-horizontal' } do |f|
+= form_for @runner, url: runner_path(@runner), html: { class: 'form-horizontal' } do |f|
.form-group
= label :active, "Active", class: 'control-label'
.col-sm-10
diff --git a/app/views/ci/runners/index.html.haml b/app/views/projects/runners/index.html.haml
similarity index 100%
rename from app/views/ci/runners/index.html.haml
rename to app/views/projects/runners/index.html.haml
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
new file mode 100644
index 0000000000..c255cd51bd
--- /dev/null
+++ b/app/views/projects/runners/show.html.haml
@@ -0,0 +1,65 @@
+= content_for :title do
+ %h3.project-title
+ Runner ##{@runner.id}
+ .pull-right
+ - if @runner.shared?
+ %span.runner-state.runner-state-shared
+ Shared
+ - else
+ %span.runner-state.runner-state-specific
+ Specific
+
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th Property Name
+ %th Value
+ %tr
+ %td
+ Tags
+ %td
+ - @runner.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
+ %tr
+ %td
+ Name
+ %td
+ = @runner.name
+ %tr
+ %td
+ Version
+ %td
+ = @runner.version
+ %tr
+ %td
+ Revision
+ %td
+ = @runner.revision
+ %tr
+ %td
+ Platform
+ %td
+ = @runner.platform
+ %tr
+ %td
+ Architecture
+ %td
+ = @runner.architecture
+ %tr
+ %td
+ Description
+ %td
+ = @runner.description
+ %tr
+ %td
+ Last contact
+ %td
+ - if @runner.contacted_at
+ #{time_ago_in_words(@runner.contacted_at)} ago
+ - else
+ Never
+
+
+
diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml
index 1065def693..c1356f6db0 100644
--- a/app/views/projects/services/index.html.haml
+++ b/app/views/projects/services/index.html.haml
@@ -2,22 +2,23 @@
%h3.page-title Project services
%p.light Project services allow you to integrate GitLab with other applications
-%table.table
- %thead
- %tr
- %th
- %th Service
- %th Description
- %th Last edit
- - @services.sort_by(&:title).each do |service|
- %tr
- %td
- = boolean_to_icon service.activated?
- %td
- = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
- %strong= service.title
- %td
- = service.description
- %td.light
- = time_ago_in_words service.updated_at
- ago
+.table-holder
+ %table.table
+ %thead
+ %tr
+ %th
+ %th Service
+ %th Description
+ %th Last edit
+ - @services.sort_by(&:title).each do |service|
+ %tr
+ %td
+ = boolean_to_icon service.activated?
+ %td
+ = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
+ %strong= service.title
+ %td
+ = service.description
+ %td.light
+ = time_ago_in_words service.updated_at
+ ago
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 6a5fc68980..585caf674c 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -2,12 +2,12 @@
- 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'
+= render 'projects/last_push'
= render "home_panel"
@@ -27,7 +27,7 @@
= link_to project_files_path(@project) do
= repository_size
- - if !prefer_readme? && @repository.readme
+ - if default_project_view != 'readme' && @repository.readme
%li
= link_to 'Readme', readme_path(@project)
@@ -63,19 +63,17 @@
= icon("exclamation-triangle fw")
Archived project! Repository is read-only
-%section
- - if prefer_readme?
- .project-show-readme
- = render 'projects/readme'
- - else
- .project-show-activity
- = render 'projects/activity'
+- if @repository.commit
+ .content-block.second-block.white
+ = render 'projects/last_commit', commit: @repository.commit, project: @project
+%div{class: "project-show-#{default_project_view}"}
+ = render default_project_view
- if current_user
- access = user_max_access_in_project(current_user, @project)
- if access
- .prepend-top-20
+ .prepend-top-20.project-footer
.gray-content-block.footer-block.center
You have #{access} access to this project.
- if @project.project_member_by_id(current_user)
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
new file mode 100644
index 0000000000..4a51546942
--- /dev/null
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -0,0 +1,11 @@
+= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped new-snippet-link', title: "New Snippet" do
+ = icon('plus')
+ New Snippet
+- if can?(current_user, :admin_project_snippet, @snippet)
+ = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-remove", title: 'Delete Snippet' do
+ = icon('trash-o')
+ Delete
+- if can?(current_user, :update_project_snippet, @snippet)
+ = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
+ = icon('pencil-square-o')
+ Edit
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 3fed2c9949..4af963e14d 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,17 +1,13 @@
- page_title "Snippets"
= render "header_title"
-%h3.page-title
- Snippets
- - if can? current_user, :create_project_snippet, @project
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Snippet" do
- Add new snippet
+.gray-content-block.top-block
+ .pull-right
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
+ = icon('plus')
+ New Snippet
-%p.light
- Share code pastes with others out of git repository
+ .oneline
+ Share code pastes with others out of git repository
-%ul.bordered-list
- = render partial: "shared/snippets/snippet", collection: @snippets
- - if @snippets.empty?
- %li
- .nothing-here-block Nothing here.
+= render 'snippets/snippets'
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index be7d4d486f..5d706942f2 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,40 +1,18 @@
- page_title @snippet.title, "Snippets"
= render "header_title"
-%h3.page-title
- = @snippet.title
+.snippet-holder
+ = render 'shared/snippets/header'
- .pull-right
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
- Add new snippet
+ %article.file-holder
+ .file-title
+ = blob_icon 0, @snippet.file_name
+ %strong
+ = @snippet.file_name
+ .file-actions.hidden-xs
+ .btn-group.tree-btn-group
+ = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
-%hr
+ = render 'shared/snippets/blob'
-.append-bottom-20
- .pull-right
- = "##{@snippet.id}"
- %span.light
- by
- = link_to user_path(@snippet.author) do
- = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16"
- = @snippet.author_name
-
- .back-link
- = link_to namespace_project_snippets_path(@project.namespace, @project) do
- ← project snippets
-
-.file-holder
- .file-title
- %i.fa.fa-file
- %strong
- = @snippet.file_name
- .file-actions
- .btn-group
- - if can?(current_user, :update_project_snippet, @snippet)
- = link_to "edit", edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", title: 'Edit Snippet'
- = link_to "raw", raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
- - if can?(current_user, :admin_project_snippet, @snippet)
- = link_to "remove", namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet'
- = render 'shared/snippets/blob'
-
-%div#notes= render "projects/notes/notes_with_form"
+ %div#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index 02ecbade21..2ddc5d504f 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -4,5 +4,5 @@
%span.str-truncated
= link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name))
%td.tree_time_ago.cgray
- = render 'spinner'
+ = render 'projects/tree/spinner'
%td.hidden-xs.tree_commit
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index f082d71186..3c5edf4b03 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
+ .file-title
+ = blob_icon readme.mode, readme.name
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
+ %strong
+ = 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
deleted file mode 100644
index 367a87927d..0000000000
--- a/app/views/projects/tree/_tree.html.haml
+++ /dev/null
@@ -1,53 +0,0 @@
-%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|
- %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
-
-%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'
-
- - 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)
-
- - if tree.readme
- = render "projects/tree/readme", readme: tree.readme
-
-%div.tree_progress
-
-:javascript
- // Load last commit log for each file in tree
- $('#tree-slider').waitForImages(function() {
- ajaxGet("#{escape_javascript(@logs_path)}");
- });
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
new file mode 100644
index 0000000000..ee4c9d1693
--- /dev/null
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -0,0 +1,40 @@
+%div.tree-content-holder
+ .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
+
+ = render_tree(tree)
+
+ - if tree.readme
+ = render "projects/tree/readme", readme: tree.readme
+
+- 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() {
+ ajaxGet("#{escape_javascript(@logs_path)}");
+ });
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
new file mode 100644
index 0000000000..1115ca6b4c
--- /dev/null
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -0,0 +1,32 @@
+.tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'tree', path: @path
+
+%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|
+ %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
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index e87138bf98..cf65057e70 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -5,5 +5,5 @@
- path = flatten_tree(tree_item)
= link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path))
%td.tree_time_ago.cgray
- = render 'spinner'
+ = render 'projects/tree/spinner'
%td.hidden-xs.tree_commit
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index dec4677f83..ec14bd7f65 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -6,12 +6,12 @@
= render 'projects/last_push'
-.tree-ref-holder
- = render 'shared/ref_switcher', destination: 'tree', path: @path
-
- if can? current_user, :download_code, @project
.tree-download-holder
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true
#tree-holder.tree-holder.clearfix
- = render "tree", tree: @tree
+ .gray-content-block.top-block
+ = render 'projects/tree/tree_header', tree: @tree
+
+ = render 'projects/tree/tree_content', tree: @tree
diff --git a/app/views/ci/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
similarity index 53%
rename from app/views/ci/triggers/_trigger.html.haml
rename to app/views/projects/triggers/_trigger.html.haml
index addfbfcb0d..48b3b5c992 100644
--- a/app/views/ci/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -11,4 +11,4 @@
%td
.pull-right
- = link_to 'Revoke', ci_project_trigger_path(@project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-danger btn-sm btn-grouped"
+ = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-danger btn-sm btn-grouped"
diff --git a/app/views/ci/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
similarity index 71%
rename from app/views/ci/triggers/index.html.haml
rename to app/views/projects/triggers/index.html.haml
index 44374a1a4d..18a37302c3 100644
--- a/app/views/ci/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -7,16 +7,17 @@
%hr.clearfix
-if @triggers.any?
- %table.table
- %thead
- %th Token
- %th Last used
- %th
- = render @triggers
+ .table-holder
+ %table.table
+ %thead
+ %th Token
+ %th Last used
+ %th
+ = render partial: 'trigger', collection: @triggers, as: :trigger
- else
%h4 No triggers
-= form_for [:ci, @project, @trigger], html: { class: 'form-horizontal' } do |f|
+= form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create'), html: { class: 'form-horizontal' } do |f|
.clearfix
= f.submit "Add Trigger", class: 'btn btn-success pull-right'
@@ -34,7 +35,7 @@
:plain
curl -X POST \
-F token=TOKEN \
- #{ci_build_trigger_url(@project.id, 'REF_NAME')}
+ #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}
%h3
Use .gitlab-ci.yml
@@ -49,7 +50,7 @@
trigger:
type: deploy
script:
- - "curl -X POST -F token=TOKEN #{ci_build_trigger_url(@project.id, 'REF_NAME')}"
+ - "curl -X POST -F token=TOKEN #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}"
%h3
Pass build variables
@@ -64,4 +65,4 @@
curl -X POST \
-F token=TOKEN \
-F "variables[RUN_NIGHTLY_BUILD]=true" \
- #{ci_build_trigger_url(@project.id, 'REF_NAME')}
+ #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}
diff --git a/app/views/ci/variables/show.html.haml b/app/views/projects/variables/show.html.haml
similarity index 77%
rename from app/views/ci/variables/show.html.haml
rename to app/views/projects/variables/show.html.haml
index ebf68341e0..29416a94ff 100644
--- a/app/views/ci/variables/show.html.haml
+++ b/app/views/projects/variables/show.html.haml
@@ -1,21 +1,21 @@
%h3.page-title
Secret Variables
-%p.light
+%p.light
These variables will be set to environment by the runner and will be hidden in the build log.
%br
- So you can use them for passwords, secret keys or whatever you want.
+ So you can use them for passwords, secret keys or whatever you want.
%hr
-= nested_form_for @project, url: url_for(controller: 'ci/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
+= nested_form_for @ci_project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
- if @project.errors.any?
#error_explanation
- %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:"
+ %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:"
.alert.alert-error
%ul
- - @project.errors.full_messages.each do |msg|
+ - @ci_project.errors.full_messages.each do |msg|
%li= msg
= f.fields_for :variables do |variable_form|
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 05d754adbe..261d4a92d7 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/history.html.haml b/app/views/projects/wikis/history.html.haml
index bfbef823b3..4322146ce3 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -7,28 +7,29 @@
%span.light History for
= link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
-%table.table
- %thead
- %tr
- %th Page version
- %th Author
- %th Commit Message
- %th Last updated
- %th Format
- %tbody
- - @page.versions.each_with_index do |version, index|
- - commit = version
+.table-holder
+ %table.table
+ %thead
%tr
- %td
- = link_to project_wiki_path_with_version(@project, @page,
- commit.id, index == 0) do
- = truncate_sha(commit.id)
- %td
- = commit.author.name
- %td
- = commit.message
- %td
- #{time_ago_with_tooltip(version.authored_date)}
- %td
- %strong
- = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
+ %th Page version
+ %th Author
+ %th Commit Message
+ %th Last updated
+ %th Format
+ %tbody
+ - @page.versions.each_with_index do |version, index|
+ - commit = version
+ %tr
+ %td
+ = link_to project_wiki_path_with_version(@project, @page,
+ commit.id, index == 0) do
+ = truncate_sha(commit.id)
+ %td
+ = commit.author.name
+ %td
+ = commit.message
+ %td
+ #{time_ago_with_tooltip(version.authored_date)}
+ %td
+ %strong
+ = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 03e6a522b2..d179a1abec 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 3938c545ca..17b0981f07 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/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 2cd422e772..2e4aab3630 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -6,7 +6,7 @@
type: 'button', |
class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
:"data-clone" => project.ssh_url_to_repo, |
- :"data-title" => "Add an SSH key to your profile
to pull or push via SSH",
+ :"data-title" => "Add an SSH key to your profile
to pull or push via SSH.",
:"data-html" => "true",
:"data-container" => "body"}
SSH
@@ -15,7 +15,7 @@
type: 'button', |
class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
:"data-clone" => project.http_url_to_repo, |
- :"data-title" => "Set a password on your account
to pull or push via #{gitlab_config.protocol.upcase}",
+ :"data-title" => "Set a password on your account
to pull or push via #{gitlab_config.protocol.upcase}.",
:"data-html" => "true",
:"data-container" => "body"}
= gitlab_config.protocol.upcase
@@ -24,4 +24,4 @@
.input-group-addon
.visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" }
= visibility_level_icon(project.visibility_level)
- = visibility_level_label(project.visibility_level).downcase
+
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index 5071ff640f..cc3f1268f8 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/_field.html.haml b/app/views/shared/_field.html.haml
index 45ec49280d..8d6e16f74c 100644
--- a/app/views/shared/_field.html.haml
+++ b/app/views/shared/_field.html.haml
@@ -8,7 +8,10 @@
- help = field[:help]
.form-group
- = form.label name, title, class: "control-label"
+ - if type == "password" && value.present?
+ = form.label name, "Change #{title}", class: "control-label"
+ - else
+ = form.label name, title, class: "control-label"
.col-sm-10
- if type == 'text'
= form.text_field name, class: "form-control", placeholder: placeholder
@@ -19,6 +22,6 @@
- elsif type == 'select'
= form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password'
- = form.password_field name, value: value, class: 'form-control'
+ = form.password_field name, autocomplete: "new-password", class: 'form-control'
- if help
%span.help-block= help
diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg
new file mode 100644
index 0000000000..da49c48acd
--- /dev/null
+++ b/app/views/shared/_logo.svg
@@ -0,0 +1,21 @@
+
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 8f16773077..0e4e9c0987 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -42,11 +42,10 @@
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Milestone'})
- - if @project
- .filter-item.inline.labels-filter
- = select_tag('label_name', project_labels_options(@project),
- class: 'select2 trigger-submit', include_blank: true,
- data: {placeholder: 'Label'})
+ .filter-item.inline.labels-filter
+ = select_tag('label_name', projects_labels_options,
+ class: 'select2 trigger-submit', include_blank: true,
+ data: {placeholder: 'Label'})
.pull-right
= render 'shared/sort_dropdown'
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 33ec726e93..594e54f404 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 58c3de64b7..3a5ad00aa9 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 16e1d8421d..357cfd6a37 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 e67e5a8a63..aee839b44e 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/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
new file mode 100644
index 0000000000..0a4a790ec5
--- /dev/null
+++ b/app/views/shared/snippets/_header.html.haml
@@ -0,0 +1,24 @@
+.snippet-details
+ .page-title
+ .snippet-box{class: visibility_level_color(@snippet.visibility_level)}
+ = visibility_level_icon(@snippet.visibility_level)
+ = visibility_level_label(@snippet.visibility_level)
+ %span.snippet-id Snippet ##{@snippet.id}
+ %span.creator
+ · created by #{link_to_member(@project, @snippet.author, size: 24)}
+ ·
+ = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
+ - if @snippet.updated_at != @snippet.created_at
+ %span
+ ·
+ = icon('edit', title: 'edited')
+ = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
+
+ .pull-right
+ - if @snippet.project_id?
+ = render "projects/snippets/actions"
+ - else
+ = render "snippets/actions"
+ .gray-content-block.middle-block
+ %h2.snippet-title
+ = gfm escape_once(@snippet.title)
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 69a713ad9a..c6294caddc 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -18,4 +18,3 @@
= image_tag avatar_icon(snippet.author_email), class: "avatar s24", alt: ''
= snippet.author_name
authored #{time_ago_with_tooltip(snippet.created_at)}
-
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
new file mode 100644
index 0000000000..751fafa894
--- /dev/null
+++ b/app/views/snippets/_actions.html.haml
@@ -0,0 +1,11 @@
+= link_to new_snippet_path, class: 'btn btn-grouped new-snippet-link', title: "New Snippet" do
+ = icon('plus')
+ New Snippet
+- if can?(current_user, :admin_personal_snippet, @snippet)
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-remove", title: 'Delete Snippet' do
+ = icon('trash-o')
+ Delete
+- if can?(current_user, :update_personal_snippet, @snippet)
+ = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
+ = icon('pencil-square-o')
+ Edit
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 97374e073d..69d8899d4c 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,41 +1,14 @@
- page_title @snippet.title, "Snippets"
-%h4.page-title
- = @snippet.title
- - if @snippet.private?
- %span.label.label-success
- %i.fa.fa-lock
- private
+.snippet-holder
+ = render 'shared/snippets/header'
- .pull-right
- = link_to new_snippet_path, class: "btn btn-new btn-sm", title: "New Snippet" do
- Add new snippet
-
-.append-bottom-10.prepend-top-10
- .pull-right
- %span.light
- created by
- = link_to user_snippets_path(@snippet.author) do
- = @snippet.author_name
-
- .back-link
- - if @snippet.author == current_user
- = link_to dashboard_snippets_path do
- ← your snippets
- - else
- = link_to explore_snippets_path do
- ← explore snippets
-
-.file-holder
- .file-title
- %i.fa.fa-file
- %strong
- = @snippet.file_name
- .file-actions
- .btn-group
- - if can?(current_user, :update_personal_snippet, @snippet)
- = link_to "edit", edit_snippet_path(@snippet), class: "btn btn-sm", title: 'Edit Snippet'
- = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
- - if can?(current_user, :admin_personal_snippet, @snippet)
- = link_to "remove", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet'
- = render 'shared/snippets/blob'
+ %article.file-holder
+ .file-title
+ = blob_icon 0, @snippet.file_name
+ %strong
+ = @snippet.file_name
+ .file-actions.hidden-xs
+ .btn-group.tree-btn-group
+ = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
+ = render 'shared/snippets/blob'
diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml
index 922b0c6ceb..7f29918dba 100644
--- a/app/views/users/calendar.html.haml
+++ b/app/views/users/calendar.html.haml
@@ -1,7 +1,3 @@
-%h4
- Contributions calendar
- .pull-right
- %small Issues, merge requests and push events
#cal-heatmap.calendar
:javascript
new Calendar(
@@ -10,3 +6,5 @@
#{@starting_month},
'#{user_calendar_activities_path}'
);
+
+.calendar-hint Summary of issues, merge requests and push events
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 37d5dba033..4ea4a1f92c 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -6,48 +6,72 @@
= render 'shared/show_aside'
-.row
+.cover-block
+ .avatar-holder
+ = link_to avatar_icon(@user, 400), target: '_blank' do
+ = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: ''
+ .cover-title
+ = @user.name
+
+ .cover-desc
+ %span
+ @#{@user.username}.
+ - if @user.bio.present?
+ %span
+ #{@user.bio}.
+ %span
+ Member since #{@user.created_at.stamp("Aug 21, 2011")}
+
+ .cover-desc
+ - unless @user.public_email.blank?
+ = link_to @user.public_email, "mailto:#{@user.public_email}"
+ - unless @user.skype.blank?
+ ·
+ = link_to "Skype", "skype:#{@user.skype}"
+ - unless @user.linkedin.blank?
+ ·
+ = link_to "LinkedIn", "http://www.linkedin.com/in/#{@user.linkedin}"
+ - unless @user.twitter.blank?
+ ·
+ = link_to "Twitter", "http://www.twitter.com/#{@user.twitter}"
+ - unless @user.website_url.blank?
+ ·
+ = link_to @user.short_website_url, @user.full_website_url
+ - unless @user.location.blank?
+ ·
+ = @user.location
+
+
+ .cover-controls
+ - if @user == current_user
+ = link_to profile_path, class: 'btn btn-gray' do
+ = icon('pencil')
+ - elsif current_user
+ .report-abuse
+ - if @user.abuse_report
+ %button.btn.btn-danger{ title: 'Already reported for abuse',
+ data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
+ = icon('exclamation-circle')
+ - else
+ = link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray',
+ title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
+ = icon('exclamation-circle')
+
+.gray-content-block.second-block
+ .user-calendar
+ %h4.center.light
+ %i.fa.fa-spinner.fa-spin
+ .user-calendar-activities
+
+
+.row.prepend-top-20
%section.col-md-7
- .header-with-avatar
- = link_to avatar_icon(@user.email, 400), target: '_blank' do
- = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
- %h3
- = @user.name
- - if @user == current_user
- .pull-right.hidden-xs
- = link_to profile_path, class: 'btn btn-sm' do
- = icon('user')
- Profile settings
- - elsif current_user
- .pull-right
- %span.dropdown
- %a.light.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"}
- = icon('exclamation-circle')
- %ul.dropdown-menu.dropdown-menu-right
- %li
- = link_to new_abuse_report_path(user_id: @user.id) do
- Report abuse
-
- .username
- @#{@user.username}
- .description
- - if @user.bio.present?
- = @user.bio
-
- .clearfix
-
- if @groups.any?
.prepend-top-20
%h4 Groups
= render 'groups', groups: @groups
%hr
- .hidden-xs
- .user-calendar
- %h4.center.light
- %i.fa.fa-spinner.fa-spin
- .user-calendar-activities
- %hr
%h4
User Activity
@@ -60,7 +84,6 @@
.content_list
= spinner
%aside.col-md-5
- = render 'profile', user: @user
= render 'projects', projects: @projects, contributed_projects: @contributed_projects
:coffeescript
diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb
new file mode 100644
index 0000000000..47c5a670ed
--- /dev/null
+++ b/app/workers/repository_archive_cache_worker.rb
@@ -0,0 +1,9 @@
+class RepositoryArchiveCacheWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform
+ Repository.clean_old_archives
+ end
+end
diff --git a/app/workers/repository_archive_worker.rb b/app/workers/repository_archive_worker.rb
deleted file mode 100644
index 021c113956..0000000000
--- a/app/workers/repository_archive_worker.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-class RepositoryArchiveWorker
- include Sidekiq::Worker
-
- sidekiq_options queue: :archive_repo
-
- attr_accessor :project, :ref, :format
-
- def perform(project_id, ref, format)
- @project = Project.find(project_id)
- @ref, @format = ref, format.downcase
-
- repository = project.repository
-
- repository.clean_old_archives
-
- return unless file_path
- return if archived? || archiving?
-
- repository.archive_repo(ref, storage_path, format)
- end
-
- private
-
- def storage_path
- Gitlab.config.gitlab.repository_downloads_path
- end
-
- def file_path
- @file_path ||= project.repository.archive_file_path(ref, storage_path, format)
- end
-
- def pid_file_path
- @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
- end
-
- def archived?
- File.exist?(file_path)
- end
-
- def archiving?
- File.exist?(pid_file_path)
- end
-end
diff --git a/config/application.rb b/config/application.rb
index a96e22211e..bfa2a809dd 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/environments/development.rb b/config/environments/development.rb
index d7d6aed160..827a110c24 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -24,7 +24,7 @@ Gitlab::Application.configure do
# Expands the lines which load the assets
# config.assets.debug = true
-
+
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 15930fc907..8b85981497 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -99,7 +99,29 @@ production: &base
# For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html
incoming_email:
enabled: false
- address: "incoming+%{key}@gitlab.example.com"
+
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ address: "gitlab-incoming+%{key}@gmail.com"
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "gitlab-incoming@gmail.com"
+ # Email account password
+ password: "[REDACTED]"
+
+ # IMAP server host
+ host: "imap.gmail.com"
+ # IMAP server port
+ port: 993
+ # Whether the IMAP server uses SSL
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
@@ -159,7 +181,7 @@ production: &base
method: 'plain' # "tls" or "ssl" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
-
+
# This setting specifies if LDAP server is Active Directory LDAP server.
# For non AD servers it skips the AD specific queries.
# If your LDAP server is not AD, set this to false.
@@ -204,13 +226,13 @@ production: &base
# The username will be used in paths for the user's own projects
# (like `gitlab.example.com/username/project`) and when mentioning
# them in issues, merge request and comments (like `@username`).
- # If the attribute specified for `username` contains an email address,
+ # If the attribute specified for `username` contains an email address,
# the GitLab username will be the part of the email address before the '@'.
username: ['uid', 'userid', 'sAMAccountName']
email: ['mail', 'email', 'userPrincipalName']
# If no full name could be found at the attribute specified for `name`,
- # the full name is determined using the attributes specified for
+ # the full name is determined using the attributes specified for
# `first_name` and `last_name`.
name: 'cn'
first_name: 'givenName'
@@ -252,28 +274,28 @@ production: &base
# arguments, followed by optional 'args' which can be either a hash or an array.
# Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
providers:
- # - { name: 'google_oauth2',
+ # - { name: 'google_oauth2',
# label: 'Google',
- # app_id: 'YOUR_APP_ID',
+ # app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# args: { access_type: 'offline', approval_prompt: '' } }
- # - { name: 'twitter',
- # app_id: 'YOUR_APP_ID',
+ # - { name: 'twitter',
+ # app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET' }
- # - { name: 'github',
+ # - { name: 'github',
# label: 'GitHub',
- # app_id: 'YOUR_APP_ID',
+ # app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# args: { scope: 'user:email' } }
- # - { name: 'gitlab',
+ # - { name: 'gitlab',
# label: 'GitLab.com',
- # app_id: 'YOUR_APP_ID',
+ # app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# args: { scope: 'api' } }
- # - { name: 'bitbucket',
- # app_id: 'YOUR_APP_ID',
+ # - { name: 'bitbucket',
+ # app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET' }
- # - { name: 'saml',
+ # - { name: 'saml',
# label: 'Our SAML Provider',
# args: {
# assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
@@ -319,6 +341,8 @@ production: &base
# # Use multipart uploads when file size reaches 100MB, see
# # http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
# multipart_chunk_size: 104857600
+ # # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
+ # # encryption: 'AES256'
## GitLab Shell settings
gitlab_shell:
@@ -422,7 +446,6 @@ test:
user_filter: ''
group_base: 'ou=groups,dc=example,dc=com'
admin_group: ''
- sync_ssh_keys: false
staging:
<<: *base
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 48601b6733..d5493ca038 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -178,7 +178,6 @@ Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious'
# CI
#
Settings['gitlab_ci'] ||= Settingslogic.new({})
-Settings.gitlab_ci['enabled'] = true if Settings.gitlab_ci['enabled'].nil?
Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_broken_builds'].nil?
Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil?
Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url)
@@ -188,7 +187,11 @@ Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci[
# Reply by email
#
Settings['incoming_email'] ||= Settingslogic.new({})
-Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil?
+Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil?
+Settings.incoming_email['port'] = 143 if Settings.incoming_email['port'].nil?
+Settings.incoming_email['ssl'] = 143 if Settings.incoming_email['ssl'].nil?
+Settings.incoming_email['start_tls'] = 143 if Settings.incoming_email['start_tls'].nil?
+Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil?
#
# Gravatar
@@ -229,6 +232,7 @@ if Settings.backup['upload']['connection']
Settings.backup['upload']['connection'] = Hash[Settings.backup['upload']['connection'].map { |k, v| [k.to_sym, v] }]
end
Settings.backup['upload']['multipart_chunk_size'] ||= 104857600
+Settings.backup['upload']['encryption'] ||= nil
#
# Git
diff --git a/config/initializers/active_record_query_trace.rb b/config/initializers/active_record_query_trace.rb
new file mode 100644
index 0000000000..4b3c2803b3
--- /dev/null
+++ b/config/initializers/active_record_query_trace.rb
@@ -0,0 +1,5 @@
+if ENV['ENABLE_QUERY_TRACE']
+ require 'active_record_query_trace'
+
+ ActiveRecordQueryTrace.enabled = 'true'
+end
diff --git a/config/initializers/bullet.rb b/config/initializers/bullet.rb
new file mode 100644
index 0000000000..95e82966c7
--- /dev/null
+++ b/config/initializers/bullet.rb
@@ -0,0 +1,6 @@
+if ENV['ENABLE_BULLET']
+ require 'bullet'
+
+ Bullet.enable = true
+ Bullet.console = true
+end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 2ce24592f8..29506970af 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -148,6 +148,10 @@ Devise.setup do |config|
# When someone else invites you to GitLab this time is also used so it should be pretty long.
config.reset_password_within = 2.days
+ # When set to false, does not sign a user in automatically after their password is
+ # reset. Defaults to true, so a user is signed in automatically after a reset.
+ config.sign_in_after_reset_password = false
+
# ==> Configuration for :encryptable
# Allow you to use another encryption algorithm besides bcrypt (default). You can use
# :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb
new file mode 100644
index 0000000000..f0c006d811
--- /dev/null
+++ b/config/initializers/rack_lineprof.rb
@@ -0,0 +1,31 @@
+# The default colors of rack-lineprof can be very hard to look at in terminals
+# with darker backgrounds. This patch tweaks the colors a bit so the output is
+# actually readable.
+if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF']
+ Gitlab::Application.config.middleware.use(Rack::Lineprof)
+
+ module Rack
+ class Lineprof
+ class Sample < Rack::Lineprof::Sample.superclass
+ def format(*)
+ formatted = if level == CONTEXT
+ sprintf " | % 3i %s", line, code
+ else
+ sprintf "% 8.1fms %5i | % 3i %s", ms, calls, line, code
+ end
+
+ case level
+ when CRITICAL
+ color.red formatted
+ when WARNING
+ color.yellow formatted
+ when NOMINAL
+ color.white formatted
+ else # CONTEXT
+ formatted
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index d8bf0878a3..22070e37f0 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -1,61 +1,63 @@
-# Additional translations at http://github.com/plataformatec/devise/wiki/I18n
+# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
en:
+ devise:
+ confirmations:
+ confirmed: "Your email address has been successfully confirmed."
+ send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
+ send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
+ failure:
+ already_authenticated: "You are already signed in."
+ inactive: "Your account is not activated yet."
+ invalid: "Invalid %{authentication_keys} or password."
+ locked: "Your account is locked."
+ last_attempt: "You have one more attempt before your account is locked."
+ not_found_in_database: "Invalid %{authentication_keys} or password."
+ timeout: "Your session expired. Please sign in again to continue."
+ unauthenticated: "You need to sign in or sign up before continuing."
+ unconfirmed: "You have to confirm your email address before continuing."
+ mailer:
+ confirmation_instructions:
+ subject: "Confirmation instructions"
+ reset_password_instructions:
+ subject: "Reset password instructions"
+ unlock_instructions:
+ subject: "Unlock instructions"
+ password_change:
+ subject: "Password Changed"
+ omniauth_callbacks:
+ failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
+ success: "Successfully authenticated from %{kind} account."
+ passwords:
+ no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
+ recently_reset: "Instructions about how to reset your password have already been sent recently. Please wait a few minutes to try again."
+ send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
+ send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
+ updated: "Your password has been changed successfully. You are now signed in."
+ updated_not_active: "Your password has been changed successfully."
+ registrations:
+ destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
+ signed_up: "Welcome! You have signed up successfully."
+ signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
+ signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
+ signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
+ update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
+ updated: "Your account has been updated successfully."
+ sessions:
+ signed_in: "Signed in successfully."
+ signed_out: "Signed out successfully."
+ already_signed_out: "Signed out successfully."
+ unlocks:
+ send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
+ send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
+ unlocked: "Your account has been unlocked successfully. Please sign in to continue."
errors:
messages:
+ already_confirmed: "was already confirmed, please try signing in"
+ confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
expired: "has expired, please request a new one"
not_found: "not found"
- already_confirmed: "was already confirmed, please try signing in"
not_locked: "was not locked"
not_saved:
one: "1 error prohibited this %{resource} from being saved:"
other: "%{count} errors prohibited this %{resource} from being saved:"
-
- devise:
- failure:
- already_authenticated: 'You are already signed in.'
- unauthenticated: 'You need to sign in before continuing.'
- unconfirmed: 'You have to confirm your account before continuing.'
- locked: 'Your account is locked.'
- not_found_in_database: 'Invalid email or password.'
- invalid: 'Invalid email or password.'
- invalid_token: 'Invalid authentication token.'
- timeout: 'Your session expired, please sign in again to continue.'
- inactive: 'Your account was not activated yet.'
- sessions:
- signed_in: ''
- signed_out: ''
- users_sessions:
- user:
- signed_in: 'Signed in successfully.'
- passwords:
- send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
- updated: 'Your password was changed successfully. You are now signed in.'
- updated_not_active: 'Your password was changed successfully.'
- send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
- no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
- confirmations:
- send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.'
- send_paranoid_instructions: 'If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes.'
- confirmed: 'Your account was successfully confirmed. You are now signed in.'
- registrations:
- signed_up: 'Welcome! You have signed up successfully.'
- updated: 'You updated your account successfully.'
- destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.'
- signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
- signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.'
- signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.'
- unlocks:
- send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.'
- unlocked: 'Your account was successfully unlocked. You are now signed in.'
- send_paranoid_instructions: 'If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.'
- omniauth_callbacks:
- success: 'Successfully authorized from %{kind} account.'
- failure: 'Could not authorize you from %{kind} because "%{reason}".'
- mailer:
- confirmation_instructions:
- subject: 'Confirmation instructions'
- reset_password_instructions:
- subject: 'Reset password instructions'
- unlock_instructions:
- subject: 'Unlock Instructions'
diff --git a/config/mail_room.yml b/config/mail_room.yml
new file mode 100644
index 0000000000..42f6f74c46
--- /dev/null
+++ b/config/mail_room.yml
@@ -0,0 +1,39 @@
+:mailboxes:
+<%
+require_relative 'config/environment.rb'
+
+if Gitlab::IncomingEmail.enabled?
+ config = Gitlab::IncomingEmail.config
+
+ redis_config_file = "config/resque.yml"
+ redis_url =
+ if File.exists?(redis_config_file)
+ YAML.load_file(redis_config_file)[Rails.env]
+ else
+ "redis://localhost:6379"
+ end
+ %>
+ -
+ :host: <%= config.host.to_json %>
+ :port: <%= config.port.to_json %>
+ :ssl: <%= config.ssl.to_json %>
+ :start_tls: <%= config.start_tls.to_json %>
+ :email: <%= config.user.to_json %>
+ :password: <%= config.password.to_json %>
+
+ :name: <%= config.mailbox.to_json %>
+
+ :delete_after_delivery: true
+
+ :delivery_method: sidekiq
+ :delivery_options:
+ :redis_url: <%= redis_url.to_json %>
+ :namespace: resque:gitlab
+ :queue: incoming_email
+ :worker: EmailReceiverWorker
+
+ :arbitration_method: redis
+ :arbitration_options:
+ :redis_url: <%= redis_url.to_json %>
+ :namespace: mail_room:gitlab
+<% end %>
diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example
deleted file mode 100644
index 82e1a42058..0000000000
--- a/config/mail_room.yml.example
+++ /dev/null
@@ -1,29 +0,0 @@
-:mailboxes:
- -
- # # IMAP server host
- # :host: "imap.gmail.com"
- # # IMAP server port
- # :port: 993
- # # Whether the IMAP server uses SSL
- # :ssl: true
- # # Whether the IMAP server uses StartTLS
- # :start_tls: false
- # # Email account username. Usually the full email address.
- # :email: "replies@gitlab.example.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.
- # :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
diff --git a/config/routes.rb b/config/routes.rb
index 4a07c449b4..f6812c9280 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -22,50 +22,9 @@ Gitlab::Application.routes.draw do
get :dumped_yaml
end
- resources :services, only: [:index, :edit, :update] do
- member do
- get :test
- end
- end
-
- resource :charts, only: [:show]
-
- resources :refs, constraints: { ref_id: /.*/ }, only: [] do
- resources :commits, only: [:show] do
- member do
- get :status
- get :cancel
- end
- end
- end
-
- resources :builds, only: [:show] do
- member do
- get :cancel
- get :status
- post :retry
- end
- end
-
- resources :web_hooks, only: [:index, :create, :destroy] do
- member do
- get :test
- end
- end
-
- resources :triggers, only: [:index, :create, :destroy]
-
- resources :runners, only: [:index, :edit, :update, :destroy, :show] do
- member do
- get :resume
- get :pause
- end
- end
-
resources :runner_projects, only: [:create, :destroy]
resources :events, only: [:index]
- resource :variables, only: [:show, :update]
end
resource :user_sessions do
@@ -262,6 +221,7 @@ Gitlab::Application.routes.draw do
put :unblock
put :unlock
put :confirm
+ post :login_as
patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end
@@ -418,6 +378,7 @@ Gitlab::Application.routes.draw do
[:new, :create, :index], path: "/") do
member do
put :transfer
+ delete :remove_fork
post :archive
post :unarchive
post :toggle_star
@@ -482,6 +443,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',
@@ -502,7 +472,11 @@ Gitlab::Application.routes.draw do
resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do
- get :branches, on: :member
+ member do
+ get :branches
+ get :ci
+ get :cancel_builds
+ end
end
resources :compare, only: [:index, :create]
@@ -570,8 +544,10 @@ Gitlab::Application.routes.draw do
member do
# tree viewer logs
get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
+ # Directories with leading dots erroneously get rejected if git
+ # ref regex used in constraints. Regex verification now done in controller.
get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: {
- id: Gitlab::Regex.git_reference_regex,
+ id: /.*/,
path: /.*/
}
end
@@ -597,6 +573,32 @@ Gitlab::Application.routes.draw do
resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
+ resource :variables, only: [:show, :update]
+ resources :triggers, only: [:index, :create, :destroy]
+ resource :ci_settings, only: [:edit, :update, :destroy]
+ resources :ci_web_hooks, only: [:index, :create, :destroy] do
+ member do
+ get :test
+ end
+ end
+
+ resources :ci_services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
+ member do
+ get :test
+ end
+ end
+
+ resources :builds, only: [:index, :show] do
+ collection do
+ get :cancel_all
+ end
+
+ member do
+ get :cancel
+ get :status
+ post :retry
+ end
+ end
resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do
member do
@@ -652,8 +654,14 @@ Gitlab::Application.routes.draw do
get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ }
end
end
- end
+ resources :runners, only: [:index, :edit, :update, :destroy, :show] do
+ member do
+ get :resume
+ get :pause
+ end
+ end
+ end
end
end
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 8f71198e47..b463999996 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/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index 378354efd5..03da29c4c6 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -1,5 +1,5 @@
Gitlab::Seeder.quiet do
- (2..20).each do |i|
+ 20.times do |i|
begin
User.create!(
username: FFaker::Internet.user_name,
@@ -15,7 +15,7 @@ Gitlab::Seeder.quiet do
end
end
- (1..5).each do |i|
+ 5.times do |i|
begin
User.create!(
username: "user#{i}",
diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb
index a43116829d..e028ac82ba 100644
--- a/db/fixtures/development/07_milestones.rb
+++ b/db/fixtures/development/07_milestones.rb
@@ -1,6 +1,6 @@
Gitlab::Seeder.quiet do
Project.all.each do |project|
- (1..5).each do |i|
+ 5.times do |i|
milestone_params = {
title: "v#{i}.0",
description: FFaker::Lorem.sentence,
diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb
index c636e96381..4fa572fca9 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -1,6 +1,6 @@
Gitlab::Seeder.quiet do
Project.all.each do |project|
- (1..10).each do |i|
+ 10.times do
issue_params = {
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index 3bd4b442ad..74898544a6 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -22,7 +22,7 @@ class Member < ActiveRecord::Base
end
eos
- (1..50).each do |i|
+ 50.times do |i|
user = User.all.sample
PersonalSnippet.seed(:id, [{
diff --git a/db/migrate/20150924125150_add_project_id_to_ci_commit.rb b/db/migrate/20150924125150_add_project_id_to_ci_commit.rb
new file mode 100644
index 0000000000..1a761fe0f8
--- /dev/null
+++ b/db/migrate/20150924125150_add_project_id_to_ci_commit.rb
@@ -0,0 +1,5 @@
+class AddProjectIdToCiCommit < ActiveRecord::Migration
+ def up
+ add_column :ci_commits, :gl_project_id, :integer
+ end
+end
diff --git a/db/migrate/20150924125436_migrate_project_id_for_ci_commits.rb b/db/migrate/20150924125436_migrate_project_id_for_ci_commits.rb
new file mode 100644
index 0000000000..2be57b6062
--- /dev/null
+++ b/db/migrate/20150924125436_migrate_project_id_for_ci_commits.rb
@@ -0,0 +1,6 @@
+class MigrateProjectIdForCiCommits < ActiveRecord::Migration
+ def up
+ subquery = 'SELECT gitlab_id FROM ci_projects WHERE ci_projects.id = ci_commits.project_id'
+ execute("UPDATE ci_commits SET gl_project_id=(#{subquery}) WHERE gl_project_id IS NULL")
+ end
+end
diff --git a/db/migrate/20150930001110_merge_request_error_field.rb b/db/migrate/20150930001110_merge_request_error_field.rb
new file mode 100644
index 0000000000..c2ee498ef3
--- /dev/null
+++ b/db/migrate/20150930001110_merge_request_error_field.rb
@@ -0,0 +1,5 @@
+class MergeRequestErrorField < ActiveRecord::Migration
+ def up
+ add_column :merge_requests, :merge_error, :string
+ end
+end
diff --git a/db/migrate/20150930095736_add_null_to_name_for_ci_projects.rb b/db/migrate/20150930095736_add_null_to_name_for_ci_projects.rb
new file mode 100644
index 0000000000..8d47dac644
--- /dev/null
+++ b/db/migrate/20150930095736_add_null_to_name_for_ci_projects.rb
@@ -0,0 +1,9 @@
+class AddNullToNameForCiProjects < ActiveRecord::Migration
+ def up
+ change_column_null :ci_projects, :name, true
+ end
+
+ def down
+ change_column_null :ci_projects, :name, false
+ end
+end
diff --git a/db/migrate/20151002112914_add_stage_idx_to_builds.rb b/db/migrate/20151002112914_add_stage_idx_to_builds.rb
new file mode 100644
index 0000000000..68a745ffef
--- /dev/null
+++ b/db/migrate/20151002112914_add_stage_idx_to_builds.rb
@@ -0,0 +1,5 @@
+class AddStageIdxToBuilds < ActiveRecord::Migration
+ def change
+ add_column :ci_builds, :stage_idx, :integer
+ end
+end
diff --git a/db/migrate/20151002121400_add_index_for_builds.rb b/db/migrate/20151002121400_add_index_for_builds.rb
new file mode 100644
index 0000000000..4ffc136391
--- /dev/null
+++ b/db/migrate/20151002121400_add_index_for_builds.rb
@@ -0,0 +1,5 @@
+class AddIndexForBuilds < ActiveRecord::Migration
+ def up
+ add_index :ci_builds, [:commit_id, :stage_idx, :created_at]
+ end
+end
diff --git a/db/migrate/20151002122929_add_ref_and_tag_to_builds.rb b/db/migrate/20151002122929_add_ref_and_tag_to_builds.rb
new file mode 100644
index 0000000000..e3d2ac1cea
--- /dev/null
+++ b/db/migrate/20151002122929_add_ref_and_tag_to_builds.rb
@@ -0,0 +1,6 @@
+class AddRefAndTagToBuilds < ActiveRecord::Migration
+ def change
+ add_column :ci_builds, :tag, :boolean
+ add_column :ci_builds, :ref, :string
+ end
+end
diff --git a/db/migrate/20151002122943_migrate_ref_and_tag_to_build.rb b/db/migrate/20151002122943_migrate_ref_and_tag_to_build.rb
new file mode 100644
index 0000000000..01d7b3f677
--- /dev/null
+++ b/db/migrate/20151002122943_migrate_ref_and_tag_to_build.rb
@@ -0,0 +1,6 @@
+class MigrateRefAndTagToBuild < ActiveRecord::Migration
+ def change
+ execute('UPDATE ci_builds SET ref=(SELECT ref FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE ref IS NULL')
+ execute('UPDATE ci_builds SET tag=(SELECT tag FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE tag IS NULL')
+ end
+end
diff --git a/db/migrate/20151005075649_add_user_id_to_build.rb b/db/migrate/20151005075649_add_user_id_to_build.rb
new file mode 100644
index 0000000000..0f4b92b8b7
--- /dev/null
+++ b/db/migrate/20151005075649_add_user_id_to_build.rb
@@ -0,0 +1,5 @@
+class AddUserIdToBuild < ActiveRecord::Migration
+ def change
+ add_column :ci_builds, :user_id, :integer
+ end
+end
diff --git a/db/migrate/20151005150751_add_layout_option_for_users.rb b/db/migrate/20151005150751_add_layout_option_for_users.rb
new file mode 100644
index 0000000000..ead9b1f897
--- /dev/null
+++ b/db/migrate/20151005150751_add_layout_option_for_users.rb
@@ -0,0 +1,5 @@
+class AddLayoutOptionForUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :layout, :integer, default: 0
+ end
+end
\ No newline at end of file
diff --git a/db/migrate/20151005162154_remove_ci_enabled_from_application_settings.rb b/db/migrate/20151005162154_remove_ci_enabled_from_application_settings.rb
new file mode 100644
index 0000000000..be6aa810bb
--- /dev/null
+++ b/db/migrate/20151005162154_remove_ci_enabled_from_application_settings.rb
@@ -0,0 +1,5 @@
+class RemoveCiEnabledFromApplicationSettings < ActiveRecord::Migration
+ def change
+ remove_column :application_settings, :ci_enabled, :boolean, null: false, default: true
+ end
+end
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 0000000000..7f6cd6d5a7
--- /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/20151008110232_add_users_lower_username_email_indexes.rb b/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb
new file mode 100644
index 0000000000..2f2dc77678
--- /dev/null
+++ b/db/migrate/20151008110232_add_users_lower_username_email_indexes.rb
@@ -0,0 +1,17 @@
+class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab::Database.postgresql?
+
+ execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_username ON users (LOWER(username));'
+ execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_email ON users (LOWER(email));'
+ end
+
+ def down
+ return unless Gitlab::Database.postgresql?
+
+ remove_index :users, :index_on_users_lower_username
+ remove_index :users, :index_on_users_lower_email
+ 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 0000000000..c72b1c611c
--- /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 0000000000..f5c44babd8
--- /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/migrate/20151008143519_add_admin_notification_email_setting.rb b/db/migrate/20151008143519_add_admin_notification_email_setting.rb
new file mode 100644
index 0000000000..0bb581efe2
--- /dev/null
+++ b/db/migrate/20151008143519_add_admin_notification_email_setting.rb
@@ -0,0 +1,5 @@
+class AddAdminNotificationEmailSetting < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :admin_notification_email, :string
+ end
+end
diff --git a/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb b/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb
new file mode 100644
index 0000000000..52a47aa9c5
--- /dev/null
+++ b/db/migrate/20151016131433_add_ci_projects_gl_project_id_index.rb
@@ -0,0 +1,5 @@
+class AddCiProjectsGlProjectIdIndex < ActiveRecord::Migration
+ def change
+ add_index :ci_commits, :gl_project_id
+ end
+end
diff --git a/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb b/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb
new file mode 100644
index 0000000000..7f1af1c758
--- /dev/null
+++ b/db/migrate/20151016195451_add_ci_builds_and_projects_indexes.rb
@@ -0,0 +1,9 @@
+class AddCiBuildsAndProjectsIndexes < ActiveRecord::Migration
+ def change
+ add_index :ci_projects, :gitlab_id
+ add_index :ci_projects, :shared_runners_enabled
+
+ add_index :ci_builds, :type
+ add_index :ci_builds, :status
+ end
+end
diff --git a/db/migrate/20151016195706_add_notes_line_code_index.rb b/db/migrate/20151016195706_add_notes_line_code_index.rb
new file mode 100644
index 0000000000..aeeb1a759f
--- /dev/null
+++ b/db/migrate/20151016195706_add_notes_line_code_index.rb
@@ -0,0 +1,5 @@
+class AddNotesLineCodeIndex < ActiveRecord::Migration
+ def change
+ add_index :notes, :line_code
+ end
+end
diff --git a/db/migrate/20151019111551_fix_build_tags.rb b/db/migrate/20151019111551_fix_build_tags.rb
new file mode 100644
index 0000000000..84b142183f
--- /dev/null
+++ b/db/migrate/20151019111551_fix_build_tags.rb
@@ -0,0 +1,5 @@
+class FixBuildTags < ActiveRecord::Migration
+ def change
+ execute("UPDATE taggings SET taggable_type='CommitStatus' WHERE taggable_type='Ci::Build'")
+ end
+end
diff --git a/db/migrate/20151019111703_fail_build_without_names.rb b/db/migrate/20151019111703_fail_build_without_names.rb
new file mode 100644
index 0000000000..546b03d812
--- /dev/null
+++ b/db/migrate/20151019111703_fail_build_without_names.rb
@@ -0,0 +1,5 @@
+class FailBuildWithoutNames < ActiveRecord::Migration
+ def change
+ execute("UPDATE ci_builds SET status='failed' WHERE name IS NULL AND status='pending'")
+ end
+end
diff --git a/db/migrate/20151020173516_ci_limits_to_mysql.rb b/db/migrate/20151020173516_ci_limits_to_mysql.rb
new file mode 100644
index 0000000000..9bb960082f
--- /dev/null
+++ b/db/migrate/20151020173516_ci_limits_to_mysql.rb
@@ -0,0 +1,9 @@
+class CiLimitsToMysql < ActiveRecord::Migration
+ def change
+ return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
+
+ # CI
+ change_column :ci_builds, :trace, :text, limit: 1073741823
+ change_column :ci_commits, :push_data, :text, limit: 16777215
+ end
+end
diff --git a/db/migrate/20151020173906_add_ci_builds_index_for_status.rb b/db/migrate/20151020173906_add_ci_builds_index_for_status.rb
new file mode 100644
index 0000000000..c3f0e0606d
--- /dev/null
+++ b/db/migrate/20151020173906_add_ci_builds_index_for_status.rb
@@ -0,0 +1,5 @@
+class AddCiBuildsIndexForStatus < ActiveRecord::Migration
+ def change
+ add_index :ci_builds, [:commit_id, :status, :type]
+ end
+end
diff --git a/db/migrate/limits_to_mysql.rb b/db/migrate/limits_to_mysql.rb
index 73605d4c5e..2b7afae6d7 100644
--- a/db/migrate/limits_to_mysql.rb
+++ b/db/migrate/limits_to_mysql.rb
@@ -6,9 +6,5 @@ class LimitsToMysql < ActiveRecord::Migration
change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
change_column :snippets, :content, :text, limit: 2147483647
change_column :notes, :st_diff, :text, limit: 2147483647
-
- # CI
- change_column :ci_builds, :trace, :text, limit: 1073741823
- change_column :ci_commits, :push_data, :text, limit: 16777215
end
end
diff --git a/db/schema.rb b/db/schema.rb
index 01ccda7a75..0fec00ebf8 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: 20150920161119) do
+ActiveRecord::Schema.define(version: 20151020173906) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -46,7 +46,7 @@ ActiveRecord::Schema.define(version: 20150920161119) do
t.integer "session_expire_delay", default: 10080, null: false
t.text "import_sources"
t.text "help_page_text"
- t.boolean "ci_enabled", default: true, null: false
+ t.string "admin_notification_email"
end
create_table "audit_events", force: true do |t|
@@ -100,12 +100,25 @@ ActiveRecord::Schema.define(version: 20150920161119) do
t.boolean "allow_failure", default: false, null: false
t.string "stage"
t.integer "trigger_request_id"
+ t.integer "stage_idx"
+ 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", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", 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
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
+ add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
+ add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree
create_table "ci_commits", force: true do |t|
t.integer "project_id"
@@ -115,11 +128,13 @@ ActiveRecord::Schema.define(version: 20150920161119) do
t.text "push_data"
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "tag", default: false
+ t.boolean "tag", default: false
t.text "yaml_errors"
t.datetime "committed_at"
+ t.integer "gl_project_id"
end
+ add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree
add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree
@@ -157,7 +172,7 @@ ActiveRecord::Schema.define(version: 20150920161119) do
add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree
create_table "ci_projects", force: true do |t|
- t.string "name", null: false
+ t.string "name"
t.integer "timeout", default: 3600, null: false
t.datetime "created_at"
t.datetime "updated_at"
@@ -179,6 +194,9 @@ ActiveRecord::Schema.define(version: 20150920161119) do
t.text "generated_yaml_config"
end
+ add_index "ci_projects", ["gitlab_id"], name: "index_ci_projects_on_gitlab_id", using: :btree
+ add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree
+
create_table "ci_runner_projects", force: true do |t|
t.integer "runner_id", null: false
t.integer "project_id", null: false
@@ -452,6 +470,7 @@ ActiveRecord::Schema.define(version: 20150920161119) do
t.integer "position", default: 0
t.datetime "locked_at"
t.integer "updated_by_id"
+ t.string "merge_error"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -518,6 +537,7 @@ ActiveRecord::Schema.define(version: 20150920161119) do
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
+ add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
@@ -751,6 +771,7 @@ ActiveRecord::Schema.define(version: 20150920161119) do
t.integer "dashboard", default: 0
t.integer "project_view", default: 0
t.integer "consumed_timestep"
+ t.integer "layout", default: 0
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/api/commits.md b/doc/api/commits.md
index eb8d6a4359..9f72adc6ed 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 bb551fc67f..ffa7f2cdf1 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/ci/api/projects.md b/doc/ci/api/projects.md
index 54584db093..5585191e82 100644
--- a/doc/ci/api/projects.md
+++ b/doc/ci/api/projects.md
@@ -100,8 +100,6 @@ Parameters:
* `name` (required) - The name of the project
* `gitlab_id` (required) - The ID of the project on the Gitlab instance
- * `path` (required) - The gitlab project path
- * `ssh_url_to_repo` (required) - The gitlab SSH url to the repo
* `default_ref` (optional) - The branch to run on (default to `master`)
### Update Project
@@ -114,9 +112,6 @@ authenticated user has access to.
Parameters:
* `name` - The name of the project
- * `gitlab_id` - The ID of the project on the Gitlab instance
- * `path` - The gitlab project path
- * `ssh_url_to_repo` - The gitlab SSH url to the repo
* `default_ref` - The branch to run on (default to `master`)
### Remove Project
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index a698fbc818..5af27470d2 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -1,6 +1,6 @@
# Using Docker Build
-GitLab CI can allows you to use Docker Engine to build and test docker-based projects.
+GitLab CI allows you to use Docker Engine to build and test docker-based projects.
**This also allows to you to use `docker-compose` and other docker-enabled tools.**
@@ -108,5 +108,4 @@ In order to do that follow the steps:
```
1. However, by enabling `--docker-privileged` you are effectively disables all security mechanisms of containers and exposing your host to privilege escalation which can lead to container breakout.
-For more information you could be interested in checking out [Runtime privilege](https://docs.docker.com/reference/run/#runtime-privilege-linux-capabilities-and-lxc-configuration).
-
+For more information, check out [Runtime privilege](https://docs.docker.com/reference/run/#runtime-privilege-linux-capabilities-and-lxc-configuration).
\ No newline at end of file
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 191e3a8144..ef8a7ec1e8 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -90,7 +90,7 @@ you need to set MYSQL_ALLOW_EMPTY_PASSWORD.
- mysql
variables:
- MYSQL_ALLOW_EMPTY_PASSWORD: yes
+ MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
```
For other possible configuration variables check the
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 04c6bf1e3a..022afb7004 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -15,21 +15,27 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables)
-| Variable | Description |
+| Variable | Runner | Description |
|-------------------------|-------------|
-| **CI** | Mark that build is executed in CI environment |
-| **GITLAB_CI** | Mark that build is executed in GitLab CI environment |
-| **CI_SERVER** | Mark that build is executed in CI environment |
-| **CI_SERVER_NAME** | CI server that is used to coordinate builds |
-| **CI_SERVER_VERSION** | Not yet defined |
-| **CI_SERVER_REVISION** | Not yet defined |
-| **CI_BUILD_REF** | The commit revision for which project is built |
-| **CI_BUILD_BEFORE_SHA** | The first commit that were included in push request |
-| **CI_BUILD_REF_NAME** | The branch or tag name for which project is built |
-| **CI_BUILD_ID** | The unique id of the current build that GitLab CI uses internally |
-| **CI_BUILD_REPO** | The URL to clone the Git repository |
-| **CI_PROJECT_ID** | The unique id of the current project that GitLab CI uses internally |
-| **CI_PROJECT_DIR** | The full path where the repository is cloned and where the build is ran |
+| **CI** | 0.4 | Mark that build is executed in CI environment |
+| **GITLAB_CI** | all | Mark that build is executed in GitLab CI environment |
+| **CI_SERVER** | all | Mark that build is executed in CI environment |
+| **CI_SERVER_NAME** | all | CI server that is used to coordinate builds |
+| **CI_SERVER_VERSION** | all | Not yet defined |
+| **CI_SERVER_REVISION** | all | Not yet defined |
+| **CI_BUILD_REF** | all | The commit revision for which project is built |
+| **CI_BUILD_TAG** | 0.5 | The commit tag name. Present only when building tags. |
+| **CI_BUILD_NAME** | 0.5 | The name of the build as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_STAGE** | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_BEFORE_SHA** | all | The first commit that were included in push request |
+| **CI_BUILD_REF_NAME** | all | The branch or tag name for which project is built |
+| **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally |
+| **CI_BUILD_REPO** | all | The URL to clone the Git repository |
+| **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was triggered |
+| **CI_PROJECT_ID** | all | The unique id of the current project that GitLab CI uses internally |
+| **CI_PROJECT_DIR** | all | The full path where the repository is cloned and where the build is ran |
+
+**Some of the variables are only available when using runner with at least defined version.**
Example values:
@@ -39,6 +45,10 @@ export CI_BUILD_ID="50"
export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a"
export CI_BUILD_REF_NAME="master"
export CI_BUILD_REPO="https://gitlab.com/gitlab-org/gitlab-ce.git"
+export CI_BUILD_TAG="1.0.0"
+export CI_BUILD_NAME="spec:other"
+export CI_BUILD_STAGE="test"
+export CI_BUILD_TRIGGERED="true"
export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
export CI_PROJECT_ID="34"
export CI_SERVER="yes"
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 4caeccacb7..ea8f72bc13 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -140,6 +140,7 @@ job_name:
| except | optional | Defines a list of git refs for which build is not created |
| tags | optional | Defines a list of tags which are used to select runner |
| allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status |
+| when | optional | Define when to run build. Can be `on_success`, `on_failure` or `always` |
### script
`script` is a shell script which is executed by runner. The shell script is prepended with `before_script`.
@@ -196,9 +197,58 @@ job:
The above specification will make sure that `job` is built by a runner that have `ruby` AND `postgres` tags defined.
+### when
+`when` is used to implement jobs that are run in case of failure or despite the failure.
+
+`when` can be set to one of the following values:
+
+1. `on_success` - execute build only when all builds from prior stages succeeded. This is the default.
+1. `on_failure` - execute build only when at least one build from prior stages failed.
+1. `always` - execute build despite the status of builds from prior stages.
+
+```
+stages:
+- build
+- cleanup_build
+- test
+- deploy
+- cleanup
+
+build:
+ stage: build
+ script:
+ - make build
+
+cleanup_build:
+ stage: cleanup_build
+ script:
+ - cleanup build when failed
+ when: on_failure
+
+test:
+ stage: test
+ script:
+ - make test
+
+deploy:
+ stage: deploy
+ script:
+ - make deploy
+
+cleanup:
+ stage: cleanup
+ script:
+ - cleanup after builds
+ when: always
+```
+
+The above script will:
+1. Execute `cleanup_build` only when the `build` failed,
+2. Always execute `cleanup` as the last step in pipeline.
+
## Validate the .gitlab-ci.yml
Each instance of GitLab CI has an embedded debug tool called Lint.
You can find the link to the Lint in the project's settings page or use short url `/lint`.
## Skipping builds
-There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped
\ No newline at end of file
+There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
index 6c141d1fb7..e993230bb8 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/development/README.md b/doc/development/README.md
index 6bc8e1888d..d5bf166ad3 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -8,3 +8,4 @@
- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
- [How to dump production data to staging](dump_db.md)
+- [Benchmarking](benchmarking.md)
diff --git a/doc/development/benchmarking.md b/doc/development/benchmarking.md
new file mode 100644
index 0000000000..88e18ee95f
--- /dev/null
+++ b/doc/development/benchmarking.md
@@ -0,0 +1,69 @@
+# Benchmarking
+
+GitLab CE comes with a set of benchmarks that are executed for every build. This
+makes it easier to measure performance of certain components over time.
+
+Benchmarks are written as RSpec tests using a few extra helpers. To write a
+benchmark, first tag the top-level `describe`:
+
+```ruby
+describe MaruTheCat, benchmark: true do
+
+end
+```
+
+This ensures the benchmark is executed separately from other test collections.
+It also exposes the various RSpec matchers used for writing benchmarks to the
+test group.
+
+Next, lets write the actual benchmark:
+
+```ruby
+describe MaruTheCat, benchmark: true do
+ let(:maru) { MaruTheChat.new }
+
+ describe '#jump_in_box' do
+ benchmark_subject { maru.jump_in_box }
+
+ it { is_expected.to iterate_per_second(9000) }
+ end
+end
+```
+
+Here `benchmark_subject` is a small wrapper around RSpec's `subject` method that
+makes it easier to specify the subject of a benchmark. Using RSpec's regular
+`subject` would require us to write the following instead:
+
+```ruby
+subject { -> { maru.jump_in_box } }
+```
+
+The `iterate_per_second` matcher defines the amount of times per second a
+subject should be executed. The higher the amount of iterations the better.
+
+By default the allowed standard deviation is a maximum of 30%. This can be
+adjusted by chaining the `with_maximum_stddev` on the `iterate_per_second`
+matcher:
+
+```ruby
+it { is_expected.to iterate_per_second(9000).with_maximum_stddev(50) }
+```
+
+This can be useful if the code in question depends on external resources of
+which the performance can vary a lot (e.g. physical HDDs, network calls, etc).
+However, in most cases 30% should be enough so only change this when really
+needed.
+
+## Benchmarks Location
+
+Benchmarks should be stored in `spec/benchmarks` and should follow the regular
+Rails specs structure. That is, model benchmarks go in `spec/benchmark/models`,
+benchmarks for code in the `lib` directory go in `spec/benchmarks/lib`, etc.
+
+## Underlying Technology
+
+The benchmark setup uses [benchmark-ips][benchmark-ips] which takes care of the
+heavy lifting such as warming up code, calculating iterations, standard
+deviation, etc.
+
+[benchmark-ips]: https://github.com/evanphx/benchmark-ips
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
new file mode 100644
index 0000000000..80c86ef921
--- /dev/null
+++ b/doc/development/profiling.md
@@ -0,0 +1,56 @@
+# Profiling
+
+To make it easier to track down performance problems GitLab comes with a set of
+profiling tools, some of these are available by default while others need to be
+explicitly enabled.
+
+## rack-mini-profiler
+
+This Gem is enabled by default in development only. It allows you to see the
+timings of the various components that made up a web request (e.g. the SQL
+queries executed and their execution timings).
+
+## Bullet
+
+Bullet is a Gem that can be used to track down N+1 query problems. Because
+Bullet adds quite a bit of logging noise it's disabled by default. To enable
+Bullet, set the environment variable `ENABLE_BULLET` to a non-empty value before
+starting GitLab. For example:
+
+ ENABLE_BULLET=true bundle exec rails s
+
+Bullet will log query problems to both the Rails log as well as the Chrome
+console.
+
+## ActiveRecord Query Trace
+
+This Gem adds backtraces for every ActiveRecord query in the Rails console. This
+can be useful to track down where a query was executed. Because this Gem adds
+quite a bit of noise (5-10 extra lines per ActiveRecord query) it's disabled by
+default. To use this Gem you'll need to set `ENABLE_QUERY_TRACE` to a non empty
+file before starting GitLab. For example:
+
+ ENABLE_QUERY_TRACE=true bundle exec rails s
+
+## rack-lineprof
+
+This is a Gem that can trace the execution time of code on a per line basis.
+Because this Gem can add quite a bit of overhead it's disabled by default. To
+enable it, set the environment variable `ENABLE_LINEPROF` to a non-empty value.
+For example:
+
+ ENABLE_LINEPROF=true bundle exec rails s
+
+Once enabled you'll need to add a query string parameter to a request to
+actually profile code execution. The name of the parameter is `lineprof` and
+should be set to a regular expression (minus the starting/ending slash) used to
+select what files to profile. To profile all files containing "foo" somewhere in
+the path you'd use the following parameter:
+
+ ?lineprof=foo
+
+Or when filtering for files containing "foo" and "bar" in their path:
+
+ ?lineprof=foo|bar
+
+Once set the profiling output will be displayed in your terminal.
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
index b904c70e98..493e1d1b09 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -23,3 +23,5 @@ Step-by-step guides on the basics of working with Git and GitLab.
* [Add an image](add-image.md)
* [Create a Merge Request](add-merge-request.md)
+
+* [Create an Issue](create-issue.md)
diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md
new file mode 100644
index 0000000000..87f078def0
--- /dev/null
+++ b/doc/gitlab-basics/create-issue.md
@@ -0,0 +1,27 @@
+# How to create an Issue in GitLab
+
+The Issue Tracker is a good place to add things that need to be improved or solved in a project.
+
+To create an Issue, sign in to GitLab.
+
+Go to the project where you'd like to create the Issue:
+
+
+
+Click on "Issues" on the left side of your screen:
+
+
+
+Click on the "+ new issue" button on the right side of your screen:
+
+
+
+Add a title and a description to your issue:
+
+
+
+You may assign the Issue to a user, add a milestone and add labels (they are all optional). Then click on "submit new issue":
+
+
+
+Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](http://doc.gitlab.com/ce/customization/issue_closing.html).
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
index f7d4f3de68..548c484bc0 100644
--- a/doc/hooks/custom_hooks.md
+++ b/doc/hooks/custom_hooks.md
@@ -2,7 +2,7 @@
**Note: Custom git hooks must be configured on the filesystem of the GitLab
server. Only GitLab server administrators will be able to complete these tasks.
-Please explore webhooks as an option if you do not have filesystem access.**
+Please explore webhooks as an option if you do not have filesystem access. For a user configurable Git Hooks interface, please see [GitLab Enterprise Edition Git Hooks](http://doc.gitlab.com/ee/git_hooks/git_hooks.html).**
Git natively supports hooks that are executed on different actions.
Examples of server-side git hooks include pre-receive, post-receive, and update.
diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md
index 01ab22321e..86d205ba7a 100644
--- a/doc/incoming_email/README.md
+++ b/doc/incoming_email/README.md
@@ -4,32 +4,75 @@ GitLab can be set up to allow users to comment on issues and merge requests by r
## Get a mailbox
-Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises.
+Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises.
-If you want to use Gmail with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
+If you want to use Gmail / Google Apps with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these instructions](./postfix.md).
## 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:
+1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature and fill in the details for your specific IMAP server and email account:
```ruby
+ # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.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_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox".
+
+ # The email address including a placeholder for the key that references the item being replied to.
+ # The `%{key}` placeholder is added after the user part, before the `@`.
+ gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com"
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ gitlab_rails['incoming_email_email'] = "incoming"
+ # Email account password
+ gitlab_rails['incoming_email_password'] = "[REDACTED]"
+
+ # IMAP server host
+ gitlab_rails['incoming_email_host'] = "gitlab.example.com"
+ # IMAP server port
+ gitlab_rails['incoming_email_port'] = 143
+ # Whether the IMAP server uses SSL
+ gitlab_rails['incoming_email_ssl'] = false
+ # Whether the IMAP server uses StartTLS
+ gitlab_rails['incoming_email_start_tls'] = false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ gitlab_rails['incoming_email_mailbox_name'] = "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`.
+ ```ruby
+ # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
+ gitlab_rails['incoming_email_enabled'] = true
+
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com"
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com"
+ # Email account password
+ gitlab_rails['incoming_email_password'] = "[REDACTED]"
+
+ # IMAP server host
+ gitlab_rails['incoming_email_host'] = "imap.gmail.com"
+ # IMAP server port
+ gitlab_rails['incoming_email_port'] = 993
+ # Whether the IMAP server uses SSL
+ gitlab_rails['incoming_email_ssl'] = true
+ # Whether the IMAP server uses StartTLS
+ gitlab_rails['incoming_email_start_tls'] = false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ gitlab_rails['incoming_email_mailbox_name'] = "inbox"
+ ```
+
+ 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:
@@ -53,155 +96,146 @@ In this example, we'll use the Gmail address `gitlab-incoming@gmail.com`.
cd /home/git/gitlab
```
-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:
+1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account:
```sh
sudo editor config/gitlab.yml
```
```yaml
+ # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.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`.
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ address: "incoming+%{key}@gitlab.example.com"
-2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`:
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "incoming"
+ # Email account password
+ password: "[REDACTED]"
- ```sh
- sudo cp config/mail_room.yml.example config/mail_room.yml
- ```
+ # 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
-3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account:
-
- ```sh
- sudo editor config/mail_room.yml
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
```
```yaml
- :mailboxes:
- -
- # IMAP server host
- :host: "imap.gmail.com"
- # IMAP server port
- :port: 993
- # Whether the IMAP server uses SSL
- :ssl: true
- # Whether the IMAP server uses StartTLS
- :start_tls: false
- # Email account username. Usually the full email address.
- :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.
- :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
+ # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
+ incoming_email:
+ enabled: true
+
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
+ address: "gitlab-incoming+%{key}@gmail.com"
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "gitlab-incoming@gmail.com"
+ # Email account password
+ password: "[REDACTED]"
+
+ # IMAP server host
+ host: "imap.gmail.com"
+ # IMAP server port
+ port: 993
+ # Whether the IMAP server uses SSL
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
```
-5. Edit the init script configuration at `/etc/default/gitlab` to enable `mail_room`:
+ 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. Enable `mail_room` in the init script at `/etc/default/gitlab`:
```sh
sudo mkdir -p /etc/default
echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab
```
-6. Restart GitLab:
+1. Restart GitLab:
```sh
sudo service gitlab restart
```
-7. Verify that everything is configured correctly:
+1. Verify that everything is configured correctly:
```sh
sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production
```
-8. Reply by email should now be working.
+1. Reply by email should now be working.
### Development
1. Go to the GitLab installation directory.
-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:
+1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account:
```yaml
+ # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email:
enabled: true
+
+ # The email address including a placeholder for the key that references the item being replied to.
+ # The `%{key}` placeholder is added after the user part, before the `@`.
address: "gitlab-incoming+%{key}@gmail.com"
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "gitlab-incoming@gmail.com"
+ # Email account password
+ password: "[REDACTED]"
+
+ # IMAP server host
+ host: "imap.gmail.com"
+ # IMAP server port
+ port: 993
+ # Whether the IMAP server uses SSL
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
```
As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`.
-2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`:
-
- ```sh
- sudo cp config/mail_room.yml.example config/mail_room.yml
- ```
-
-3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account:
-
- ```yaml
- :mailboxes:
- -
- # IMAP server host
- :host: "imap.gmail.com"
- # IMAP server port
- :port: 993
- # Whether the IMAP server uses SSL
- :ssl: true
- # Whether the IMAP server uses StartTLS
- :start_tls: false
- # Email account username. Usually the full email address.
- :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.
- :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
- ```
-
-4. Uncomment the `mail_room` line in your `Procfile`:
+1. Uncomment the `mail_room` line in your `Procfile`:
```yaml
mail_room: bundle exec mail_room -q -c config/mail_room.yml
```
-6. Restart GitLab:
+1. Restart GitLab:
```sh
bundle exec foreman start
```
-7. Verify that everything is configured correctly:
+1. Verify that everything is configured correctly:
```sh
bundle exec rake gitlab:incoming_email:check RAILS_ENV=development
```
-8. Reply by email should now be working.
+1. Reply by email should now be working.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 039bb3c256..2e9ac7393e 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -115,8 +115,9 @@ Remove the old Ruby 1.8 if present
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.6.tar.gz | tar xz
- cd ruby-2.1.6
+ curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.7.tar.gz
+ echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075 ruby-2.1.7.tar.gz' | shasum -c - && tar xzf ruby-2.1.7.tar.gz
+ cd ruby-2.1.7
./configure --disable-install-rdoc
make
sudo make install
@@ -130,12 +131,15 @@ Install the Bundler Gem:
Since GitLab 8.0, Git HTTP requests are handled by gitlab-git-http-server.
This is a small daemon written in Go.
To install gitlab-git-http-server we need a Go compiler.
+The instructions below assume you use 64-bit Linux. You can find
+downloads for other platforms at the [Go download
+page](https://golang.org/dl).
- curl -O --progress https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz
- echo '5817fa4b2252afdb02e11e8b9dc1d9173ef3bd5a go1.5.linux-amd64.tar.gz' | shasum -c - && \
- sudo tar -C /usr/local -xzf go1.5.linux-amd64.tar.gz
+ curl -O --progress https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
+ echo '46eecd290d8803887dec718c691cc243f2175fe0 go1.5.1.linux-amd64.tar.gz' | shasum -c - && \
+ sudo tar -C /usr/local -xzf go1.5.1.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
- rm go1.5.linux-amd64.tar.gz
+ rm go1.5.1.linux-amd64.tar.gz
## 4. System Users
@@ -207,9 +211,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-0-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-1-stable gitlab
-**Note:** You can change `8-0-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -321,6 +325,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git
cd gitlab-git-http-server
+ sudo -u git -H git checkout 0.3.0
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index 3bc5df21ef..9b7d8fa396 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -173,3 +173,23 @@ Tip: if you want to limit access to the nested members of an Active Directory gr
```
Please note that GitLab does not support the custom filter syntax used by omniauth-ldap.
+
+## Limitations
+
+GitLab's LDAP client is based on [omniauth-ldap](https://gitlab.com/gitlab-org/omniauth-ldap)
+which encapsulates Ruby's `Net::LDAP` class. It provides a pure-Ruby implementation
+of the LDAP client protocol. As a result, GitLab is limited by `omniauth-ldap` and may impact your LDAP
+server settings.
+
+### TLS Client Authentication
+Not implemented by `Net::LDAP`.
+So you should disable anonymous LDAP authentication and enable simple or SASL
+authentication. TLS client authentication setting in your LDAP server cannot be
+mandatory and clients cannot be authenticated with the TLS protocol.
+
+### TLS Server Authentication
+Not supported by GitLab's configuration options.
+When setting `method: ssl`, the underlying authentication method used by
+`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
+the LDAP server before any LDAP-protocol data is exchanged but no validation of
+the LDAP server's SSL certificate is performed.
\ No newline at end of file
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index d83838127c..ac3851f8c9 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -274,7 +274,7 @@ The IDs are generated from the content of the header according to the following
1. All spaces are converted to hyphens
1. Two or more hyphens in a row are converted to one
1. If a header with the same ID has already been generated, a unique
- incrementing number is appended.
+ incrementing number is appended, starting at 1.
For example:
@@ -291,8 +291,8 @@ Would generate the following link IDs:
1. `this-header-has-spaces-in-it`
1. `this-header-has-a-in-it`
1. `this-header-has-unicode-in-it-한글`
+1. `this-header-has-spaces-in-it`
1. `this-header-has-spaces-in-it-1`
-1. `this-header-has-spaces-in-it-2`
Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID.
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 2725bf343e..5ec0a2069b 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -28,13 +28,20 @@ upgrade to 8.0 until you finish the migration procedure.
### Before upgrading
-If you have GitLab CI installed using omnibus-gitlab packages but *you don't want to migrate your existing data*:
+If you have GitLab CI installed using omnibus-gitlab packages but **you don't want to migrate your existing data**:
```bash
mv /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds.$(date +%s)
```
-and run `sudo gitlab-ctl reconfigure`.
+run `sudo gitlab-ctl reconfigure` and you can reach CI at `gitlab.example.com/ci`.
+
+If you want to migrate your existing data, continue reading.
+
+#### 0. Updating Omnibus from versions prior to 7.13
+
+If you are updating from older versions you should first update to 7.14 and then to 8.0.
+Otherwise it's pretty likely that you will encounter problems described in the [Troubleshooting](#troubleshooting).
#### 1. Verify that backups work
@@ -43,6 +50,7 @@ Make sure that the backup script on both servers can connect to the database.
```
# On your CI server:
# Omnibus
+sudo chown gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
sudo gitlab-ci-rake backup:create
# Source
@@ -143,7 +151,7 @@ sudo gitlab-ctl stop ci-sidekiq
# Source
sudo service gitlab_ci stop
cd /home/gitlab_ci/gitlab-ci
-sudo -u gitlab_ci -H bundle exec whenever --clear-crontab
+sudo -u gitlab_ci -H bundle exec whenever --clear-crontab RAILS_ENV=production
```
### II. Moving data
@@ -176,6 +184,7 @@ will need this file later.
```
# On your CI server:
# Omnibus
+sudo chown gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
sudo gitlab-ci-rake backup:create
# Source
@@ -222,6 +231,7 @@ be no CI data yet because you turned CI on the GitLab server off earlier.
```
# On your GitLab server:
# Omnibus
+sudo chown git:git /var/opt/gitlab/gitlab-ci/builds
sudo gitlab-rake ci:migrate
# Source
@@ -319,3 +329,107 @@ You should also make sure that you can:
If something went wrong and you need to restore a backup, consult the [Backup
restoration](../raketasks/backup_restore.md) guide.
+
+### Troubleshooting
+
+#### show:secrets problem (Omnibus-only)
+If you see errors like this:
+```
+Missing `secret_key_base` or `db_key_base` for 'production' environment. The secrets will be generated and stored in `config/secrets.yml`
+rake aborted!
+Errno::EACCES: Permission denied @ rb_sysopen - config/secrets.yml
+```
+
+This can happen if you are updating from versions prior to 7.13 straight to 8.0.
+The fix for this is to update to Omnibus 7.14 first and then update it to 8.0.
+
+#### Permission denied when accessing /var/opt/gitlab/gitlab-ci/builds
+To fix that issue you have to change builds/ folder permission before doing final backup:
+```
+sudo chown -R gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
+```
+
+Then before executing `ci:migrate` you need to fix builds folder permission:
+```
+sudo chown git:git /var/opt/gitlab/gitlab-ci/builds
+```
+
+#### Problems when importing CI database to GitLab
+If you were migrating CI database from MySQL to PostgreSQL manually you can see errros during import about missing sequences:
+```
+ALTER SEQUENCE
+ERROR: relation "ci_builds_id_seq" does not exist
+ERROR: relation "ci_commits_id_seq" does not exist
+ERROR: relation "ci_events_id_seq" does not exist
+ERROR: relation "ci_jobs_id_seq" does not exist
+ERROR: relation "ci_projects_id_seq" does not exist
+ERROR: relation "ci_runner_projects_id_seq" does not exist
+ERROR: relation "ci_runners_id_seq" does not exist
+ERROR: relation "ci_services_id_seq" does not exist
+ERROR: relation "ci_taggings_id_seq" does not exist
+ERROR: relation "ci_tags_id_seq" does not exist
+CREATE TABLE
+```
+
+To fix that you need to apply this SQL statement before doing final backup:
+```
+# Omnibus
+gitlab-ci-rails dbconsole <= 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/8.0-to-8.1.md b/doc/update/8.0-to-8.1.md
new file mode 100644
index 0000000000..d57c0d0674
--- /dev/null
+++ b/doc/update/8.0-to-8.1.md
@@ -0,0 +1,171 @@
+# From 8.0 to 8.1
+
+**NOTE:** GitLab 8.0 introduced several significant changes related to
+installation and configuration which *are not duplicated here*. Be sure you're
+already running a working version of 8.0 before proceeding with this guide.
+
+### 0. Double-check your Git version
+
+**This notice applies only to /usr/local/bin/git**
+
+If you compiled Git from source on your GitLab server then please double-check
+that you are using a version that protects against CVE-2014-9390. For six
+months after this vulnerability became known the GitLab installation guide
+still contained instructions that would install the outdated, 'vulnerable' Git
+version 2.1.2.
+
+Run the following command to get your current Git version:
+
+```sh
+/usr/local/bin/git --version
+```
+
+If you see 'No such file or directory' then you did not install Git according
+to the outdated instructions from the GitLab installation guide and you can go
+to the next step 'Stop server' below.
+
+If you see a version string then it should be v1.8.5.6, v1.9.5, v2.0.5, v2.1.4,
+v2.2.1 or newer. You can use the [instructions in the GitLab source
+installation
+guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
+to install a newer version of Git.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-1-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-1-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.6.5
+```
+
+### 5. Update gitlab-git-http-server
+
+```bash
+cd /home/git/gitlab-git-http-server
+sudo -u git -H git fetch origin
+sudo -u git -H git checkout 0.3.0
+sudo -u git -H make
+```
+
+### 6. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 7. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-0-stable:config/gitlab.yml.example origin/8-1-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+View changes between the previous recommended Nginx configuration and the
+current one:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-0-stable:lib/support/nginx/gitlab-ssl origin/8-1-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-0-stable:lib/support/nginx/gitlab origin/8-1-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-git-http-server listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-1-stable/lib/support/init.d/gitlab.default.example#L34
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.0)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 7.14 to 8.0](7.14-to-8.0.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+## Troubleshooting
+
+### "You appear to have cloned an empty repository."
+
+See the [7.14 to 8.0 update guide](7.14-to-8.0.md#troubleshooting).
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index a66a863f6c..593722eb01 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
@@ -47,9 +49,7 @@ sudo -u git -H bundle install --without development test mysql --deployment
sudo -u git -H bundle install --without development test postgres --deployment
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
-sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
-sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
```
### 5. Start application
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index c185ccfcac..ef99a69f60 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/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000..e5a5d8dd53
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,2 @@
+app:
+ image: gitlab/gitlab-ce:latest
diff --git a/docker/.dockerignore b/docker/.dockerignore
deleted file mode 100644
index dd449725e1..0000000000
--- a/docker/.dockerignore
+++ /dev/null
@@ -1 +0,0 @@
-*.md
diff --git a/docker/Dockerfile b/docker/Dockerfile
deleted file mode 100644
index 304bb97409..0000000000
--- a/docker/Dockerfile
+++ /dev/null
@@ -1,50 +0,0 @@
-FROM ubuntu:14.04
-MAINTAINER Sytse Sijbrandij
-
-# Install required packages
-RUN apt-get update -q \
- && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
- ca-certificates \
- openssh-server \
- wget \
- apt-transport-https \
- vim \
- nano
-
-# Download & Install GitLab
-# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
-RUN echo "deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/gitlab_gitlab-ce.list
-RUN wget -q -O - https://packages.gitlab.com/gpg.key | apt-key add -
-RUN apt-get update && apt-get install -yq --no-install-recommends gitlab-ce
-
-# Manage SSHD through runit
-RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
- && mkfifo /opt/gitlab/sv/sshd/supervise/ok \
- && printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \
- && chmod a+x /opt/gitlab/sv/sshd/run \
- && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
- && mkdir -p /var/run/sshd
-
-# Disabling use DNS in ssh since it tends to slow connecting
-RUN echo "UseDNS no" >> /etc/ssh/sshd_config
-
-# Prepare default configuration
-RUN ( \
- echo "" && \
- echo "# Docker options" && \
- echo "# Prevent Postgres from trying to allocate 25% of total memory" && \
- echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb && \
- mkdir -p /assets/ && \
- cp /etc/gitlab/gitlab.rb /assets/gitlab.rb
-
-# Expose web & ssh
-EXPOSE 443 80 22
-
-# Define data volumes
-VOLUME ["/etc/gitlab", "/var/opt/gitlab", "/var/log/gitlab"]
-
-# Copy assets
-COPY assets/wrapper /usr/local/bin/
-
-# Wrapper to handle signal, trigger runit and reconfigure GitLab
-CMD ["/usr/local/bin/wrapper"]
diff --git a/docker/README.md b/docker/README.md
index e4d56cdb33..7514d610ae 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,169 +1,7 @@
# GitLab Docker images
-The GitLab docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/).
-
-## After starting a container
-
-After starting a container you can go to [http://localhost:8080/](http://localhost:8080/) or [http://192.168.59.103:8080/](http://192.168.59.103:8080/) if you use boot2docker.
-
-It might take a while before the docker container is responding to queries.
-
-You can check the status with something like `sudo docker logs -f gitlab`.
-
-You can login to the web interface with username `root` and password `5iveL!fe`.
-
-Next time, you can just use docker start and stop to run the container.
-
-## Run the image
-
-Run the image:
-```bash
-sudo docker run --detach \
- --publish 8443:443 --publish 8080:80 --publish 2222:22 \
- --name gitlab \
- --restart always \
- --volume /srv/gitlab/config:/etc/gitlab \
- --volume /srv/gitlab/logs:/var/log/gitlab \
- --volume /srv/gitlab/data:/var/opt/gitlab \
- gitlab/gitlab-ce:latest
-```
-
-This will download and start GitLab CE container and publish ports needed to access SSH, HTTP and HTTPS.
-All GitLab data will be stored as subdirectories of `/srv/gitlab/`.
-The container will automatically `restart` after system reboot.
-
-After this you can login to the web interface as explained above in 'After starting a container'.
-
-## Where is the data stored?
-
-The GitLab container uses host mounted volumes to store persistent data:
-- `/srv/gitlab/data` mounted as `/var/opt/gitlab` in the container is used for storing *application data*
-- `/srv/gitlab/logs` mounted as `/var/log/gitlab` in the container is used for storing *logs*
-- `/srv/gitlab/config` mounted as `/etc/gitlab` in the container is used for storing *configuration*
-
-You can fine tune these directories to meet your requirements.
-
-### Configure GitLab
-
-This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
-
-To access GitLab configuration, you can start an bash in a new the context of running container, you will be able to browse all directories and use your favorite text editor:
-```bash
-sudo docker exec -it gitlab /bin/bash
-```
-
-You can also edit just `/etc/gitlab/gitlab.rb`:
-```bash
-sudo docker exec -it gitlab vi /etc/gitlab/gitlab.rb
-```
-
-**You should set the `external_url` to point to a valid URL.**
-
-**You may also be interesting in [Enabling HTTPS](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/nginx.md#enable-https).**
-
-**To receive e-mails from GitLab you have to configure the [SMTP settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md),
-because Docker image doesn't have a SMTP server.**
-
-**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab:
-
-```bash
-sudo docker restart gitlab
-```
-
-For more options for configuring the container please check [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
-
-## Diagnose potential problems
-
-Read container logs:
-```bash
-sudo docker logs gitlab
-```
-
-Enter running container:
-```bash
-sudo docker exec -it gitlab /bin/bash
-```
-
-From within container you can administrer GitLab container as you would normally administer Omnibus installation: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md.
-
-### Upgrade GitLab to newer version
-
-To upgrade GitLab to new version you have to do:
-1. pull new image,
-```bash
-sudo docker stop gitlab
-```
-
-1. stop running container,
-```bash
-sudo docker rm gitlab
-```
-
-1. remove existing container,
-```bash
-sudo docker pull gitlab/gitlab-ce:latest
-```
-
-1. create the container once again with previously specified options.
-```bash
-sudo docker run --detach \
- --publish 8443:443 --publish 8080:80 --publish 2222:22 \
- --name gitlab \
- --restart always \
- --volume /srv/gitlab/config:/etc/gitlab \
- --volume /srv/gitlab/logs:/var/log/gitlab \
- --volume /srv/gitlab/data:/var/opt/gitlab \
- gitlab/gitlab-ce:latest
-```
-
-On the first run GitLab will reconfigure and update itself.
-
-### Run GitLab CE on public IP address
-
-You can make Docker to use your IP address and forward all traffic to the GitLab CE container.
-You can do that by modifying the `--publish` ([Binding container ports to the host](https://docs.docker.com/articles/networking/#binding-ports)):
-
-> --publish=[] : Publish a container᾿s port or a range of ports to the host format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
-
-To expose GitLab CE on IP 1.1.1.1:
-
-```bash
-sudo docker run --detach \
- --publish 1.1.1.1:443:443 --publish 1.1.1.1:80:80 --publish 1.1.1.1:22:22 \
- --name gitlab \
- --restart always \
- --volume /srv/gitlab/config:/etc/gitlab \
- --volume /srv/gitlab/logs:/var/log/gitlab \
- --volume /srv/gitlab/data:/var/opt/gitlab \
- gitlab/gitlab-ce:latest
-```
-
-You can then access GitLab instance at http://1.1.1.1/ and https://1.1.1.1/.
-
-### Build the image
-
-This guide will also let you know how to build docker image yourself.
-Please run the command from the GitLab repo root directory.
-People using boot2docker should run all the commands without sudo.
-
-```bash
-sudo docker build --tag gitlab/gitlab-ce:latest docker/
-```
-
-### Publish the image to Dockerhub
-
-- Ensure the containers are running
-- Login to Dockerhub with `sudo docker login`
-
-```bash
-sudo docker login
-sudo docker push gitlab/gitlab-ce:latest
-```
-
-## Troubleshooting
-
-Please see the [troubleshooting](troubleshooting.md) file in this directory.
-
-Note: We use `fig.yml` to have compatibility with fig and because docker-compose also supports it.
-
-Our docker image runs chef at every start to generate GitLab configuration.
+* The official GitLab Community Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/).
+* The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ee/).
+* The complete usage guide can be found in [Using GitLab Docker images](http://doc.gitlab.com/omnibus/docker/)
+* The Dockerfile used for building public images is in [Omnibus Repository](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker)
+* Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#Build-Docker-image)
diff --git a/docker/assets/wrapper b/docker/assets/wrapper
deleted file mode 100755
index 8bc8370fbc..0000000000
--- a/docker/assets/wrapper
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-function sigterm_handler() {
- echo "SIGTERM signal received, try to gracefully shutdown all services..."
- gitlab-ctl stop
-}
-
-trap "sigterm_handler; exit" TERM
-
-function entrypoint() {
- /opt/gitlab/embedded/bin/runsvdir-start &
- gitlab-ctl reconfigure # will also start everything
- gitlab-ctl tail # tail all logs
-}
-
-if [[ ! -e /etc/gitlab/gitlab.rb ]]; then
- cp /assets/gitlab.rb /etc/gitlab/gitlab.rb
- chmod 0600 /etc/gitlab/gitlab.rb
-fi
-
-entrypoint
diff --git a/docker/fig.yml b/docker/fig.yml
deleted file mode 100644
index 989551cbfe..0000000000
--- a/docker/fig.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-app:
- build: .
diff --git a/docker/marathon.json b/docker/marathon.json
deleted file mode 100644
index 9b2091a8c2..0000000000
--- a/docker/marathon.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "id": "/gitlab",
- "ports": [0,0],
- "cpus": 2,
- "mem": 2048.0,
- "disk": 10240.0,
- "container": {
- "type": "DOCKER",
- "docker": {
- "network": "HOST",
- "image": "gitlab/gitlab-ce:latest"
- },
- "volumes": [
- {
- "containerPath": "/etc/gitlab",
- "hostPath": "/var/data/etc/gitlab",
- "mode": "RW"
- },
- {
- "containerPath": "/var/opt/gitlab",
- "hostPath": "/var/data/opt/gitlab",
- "mode": "RW"
- },
- {
- "containerPath": "/var/log/gitlab",
- "hostPath": "/var/data/log/gitlab",
- "mode": "RW"
- }
- ]
- }
-}
\ No newline at end of file
diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md
deleted file mode 100644
index 63482547da..0000000000
--- a/docker/troubleshooting.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# Troubleshooting
-
-This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245
-But it might contain useful commands for other cases as well.
-
-The configuration to add the postgres log in vim is:
-postgresql['log_directory'] = '/var/log/gitlab/postgresql'
-
-# Commands
-
-```bash
-sudo docker build --tag gitlab/gitlab-ce:latest docker/
-
-sudo docker rm -f gitlab
-
-sudo docker exec -it gitlab vim /etc/gitlab/gitlab.rb
-
-sudo docker exec gitlab tail -f /var/log/gitlab/reconfigure.log
-
-sudo docker exec gitlab tail -f /var/log/gitlab/postgresql/current
-
-sudo docker exec gitlab cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
-
-sudo docker exec gitlab cat /etc/gitlab/gitlab.rb
-```
-
-# Interactively
-
-```bash
-# First start a GitLab container without starting GitLab
-# This is almost the same as starting the GitLab container except:
-# - we run interactively (-t -i)
-# - we define TERM=linux because it allows to use arrow keys in vi (!!!)
-# - we choose another startup command (bash)
-sudo docker run --ti \
- -e TERM=linux
- --publish 80443:443 --publish 8080:80 --publish 2222:22 \
- --name gitlab \
- --restart always \
- --volume /srv/gitlab/config:/etc/gitlab \
- --volume /srv/gitlab/logs:/var/log/gitlab \
- --volume /srv/gitlab/data:/var/opt/gitlab \
- gitlab/gitlab-ce:latest \
- bash
-
-# Configure GitLab to redirect PostgreSQL logs
-echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
-
-# Prevent Postgres from allocating 25% of total memory
-echo "postgresql['shared_buffers'] = '1MB'" >> /etc/gitlab/gitlab.rb
-
-# You can now start GitLab manually from Bash (in the background)
-# Maybe the command below is still missing something to run in the background
-gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start &
-
-# Inspect PostgreSQL config
-cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
-
-# And tail the logs (PostgreSQL log may not exist immediately)
-tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current
-
-# And get the memory
-cat /proc/meminfo
-head /proc/sys/kernel/shmmax /proc/sys/kernel/shmall
-free -m
-
-```
-
-# Cleanup
-
-Remove ALL docker containers and images (also non GitLab ones).
-**Be careful, because the `-v` also removes volumes attached to the images.**
-
-```bash
-# Remove all containers with attached volumes
-docker rm -v $(docker ps -a -q)
-
-# Remove all images
-docker rmi $(docker images -q)
-
-# Remove GitLab persistent data
-rm -rf /srv/gitlab
-```
-
diff --git a/features/abuse_report.feature b/features/abuse_report.feature
index 3e1cb455b7..212972a762 100644
--- a/features/abuse_report.feature
+++ b/features/abuse_report.feature
@@ -8,3 +8,10 @@ Feature: Abuse reports
And I click "Report abuse" button
When I fill and submit abuse form
Then I should see success message
+
+ Scenario: Report abuse available only once
+ Given I visit "Mike" user page
+ And I click "Report abuse" button
+ When I fill and submit abuse form
+ And I visit "Mike" user page
+ Then I should see a red "Report abuse" button
diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature
index bbd82a85e3..7639206835 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/commits/commits.feature b/features/project/commits/commits.feature
index 34161b81d4..e4beeb59ad 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -20,6 +20,8 @@ Feature: Project Commits
Given commit has ci status
And I click on commit link
Then I see commit ci info
+ And I click status link
+ Then I see builds list
Scenario: I browse commit with side-by-side diff view
Given I click on commit link
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 947f668e43..83055188ba 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -115,40 +115,40 @@ Feature: Project Merge Requests
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And I click on the Changes tab
- And I leave a comment like "Line is wrong" on line 39 of the second file
- And I click link "Hide inline discussion" of the second file
- Then I should not see a comment like "Line is wrong here" in the second file
+ And I leave a comment like "Line is wrong" on line 39 of the third file
+ And I click link "Hide inline discussion" of the third file
+ Then I should not see a comment like "Line is wrong here" in the third file
@javascript
Scenario: I show comments on a merge request diff with comments in a single file
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And I click on the Changes tab
- And I leave a comment like "Line is wrong" on line 39 of the second file
- Then I should see a comment like "Line is wrong" in the second file
+ And I leave a comment like "Line is wrong" on line 39 of the third file
+ Then I should see a comment like "Line is wrong" in the third file
@javascript
Scenario: I hide comments on a merge request diff with comments in multiple files
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And I click on the Changes tab
- And I leave a comment like "Line is correct" on line 12 of the first file
- And I leave a comment like "Line is wrong" on line 39 of the second file
- And I click link "Hide inline discussion" of the second file
- Then I should not see a comment like "Line is wrong here" in the second file
- And I should still see a comment like "Line is correct" in the first file
+ And I leave a comment like "Line is correct" on line 12 of the second file
+ And I leave a comment like "Line is wrong" on line 39 of the third file
+ And I click link "Hide inline discussion" of the third file
+ Then I should not see a comment like "Line is wrong here" in the third file
+ And I should still see a comment like "Line is correct" in the second file
@javascript
Scenario: I show comments on a merge request diff with comments in multiple files
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And I click on the Changes tab
- And I leave a comment like "Line is correct" on line 12 of the first file
- And I leave a comment like "Line is wrong" on line 39 of the second file
- And I click link "Hide inline discussion" of the second file
- And I click link "Show inline discussion" of the second file
- Then I should see a comment like "Line is wrong" in the second file
- And I should still see a comment like "Line is correct" in the first file
+ And I leave a comment like "Line is correct" on line 12 of the second file
+ And I leave a comment like "Line is wrong" on line 39 of the third file
+ And I click link "Hide inline discussion" of the third file
+ And I click link "Show inline discussion" of the third file
+ Then I should see a comment like "Line is wrong" in the third file
+ And I should still see a comment like "Line is correct" in the second file
@javascript
Scenario: I unfold diff
@@ -163,8 +163,8 @@ Feature: Project Merge Requests
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
And I click on the Changes tab
- And I leave a comment like "Line is correct" on line 12 of the first file
- And I leave a comment like "Line is wrong" on line 39 of the second file
+ And I leave a comment like "Line is correct" on line 12 of the second file
+ And I leave a comment like "Line is wrong" on line 39 of the third file
And I click Side-by-side Diff tab
Then I should see comments on the side-by-side diff page
diff --git a/features/project/project.feature b/features/project/project.feature
index b3fb079454..1a53945eb0 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -31,6 +31,12 @@ Feature: Project
And I visit project "Shop" page
Then I should see project "Shop" README
+ Scenario: I should see last commit with CI
+ Given project "Shop" has CI enabled
+ Given project "Shop" has CI build
+ And I visit project "Shop" page
+ And I should see last commit with CI status
+
@javascript
Scenario: I should see project activity
When I visit project "Shop" activity page
diff --git a/features/project/service.feature b/features/project/service.feature
index fdff640ec8..5014b52b9f 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -72,6 +72,7 @@ Feature: Project Services
And I click Atlassian Bamboo CI service link
And I fill Atlassian Bamboo CI settings
Then I should see Atlassian Bamboo CI service settings saved
+ And I should see empty field Change Password
Scenario: Activate jetBrains TeamCity CI service
When I visit project "Shop" services page
diff --git a/features/project/snippets.feature b/features/project/snippets.feature
index 77e42a1a38..270557cbde 100644
--- a/features/project/snippets.feature
+++ b/features/project/snippets.feature
@@ -30,5 +30,5 @@ Feature: Project Snippets
Scenario: I destroy "Snippet one"
Given I visit snippet page "Snippet one"
- And I click link "Remove Snippet"
+ And I click link "Delete"
Then I should not see "Snippet one" in snippets
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 58574166ef..6b0484b6a3 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
@@ -188,3 +205,9 @@ Feature: Project Source Browse Files
And I see the ref 'test' has been selected
And I visit the 'test' tree
Then I see the commit data
+
+ @javascript
+ Scenario: I browse code with a leading dot in the directory
+ Given I switch ref to fix
+ And I visit the fix tree
+ Then I see the commit data for a directory with a leading dot
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
index 4f617b6bed..e15d7c7934 100644
--- a/features/snippets/snippets.feature
+++ b/features/snippets/snippets.feature
@@ -24,7 +24,7 @@ Feature: Snippets
Scenario: I destroy "Personal snippet one"
Given I visit snippet page "Personal snippet one"
- And I click link "Destroy"
+ And I click link "Delete"
Then I should not see "Personal snippet one" in snippets
Scenario: I create new internal snippet
diff --git a/features/steps/abuse_reports.rb b/features/steps/abuse_reports.rb
index 8f9ddb2899..499accb0b0 100644
--- a/features/steps/abuse_reports.rb
+++ b/features/steps/abuse_reports.rb
@@ -22,6 +22,10 @@ class Spinach::Features::AbuseReports < Spinach::FeatureSteps
user_mike
end
+ step 'I should see a red "Report abuse" button' do
+ expect(page).to have_button("Already reported for abuse")
+ end
+
def user_mike
@user_mike ||= create(:user, name: 'Mike')
end
diff --git a/features/steps/admin/labels.rb b/features/steps/admin/labels.rb
index d64380abf7..b45d98658b 100644
--- a/features/steps/admin/labels.rb
+++ b/features/steps/admin/labels.rb
@@ -38,7 +38,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
step 'I should see labels help message' do
page.within '.labels' do
- expect(page).to have_content 'There are no any labels yet'
+ expect(page).to have_content 'There are no labels yet'
end
end
diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb
index 17233f89f3..5a1cc9aa15 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 1e09162a5b..44a4aa9844 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/groups.rb b/features/steps/groups.rb
index 95bc9baf8d..69ddfa42c0 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -282,9 +282,9 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
milestone1_project2 = create :milestone,
title: "Version 7.2",
project: project2
- milestone1_project3 = create :milestone,
- title: "Version 7.2",
- project: @project3
+ create :milestone,
+ title: "Version 7.2",
+ project: @project3
milestone2_project1 = create :milestone,
title: "GL-113",
project: @project1
@@ -301,28 +301,28 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
assignee: current_user,
author: current_user,
milestone: milestone2_project1
- issue2 = create :issue,
- project: project2,
- assignee: current_user,
- author: current_user,
- milestone: milestone1_project2
- issue3 = create :issue,
- project: @project3,
- assignee: current_user,
- author: current_user,
- milestone: milestone1_project1
- mr1 = create :merge_request,
- source_project: @project1,
- target_project: @project1,
- assignee: current_user,
- author: current_user,
- milestone: milestone2_project1
- mr2 = create :merge_request,
- source_project: project2,
- target_project: project2,
- assignee: current_user,
- author: current_user,
- milestone: milestone2_project2
+ create :issue,
+ project: project2,
+ assignee: current_user,
+ author: current_user,
+ milestone: milestone1_project2
+ create :issue,
+ project: @project3,
+ assignee: current_user,
+ author: current_user,
+ milestone: milestone1_project1
+ create :merge_request,
+ source_project: @project1,
+ target_project: @project1,
+ assignee: current_user,
+ author: current_user,
+ milestone: milestone2_project1
+ create :merge_request,
+ source_project: project2,
+ target_project: project2,
+ assignee: current_user,
+ author: current_user,
+ milestone: milestone2_project2
@mr3 = create :merge_request,
source_project: @project3,
target_project: @project3,
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index 56f1f06fb0..e5b3f27135 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -103,11 +103,21 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'commit has ci status' do
- @project.enable_ci(@user)
- create :ci_commit, project: @project.gitlab_ci_project, sha: sample_commit.id
+ @project.enable_ci
+ ci_commit = create :ci_commit, gl_project: @project, sha: sample_commit.id
+ create :ci_build, commit: ci_commit
end
step 'I see commit ci info' do
- expect(page).to have_content "build: skipped"
+ expect(page).to have_content "build: pending"
+ end
+
+ step 'I click status link' do
+ find('.commit-ci-menu').click_link "Builds"
+ end
+
+ step 'I see builds list' do
+ expect(page).to have_content "build: pending"
+ expect(page).to have_content "Latest builds"
end
end
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 370960845c..b0230add34 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -5,8 +5,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I click link "Fork"' do
expect(page).to have_content "Shop"
- expect(page).to have_content "Fork"
- click_link "Fork"
+ click_link "Fork project"
end
step 'I am a member of project "Shop"' do
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index 9453d63644..4abd5288d5 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -32,6 +32,6 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
end
def project
- project ||= Project.find_by(name: "Shop")
+ @project ||= Project.find_by(name: "Shop")
end
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 239392eab9..af2da41bad 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -223,11 +223,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do
- issue = create(:issue, title: 'Bugfix1', description: 'Description for issue1', project: project)
+ create(:issue, title: 'Bugfix1', description: 'Description for issue1', project: project)
end
step 'project \'Shop\' has issue \'Feature1\' with description: \'Feature submitted for issue1\'' do
- issue = create(:issue, title: 'Feature1', description: 'Feature submitted for issue1', project: project)
+ create(:issue, title: 'Feature1', description: 'Feature submitted for issue1', project: project)
end
step 'I fill in issue search with \'Description for issue1\'' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index c92998631f..875bf6c467 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -224,43 +224,43 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
end
- step 'I click link "Hide inline discussion" of the second file' do
- page.within '.files [id^=diff]:nth-child(2)' do
+ step 'I click link "Hide inline discussion" of the third file' do
+ page.within '.files [id^=diff]:nth-child(3)' do
find('.js-toggle-diff-comments').trigger('click')
end
end
- step 'I click link "Show inline discussion" of the second file' do
- page.within '.files [id^=diff]:nth-child(2)' do
+ step 'I click link "Show inline discussion" of the third file' do
+ page.within '.files [id^=diff]:nth-child(3)' do
find('.js-toggle-diff-comments').trigger('click')
end
end
- step 'I should not see a comment like "Line is wrong" in the second file' do
- page.within '.files [id^=diff]:nth-child(2)' do
+ step 'I should not see a comment like "Line is wrong" in the third file' do
+ page.within '.files [id^=diff]:nth-child(3)' do
expect(page).not_to have_visible_content "Line is wrong"
end
end
- step 'I should see a comment like "Line is wrong" in the second file' do
- page.within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
+ step 'I should see a comment like "Line is wrong" in the third file' do
+ page.within '.files [id^=diff]:nth-child(3) .note-body > .note-text' do
expect(page).to have_visible_content "Line is wrong"
end
end
- step 'I should not see a comment like "Line is wrong here" in the second file' do
- page.within '.files [id^=diff]:nth-child(2)' do
+ step 'I should not see a comment like "Line is wrong here" in the third file' do
+ page.within '.files [id^=diff]:nth-child(3)' do
expect(page).not_to have_visible_content "Line is wrong here"
end
end
- step 'I should see a comment like "Line is wrong here" in the second file' do
- page.within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
+ step 'I should see a comment like "Line is wrong here" in the third file' do
+ page.within '.files [id^=diff]:nth-child(3) .note-body > .note-text' do
expect(page).to have_visible_content "Line is wrong here"
end
end
- step 'I leave a comment like "Line is correct" on line 12 of the first file' do
+ step 'I leave a comment like "Line is correct" on line 12 of the second file' do
init_diff_note_first_file
page.within(".js-discussion-note-form") do
@@ -268,12 +268,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_button "Add Comment"
end
- page.within ".files [id^=diff]:nth-child(1) .note-body > .note-text" do
+ page.within ".files [id^=diff]:nth-child(2) .note-body > .note-text" do
expect(page).to have_content "Line is correct"
end
end
- step 'I leave a comment like "Line is wrong" on line 39 of the second file' do
+ step 'I leave a comment like "Line is wrong" on line 39 of the third file' do
init_diff_note_second_file
page.within(".js-discussion-note-form") do
@@ -282,8 +282,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
end
- step 'I should still see a comment like "Line is correct" in the first file' do
- page.within '.files [id^=diff]:nth-child(1) .note-body > .note-text' do
+ step 'I should still see a comment like "Line is correct" in the second file' do
+ page.within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
expect(page).to have_visible_content "Line is correct"
end
end
@@ -303,7 +303,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see comments on the side-by-side diff page' do
- page.within '.files [id^=diff]:nth-child(1) .parallel .note-body > .note-text' do
+ page.within '.files [id^=diff]:nth-child(2) .parallel .note-body > .note-text' do
expect(page).to have_visible_content "Line is correct"
end
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 15f77734cb..d76891d5bd 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -86,13 +86,13 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project "Forum" README' do
- page.within('#README') do
+ page.within('.readme-holder') do
expect(page).to have_content 'Sample repo for testing gitlab features'
end
end
step 'I should see project "Shop" README' do
- page.within('#README') do
+ page.within('.readme-holder') do
expect(page).to have_content 'testme'
end
end
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
index 0e724138a8..1ffd5cb9de 100644
--- a/features/steps/project/redirects.rb
+++ b/features/steps/project/redirects.rb
@@ -39,7 +39,6 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'Authenticate' do
admin = create(:admin)
- project = Project.find_by(name: 'Community')
fill_in "user_login", with: admin.email
fill_in "user_password", with: admin.password
click_button "Sign in"
@@ -54,7 +53,6 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I get redirected to signin page where I sign in' do
admin = create(:admin)
- project = Project.find_by(name: 'Enterprise')
fill_in "user_login", with: admin.email
fill_in "user_password", with: admin.password
click_button "Sign in"
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index d3b462bfd3..1c700df0c6 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -202,6 +202,10 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
expect(find_field('Username').value).to eq 'user'
end
+ step 'I should see empty field Change Password' do
+ expect(find_field('Change Password').value).to be_nil
+ end
+
step 'I click JetBrains TeamCity CI service link' do
click_link 'JetBrains TeamCity CI'
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index db8ad08bb9..a3aef9bf8c 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -22,7 +22,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I click link "New Snippet"' do
- click_link "Add new snippet"
+ click_link "New Snippet"
end
step 'I click link "Snippet one"' do
@@ -42,13 +42,13 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I click link "Edit"' do
- page.within ".file-title" do
+ page.within ".page-title" do
click_link "Edit"
end
end
- step 'I click link "Remove Snippet"' do
- click_link "remove"
+ step 'I click link "Delete"' do
+ click_link "Delete"
end
step 'I submit new snippet "Snippet three"' do
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index a1a49dd58a..1b27500497 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
@@ -251,6 +286,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
select "'test'", from: 'ref'
end
+ step "I switch ref to fix" do
+ select "fix", from: 'ref'
+ end
+
step "I see the ref 'test' has been selected" do
expect(page).to have_selector '.select2-chosen', text: "'test'"
end
@@ -259,11 +298,20 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
visit namespace_project_tree_path(@project.namespace, @project, "'test'")
end
+ step "I visit the fix tree" do
+ visit namespace_project_tree_path(@project.namespace, @project, "fix/.testdir")
+ end
+
step 'I see the commit data' do
expect(page).to have_css('.tree-commit-link', visible: true)
expect(page).not_to have_content('Loading commit data...')
end
+ step 'I see the commit data for a directory with a leading dot' do
+ expect(page).to have_css('.tree-commit-link', visible: true)
+ expect(page).not_to have_content('Loading commit data...')
+ end
+
private
def set_new_content
@@ -287,6 +335,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/features/steps/shared/group.rb b/features/steps/shared/group.rb
index 2d17fb34cc..83a0457697 100644
--- a/features/steps/shared/group.rb
+++ b/features/steps/shared/group.rb
@@ -37,7 +37,7 @@ module SharedGroup
group = Group.find_by(name: groupname) || create(:group, name: groupname)
group.add_user(user, role)
project ||= create(:project, namespace: group, path: "project#{@project_count}")
- event ||= create(:closed_issue_event, project: project)
+ create(:closed_issue_event, project: project)
project.team << [user, :master]
@project_count += 1
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index fa841f6751..7021fac5fe 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -199,11 +199,18 @@ module SharedProject
step 'project "Shop" has CI enabled' do
project = Project.find_by(name: "Shop")
- project.enable_ci(@user)
+ project.enable_ci
end
step 'project "Shop" has CI build' do
project = Project.find_by(name: "Shop")
- create :ci_commit, project: project.gitlab_ci_project, sha: project.commit.sha
+ create :ci_commit, gl_project: project, sha: project.commit.sha
+ end
+
+ step 'I should see last commit with CI status' do
+ page.within ".project-last-commit" do
+ expect(page).to have_content(project.commit.sha[0..6])
+ expect(page).to have_content("skipped")
+ end
end
end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index 6ff48e0c6b..80d1ddeef0 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -13,13 +13,13 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
end
step 'I click link "Edit"' do
- page.within ".file-title" do
+ page.within ".page-title" do
click_link "Edit"
end
end
- step 'I click link "Destroy"' do
- click_link "remove"
+ step 'I click link "Delete"' do
+ click_link "Delete"
end
step 'I submit new snippet "Personal snippet three"' do
diff --git a/features/steps/snippets/user.rb b/features/steps/snippets/user.rb
index dea3256229..997c605bce 100644
--- a/features/steps/snippets/user.rb
+++ b/features/steps/snippets/user.rb
@@ -32,19 +32,19 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps
end
step 'I click "Internal" filter' do
- page.within('.nav-tabs') do
+ page.within('.snippet-scope-menu') do
click_link "Internal"
end
end
step 'I click "Private" filter' do
- page.within('.nav-tabs') do
+ page.within('.snippet-scope-menu') do
click_link "Private"
end
end
step 'I click "Public" filter' do
- page.within('.nav-tabs') do
+ page.within('.snippet-scope-menu') do
click_link "Public"
end
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index c09488d354..afc0402f9e 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 0000000000..2c0596c9df
--- /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 9620d36ac4..883a5e14b1 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/helpers.rb b/lib/api/helpers.rb
index 7fada98fcd..549b1f9e9a 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -63,11 +63,11 @@ module API
user_project.build_missing_services
service_method = "#{underscored_service}_service"
-
+
send_service(service_method)
end
end
-
+
@project_service || not_found!("Service")
end
@@ -149,7 +149,6 @@ module API
end
def attributes_for_keys(keys, custom_params = nil)
- params_hash = custom_params || params
attrs = {}
keys.each do |key|
if params[key].present? or (params.has_key?(key) and params[key] == false)
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 63ea2f0543..6eb84baf9c 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
@@ -249,8 +249,16 @@ module API
required_attributes! [:note]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
- note = merge_request.notes.new(note: params[:note], project_id: user_project.id)
- note.author = current_user
+
+ authorize! :create_note, merge_request
+
+ opts = {
+ note: params[:note],
+ noteable_type: 'MergeRequest',
+ noteable_id: merge_request.id
+ }
+
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
if note.save
present note, with: Entities::MRNote
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index c2fb36b414..67ee66a205 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -246,8 +246,8 @@ module API
# Example Request:
# DELETE /projects/:id/fork
delete ":id/fork" do
- authenticated_as_admin!
- unless user_project.forked_project_link.nil?
+ authorize! :remove_fork_project, user_project
+ if user_project.forked?
user_project.forked_project_link.destroy
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 2d96c9666d..20d568cf46 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -133,7 +133,7 @@ module API
authorize! :download_code, user_project
begin
- file_path = ArchiveRepositoryService.new(
+ ArchiveRepositoryService.new(
user_project,
params[:sha],
params[:format]
@@ -141,17 +141,6 @@ module API
rescue
not_found!('File')
end
-
- if file_path && File.exists?(file_path)
- data = File.open(file_path, 'rb').read
- basename = File.basename(file_path)
- header['Content-Disposition'] = "attachment; filename=\"#{basename}\""
- content_type MIME::Types.type_for(file_path).first.content_type
- env['api.format'] = :binary
- present data
- else
- redirect request.fullpath
- end
end
# Compare two branches, tags or commits
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 6727e80ac1..203f04a625 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/backup/manager.rb b/lib/backup/manager.rb
index ac63f89c6e..5c42f25f4a 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -45,7 +45,8 @@ module Backup
directory = connection.directories.get(remote_directory)
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
- multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size)
+ multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
+ encryption: Gitlab.config.backup.upload.encryption)
$progress.puts "done".green
else
puts "uploading backup to #{remote_directory} failed".red
@@ -55,7 +56,7 @@ module Backup
def cleanup
$progress.print "Deleting tmp directories ... "
-
+
backup_contents.each do |dir|
next unless File.exist?(File.join(Gitlab.config.backup.path, dir))
@@ -75,7 +76,7 @@ module Backup
if keep_time > 0
removed = 0
-
+
Dir.chdir(Gitlab.config.backup.path) do
file_list = Dir.glob('*_gitlab_backup.tar')
file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ }
diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
index 5109c84e0e..218d8c3adc 100644
--- a/lib/ci/api/api.rb
+++ b/lib/ci/api/api.rb
@@ -23,10 +23,6 @@ module Ci
rack_response({ 'message' => '500 Internal Server Error' }, 500)
end
- before do
- check_enable_flag!
- end
-
format :json
helpers Helpers
diff --git a/lib/ci/api/commits.rb b/lib/ci/api/commits.rb
index bac463a590..a60769d830 100644
--- a/lib/ci/api/commits.rb
+++ b/lib/ci/api/commits.rb
@@ -51,7 +51,7 @@ module Ci
required_attributes! [:project_id, :data, :project_token]
project = Ci::Project.find(params[:project_id])
authenticate_project_token!(project)
- commit = Ci::CreateCommitService.new.execute(project, params[:data])
+ commit = Ci::CreateCommitService.new.execute(project, current_user, params[:data])
if commit.persisted?
present commit, with: Entities::CommitWithBuilds
diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb
index f47bc1236b..b80c0b8b27 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/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 8e893aa5cc..e602cda81d 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -3,12 +3,6 @@ module Ci
module Helpers
UPDATE_RUNNER_EVERY = 60
- def check_enable_flag!
- unless current_application_settings.ci_enabled
- render_api_error!('400 (Bad request) CI is disabled', 400)
- end
- end
-
def authenticate_runners!
forbidden! unless params[:token] == GitlabCi::REGISTRATION_TOKEN
end
diff --git a/lib/ci/api/projects.rb b/lib/ci/api/projects.rb
index 66bcf65e8c..d719ad9e8d 100644
--- a/lib/ci/api/projects.rb
+++ b/lib/ci/api/projects.rb
@@ -75,23 +75,17 @@ module Ci
# Create Gitlab CI project using Gitlab project info
#
# Parameters:
- # name (required) - The name of the project
# gitlab_id (required) - The gitlab id of the project
- # path (required) - The gitlab project path, ex. randx/six
- # ssh_url_to_repo (required) - The gitlab ssh url to the repo
# default_ref - The branch to run against (defaults to `master`)
# Example Request:
# POST /projects
post do
- required_attributes! [:name, :gitlab_id, :ssh_url_to_repo]
+ required_attributes! [:gitlab_id]
filtered_params = {
- name: params[:name],
gitlab_id: params[:gitlab_id],
# we accept gitlab_url for backward compatibility for a while (added to 7.11)
- path: params[:path] || params[:gitlab_url].sub(/.*\/(.*\/.*)$/, '\1'),
- default_ref: params[:default_ref] || 'master',
- ssh_url_to_repo: params[:ssh_url_to_repo]
+ default_ref: params[:default_ref] || 'master'
}
project = Ci::Project.new(filtered_params)
@@ -109,11 +103,7 @@ module Ci
#
# Parameters:
# id (required) - The ID of a project
- # name - The name of the project
- # gitlab_id - The gitlab id of the project
- # path - The gitlab project path, ex. randx/six
- # ssh_url_to_repo - The gitlab ssh url to the repo
- # default_ref - The branch to run against (defaults to `master`)
+ # default_ref - The branch to run against (defaults to `master`)
# Example Request:
# PUT /projects/:id
put ":id" do
@@ -121,12 +111,7 @@ module Ci
unauthorized! unless can?(current_user, :admin_project, project.gl_project)
- attrs = attributes_for_keys [:name, :gitlab_id, :path, :gitlab_url, :default_ref, :ssh_url_to_repo]
-
- # we accept gitlab_url for backward compatibility for a while (added to 7.11)
- if attrs[:gitlab_url] && !attrs[:path]
- attrs[:path] = attrs[:gitlab_url].sub(/.*\/(.*\/.*)$/, '\1')
- end
+ attrs = attributes_for_keys [:default_ref]
if project.update_attributes(attrs)
present project, with: Entities::Project
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index e625e790df..efcd2faffc 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -5,7 +5,7 @@ module Ci
DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test'
ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables]
- ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage]
+ ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when]
attr_reader :before_script, :image, :services, :variables
@@ -85,13 +85,15 @@ module Ci
def build_job(name, job)
{
+ stage_idx: stages.index(job[:stage]),
stage: job[:stage],
- script: "#{@before_script.join("\n")}\n#{normalize_script(job[:script])}",
- tags: job[:tags] || [],
+ commands: "#{@before_script.join("\n")}\n#{normalize_script(job[:script])}",
+ tag_list: job[:tags] || [],
name: name,
only: job[:only],
except: job[:except],
allow_failure: job[:allow_failure] || false,
+ when: job[:when] || 'on_success',
options: {
image: job[:image] || @image,
services: job[:services] || @services
@@ -137,62 +139,74 @@ module Ci
end
@jobs.each do |name, job|
- validate_job!("#{name} job", job)
+ validate_job!(name, job)
end
true
end
def validate_job!(name, job)
+ if name.blank? || !validate_string(name)
+ raise ValidationError, "job name should be non-empty string"
+ end
+
job.keys.each do |key|
unless ALLOWED_JOB_KEYS.include? key
- raise ValidationError, "#{name}: unknown parameter #{key}"
+ raise ValidationError, "#{name} job: unknown parameter #{key}"
end
end
- if !job[:script].is_a?(String) && !validate_array_of_strings(job[:script])
- raise ValidationError, "#{name}: script should be a string or an array of a strings"
+ if !validate_string(job[:script]) && !validate_array_of_strings(job[:script])
+ raise ValidationError, "#{name} job: script should be a string or an array of a strings"
end
if job[:stage]
unless job[:stage].is_a?(String) && job[:stage].in?(stages)
- raise ValidationError, "#{name}: stage parameter should be #{stages.join(", ")}"
+ raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
end
end
- if job[:image] && !job[:image].is_a?(String)
- raise ValidationError, "#{name}: image should be a string"
+ if job[:image] && !validate_string(job[:image])
+ raise ValidationError, "#{name} job: image should be a string"
end
if job[:services] && !validate_array_of_strings(job[:services])
- raise ValidationError, "#{name}: services should be an array of strings"
+ raise ValidationError, "#{name} job: services should be an array of strings"
end
if job[:tags] && !validate_array_of_strings(job[:tags])
- raise ValidationError, "#{name}: tags parameter should be an array of strings"
+ raise ValidationError, "#{name} job: tags parameter should be an array of strings"
end
if job[:only] && !validate_array_of_strings(job[:only])
- raise ValidationError, "#{name}: only parameter should be an array of strings"
+ raise ValidationError, "#{name} job: only parameter should be an array of strings"
end
if job[:except] && !validate_array_of_strings(job[:except])
- raise ValidationError, "#{name}: except parameter should be an array of strings"
+ raise ValidationError, "#{name} job: except parameter should be an array of strings"
end
if job[:allow_failure] && !job[:allow_failure].in?([true, false])
- raise ValidationError, "#{name}: allow_failure parameter should be an boolean"
+ raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
+ end
+
+ if job[:when] && !job[:when].in?(%w(on_success on_failure always))
+ raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
end
end
private
def validate_array_of_strings(values)
- values.is_a?(Array) && values.all? {|tag| tag.is_a?(String)}
+ values.is_a?(Array) && values.all? { |value| validate_string(value) }
end
def validate_variables(variables)
- variables.is_a?(Hash) && variables.all? {|key, value| key.is_a?(Symbol) && value.is_a?(String)}
+ variables.is_a?(Hash) && variables.all? { |key, value| validate_string(key) && validate_string(value) }
+ end
+
+ def validate_string(value)
+ value.is_a?(String) || value.is_a?(Symbol)
end
end
end
diff --git a/lib/ci/migrate/builds.rb b/lib/ci/migrate/builds.rb
deleted file mode 100644
index c4f62e5529..0000000000
--- a/lib/ci/migrate/builds.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module Ci
- module Migrate
- class Builds
- attr_reader :app_builds_dir, :backup_builds_tarball, :backup_dir
-
- def initialize
- @app_builds_dir = Settings.gitlab_ci.builds_path
- @backup_dir = Gitlab.config.backup.path
- @backup_builds_tarball = File.join(backup_dir, 'builds/builds.tar.gz')
- end
-
- def restore
- backup_existing_builds_dir
-
- FileUtils.mkdir_p(app_builds_dir, mode: 0700)
- unless system('tar', '-C', app_builds_dir, '-zxf', backup_builds_tarball)
- abort 'Restore failed'.red
- end
- end
-
- def backup_existing_builds_dir
- timestamped_builds_path = File.join(app_builds_dir, '..', "builds.#{Time.now.to_i}")
- if File.exists?(app_builds_dir)
- FileUtils.mv(app_builds_dir, File.expand_path(timestamped_builds_path))
- end
- end
- end
- end
-end
diff --git a/lib/ci/migrate/database.rb b/lib/ci/migrate/database.rb
deleted file mode 100644
index bf9b80f1f6..0000000000
--- a/lib/ci/migrate/database.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'yaml'
-
-module Ci
- module Migrate
- class Database
- attr_reader :config
-
- def initialize
- @config = YAML.load_file(File.join(Rails.root, 'config', 'database.yml'))[Rails.env]
- end
-
- def restore
- decompress_rd, decompress_wr = IO.pipe
- decompress_pid = spawn(*%W(gzip -cd), out: decompress_wr, in: db_file_name)
- decompress_wr.close
-
- restore_pid = case config["adapter"]
- when /^mysql/ then
- $progress.print "Restoring MySQL database #{config['database']} ... "
- # Workaround warnings from MySQL 5.6 about passwords on cmd line
- ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
- spawn('mysql', *mysql_args, config['database'], in: decompress_rd)
- when "postgresql" then
- $progress.print "Restoring PostgreSQL database #{config['database']} ... "
- pg_env
- spawn('psql', config['database'], in: decompress_rd)
- end
- decompress_rd.close
-
- success = [decompress_pid, restore_pid].all? { |pid| Process.waitpid(pid); $?.success? }
- abort 'Restore failed' unless success
- end
-
- protected
-
- def db_file_name
- File.join(Gitlab.config.backup.path, 'db', 'database.sql.gz')
- end
-
- def mysql_args
- args = {
- 'host' => '--host',
- 'port' => '--port',
- 'socket' => '--socket',
- 'username' => '--user',
- 'encoding' => '--default-character-set'
- }
- args.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
- end
-
- def pg_env
- ENV['PGUSER'] = config["username"] if config["username"]
- ENV['PGHOST'] = config["host"] if config["host"]
- ENV['PGPORT'] = config["port"].to_s if config["port"]
- ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
- end
-
- def report_success(success)
- if success
- puts '[DONE]'.green
- else
- puts '[FAILED]'.red
- end
- end
- end
- end
-end
diff --git a/lib/ci/migrate/manager.rb b/lib/ci/migrate/manager.rb
deleted file mode 100644
index e5e4fb784e..0000000000
--- a/lib/ci/migrate/manager.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-module Ci
- module Migrate
- class Manager
- CI_IMPORT_PREFIX = '8.0' # Only allow imports from CI 8.0.x
-
- def cleanup
- $progress.print "Deleting tmp directories ... "
-
- backup_contents.each do |dir|
- next unless File.exist?(File.join(Gitlab.config.backup.path, dir))
-
- if FileUtils.rm_rf(File.join(Gitlab.config.backup.path, dir))
- $progress.puts "done".green
- else
- puts "deleting tmp directory '#{dir}' failed".red
- abort 'Backup failed'
- end
- end
- end
-
- def unpack
- Dir.chdir(Gitlab.config.backup.path)
-
- # check for existing backups in the backup dir
- file_list = Dir.glob("*_gitlab_ci_backup.tar").each.map { |f| f.split(/_/).first.to_i }
- puts "no backups found" if file_list.count == 0
-
- if file_list.count > 1 && ENV["BACKUP"].nil?
- puts "Found more than one backup, please specify which one you want to restore:"
- puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup"
- exit 1
- end
-
- tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_ci_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_ci_backup.tar")
-
- unless File.exists?(tar_file)
- puts "The specified CI backup doesn't exist!"
- exit 1
- end
-
- $progress.print "Unpacking backup ... "
-
- unless Kernel.system(*%W(tar -xf #{tar_file}))
- puts "unpacking backup failed".red
- exit 1
- else
- $progress.puts "done".green
- end
-
- ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
-
- # restoring mismatching backups can lead to unexpected problems
- if !settings[:gitlab_version].start_with?(CI_IMPORT_PREFIX)
- puts "GitLab CI version mismatch:".red
- puts " Your current GitLab CI version (#{GitlabCi::VERSION}) differs from the GitLab CI (#{settings[:gitlab_version]}) version in the backup!".red
- exit 1
- end
- end
-
- private
-
- def backup_contents
- ["db", "builds", "backup_information.yml"]
- end
-
- def settings
- @settings ||= YAML.load_file("backup_information.yml")
- end
- end
- end
-end
-
diff --git a/lib/ci/migrate/tags.rb b/lib/ci/migrate/tags.rb
deleted file mode 100644
index 97e043ece2..0000000000
--- a/lib/ci/migrate/tags.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require 'yaml'
-
-module Ci
- module Migrate
- class Tags
- def restore
- puts 'Inserting tags...'
- connection.select_all('SELECT ci_tags.name FROM ci_tags').each do |tag|
- begin
- connection.execute("INSERT INTO tags (name) VALUES(#{ActiveRecord::Base::sanitize(tag['name'])})")
- rescue ActiveRecord::RecordNotUnique
- end
- end
-
- ActiveRecord::Base.transaction do
- puts 'Deleting old taggings...'
- connection.execute "DELETE FROM taggings WHERE context = 'tags' AND taggable_type LIKE 'Ci::%'"
-
- puts 'Inserting taggings...'
- connection.execute(
- 'INSERT INTO taggings (taggable_type, taggable_id, tag_id, context) ' +
- "SELECT CONCAT('Ci::', ci_taggings.taggable_type), ci_taggings.taggable_id, tags.id, 'tags' FROM ci_taggings " +
- 'JOIN ci_tags ON ci_tags.id = ci_taggings.tag_id ' +
- 'JOIN tags ON tags.name = ci_tags.name '
- )
-
- puts 'Resetting counters... '
- connection.execute(
- 'UPDATE tags SET ' +
- 'taggings_count = (SELECT COUNT(*) FROM taggings WHERE tags.id = taggings.tag_id)'
- )
- end
- end
-
- protected
-
- def connection
- ActiveRecord::Base.connection
- end
- end
- end
-end
diff --git a/lib/ci/status.rb b/lib/ci/status.rb
new file mode 100644
index 0000000000..c02b3b8f3e
--- /dev/null
+++ b/lib/ci/status.rb
@@ -0,0 +1,21 @@
+module Ci
+ class Status
+ def self.get_status(statuses)
+ statuses.reject! { |status| status.try(&:allow_failure?) }
+
+ if statuses.none?
+ 'skipped'
+ elsif statuses.all?(&:success?)
+ 'success'
+ elsif statuses.all?(&:pending?)
+ 'pending'
+ elsif statuses.any?(&:running?) || statuses.any?(&:pending?)
+ 'running'
+ elsif statuses.all?(&:canceled?)
+ 'canceled'
+ else
+ 'failed'
+ end
+ end
+ end
+end
diff --git a/lib/event_filter.rb b/lib/event_filter.rb
index 163937c02c..f15b2cfd23 100644
--- a/lib/event_filter.rb
+++ b/lib/event_filter.rb
@@ -47,7 +47,7 @@ class EventFilter
actions << Event::COMMENTED if filter.include? 'comments'
- events = events.where(action: actions)
+ events.where(action: actions)
end
def options(key)
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 322aed5e27..51e46da82c 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/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 0353b3b7ed..6830a916bc 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -193,7 +193,14 @@ module Grack
end
def render_grack_auth_ok
- [200, { "Content-Type" => "application/json" }, [JSON.dump({ 'GL_ID' => Gitlab::ShellEnv.gl_id(@user) })]]
+ [
+ 200,
+ { "Content-Type" => "application/json" },
+ [JSON.dump({
+ 'GL_ID' => Gitlab::ShellEnv.gl_id(@user),
+ 'RepoPath' => project.repository.path_to_repo,
+ })]
+ ]
end
def render_not_found
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 14ee4701e7..01b8bda05c 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/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 45bb904ed7..8a7f8dc500 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -12,7 +12,6 @@ module Gitlab
@timestamps = {}
date_from = 1.year.ago
- date_to = Date.today
events = Event.reorder(nil).contributions.where(author_id: user.id).
where("created_at > ?", date_from).where(project_id: projects).
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
new file mode 100644
index 0000000000..71f37f1fef
--- /dev/null
+++ b/lib/gitlab/database.rb
@@ -0,0 +1,11 @@
+module Gitlab
+ module Database
+ def self.mysql?
+ ActiveRecord::Base.connection.adapter_name.downcase == 'mysql2'
+ end
+
+ def self.postgresql?
+ ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql'
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 4daf65331e..142058aa69 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -44,6 +44,14 @@ module Gitlab
diff.old_path
end
end
+
+ def added_lines
+ diff_lines.select(&:added?).size
+ end
+
+ def removed_lines
+ diff_lines.select(&:removed?).size
+ end
end
end
end
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index 8ac1b15e88..0072194606 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -7,6 +7,14 @@ module Gitlab
@text, @type, @index = text, type, index
@old_pos, @new_pos = old_pos, new_pos
end
+
+ def added?
+ type == 'new'
+ end
+
+ def removed?
+ type == 'old'
+ end
end
end
end
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index c1d9520ddf..7015fe36c3 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -14,8 +14,6 @@ module Gitlab
lines_arr = ::Gitlab::InlineDiff.processing lines
lines_arr.each do |line|
- raw_line = line.dup
-
next if filename?(line)
full_line = html_escape(line.gsub(/\n/, ''))
diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb
index f02ea43910..8b1b6f48ed 100644
--- a/lib/gitlab/fogbugz_import/project_creator.rb
+++ b/lib/gitlab/fogbugz_import/project_creator.rb
@@ -23,7 +23,7 @@ module Gitlab
import_url: Project::UNKNOWN_IMPORT_URL
).execute
- import_data = project.create_import_data(
+ project.create_import_data(
data: {
'repo' => repo.raw_data,
'user_map' => user_map,
diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb
index 0cfeaf9d61..1cb7d16aeb 100644
--- a/lib/gitlab/google_code_import/project_creator.rb
+++ b/lib/gitlab/google_code_import/project_creator.rb
@@ -23,7 +23,7 @@ module Gitlab
import_url: repo.import_url
).execute
- import_data = project.create_import_data(
+ project.create_import_data(
data: {
"repo" => repo.raw_data,
"user_map" => user_map
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 856ccc7108..9068d79c95 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -24,12 +24,12 @@ module Gitlab
match[1]
end
- private
-
def config
Gitlab.config.incoming_email
end
+ private
+
def address_regex
wildcard_address = config.address
return nil unless wildcard_address
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index cb66fd500f..4be99dd88c 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -14,7 +14,7 @@ module Gitlab
# LDAP distinguished name is case-insensitive
identity = ::Identity.
where(provider: provider).
- where('lower(extern_uid) = ?', uid.downcase).last
+ where('lower(extern_uid) = ?', uid.mb_chars.downcase.to_s).last
identity && identity.user
end
end
@@ -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 ae5f254469..b082bfc434 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -7,6 +7,14 @@ module Gitlab
module Markdown
# Convert a Markdown String into an HTML-safe String of HTML
#
+ # Note that while the returned HTML will have been sanitized of dangerous
+ # HTML, it may post a risk of information leakage if it's not also passed
+ # through `post_process`.
+ #
+ # Also note that the returned String is always HTML, not XHTML. Views
+ # requiring XHTML, such as Atom feeds, need to call `post_process` on the
+ # result, providing the appropriate `pipeline` option.
+ #
# markdown - Markdown String
# context - Hash of context options passed to our HTML Pipeline
#
@@ -31,6 +39,33 @@ module Gitlab
renderer.render(markdown)
end
+ # Perform post-processing on an HTML String
+ #
+ # This method is used to perform state-dependent changes to a String of
+ # HTML, such as removing references that the current user doesn't have
+ # permission to make (`RedactorFilter`).
+ #
+ # html - String to process
+ # options - Hash of options to customize output
+ # :pipeline - Symbol pipeline type
+ # :project - Project
+ # :user - User object
+ #
+ # Returns an HTML-safe String
+ def self.post_process(html, options)
+ context = {
+ project: options[:project],
+ current_user: options[:user]
+ }
+ doc = post_processor.to_document(html, context)
+
+ if options[:pipeline] == :atom
+ doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML)
+ else
+ doc.to_html
+ end.html_safe
+ end
+
# Provide autoload paths for filters to prevent a circular dependency error
autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter'
autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter'
@@ -41,6 +76,7 @@ module Gitlab
autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
+ autoload :RedactorFilter, 'gitlab/markdown/redactor_filter'
autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
@@ -48,27 +84,22 @@ 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'
- # Public: Parse the provided text with GitLab-Flavored Markdown
+ # Public: Parse the provided HTML with GitLab-Flavored Markdown
#
- # text - the source text
- # options - A Hash of options used to customize output (default: {}):
- # :xhtml - output XHTML instead of HTML
- # :reference_only_path - Use relative path for reference links
- def self.gfm(text, options = {})
- return text if text.nil?
-
- # Duplicate the string so we don't alter the original, then call to_str
- # to cast it back to a String instead of a SafeBuffer. This is required
- # for gsub calls to work as we need them to.
- text = text.dup.to_str
-
- options.reverse_merge!(
- xhtml: false,
- reference_only_path: true,
- project: options[:project],
- current_user: options[:current_user]
- )
+ # html - HTML String
+ # options - A Hash of options used to customize output (default: {})
+ # :no_header_anchors - Disable header anchors in TableOfContentsFilter
+ # :path - Current path String
+ # :pipeline - Symbol pipeline type
+ # :project - Current Project object
+ # :project_wiki - Current ProjectWiki object
+ # :ref - Current ref String
+ #
+ # Returns an HTML-safe String
+ def self.gfm(html, options = {})
+ return '' unless html.present?
@pipeline ||= HTML::Pipeline.new(filters)
@@ -77,41 +108,36 @@ module Gitlab
pipeline: options[:pipeline],
# EmojiFilter
- asset_root: Gitlab.config.gitlab.base_url,
asset_host: Gitlab::Application.config.asset_host,
-
- # TableOfContentsFilter
- no_header_anchors: options[:no_header_anchors],
+ asset_root: Gitlab.config.gitlab.base_url,
# ReferenceFilter
- current_user: options[:current_user],
- only_path: options[:reference_only_path],
- project: options[:project],
+ only_path: only_path_pipeline?(options[:pipeline]),
+ project: options[:project],
# RelativeLinkFilter
+ project_wiki: options[:project_wiki],
ref: options[:ref],
requested_path: options[:path],
- project_wiki: options[:project_wiki]
+
+ # TableOfContentsFilter
+ no_header_anchors: options[:no_header_anchors]
}
- result = @pipeline.call(text, context)
-
- save_options = 0
- if options[:xhtml]
- save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML
- end
-
- text = result[:output].to_html(save_with: save_options)
-
- text.html_safe
+ @pipeline.to_html(html, context).html_safe
end
private
- def self.renderer
- @markdown ||= begin
- renderer = Redcarpet::Render::HTML.new
- Redcarpet::Markdown.new(renderer, redcarpet_options)
+ # Check if a pipeline enables the `only_path` context option
+ #
+ # Returns Boolean
+ def self.only_path_pipeline?(pipeline)
+ case pipeline
+ when :atom, :email
+ false
+ else
+ true
end
end
@@ -129,6 +155,17 @@ module Gitlab
}.freeze
end
+ def self.renderer
+ @markdown ||= begin
+ renderer = Redcarpet::Render::HTML.new
+ Redcarpet::Markdown.new(renderer, redcarpet_options)
+ end
+ end
+
+ def self.post_processor
+ @post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter])
+ end
+
# Filters used in our pipeline
#
# SanitizationFilter should come first so that all generated reference HTML
@@ -140,6 +177,7 @@ module Gitlab
Gitlab::Markdown::SyntaxHighlightFilter,
Gitlab::Markdown::SanitizationFilter,
+ Gitlab::Markdown::UploadLinkFilter,
Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb
index bb496135d9..e070edae0a 100644
--- a/lib/gitlab/markdown/commit_range_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_range_reference_filter.rb
@@ -26,6 +26,18 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ project = Project.find(node.attr("data-project")) rescue nil
+ return unless project
+
+ id = node.attr("data-commit-range")
+ range = CommitRange.new(id, project)
+
+ return unless range.valid_commits?
+
+ { commit_range: range }
+ end
+
def initialize(*args)
super
@@ -53,13 +65,11 @@ module Gitlab
range = CommitRange.new(id, project)
if range.valid_commits?
- push_result(:commit_range, range)
-
url = url_for_commit_range(project, range)
title = range.reference_title
klass = reference_class(:commit_range)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, commit_range: id)
project_ref += '@' if project_ref
diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb
index fcbb2e936a..8cdbeb1f9c 100644
--- a/lib/gitlab/markdown/commit_reference_filter.rb
+++ b/lib/gitlab/markdown/commit_reference_filter.rb
@@ -26,6 +26,18 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ project = Project.find(node.attr("data-project")) rescue nil
+ return unless project
+
+ id = node.attr("data-commit")
+ commit = commit_from_ref(project, id)
+
+ return unless commit
+
+ { commit: commit }
+ end
+
def call
replace_text_nodes_matching(Commit.reference_pattern) do |content|
commit_link_filter(content)
@@ -39,17 +51,15 @@ module Gitlab
# Returns a String with commit references replaced with links. All links
# have `gfm` and `gfm-commit` class names attached for styling.
def commit_link_filter(text)
- self.class.references_in(text) do |match, commit_ref, project_ref|
+ self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
- if commit = commit_from_ref(project, commit_ref)
- push_result(:commit, commit)
-
+ if commit = self.class.commit_from_ref(project, id)
url = url_for_commit(project, commit)
title = escape_once(commit.link_title)
klass = reference_class(:commit)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, commit: id)
project_ref += '@' if project_ref
@@ -62,9 +72,9 @@ module Gitlab
end
end
- def commit_from_ref(project, commit_ref)
+ def self.commit_from_ref(project, id)
if project && project.valid_repo?
- project.commit(commit_ref)
+ project.commit(id)
end
end
diff --git a/lib/gitlab/markdown/cross_project_reference.rb b/lib/gitlab/markdown/cross_project_reference.rb
index 855748fdcc..6ab04a584b 100644
--- a/lib/gitlab/markdown/cross_project_reference.rb
+++ b/lib/gitlab/markdown/cross_project_reference.rb
@@ -13,18 +13,11 @@ module Gitlab
#
# ref - String reference.
#
- # Returns a Project, or nil if the reference can't be accessed
+ # Returns a Project, or nil if the reference can't be found
def project_from_ref(ref)
return context[:project] unless ref
- other = Project.find_with_namespace(ref)
- return nil unless other && user_can_reference_project?(other)
-
- other
- end
-
- def user_can_reference_project?(project, user = context[:current_user])
- Ability.abilities.allowed?(user, :read_project, project)
+ Project.find_with_namespace(ref)
end
end
end
diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb
index f7c43e1ca8..8f86f13976 100644
--- a/lib/gitlab/markdown/external_issue_reference_filter.rb
+++ b/lib/gitlab/markdown/external_issue_reference_filter.rb
@@ -47,8 +47,9 @@ module Gitlab
title = escape_once("Issue in #{project.external_issue_tracker.title}")
klass = reference_class(:issue)
+ data = data_attribute(project: project.id)
- %(#{match})
end
diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb
index 01320f8079..481d282f7b 100644
--- a/lib/gitlab/markdown/issue_reference_filter.rb
+++ b/lib/gitlab/markdown/issue_reference_filter.rb
@@ -27,6 +27,10 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ { issue: LazyReference.new(Issue, node.attr("data-issue")) }
+ end
+
def call
replace_text_nodes_matching(Issue.reference_pattern) do |content|
issue_link_filter(content)
@@ -45,13 +49,11 @@ module Gitlab
project = self.project_from_ref(project_ref)
if project && issue = project.get_issue(id)
- push_result(:issue, issue)
-
url = url_for_issue(id, project, only_path: context[:only_path])
title = escape_once("Issue: #{issue.title}")
klass = reference_class(:issue)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, issue: issue.id)
%(#{render_colored_label(label)})
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb
index ecbd263d0e..5bc6326980 100644
--- a/lib/gitlab/markdown/merge_request_reference_filter.rb
+++ b/lib/gitlab/markdown/merge_request_reference_filter.rb
@@ -27,6 +27,10 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ { merge_request: LazyReference.new(MergeRequest, node.attr("data-merge-request")) }
+ end
+
def call
replace_text_nodes_matching(MergeRequest.reference_pattern) do |content|
merge_request_link_filter(content)
@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref)
if project && merge_request = project.merge_requests.find_by(iid: id)
- push_result(:merge_request, merge_request)
-
title = escape_once("Merge Request: #{merge_request.title}")
klass = reference_class(:merge_request)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, merge_request: merge_request.id)
url = url_for_merge_request(merge_request, project)
diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/gitlab/markdown/redactor_filter.rb
new file mode 100644
index 0000000000..a1f3a8a8eb
--- /dev/null
+++ b/lib/gitlab/markdown/redactor_filter.rb
@@ -0,0 +1,40 @@
+require 'gitlab/markdown'
+require 'html/pipeline/filter'
+
+module Gitlab
+ module Markdown
+ # HTML filter that removes references to records that the current user does
+ # not have permission to view.
+ #
+ # Expected to be run in its own post-processing pipeline.
+ #
+ class RedactorFilter < HTML::Pipeline::Filter
+ def call
+ doc.css('a.gfm').each do |node|
+ unless user_can_reference?(node)
+ node.replace(node.text)
+ end
+ end
+
+ doc
+ end
+
+ private
+
+ def user_can_reference?(node)
+ if node.has_attribute?('data-reference-filter')
+ reference_type = node.attr('data-reference-filter')
+ reference_filter = reference_type.constantize
+
+ reference_filter.user_can_reference?(current_user, node, context)
+ else
+ true
+ end
+ end
+
+ def current_user
+ context[:current_user]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb
index 9b293c957d..a4c560f578 100644
--- a/lib/gitlab/markdown/reference_filter.rb
+++ b/lib/gitlab/markdown/reference_filter.rb
@@ -11,30 +11,57 @@ module Gitlab
# Context options:
# :project (required) - Current project, ignored if reference is cross-project.
# :only_path - Generate path-only links.
- #
- # Results:
- # :references - A Hash of references that were found and replaced.
class ReferenceFilter < HTML::Pipeline::Filter
- def initialize(*args)
- super
+ LazyReference = Struct.new(:klass, :ids) do
+ def self.load(refs)
+ lazy_references, values = refs.partition { |ref| ref.is_a?(self) }
- result[:references] = Hash.new { |hash, type| hash[type] = [] }
+ lazy_values = lazy_references.group_by(&:klass).flat_map do |klass, refs|
+ ids = refs.flat_map(&:ids)
+ klass.where(id: ids)
+ end
+
+ values + lazy_values
+ end
+
+ def load
+ self.klass.where(id: self.ids)
+ end
+ end
+
+ def self.user_can_reference?(user, node, context)
+ if node.has_attribute?('data-project')
+ project_id = node.attr('data-project').to_i
+ return true if project_id == context[:project].try(:id)
+
+ project = Project.find(project_id) rescue nil
+ Ability.abilities.allowed?(user, :read_project, project)
+ else
+ true
+ end
+ end
+
+ def self.referenced_by(node)
+ raise NotImplementedError, "#{self} does not implement #{__method__}"
end
# Returns a data attribute String to attach to a reference link
#
- # id - Object ID
- # type - Object type (default: :project)
+ # attributes - Hash, where the key becomes the data attribute name and the
+ # value is the data attribute value
#
# Examples:
#
- # data_attribute(1) # => "data-project-id=\"1\""
- # data_attribute(2, :user) # => "data-user-id=\"2\""
- # data_attribute(3, :group) # => "data-group-id=\"3\""
+ # data_attribute(project: 1, issue: 2)
+ # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\""
+ #
+ # data_attribute(project: 3, merge_request: 4)
+ # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\""
#
# Returns a String
- def data_attribute(id, type = :project)
- %Q(data-#{type}-id="#{id}")
+ def data_attribute(attributes = {})
+ attributes[:reference_filter] = self.class.name
+ attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ")
end
def escape_once(html)
@@ -59,16 +86,6 @@ module Gitlab
context[:project]
end
- # Add a reference to the pipeline's result Hash
- #
- # type - Singular Symbol reference type (e.g., :issue, :user, etc.)
- # values - One or more Objects to add
- def push_result(type, *values)
- return if values.empty?
-
- result[:references][type].push(*values)
- end
-
def reference_class(type)
"gfm gfm-#{type}"
end
@@ -85,15 +102,15 @@ module Gitlab
# Yields the current node's String contents. The result of the block will
# replace the node's existing content and update the current document.
#
- # Returns the updated Nokogiri::XML::Document object.
+ # Returns the updated Nokogiri::HTML::DocumentFragment object.
def replace_text_nodes_matching(pattern)
return doc if project.nil?
search_text_nodes(doc).each do |node|
- content = node.to_html
-
- next unless content.match(pattern)
next if ignored_ancestry?(node)
+ next unless node.text =~ pattern
+
+ content = node.to_html
html = yield content
diff --git a/lib/gitlab/markdown/reference_gatherer_filter.rb b/lib/gitlab/markdown/reference_gatherer_filter.rb
new file mode 100644
index 0000000000..00f983675e
--- /dev/null
+++ b/lib/gitlab/markdown/reference_gatherer_filter.rb
@@ -0,0 +1,63 @@
+require 'gitlab/markdown'
+require 'html/pipeline/filter'
+
+module Gitlab
+ module Markdown
+ # HTML filter that gathers all referenced records that the current user has
+ # permission to view.
+ #
+ # Expected to be run in its own post-processing pipeline.
+ #
+ class ReferenceGathererFilter < HTML::Pipeline::Filter
+ def initialize(*)
+ super
+
+ result[:references] ||= Hash.new { |hash, type| hash[type] = [] }
+ end
+
+ def call
+ doc.css('a.gfm').each do |node|
+ gather_references(node)
+ end
+
+ load_lazy_references unless context[:load_lazy_references] == false
+
+ doc
+ end
+
+ private
+
+ def gather_references(node)
+ return unless node.has_attribute?('data-reference-filter')
+
+ reference_type = node.attr('data-reference-filter')
+ reference_filter = reference_type.constantize
+
+ return if context[:reference_filter] && reference_filter != context[:reference_filter]
+
+ return unless reference_filter.user_can_reference?(current_user, node, context)
+
+ references = reference_filter.referenced_by(node)
+ return unless references
+
+ references.each do |type, values|
+ Array.wrap(values).each do |value|
+ result[:references][type] << value
+ end
+ end
+ end
+
+ # Will load all references of one type using one query.
+ def load_lazy_references
+ refs = result[:references]
+ refs.each do |type, values|
+ refs[type] = ReferenceFilter::LazyReference.load(values)
+ end
+ end
+
+ def current_user
+ context[:current_user]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb
index e2cf89cb1d..f783f95171 100644
--- a/lib/gitlab/markdown/snippet_reference_filter.rb
+++ b/lib/gitlab/markdown/snippet_reference_filter.rb
@@ -27,6 +27,10 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ { snippet: LazyReference.new(Snippet, node.attr("data-snippet")) }
+ end
+
def call
replace_text_nodes_matching(Snippet.reference_pattern) do |content|
snippet_link_filter(content)
@@ -45,11 +49,9 @@ module Gitlab
project = self.project_from_ref(project_ref)
if project && snippet = project.snippets.find_by(id: id)
- push_result(:snippet, snippet)
-
title = escape_once("Snippet: #{snippet.title}")
klass = reference_class(:snippet)
- data = data_attribute(project.id)
+ data = data_attribute(project: project.id, snippet: snippet.id)
url = url_for_snippet(snippet, project)
diff --git a/lib/gitlab/markdown/upload_link_filter.rb b/lib/gitlab/markdown/upload_link_filter.rb
new file mode 100644
index 0000000000..fbada73ab8
--- /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/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb
index 6f436ea716..2a594e1662 100644
--- a/lib/gitlab/markdown/user_reference_filter.rb
+++ b/lib/gitlab/markdown/user_reference_filter.rb
@@ -23,6 +23,31 @@ module Gitlab
end
end
+ def self.referenced_by(node)
+ if node.has_attribute?('data-group')
+ group = Group.find(node.attr('data-group')) rescue nil
+ return unless group
+
+ { user: group.users }
+ elsif node.has_attribute?('data-user')
+ { user: LazyReference.new(User, node.attr('data-user')) }
+ elsif node.has_attribute?('data-project')
+ project = Project.find(node.attr('data-project')) rescue nil
+ return unless project
+
+ { user: project.team.members.flatten }
+ end
+ end
+
+ def self.user_can_reference?(user, node, context)
+ if node.has_attribute?('data-group')
+ group = Group.find(node.attr('data-group')) rescue nil
+ Ability.abilities.allowed?(user, :read_group, group)
+ else
+ super
+ end
+ end
+
def call
replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content)
@@ -61,14 +86,12 @@ module Gitlab
def link_to_all
project = context[:project]
- # FIXME (rspeicher): Law of Demeter
- push_result(:user, *project.team.members.flatten)
-
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
+ data = data_attribute(project: project.id)
text = User.reference_prefix + 'all'
- %(#{text})
+ %(#{text})
end
def link_to_namespace(namespace)
@@ -80,30 +103,20 @@ module Gitlab
end
def link_to_group(group, namespace)
- return unless user_can_reference_group?(namespace)
-
- push_result(:user, *namespace.users)
-
url = urls.group_url(group, only_path: context[:only_path])
- data = data_attribute(namespace.id, :group)
+ data = data_attribute(group: namespace.id)
text = Group.reference_prefix + group
%(#{text})
end
def link_to_user(user, namespace)
- push_result(:user, namespace.owner)
-
url = urls.user_url(user, only_path: context[:only_path])
- data = data_attribute(namespace.owner_id, :user)
+ data = data_attribute(user: namespace.owner_id)
text = User.reference_prefix + user
%(#{text})
end
-
- def user_can_reference_group?(group)
- Ability.abilities.allowed?(context[:current_user], :read_group, group)
- end
end
end
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 0961bd8042..da8df8a302 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -3,11 +3,12 @@ require 'gitlab/markdown'
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor
- attr_accessor :project, :current_user
+ attr_accessor :project, :current_user, :load_lazy_references
- def initialize(project, current_user = nil)
+ def initialize(project, current_user = nil, load_lazy_references: true)
@project = project
@current_user = current_user
+ @load_lazy_references = load_lazy_references
end
def analyze(text)
@@ -26,9 +27,9 @@ module Gitlab
def references
@references ||= Hash.new do |references, type|
type = type.to_sym
- return references[type] if references.has_key?(type)
+ next references[type] if references.has_key?(type)
- references[type] = pipeline_result(type).uniq
+ references[type] = pipeline_result(type)
end
end
@@ -39,21 +40,34 @@ module Gitlab
#
# Returns the results Array for the requested filter type
def pipeline_result(filter_type)
- klass = filter_type.to_s.camelize + 'ReferenceFilter'
+ return [] if @text.blank?
+
+ klass = "#{filter_type.to_s.camelize}ReferenceFilter"
filter = Gitlab::Markdown.const_get(klass)
context = {
project: project,
current_user: current_user,
+
# We don't actually care about the links generated
only_path: true,
- ignore_blockquotes: true
+ ignore_blockquotes: true,
+
+ # ReferenceGathererFilter
+ load_lazy_references: false,
+ reference_filter: filter
}
- pipeline = HTML::Pipeline.new([filter], context)
+ pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
result = pipeline.call(@text)
- result[:references][filter_type]
+ values = result[:references][filter_type].uniq
+
+ if @load_lazy_references
+ values = Gitlab::Markdown::ReferenceFilter::LazyReference.load(values).uniq
+ end
+
+ values
end
end
end
diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb
new file mode 100644
index 0000000000..be8fcc7b2d
--- /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/support/nginx/gitlab b/lib/support/nginx/gitlab
index 7218a4d2f2..1e55c5a048 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -113,7 +113,25 @@ server {
proxy_pass http://gitlab;
}
- location ~ [-\/\w\.]+\.git\/ {
+ location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location ~ ^/api/v3/projects/.*/repository/archive {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location @gitlab-git-http-server {
## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack.
# gzip off;
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 7dabfba87e..08641bbcc1 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -160,7 +160,25 @@ server {
proxy_pass http://gitlab;
}
- location ~ [-\/\w\.]+\.git\/ {
+ location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location ~ ^/api/v3/projects/.*/repository/archive {
+ # 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
+ error_page 418 = @gitlab-git-http-server;
+ return 418;
+ }
+
+ location @gitlab-git-http-server {
## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack.
gzip off;
diff --git a/lib/tasks/ci/migrate.rake b/lib/tasks/ci/migrate.rake
deleted file mode 100644
index 1de664c85e..0000000000
--- a/lib/tasks/ci/migrate.rake
+++ /dev/null
@@ -1,87 +0,0 @@
-namespace :ci do
- desc 'GitLab | Import and migrate CI database'
- task migrate: :environment do
- warn_user_is_not_gitlab
- configure_cron_mode
-
- unless ENV['force'] == 'yes'
- puts 'This will remove all CI related data and restore it from the provided backup.'
- ask_to_continue
- puts ''
- end
-
- # disable CI for time of migration
- enable_ci(false)
-
- # unpack archives
- migrate = Ci::Migrate::Manager.new
- migrate.unpack
-
- Rake::Task['ci:migrate:db'].invoke
- Rake::Task['ci:migrate:builds'].invoke
- Rake::Task['ci:migrate:tags'].invoke
- Rake::Task['ci:migrate:services'].invoke
-
- # enable CI for time of migration
- enable_ci(true)
-
- migrate.cleanup
- end
-
- namespace :migrate do
- desc 'GitLab | Import CI database'
- task db: :environment do
- configure_cron_mode
- $progress.puts 'Restoring database ... '.blue
- Ci::Migrate::Database.new.restore
- $progress.puts 'done'.green
- end
-
- desc 'GitLab | Import CI builds'
- task builds: :environment do
- configure_cron_mode
- $progress.puts 'Restoring builds ... '.blue
- Ci::Migrate::Builds.new.restore
- $progress.puts 'done'.green
- end
-
- desc 'GitLab | Migrate CI tags'
- task tags: :environment do
- configure_cron_mode
- $progress.puts 'Migrating tags ... '.blue
- ::Ci::Migrate::Tags.new.restore
- $progress.puts 'done'.green
- end
-
- desc 'GitLab | Migrate CI auto-increments'
- task autoincrements: :environment do
- c = ActiveRecord::Base.connection
- c.tables.select { |t| t.start_with?('ci_') }.each do |table|
- result = c.select_one("SELECT id FROM #{table} ORDER BY id DESC LIMIT 1")
- if result
- ai_val = result['id'].to_i + 1
- puts "Resetting auto increment ID for #{table} to #{ai_val}"
- if c.adapter_name == 'PostgreSQL'
- c.execute("ALTER SEQUENCE #{table}_id_seq RESTART WITH #{ai_val}")
- else
- c.execute("ALTER TABLE #{table} AUTO_INCREMENT = #{ai_val}")
- end
- end
- end
- end
-
- desc 'GitLab | Migrate CI services'
- task services: :environment do
- $progress.puts 'Migrating services ... '.blue
- c = ActiveRecord::Base.connection
- c.execute("UPDATE ci_services SET type=CONCAT('Ci::', type) WHERE type NOT LIKE 'Ci::%'")
- $progress.puts 'done'.green
- end
- end
-
- def enable_ci(enabled)
- settings = ApplicationSetting.current || ApplicationSetting.create_from_defaults
- settings.ci_enabled = enabled
- settings.save!
- end
-end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 66f1ecf385..2e73f792a9 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -335,7 +335,7 @@ namespace :gitlab do
print "Redis version >= #{min_redis_version}? ... "
redis_version = run(%W(redis-cli --version))
- redis_version = redis_version.try(:match, /redis-cli (.*)/)
+ redis_version = redis_version.try(:match, /redis-cli (\d+\.\d+\.\d+)/)
if redis_version &&
(Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version))
puts "yes".green
@@ -642,7 +642,6 @@ namespace :gitlab do
if Gitlab.config.incoming_email.enabled
check_address_formatted_correctly
- check_mail_room_config_exists
check_imap_authentication
if Rails.env.production?
@@ -744,42 +743,16 @@ namespace :gitlab do
end
end
- def check_mail_room_config_exists
- print "MailRoom config exists? ... "
-
- mail_room_config_file = Rails.root.join("config", "mail_room.yml")
-
- if File.exists?(mail_room_config_file)
- puts "yes".green
- else
- puts "no".red
- try_fixing_it(
- "Copy config/mail_room.yml.example to config/mail_room.yml",
- "Check that the information in config/mail_room.yml is correct"
- )
- for_more_information(
- "doc/incoming_email/README.md"
- )
- fix_and_rerun
- end
- end
-
def check_imap_authentication
print "IMAP server credentials are correct? ... "
- mail_room_config_file = Rails.root.join("config", "mail_room.yml")
-
- unless File.exists?(mail_room_config_file)
- puts "can't check because of previous errors".magenta
- return
- end
-
- config = YAML.load_file(mail_room_config_file)[:mailboxes].first rescue nil
+ config = Gitlab.config.incoming_email
if config
begin
- imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
- imap.login(config[:email], config[:password])
+ imap = Net::IMAP.new(config.host, port: config.port, ssl: config.ssl)
+ imap.starttls if config.start_tls
+ imap.login(config.user, config.password)
connected = true
rescue
connected = false
@@ -791,7 +764,7 @@ namespace :gitlab do
else
puts "no".red
try_fixing_it(
- "Check that the information in config/mail_room.yml is correct"
+ "Check that the information in config/gitlab.yml is correct"
)
for_more_information(
"doc/incoming_email/README.md"
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 6b1e371614..9f5852ac61 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -46,43 +46,24 @@ namespace :gitlab do
desc "GitLab | Cleanup | Clean repositories"
task repos: :environment do
warn_user_is_not_gitlab
- remove_flag = ENV['REMOVE']
- git_base_path = Gitlab.config.gitlab_shell.repos_path
- all_dirs = Dir.glob(git_base_path + '/*')
-
- global_projects = Project.in_namespace(nil).pluck(:path)
-
- puts git_base_path.yellow
- puts "Looking for global repos to remove... "
-
- # skip non git repo
- all_dirs.select! do |dir|
- dir =~ /.git$/
- end
-
- # skip existing repos
- all_dirs.reject! do |dir|
- repo_name = File.basename dir
- path = repo_name.gsub(/\.git$/, "")
- global_projects.include?(path)
- end
-
- all_dirs.each do |dir_path|
- if remove_flag
- if FileUtils.rm_rf dir_path
- puts "Removed...#{dir_path}".red
- else
- puts "Cannot remove #{dir_path}".red
- end
- else
- puts "Can be removed: #{dir_path}".red
+ move_suffix = "+orphaned+#{Time.now.to_i}"
+ repo_root = Gitlab.config.gitlab_shell.repos_path
+ # Look for global repos (legacy, depth 1) and normal repos (depth 2)
+ IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find|
+ find.each_line do |path|
+ path.chomp!
+ repo_with_namespace = path.
+ sub(repo_root, '').
+ sub(%r{^/*}, '').
+ chomp('.git').
+ chomp('.wiki')
+ next if Project.find_with_namespace(repo_with_namespace)
+ new_path = path + move_suffix
+ puts path.inspect + ' -> ' + new_path.inspect
+ File.rename(path, new_path)
end
end
-
- unless remove_flag
- puts "To cleanup this directories run this command with REMOVE=true".yellow
- end
end
desc "GitLab | Cleanup | Block users that have been removed in LDAP"
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index 0ac4b0fa8a..4cbccf2ca8 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 0000000000..9196677a01
--- /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 0000000000..141a0b74ec
--- /dev/null
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -0,0 +1,8 @@
+require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes')
+require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes')
+
+desc 'GitLab | Sets up PostgreSQL'
+task setup_postgresql: :environment do
+ NamespacesProjectsPathLowerIndexes.new.up
+ AddUsersLowerUsernameEmailIndexes.new.up
+end
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
index 831746815d..365ff2defd 100644
--- a/lib/tasks/spec.rake
+++ b/lib/tasks/spec.rake
@@ -19,11 +19,20 @@ namespace :spec do
run_commands(cmds)
end
+ desc 'GitLab | Rspec | Run benchmark specs'
+ task :benchmark do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(rspec spec --tag @benchmark)
+ ]
+ run_commands(cmds)
+ end
+
desc 'GitLab | Rspec | Run other specs'
task :other do
cmds = [
%W(rake gitlab:setup),
- %W(rspec spec --tag ~@api --tag ~@feature)
+ %W(rspec spec --tag ~@api --tag ~@feature --tag ~@benchmark)
]
run_commands(cmds)
end
@@ -33,7 +42,7 @@ desc "GitLab | Run specs"
task :spec do
cmds = [
%W(rake gitlab:setup),
- %W(rspec spec),
+ %W(rspec spec --tag ~@benchmark),
]
run_commands(cmds)
end
diff --git a/public/robots.txt b/public/robots.txt
index 528f421083..4f616c7f4c 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 e69de29bb2..0000000000
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 0000000000..551ce21840
--- /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/lib/gitlab/markdown/reference_filter_spec.rb b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
new file mode 100644
index 0000000000..34cd9f7e4e
--- /dev/null
+++ b/spec/benchmarks/lib/gitlab/markdown/reference_filter_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Gitlab::Markdown::ReferenceFilter, benchmark: true do
+ let(:input) do
+ html = <<-EOF
+Hello @alice and @bob, how are you doing today?
+This is simple @dummy text to see how the @ReferenceFilter class performs
+when @processing HTML.
+ EOF
+
+ Nokogiri::HTML.fragment(html)
+ end
+
+ let(:project) { create(:empty_project) }
+
+ let(:filter) { described_class.new(input, project: project) }
+
+ describe '#replace_text_nodes_matching' do
+ let(:iterations) { 6000 }
+
+ describe 'with identical input and output HTML' do
+ benchmark_subject do
+ filter.replace_text_nodes_matching(User.reference_pattern) do |content|
+ content
+ end
+ end
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+
+ describe 'with different input and output HTML' do
+ benchmark_subject do
+ filter.replace_text_nodes_matching(User.reference_pattern) do |content|
+ '@eve'
+ end
+ end
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+ end
+end
diff --git a/spec/benchmarks/models/milestone_spec.rb b/spec/benchmarks/models/milestone_spec.rb
new file mode 100644
index 0000000000..a94afc4c40
--- /dev/null
+++ b/spec/benchmarks/models/milestone_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Milestone, benchmark: true do
+ describe '#sort_issues' do
+ let(:milestone) { create(:milestone) }
+
+ let(:issue1) { create(:issue, milestone: milestone) }
+ let(:issue2) { create(:issue, milestone: milestone) }
+ let(:issue3) { create(:issue, milestone: milestone) }
+
+ let(:issue_ids) { [issue3.id, issue2.id, issue1.id] }
+
+ benchmark_subject { milestone.sort_issues(issue_ids) }
+
+ 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 0000000000..cee0949edc
--- /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/benchmarks/models/project_team_spec.rb b/spec/benchmarks/models/project_team_spec.rb
new file mode 100644
index 0000000000..8b039ef731
--- /dev/null
+++ b/spec/benchmarks/models/project_team_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe ProjectTeam, benchmark: true do
+ describe '#max_member_access' do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, group: group) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+
+ 5.times do
+ project.team << [create(:user), :reporter]
+
+ project.group.add_user(create(:user), :reporter)
+ end
+ end
+
+ benchmark_subject { project.team.max_member_access(user.id) }
+
+ it { is_expected.to iterate_per_second(35000) }
+ end
+end
diff --git a/spec/benchmarks/models/user_spec.rb b/spec/benchmarks/models/user_spec.rb
new file mode 100644
index 0000000000..cc5c390419
--- /dev/null
+++ b/spec/benchmarks/models/user_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe User, benchmark: true do
+ describe '.by_login' do
+ before do
+ %w{Alice Bob Eve}.each do |name|
+ create(:user,
+ email: "#{name}@gitlab.com",
+ username: name,
+ name: name)
+ end
+ end
+
+ # The iteration count is based on the query taking little over 1 ms when
+ # using PostgreSQL.
+ let(:iterations) { 900 }
+
+ describe 'using a capitalized username' do
+ benchmark_subject { User.by_login('Alice') }
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+
+ describe 'using a lowercase username' do
+ benchmark_subject { User.by_login('alice') }
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+
+ describe 'using a capitalized Email address' do
+ benchmark_subject { User.by_login('Alice@gitlab.com') }
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+
+ describe 'using a lowercase Email address' do
+ benchmark_subject { User.by_login('alice@gitlab.com') }
+
+ it { is_expected.to iterate_per_second(iterations) }
+ end
+ end
+end
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
new file mode 100644
index 0000000000..0faab8d7ff
--- /dev/null
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe AbuseReportsController do
+ let(:reporter) { create(:user) }
+ let(:user) { create(:user) }
+ let(:message) { "This user is a spammer" }
+
+ before do
+ sign_in(reporter)
+ end
+
+ describe "POST create" do
+ context "with admin notification email set" do
+ let(:admin_email) { "admin@example.com"}
+
+ before(:each) do
+ stub_application_setting(admin_notification_email: admin_email)
+ end
+
+ it "sends a notification email" do
+ post :create,
+ abuse_report: {
+ user_id: user.id,
+ message: message
+ }
+
+ email = ActionMailer::Base.deliveries.last
+
+ expect(email.to).to eq([admin_email])
+ expect(email.subject).to include(user.username)
+ expect(email.text_part.body).to include(message)
+ end
+
+ it "saves the abuse report" do
+ expect do
+ post :create,
+ abuse_report: {
+ user_id: user.id,
+ message: message
+ }
+ end.to change { AbuseReport.count }.by(1)
+ end
+ end
+
+ context "without admin notification email set" do
+ before(:each) do
+ stub_application_setting(admin_notification_email: nil)
+ end
+
+ it "does not send a notification email" do
+ expect do
+ post :create,
+ abuse_report: {
+ user_id: user.id,
+ message: message
+ }
+ end.not_to change { ActionMailer::Base.deliveries.count }
+ end
+
+ it "saves the abuse report" do
+ expect do
+ post :create,
+ abuse_report: {
+ user_id: user.id,
+ message: message
+ }
+ end.to change { AbuseReport.count }.by(1)
+ end
+ end
+ end
+
+end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index c40b2c2a58..fcbe62cace 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -7,6 +7,21 @@ describe Admin::UsersController do
sign_in(admin)
end
+ describe 'POST login_as' do
+ let(:user) { create(:user) }
+
+ it 'logs admin as another user' do
+ expect(warden.authenticate(scope: :user)).not_to eq(user)
+ post :login_as, id: user.username
+ expect(warden.authenticate(scope: :user)).to eq(user)
+ end
+
+ it 'redirects user to homepage' do
+ post :login_as, id: user.username
+ expect(response).to redirect_to(root_path)
+ end
+ end
+
describe 'DELETE #user with projects' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
@@ -22,6 +37,32 @@ describe Admin::UsersController do
end
end
+ describe 'PUT block/:id' do
+ let(:user) { create(:user) }
+
+ it 'blocks user' do
+ put :block, id: user.username
+ user.reload
+ expect(user.blocked?).to be_truthy
+ expect(flash[:notice]).to eq 'Successfully blocked'
+ end
+ end
+
+ describe 'PUT unblock/:id' do
+ let(:user) { create(:user) }
+
+ before do
+ user.block
+ end
+
+ it 'unblocks user' do
+ put :unblock, id: user.username
+ user.reload
+ expect(user.blocked?).to be_falsey
+ expect(flash[:notice]).to eq 'Successfully unblocked'
+ end
+ end
+
describe 'PUT unlock/:id' do
let(:user) { create(:user) }
diff --git a/spec/controllers/ci/commits_controller_spec.rb b/spec/controllers/ci/commits_controller_spec.rb
deleted file mode 100644
index b71e750573..0000000000
--- a/spec/controllers/ci/commits_controller_spec.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require "spec_helper"
-
-describe Ci::CommitsController do
- before do
- @project = FactoryGirl.create :ci_project
- end
-
- describe "GET /status" do
- it "returns status of commit" do
- commit = FactoryGirl.create :ci_commit, project: @project
- get :status, id: commit.sha, ref_id: commit.ref, project_id: @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, project: @project
- get :status, id: commit.sha, ref_id: "deploy", project_id: @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/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 766be578f7..bbf8adef53 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -41,7 +41,7 @@ describe Import::GithubController do
it "assigns variables" do
@project = create(:project, import_type: 'github', creator_id: user.id)
- stub_client(repos: [@repo], orgs: [@org], org_repos: [@org_repo])
+ stub_client(repos: [@repo, @org_repo], orgs: [@org], org_repos: [@org_repo])
get :status
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
new file mode 100644
index 0000000000..3c6e54839b
--- /dev/null
+++ b/spec/controllers/invites_controller_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe InvitesController do
+ let(:token) { '123456' }
+ let(:user) { create(:user) }
+ let(:member) { create(:project_member, invite_token: token, invite_email: 'test@abc.com', user: user) }
+
+ before do
+ controller.instance_variable_set(:@member, member)
+ sign_in(user)
+ end
+
+ describe 'GET #accept' do
+ it 'accepts user' do
+ get :accept, id: token
+ member.reload
+
+ expect(response.status).to eq(302)
+ expect(member.user).to eq(user)
+ expect(flash[:notice]).to include 'You have been granted'
+ end
+ end
+
+ describe 'GET #decline' do
+ it 'declines user' do
+ get :decline, id: token
+ expect{member.reload}.to raise_error ActiveRecord::RecordNotFound
+
+ expect(response.status).to eq(302)
+ expect(flash[:notice]).to include 'You have declined the invitation to join'
+ end
+ end
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 871b9219ca..76d56bc989 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -8,28 +8,34 @@ describe Projects::IssuesController do
before do
sign_in(user)
project.team << [user, :developer]
- controller.instance_variable_set(:@project, project)
end
describe "GET #index" do
it "returns index" do
- get :index, namespace_id: project.namespace.id, project_id: project.id
+ get :index, namespace_id: project.namespace.path, project_id: project.path
expect(response.status).to eq(200)
end
+ it "return 301 if request path doesn't match project path" do
+ get :index, namespace_id: project.namespace.path, project_id: project.path.upcase
+
+ expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
+ end
+
it "returns 404 when issues are disabled" do
project.issues_enabled = false
project.save
- get :index, namespace_id: project.namespace.id, project_id: project.id
+ get :index, namespace_id: project.namespace.path, project_id: project.path
expect(response.status).to eq(404)
end
it "returns 404 when external issue tracker is enabled" do
+ controller.instance_variable_set(:@project, project)
allow(project).to receive(:default_issues_tracker?).and_return(false)
- get :index, namespace_id: project.namespace.id, project_id: project.id
+ get :index, namespace_id: project.namespace.path, project_id: project.path
expect(response.status).to eq(404)
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 91856ed0cc..18a30033ed 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -33,33 +33,5 @@ describe Projects::RepositoriesController do
expect(response.status).to eq(404)
end
end
-
- context "when the service doesn't return a path" do
-
- before do
- allow(service).to receive(:execute).and_return(nil)
- end
-
- it "reloads the page" do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
-
- expect(response).to redirect_to(archive_namespace_project_repository_path(project.namespace, project, ref: "master", format: "zip"))
- end
- end
-
- context "when the service returns a path" do
-
- let(:path) { Rails.root.join("spec/fixtures/dk.png").to_s }
-
- before do
- allow(service).to receive(:execute).and_return(path)
- end
-
- it "sends the file" do
- get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
-
- expect(response.body).to eq(File.binread(path))
- end
- end
end
end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index d4ecd98e12..ccd8c741c8 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -10,26 +10,43 @@ describe Projects::ServicesController do
project.team << [user, :master]
controller.instance_variable_set(:@project, project)
controller.instance_variable_set(:@service, service)
- request.env["HTTP_REFERER"] = "/"
end
- describe "#test" do
- context 'success' do
- it "should redirect and show success message" do
- expect(service).to receive(:test).and_return({ success: true, result: 'done' })
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
- expect(response.status).to redirect_to('/')
- expect(flash[:notice]).to eq('We sent a request to the provided URL')
- end
+ shared_examples_for 'services controller' do |referrer|
+ before do
+ request.env["HTTP_REFERER"] = referrer
end
- context 'failure' do
- it "should redirect and show failure message" do
- expect(service).to receive(:test).and_return({ success: false, result: 'Bad test' })
- get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
- expect(response.status).to redirect_to('/')
- expect(flash[:alert]).to eq('We tried to send a request to the provided URL but an error occurred: Bad test')
+ describe "#test" do
+ context 'success' do
+ it "should redirect and show success message" do
+ expect(service).to receive(:test).and_return({ success: true, result: 'done' })
+ get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ expect(response.status).to redirect_to('/')
+ expect(flash[:notice]).to eq('We sent a request to the provided URL')
+ end
end
+
+ context 'failure' do
+ it "should redirect and show failure message" do
+ expect(service).to receive(:test).and_return({ success: false, result: 'Bad test' })
+ get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
+ expect(response.status).to redirect_to('/')
+ expect(flash[:alert]).to eq('We tried to send a request to the provided URL but an error occurred: Bad test')
+ end
+ end
+ end
+ end
+
+ describe 'referrer defined' do
+ it_should_behave_like 'services controller' do
+ let!(:referrer) { "/" }
+ end
+ end
+
+ describe 'referrer undefined' do
+ it_should_behave_like 'services controller' do
+ let!(:referrer) { nil }
end
end
end
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index 5391585635..a474574c6e 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 f51abfedae..93c4494c66 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/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 29233e9fae..4bb47c6b02 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -21,6 +21,71 @@ describe ProjectsController do
expect(response.body).to include("content='#{content}'")
end
end
+
+ context "rendering default project view" do
+ render_views
+
+ it "renders the activity view" do
+ allow(controller).to receive(:current_user).and_return(user)
+ allow(user).to receive(:project_view).and_return('activity')
+
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ expect(response).to render_template('_activity')
+ end
+
+ it "renders the readme view" do
+ allow(controller).to receive(:current_user).and_return(user)
+ allow(user).to receive(:project_view).and_return('readme')
+
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ expect(response).to render_template('_readme')
+ end
+
+ it "renders the files view" do
+ allow(controller).to receive(:current_user).and_return(user)
+ allow(user).to receive(:project_view).and_return('files')
+
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path
+ expect(response).to render_template('_files')
+ end
+ end
+
+ context "when requested with case sensitive namespace and project path" do
+ context "when there is a match with the same casing" do
+ it "loads the project" do
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path
+
+ expect(assigns(:project)).to eq(public_project)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context "when there is a match with different casing" do
+ it "redirects to the normalized path" do
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
+
+ expect(assigns(:project)).to eq(public_project)
+ expect(response).to redirect_to("/#{public_project.path_with_namespace}")
+ end
+
+
+ # MySQL queries are case insensitive by default, so this spec would fail.
+ if Gitlab::Database.postgresql?
+ context "when there is also a match with the same casing" do
+
+ let!(:other_project) { create(:project, :public, namespace: public_project.namespace, path: public_project.path.upcase) }
+
+ it "loads the exactly matched project" do
+
+ get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
+
+ expect(assigns(:project)).to eq(other_project)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+ end
+ end
end
describe "POST #toggle_star" do
@@ -48,4 +113,50 @@ describe ProjectsController do
expect(user.starred?(public_project)).to be_falsey
end
end
+
+ describe "DELETE remove_fork" do
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'with forked project' do
+ let(:project_fork) { create(:project, namespace: user.namespace) }
+
+ before do
+ create(:forked_project_link, forked_to_project: project_fork)
+ end
+
+ it 'should remove fork from project' do
+ delete(:remove_fork,
+ namespace_id: project_fork.namespace.to_param,
+ id: project_fork.to_param, format: :js)
+
+ expect(project_fork.forked?).to be_falsey
+ expect(flash[:notice]).to eq('The fork relationship has been removed.')
+ expect(response).to render_template(:remove_fork)
+ end
+ end
+
+ context 'when project not forked' do
+ let(:unforked_project) { create(:project, namespace: user.namespace) }
+
+ it 'should do nothing if project was not forked' do
+ delete(:remove_fork,
+ namespace_id: unforked_project.namespace.to_param,
+ id: unforked_project.to_param, format: :js)
+
+ expect(flash[:notice]).to be_nil
+ expect(response).to render_template(:remove_fork)
+ end
+ end
+ end
+
+ it "does nothing if user is not signed in" do
+ delete(:remove_fork,
+ namespace_id: project.namespace.to_param,
+ id: project.to_param, format: :js)
+ expect(response.status).to eq(401)
+ end
+ end
end
diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb
index 64dfe8f34e..5a104ae7c9 100644
--- a/spec/controllers/root_controller_spec.rb
+++ b/spec/controllers/root_controller_spec.rb
@@ -10,7 +10,7 @@ describe RootController do
allow(subject).to receive(:current_user).and_return(user)
end
- context 'who has customized their dashboard setting' do
+ context 'who has customized their dashboard setting for starred projects' do
before do
user.update_attribute(:dashboard, 'stars')
end
@@ -21,6 +21,28 @@ describe RootController do
end
end
+ context 'who has customized their dashboard setting for project activities' do
+ before do
+ user.update_attribute(:dashboard, 'project_activity')
+ end
+
+ it 'redirects to the activity list' do
+ get :index
+ expect(response).to redirect_to activity_dashboard_path
+ end
+ end
+
+ context 'who has customized their dashboard setting for starred project activities' do
+ before do
+ user.update_attribute(:dashboard, 'starred_project_activity')
+ end
+
+ it 'redirects to the activity list' do
+ get :index
+ expect(response).to redirect_to activity_dashboard_path(filter: 'starred')
+ end
+ end
+
context 'who uses the default dashboard setting' do
it 'renders the default dashboard' do
get :index
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 99da5a1877..2fcd70182b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -27,6 +27,9 @@
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'
finished_at 'Di 29. Okt 09:53:28 CET 2013'
commands 'ls -a'
@@ -43,5 +46,9 @@ FactoryGirl.define do
started_at nil
finished_at nil
end
+
+ factory :ci_build_tag do
+ tag true
+ end
end
end
diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/commits.rb
index 70930c789c..79e000b7cc 100644
--- a/spec/factories/ci/commits.rb
+++ b/spec/factories/ci/commits.rb
@@ -17,58 +17,32 @@
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
- factory :ci_commit, class: Ci::Commit do
- ref 'master'
- before_sha '76de212e80737a608d939f648d959671fb0a0142'
+ factory :ci_empty_commit, class: Ci::Commit do
sha '97de212e80737a608d939f648d959671fb0a0142'
- push_data do
- {
- ref: 'refs/heads/master',
- before: '76de212e80737a608d939f648d959671fb0a0142',
- after: '97de212e80737a608d939f648d959671fb0a0142',
- user_name: 'Git User',
- user_email: 'git@example.com',
- repository: {
- name: 'test-data',
- url: 'ssh://git@gitlab.com/test/test-data.git',
- description: '',
- homepage: 'http://gitlab.com/test/test-data'
- },
- commits: [
- {
- id: '97de212e80737a608d939f648d959671fb0a0142',
- message: 'Test commit message',
- timestamp: '2014-09-23T13:12:25+02:00',
- url: 'https://gitlab.com/test/test-data/commit/97de212e80737a608d939f648d959671fb0a0142',
- author: {
- name: 'Git User',
- email: 'git@user.com'
- }
- }
- ],
- total_commits_count: 1,
- ci_yaml_file: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
- }
- end
+
+ gl_project factory: :empty_project
factory :ci_commit_without_jobs do
- after(:create) do |commit, evaluator|
- commit.push_data[:ci_yaml_file] = YAML.dump({})
- commit.save
+ after(:build) do |commit|
+ allow(commit).to receive(:ci_yaml_file) { YAML.dump({}) }
end
end
factory :ci_commit_with_one_job do
- after(:create) do |commit, evaluator|
- commit.push_data[:ci_yaml_file] = YAML.dump({ rspec: { script: "ls" } })
- commit.save
+ after(:build) do |commit|
+ allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" } }) }
end
end
factory :ci_commit_with_two_jobs do
- after(:create) do |commit, evaluator|
- commit.push_data[:ci_yaml_file] = YAML.dump({ rspec: { script: "ls" }, spinach: { script: "ls" } })
- commit.save
+ after(:build) do |commit|
+ allow(commit).to receive(:ci_yaml_file) { YAML.dump({ rspec: { script: "ls" }, spinach: { script: "ls" } }) }
+ end
+ end
+
+ factory :ci_commit do
+ after(:build) do |commit|
+ allow(commit).to receive(:ci_yaml_file) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
end
end
end
diff --git a/spec/factories/ci/projects.rb b/spec/factories/ci/projects.rb
index e6bd0685f8..111e1a8281 100644
--- a/spec/factories/ci/projects.rb
+++ b/spec/factories/ci/projects.rb
@@ -29,21 +29,9 @@
FactoryGirl.define do
factory :ci_project_without_token, class: Ci::Project do
- sequence :name do |n|
- "GitLab / gitlab-shell#{n}"
- end
-
default_ref 'master'
- sequence :path do |n|
- "gitlab/gitlab-shell#{n}"
- end
-
- sequence :ssh_url_to_repo do |n|
- "git@demo.gitlab.com:gitlab/gitlab-shell#{n}.git"
- end
-
- gl_project factory: :project
+ gl_project factory: :empty_project
factory :ci_project do
token 'iPWx6WM4lhHNedGfBpPJNP'
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
new file mode 100644
index 0000000000..52de437052
--- /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/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 8671776158..c2c7364f6c 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -111,6 +111,27 @@ describe "Admin::Users", feature: true do
expect(page).to have_content(@user.name)
end
+ describe 'Login as another user' do
+ it 'should show login button for other users and check that it works' do
+ another_user = create(:user)
+
+ visit admin_user_path(another_user)
+
+ click_link 'Log in as this user'
+
+ expect(page).to have_content("Logged in as #{another_user.username}")
+
+ page.within '.sidebar-user .username' do
+ expect(page).to have_content(another_user.username)
+ end
+ end
+
+ it 'should not show login button for admin itself' do
+ visit admin_user_path(@user)
+ expect(page).not_to have_content('Log in as this user')
+ end
+ end
+
describe 'Two-factor Authentication status' do
it 'shows when enabled' do
@user.update_attribute(:two_factor_enabled, true)
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
new file mode 100644
index 0000000000..154857e77f
--- /dev/null
+++ b/spec/features/builds_spec.rb
@@ -0,0 +1,89 @@
+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" do
+ context "Running scope" do
+ before do
+ @build.run!
+ visit namespace_project_builds_path(@gl_project.namespace, @gl_project)
+ end
+
+ it { expect(page).to have_content 'Running' }
+ it { expect(page).to have_content 'Cancel all' }
+ it { expect(page).to have_content @build.short_sha }
+ it { expect(page).to have_content @build.ref }
+ it { expect(page).to have_content @build.name }
+ end
+
+ context "Finished scope" do
+ before do
+ @build.run!
+ visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :finished)
+ end
+
+ it { expect(page).to have_content 'No builds to show' }
+ it { expect(page).to have_content 'Cancel all' }
+ end
+
+ context "All builds" do
+ before do
+ @gl_project.ci_builds.running_or_pending.each(&:success)
+ visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :all)
+ end
+
+ it { expect(page).to have_content 'All' }
+ it { expect(page).to have_content @build.short_sha }
+ it { expect(page).to have_content @build.ref }
+ it { expect(page).to have_content @build.name }
+ it { expect(page).to_not have_content 'Cancel all' }
+ end
+ end
+
+ describe "GET /:project/builds/:id/cancel_all" do
+ before do
+ @build.run!
+ visit cancel_all_namespace_project_builds_path(@gl_project.namespace, @gl_project)
+ end
+
+ it { expect(page).to have_content 'No builds to show' }
+ it { expect(page).to_not have_content 'Cancel all' }
+ end
+
+ describe "GET /:project/builds/:id" do
+ before do
+ visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
+ end
+
+ it { expect(page).to have_content @commit.sha[0..7] }
+ 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/admin/builds_spec.rb b/spec/features/ci/admin/builds_spec.rb
index 88ef9c144a..623d466c67 100644
--- a/spec/features/ci/admin/builds_spec.rb
+++ b/spec/features/ci/admin/builds_spec.rb
@@ -1,8 +1,7 @@
require 'spec_helper'
describe "Admin Builds" do
- let(:project) { FactoryGirl.create :ci_project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
+ let(:commit) { FactoryGirl.create :ci_commit }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
before do
@@ -22,10 +21,10 @@ describe "Admin Builds" do
describe "Tabs" do
it "shows all builds" do
- build = FactoryGirl.create :ci_build, commit: commit, status: "pending"
- build1 = FactoryGirl.create :ci_build, commit: commit, status: "running"
- build2 = FactoryGirl.create :ci_build, commit: commit, status: "success"
- build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed"
+ FactoryGirl.create :ci_build, commit: commit, status: "pending"
+ FactoryGirl.create :ci_build, commit: commit, status: "running"
+ FactoryGirl.create :ci_build, commit: commit, status: "success"
+ FactoryGirl.create :ci_build, commit: commit, status: "failed"
visit ci_admin_builds_path
diff --git a/spec/features/ci/admin/runners_spec.rb b/spec/features/ci/admin/runners_spec.rb
index b25121f080..b83744f53a 100644
--- a/spec/features/ci/admin/runners_spec.rb
+++ b/spec/features/ci/admin/runners_spec.rb
@@ -2,8 +2,7 @@ require 'spec_helper'
describe "Admin Runners" do
before do
- skip_ci_admin_auth
- login_as :user
+ login_as :admin
end
describe "Runners page" do
@@ -20,16 +19,16 @@ describe "Admin Runners" do
describe 'search' do
before do
- FactoryGirl.create :ci_runner, description: 'foo'
- FactoryGirl.create :ci_runner, description: 'bar'
+ FactoryGirl.create :ci_runner, description: 'runner-foo'
+ FactoryGirl.create :ci_runner, description: 'runner-bar'
search_form = find('#runners-search')
- search_form.fill_in 'search', with: 'foo'
+ search_form.fill_in 'search', with: 'runner-foo'
search_form.click_button 'Search'
end
- it { expect(page).to have_content("foo") }
- it { expect(page).not_to have_content("bar") }
+ it { expect(page).to have_content("runner-foo") }
+ it { expect(page).not_to have_content("runner-bar") }
end
end
@@ -37,8 +36,8 @@ describe "Admin Runners" do
let(:runner) { FactoryGirl.create :ci_runner }
before do
- FactoryGirl.create(:ci_project, name: "foo")
- FactoryGirl.create(:ci_project, name: "bar")
+ @project1 = FactoryGirl.create(:ci_project)
+ @project2 = FactoryGirl.create(:ci_project)
visit ci_admin_runner_path(runner)
end
@@ -47,19 +46,19 @@ describe "Admin Runners" do
end
describe 'projects' do
- it { expect(page).to have_content("foo") }
- it { expect(page).to have_content("bar") }
+ it { expect(page).to have_content(@project1.name_with_namespace) }
+ it { expect(page).to have_content(@project2.name_with_namespace) }
end
describe 'search' do
before do
search_form = find('#runner-projects-search')
- search_form.fill_in 'search', with: 'foo'
+ search_form.fill_in 'search', with: @project1.gl_project.name
search_form.click_button 'Search'
end
- it { expect(page).to have_content("foo") }
- it { expect(page).not_to have_content("bar") }
+ it { expect(page).to have_content(@project1.name_with_namespace) }
+ it { expect(page).not_to have_content(@project2.name_with_namespace) }
end
end
end
diff --git a/spec/features/ci/builds_spec.rb b/spec/features/ci/builds_spec.rb
deleted file mode 100644
index 2f020e524e..0000000000
--- a/spec/features/ci/builds_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'spec_helper'
-
-describe "Builds" do
- context :private_project do
- before do
- @project = FactoryGirl.create :ci_project
- @commit = FactoryGirl.create :ci_commit, project: @project
- @build = FactoryGirl.create :ci_build, commit: @commit
- login_as :user
- @project.gl_project.team << [@user, :master]
- end
-
- describe "GET /:project/builds/:id" do
- before do
- visit ci_project_build_path(@project, @build)
- end
-
- it { expect(page).to have_content @commit.sha[0..7] }
- 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_ci_project_build_path(@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
- @build.cancel!
- visit ci_project_build_path(@project, @build)
- click_link 'Retry'
- end
-
- it { expect(page).to have_content 'pending' }
- it { expect(page).to have_content 'Cancel' }
- end
- end
-
- context :public_project do
- describe "Show page public accessible" do
- before do
- @project = FactoryGirl.create :ci_public_project
- @commit = FactoryGirl.create :ci_commit, project: @project
- @runner = FactoryGirl.create :ci_specific_runner
- @build = FactoryGirl.create :ci_build, commit: @commit, runner: @runner
-
- stub_gitlab_calls
- visit ci_project_build_path(@project, @build)
- end
-
- it { expect(page).to have_content @commit.sha[0..7] }
- end
- end
-end
diff --git a/spec/features/ci/commits_spec.rb b/spec/features/ci/commits_spec.rb
deleted file mode 100644
index 40a62ca457..0000000000
--- a/spec/features/ci/commits_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-require 'spec_helper'
-
-describe "Commits" do
- include Ci::CommitsHelper
-
- context "Authenticated user" do
- before do
- @project = FactoryGirl.create :ci_project
- @commit = FactoryGirl.create :ci_commit, project: @project
- @build = FactoryGirl.create :ci_build, commit: @commit
- login_as :user
- @project.gl_project.team << [@user, :master]
- end
-
- describe "GET /:project/commits/:sha" do
- before do
- visit ci_commit_path(@commit)
- end
-
- it { expect(page).to have_content @commit.sha[0..7] }
- it { expect(page).to have_content @commit.git_commit_message }
- it { expect(page).to have_content @commit.git_author_name }
- end
-
- describe "Cancel commit" do
- it "cancels commit" do
- visit ci_commit_path(@commit)
- click_on "Cancel"
-
- expect(page).to have_content "canceled"
- end
- end
-
- describe ".gitlab-ci.yml not found warning" do
- it "does not show warning" do
- visit ci_commit_path(@commit)
-
- expect(page).not_to have_content ".gitlab-ci.yml not found in this commit"
- end
-
- it "shows warning" do
- @commit.push_data[:ci_yaml_file] = nil
- @commit.save
-
- visit ci_commit_path(@commit)
-
- expect(page).to have_content ".gitlab-ci.yml not found in this commit"
- end
- end
- end
-
- context "Public pages" do
- before do
- @project = FactoryGirl.create :ci_public_project
- @commit = FactoryGirl.create :ci_commit, project: @project
- @build = FactoryGirl.create :ci_build, commit: @commit
- end
-
- describe "GET /:project/commits/:sha" do
- before do
- visit ci_commit_path(@commit)
- end
-
- it { expect(page).to have_content @commit.sha[0..7] }
- it { expect(page).to have_content @commit.git_commit_message }
- it { expect(page).to have_content @commit.git_author_name }
- end
- end
-end
diff --git a/spec/features/ci/projects_spec.rb b/spec/features/ci/projects_spec.rb
deleted file mode 100644
index 7c8cd1ce5c..0000000000
--- a/spec/features/ci/projects_spec.rb
+++ /dev/null
@@ -1,38 +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
-
- describe "GET /ci/projects/:id/edit" do
- before do
- visit edit_ci_project_path(@project)
- end
-
- it { expect(page).to have_content @project.name }
- it { expect(page).to have_content 'Build Schedule' }
-
- it "updates configuration" do
- fill_in 'Timeout', with: '70'
- click_button 'Save changes'
-
- expect(page).to have_content 'was successfully updated'
-
- expect(find_field('Timeout').value).to eq '70'
- end
- end
-end
diff --git a/spec/features/ci_settings_spec.rb b/spec/features/ci_settings_spec.rb
new file mode 100644
index 0000000000..7e25e88301
--- /dev/null
+++ b/spec/features/ci_settings_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe "CI settings" do
+ let(:user) { create(:user) }
+ before { login_as(user) }
+
+ before do
+ @project = FactoryGirl.create :ci_project
+ @gl_project = @project.gl_project
+ @gl_project.team << [user, :master]
+ visit edit_namespace_project_ci_settings_path(@gl_project.namespace, @gl_project)
+ end
+
+ it { expect(page).to have_content 'Build Schedule' }
+
+ it "updates configuration" do
+ fill_in 'Timeout', with: '70'
+ click_button 'Save changes'
+ expect(page).to have_content 'was successfully updated'
+ expect(find_field('Timeout').value).to eq '70'
+ end
+end
diff --git a/spec/features/ci_web_hooks_spec.rb b/spec/features/ci_web_hooks_spec.rb
new file mode 100644
index 0000000000..efae0a42c1
--- /dev/null
+++ b/spec/features/ci_web_hooks_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe 'CI web hooks' do
+ let(:user) { create(:user) }
+ before { login_as(user) }
+
+ before do
+ @project = FactoryGirl.create :ci_project
+ @gl_project = @project.gl_project
+ @gl_project.team << [user, :master]
+ visit namespace_project_ci_web_hooks_path(@gl_project.namespace, @gl_project)
+ end
+
+ context 'create a trigger' do
+ before do
+ fill_in 'web_hook_url', with: 'http://example.com'
+ click_on 'Add Web Hook'
+ end
+
+ it { expect(@project.web_hooks.count).to eq(1) }
+
+ it 'revokes the trigger' do
+ click_on 'Remove'
+ expect(@project.web_hooks.count).to eq(0)
+ end
+ end
+end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
new file mode 100644
index 0000000000..1adc2cdf70
--- /dev/null
+++ b/spec/features/commits_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe "Commits" do
+ include CiStatusHelper
+
+ let(:project) { create(:project) }
+
+ describe "CI" do
+ before do
+ login_as :user
+ project.team << [@user, :master]
+ @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
+ stub_ci_commit_to_return_yaml_file
+ end
+
+ describe "GET /:project/commits/:sha" do
+ before do
+ visit ci_status_path(@commit)
+ end
+
+ it { expect(page).to have_content @commit.sha[0..7] }
+ it { expect(page).to have_content @commit.git_commit_message }
+ it { expect(page).to have_content @commit.git_author_name }
+ end
+
+ 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"
+ end
+ end
+
+ describe ".gitlab-ci.yml not found warning" do
+ it "does not show warning" do
+ visit ci_status_path(@commit)
+ expect(page).not_to have_content ".gitlab-ci.yml not found in this commit"
+ end
+
+ it "shows warning" do
+ stub_ci_commit_yaml_file(nil)
+ visit ci_status_path(@commit)
+ expect(page).to have_content ".gitlab-ci.yml not found in this commit"
+ end
+ end
+ end
+end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index cef432e512..922c76285d 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -95,7 +95,7 @@ feature 'Login', feature: true do
user = create(:user, password: 'not-the-default')
login_with(user)
- expect(page).to have_content('Invalid email or password.')
+ expect(page).to have_content('Invalid login or password.')
end
end
end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index c557a1061a..fdd8cf07b1 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -220,7 +220,7 @@ describe 'GitLab Markdown', feature: true do
end
end
- # `markdown` calls these two methods
+ # Fake a `current_user` helper
def current_user
@feat.user
end
diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb
index 2b6311e4fd..85e70b4d47 100644
--- a/spec/features/password_reset_spec.rb
+++ b/spec/features/password_reset_spec.rb
@@ -1,53 +1,43 @@
require 'spec_helper'
feature 'Password reset', feature: true do
- def forgot_password
+ describe 'throttling' do
+ it 'sends reset instructions when not previously sent' do
+ visit root_path
+ forgot_password(create(:user))
+
+ expect(page).to have_content(I18n.t('devise.passwords.send_instructions'))
+ expect(current_path).to eq new_user_session_path
+ end
+
+ it 'sends reset instructions when previously sent more than a minute ago' do
+ user = create(:user)
+ user.send_reset_password_instructions
+ user.update_attribute(:reset_password_sent_at, 5.minutes.ago)
+
+ visit root_path
+ forgot_password(user)
+
+ expect(page).to have_content(I18n.t('devise.passwords.send_instructions'))
+ expect(current_path).to eq new_user_session_path
+ end
+
+ it "throttles multiple resets in a short timespan" do
+ user = create(:user)
+ user.send_reset_password_instructions
+
+ visit root_path
+ forgot_password(user)
+
+ expect(page).to have_content(I18n.t('devise.passwords.recently_reset'))
+ expect(current_path).to eq new_user_password_path
+ end
+ end
+
+ def forgot_password(user)
click_on 'Forgot your password?'
fill_in 'Email', with: user.email
click_button 'Reset password'
user.reload
end
-
- def get_reset_token
- mail = ActionMailer::Base.deliveries.last
- body = mail.body.encoded
- body.scan(/reset_password_token=(.+)\"/).flatten.first
- end
-
- def reset_password(password = 'password')
- visit edit_user_password_path(reset_password_token: get_reset_token)
-
- fill_in 'New password', with: password
- fill_in 'Confirm new password', with: password
- click_button 'Change your password'
- end
-
- describe 'with two-factor authentication' do
- let(:user) { create(:user, :two_factor) }
-
- it 'requires login after password reset' do
- visit root_path
-
- forgot_password
- reset_password
-
- expect(page).to have_content("Your password was changed successfully.")
- expect(page).not_to have_content("You are now signed in.")
- expect(current_path).to eq new_user_session_path
- end
- end
-
- describe 'without two-factor authentication' do
- let(:user) { create(:user) }
-
- it 'automatically logs in after password reset' do
- visit root_path
-
- forgot_password
- reset_password
-
- expect(current_path).to eq root_path
- expect(page).to have_content("Your password was changed successfully. You are now signed in.")
- end
- end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index a362c54b9a..09fcff2444 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -34,6 +34,27 @@ feature 'Project', feature: true do
end
end
+ describe 'remove forked relationship', js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ login_with user
+ create(:forked_project_link, forked_to_project: project)
+ visit edit_namespace_project_path(project.namespace, project)
+ end
+
+ it 'should remove fork' do
+ expect(page).to have_content 'Remove fork relationship'
+
+ remove_with_confirm('Remove fork relationship', project.path)
+
+ expect(page).to have_content 'The fork relationship has been removed.'
+ expect(project.forked?).to be_falsey
+ expect(page).not_to have_content 'Remove fork relationship'
+ end
+ end
+
describe 'removal', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
@@ -45,13 +66,13 @@ feature 'Project', feature: true do
end
it 'should remove project' do
- expect { remove_project }.to change {Project.count}.by(-1)
+ expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1)
end
end
- def remove_project
- click_link "Remove project"
- fill_in 'confirm_name_input', with: project.path
+ def remove_with_confirm(button_text, confirm_with)
+ click_button button_text
+ fill_in 'confirm_name_input', with: confirm_with
click_button 'Confirm'
end
end
diff --git a/spec/features/ci/runners_spec.rb b/spec/features/runners_spec.rb
similarity index 87%
rename from spec/features/ci/runners_spec.rb
rename to spec/features/runners_spec.rb
index 15147f15eb..06adb7633b 100644
--- a/spec/features/ci/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -1,11 +1,10 @@
require 'spec_helper'
describe "Runners" do
- let(:user) { create(:user) }
+ include GitlabRoutingHelper
- before do
- login_as(user)
- end
+ let(:user) { create(:user) }
+ before { login_as(user) }
describe "specific runners" do
before do
@@ -20,18 +19,17 @@ describe "Runners" do
@specific_runner2 = FactoryGirl.create :ci_specific_runner
@project.runners << @specific_runner
@project2.runners << @specific_runner2
+
+ visit runners_path(@project.gl_project)
end
it "places runners in right places" do
- visit ci_project_runners_path(@project)
expect(page.find(".available-specific-runners")).to have_content(@specific_runner2.display_name)
expect(page.find(".activated-specific-runners")).to have_content(@specific_runner.display_name)
expect(page.find(".available-shared-runners")).to have_content(@shared_runner.display_name)
end
it "enables specific runner for project" do
- visit ci_project_runners_path(@project)
-
within ".available-specific-runners" do
click_on "Enable for this project"
end
@@ -41,8 +39,7 @@ describe "Runners" do
it "disables specific runner for project" do
@project2.runners << @specific_runner
-
- visit ci_project_runners_path(@project)
+ visit runners_path(@project.gl_project)
within ".activated-specific-runners" do
click_on "Disable for this project"
@@ -52,8 +49,6 @@ describe "Runners" do
end
it "removes specific runner for project if this is last project for that runners" do
- visit ci_project_runners_path(@project)
-
within ".activated-specific-runners" do
click_on "Remove runner"
end
@@ -66,13 +61,11 @@ describe "Runners" do
before do
@project = FactoryGirl.create :ci_project
@project.gl_project.team << [user, :master]
+ visit runners_path(@project.gl_project)
end
it "enables shared runners" do
- visit ci_project_runners_path(@project)
-
click_on "Enable shared runners"
-
expect(@project.reload.shared_runners_enabled).to be_truthy
end
end
@@ -83,13 +76,11 @@ describe "Runners" do
@project.gl_project.team << [user, :master]
@specific_runner = FactoryGirl.create :ci_specific_runner
@project.runners << @specific_runner
+ visit runners_path(@project.gl_project)
end
it "shows runner information" do
- visit ci_project_runners_path(@project)
-
click_on @specific_runner.short_sha
-
expect(page).to have_content(@specific_runner.platform)
end
end
diff --git a/spec/features/ci/triggers_spec.rb b/spec/features/triggers_spec.rb
similarity index 68%
rename from spec/features/ci/triggers_spec.rb
rename to spec/features/triggers_spec.rb
index c6afeb7462..69492d5887 100644
--- a/spec/features/ci/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -1,13 +1,14 @@
require 'spec_helper'
describe 'Triggers' do
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
+ before { login_as(user) }
before do
- login_as(user)
@project = FactoryGirl.create :ci_project
- @project.gl_project.team << [user, :master]
- visit ci_project_triggers_path(@project)
+ @gl_project = @project.gl_project
+ @gl_project.team << [user, :master]
+ visit namespace_project_triggers_path(@gl_project.namespace, @gl_project)
end
context 'create a trigger' do
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index efcb8a31ab..c124816203 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
feature 'Users', feature: true do
+ let(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') }
+
scenario 'GET /users/sign_in creates a new user account' do
visit new_user_session_path
fill_in 'user_name', with: 'Name Surname'
@@ -11,7 +13,6 @@ feature 'Users', feature: true do
end
scenario 'Successful user signin invalidates password reset token' do
- user = create(:user)
expect(user.reset_password_token).to be_nil
visit new_user_password_path
@@ -28,7 +29,6 @@ feature 'Users', feature: true do
expect(user.reset_password_token).to be_nil
end
- let!(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') }
scenario 'Should show one error if email is already taken' do
visit new_user_session_path
fill_in 'user_name', with: 'Another user name'
diff --git a/spec/features/ci/variables_spec.rb b/spec/features/variables_spec.rb
similarity index 68%
rename from spec/features/ci/variables_spec.rb
rename to spec/features/variables_spec.rb
index e387b3be55..adb602f3ed 100644
--- a/spec/features/ci/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -1,28 +1,25 @@
require 'spec_helper'
describe "Variables" do
- let(:user) { create(:user) }
-
- before do
- login_as(user)
- end
+ let(:user) { create(:user) }
+ before { login_as(user) }
describe "specific runners" do
before do
@project = FactoryGirl.create :ci_project
- @project.gl_project.team << [user, :master]
+ @gl_project = @project.gl_project
+ @gl_project.team << [user, :master]
end
it "creates variable", js: true do
- visit ci_project_variables_path(@project)
+ visit namespace_project_variables_path(@gl_project.namespace, @gl_project)
click_on "Add a variable"
fill_in "Key", with: "SECRET_KEY"
fill_in "Value", with: "SECRET_VALUE"
click_on "Save changes"
-
+
expect(page).to have_content("Variables were successfully updated.")
expect(@project.variables.count).to eq(1)
end
-
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index db20b23f87..b164805546 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 0000000000..a49cbfd516
--- /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/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index eadabb2e7b..670be75f76 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -99,6 +99,15 @@ describe ApplicationHelper do
helper.avatar_icon('foo@example.com', 20, 2)
end
+
+ describe 'using a User' do
+ it 'should return an URL for the avatar' do
+ user = create(:user, avatar: File.open(avatar_file_path))
+
+ expect(helper.avatar_icon(user).to_s).
+ to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
+ end
+ end
end
describe 'gravatar_icon' do
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index be0e0c747b..762ec25c4f 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -11,12 +11,15 @@ describe GitlabMarkdownHelper do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:snippet) { create(:project_snippet, project: project) }
- # Helper expects a current_user method.
- let(:current_user) { user }
-
before do
+ # Ensure the generated reference links aren't redacted
+ project.team << [user, :master]
+
# Helper expects a @project instance variable
- @project = project
+ helper.instance_variable_set(:@project, project)
+
+ # Stub the `current_user` helper
+ allow(helper).to receive(:current_user).and_return(user)
end
describe "#markdown" do
@@ -25,23 +28,23 @@ describe GitlabMarkdownHelper do
it "should link to the merge request" do
expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
- expect(markdown(actual)).to match(expected)
+ expect(helper.markdown(actual)).to match(expected)
end
it "should link to the commit" do
expected = namespace_project_commit_path(project.namespace, project, commit)
- expect(markdown(actual)).to match(expected)
+ expect(helper.markdown(actual)).to match(expected)
end
it "should link to the issue" do
expected = namespace_project_issue_path(project.namespace, project, issue)
- expect(markdown(actual)).to match(expected)
+ expect(helper.markdown(actual)).to match(expected)
end
end
describe "override default project" do
let(:actual) { issue.to_reference }
- let(:second_project) { create(:project) }
+ let(:second_project) { create(:project, :public) }
let(:second_issue) { create(:issue, project: second_project) }
it 'should link to the issue' do
@@ -56,7 +59,7 @@ describe GitlabMarkdownHelper do
let(:issues) { create_list(:issue, 2, project: project) }
it 'should handle references nested in links with all the text' do
- actual = link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
+ actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup
@@ -86,7 +89,7 @@ describe GitlabMarkdownHelper do
end
it 'should forward HTML options' do
- actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
+ actual = helper.link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
doc = Nokogiri::HTML.parse(actual)
expect(doc.css('a')).to satisfy do |v|
@@ -97,15 +100,21 @@ describe GitlabMarkdownHelper do
it "escapes HTML passed in as the body" do
actual = "This is a test
- see #{issues[0].to_reference}"
- expect(link_to_gfm(actual, commit_path)).
+ expect(helper.link_to_gfm(actual, commit_path)).
to match('<h1>test</h1>')
end
it 'ignores reference links when they are the entire body' do
text = issues[0].to_reference
- act = link_to_gfm(text, '/foo')
+ act = helper.link_to_gfm(text, '/foo')
expect(act).to eq %Q(#{issues[0].to_reference})
end
+
+ it 'should replace commit message with emoji to link' do
+ actual = link_to_gfm(':book:Book', '/foo')
+ expect(actual).
+ to eq %Q(
Book)
+ end
end
describe '#render_wiki_content' do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index c08ddb4cae..78a6b631eb 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -117,4 +117,14 @@ describe IssuesHelper do
end
end
+ describe "#merge_requests_sentence" do
+ subject { merge_requests_sentence(merge_requests)}
+ let(:merge_requests) do
+ [ build(:merge_request, iid: 1), build(:merge_request, iid: 2),
+ build(:merge_request, iid: 3)]
+ end
+
+ it { is_expected.to eq("!1, !2, or !3") }
+ end
+
end
diff --git a/spec/helpers/merge_requests_helper.rb b/spec/helpers/merge_requests_helper.rb
deleted file mode 100644
index 5262d64404..0000000000
--- a/spec/helpers/merge_requests_helper.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-
-describe MergeRequestsHelper do
- describe :issues_sentence do
- subject { issues_sentence(issues) }
- let(:issues) do
- [build(:issue, iid: 1), build(:issue, iid: 2), build(:issue, iid: 3)]
- end
-
- it { is_expected.to eq('#1, #2, and #3') }
- end
-end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
new file mode 100644
index 0000000000..0ef1efb8bc
--- /dev/null
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe MergeRequestsHelper do
+ describe "#issues_sentence" do
+ subject { issues_sentence(issues) }
+ let(:issues) do
+ [build(:issue, iid: 1), build(:issue, iid: 2), build(:issue, iid: 3)]
+ end
+
+ it { is_expected.to eq('#1, #2, and #3') }
+ end
+
+ describe "#format_mr_branch_names" do
+ describe "within the same project" do
+ let(:merge_request) { create(:merge_request) }
+ subject { format_mr_branch_names(merge_request) }
+
+ it { is_expected.to eq([merge_request.source_branch, merge_request.target_branch]) }
+ end
+
+ describe "within different projects" do
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:project, forked_from_project: project) }
+ let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) }
+ subject { format_mr_branch_names(merge_request) }
+ let(:source_title) { "#{fork_project.path_with_namespace}:#{merge_request.source_branch}" }
+ let(:target_title) { "#{project.path_with_namespace}:#{merge_request.target_branch}" }
+
+ it { is_expected.to eq([source_title, target_title]) }
+ end
+ end
+end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 06f69262b7..e5df59c4fb 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -8,14 +8,18 @@ describe PreferencesHelper do
end
it 'raises an exception when defined choices may be using the wrong key' do
- expect(User).to receive(:dashboards).and_return(foo: 'foo', bar: 'bar')
+ dashboards = User.dashboards.dup
+ dashboards[:projects_changed] = dashboards.delete :projects
+ expect(User).to receive(:dashboards).and_return(dashboards)
expect { helper.dashboard_choices }.to raise_error(KeyError)
end
it 'provides better option descriptions' do
expect(helper.dashboard_choices).to match_array [
['Your Projects (default)', 'projects'],
- ['Starred Projects', 'stars']
+ ['Starred Projects', 'stars'],
+ ["Your Projects' Activity", 'project_activity'],
+ ["Starred Projects' Activity", 'starred_project_activity']
]
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 99abb95d90..f2efb528ae 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -61,13 +61,27 @@ describe ProjectsHelper do
end
it "returns a valid cach key" do
- expect(helper.send(:readme_cache_key)).to eq("#{project.id}-#{project.commit.id}-readme")
+ expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-#{project.commit.id}-readme")
end
it "returns a valid cache key if HEAD does not exist" do
allow(project).to receive(:commit) { nil }
- expect(helper.send(:readme_cache_key)).to eq("#{project.id}-nil-readme")
+ expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-nil-readme")
+ end
+ end
+
+ describe 'link_to_member' do
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, group: group) }
+ let(:user) { create(:user) }
+
+ describe 'using the default options' do
+ it 'returns an HTML link to the user' do
+ link = helper.link_to_member(project, user)
+
+ expect(link).to match(%r{/u/#{user.username}})
+ end
end
end
end
diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/runners_helper_spec.rb
similarity index 81%
rename from spec/helpers/ci/runners_helper_spec.rb
rename to spec/helpers/runners_helper_spec.rb
index 6d0e2d3d1e..35f91b7dec 100644
--- a/spec/helpers/ci/runners_helper_spec.rb
+++ b/spec/helpers/runners_helper_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::RunnersHelper do
+describe RunnersHelper do
it "returns - not contacted yet" do
runner = FactoryGirl.build :ci_runner
expect(runner_status_icon(runner)).to include("not connected yet")
@@ -12,7 +12,7 @@ describe Ci::RunnersHelper do
end
it "returns online text" do
- runner = FactoryGirl.build(:ci_runner, contacted_at: 1.hour.ago, active: true)
+ runner = FactoryGirl.build(:ci_runner, contacted_at: 1.second.ago, active: true)
expect(runner_status_icon(runner)).to include("Runner is online")
end
end
diff --git a/spec/helpers/ci/application_helper_spec.rb b/spec/helpers/time_helper_spec.rb
similarity index 96%
rename from spec/helpers/ci/application_helper_spec.rb
rename to spec/helpers/time_helper_spec.rb
index 6a216715b7..3f62527c5b 100644
--- a/spec/helpers/ci/application_helper_spec.rb
+++ b/spec/helpers/time_helper_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::ApplicationHelper do
+describe TimeHelper do
describe "#duration_in_words" do
it "returns minutes and seconds" do
intervals_in_words = {
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 0000000000..09708c12ed
--- /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 0000000000..b80a28a33e
--- /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 da1ebcdb23..514877340e 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 57453c716a..a073f21e7b 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 47b41dd2c8..90b02a6aec 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/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb
index 24894e8198..83e2ad220b 100644
--- a/spec/lib/ci/charts_spec.rb
+++ b/spec/lib/ci/charts_spec.rb
@@ -4,13 +4,12 @@ describe "Charts" do
context "build_times" do
before do
- @project = FactoryGirl.create(:ci_project)
- @commit = FactoryGirl.create(:ci_commit, project: @project)
+ @commit = FactoryGirl.create(:ci_commit)
FactoryGirl.create(:ci_build, commit: @commit)
end
it 'should return build times in minutes' do
- chart = Ci::Charts::BuildTime.new(@project)
+ chart = Ci::Charts::BuildTime.new(@commit.project)
expect(chart.build_times).to eq([2])
end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 49482ac2b1..abdb6b89ac 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -17,13 +17,15 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({
stage: "test",
+ stage_idx: 1,
except: nil,
name: :rspec,
only: nil,
- script: "pwd\nrspec",
- tags: [],
+ commands: "pwd\nrspec",
+ tag_list: [],
options: {},
- allow_failure: false
+ allow_failure: false,
+ when: "on_success"
})
end
@@ -115,15 +117,17 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
except: nil,
stage: "test",
+ stage_idx: 1,
name: :rspec,
only: nil,
- script: "pwd\nrspec",
- tags: [],
+ commands: "pwd\nrspec",
+ tag_list: [],
options: {
image: "ruby:2.1",
services: ["mysql"]
},
- allow_failure: false
+ allow_failure: false,
+ when: "on_success"
})
end
@@ -141,15 +145,17 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
except: nil,
stage: "test",
+ stage_idx: 1,
name: :rspec,
only: nil,
- script: "pwd\nrspec",
- tags: [],
+ commands: "pwd\nrspec",
+ tag_list: [],
options: {
image: "ruby:2.5",
services: ["postgresql"]
},
- allow_failure: false
+ allow_failure: false,
+ when: "on_success"
})
end
end
@@ -171,6 +177,21 @@ module Ci
end
end
+ describe "When" do
+ %w(on_success on_failure always).each do |when_state|
+ it "returns #{when_state} when defined" do
+ config = YAML.dump({
+ rspec: { script: "rspec", when: when_state }
+ })
+
+ config_processor = GitlabCiYamlProcessor.new(config)
+ builds = config_processor.builds_for_stage_and_ref("test", "master")
+ expect(builds.size).to eq(1)
+ expect(builds.first[:when]).to eq(when_state)
+ end
+ end
+ end
+
describe "Error handling" do
it "indicates that object is invalid" do
expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
@@ -197,6 +218,20 @@ module Ci
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image should be a string")
end
+ it "returns errors if job name is blank" do
+ config = YAML.dump({ '' => { script: "test" } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
+ end
+
+ it "returns errors if job name is non-string" do
+ config = YAML.dump({ 10 => { script: "test" } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
+ end
+
it "returns errors if job image parameter is invalid" do
config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
expect do
@@ -308,6 +343,13 @@ module Ci
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
end
+
+ it "returns errors if job when is not on_success, on_failure or always" do
+ config = YAML.dump({ rspec: { script: "test", when: 1 } })
+ expect do
+ GitlabCiYamlProcessor.new(config)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
+ end
end
end
end
diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb
index 829a9c197e..37c527221a 100644
--- a/spec/lib/gitlab/backend/grack_auth_spec.rb
+++ b/spec/lib/gitlab/backend/grack_auth_spec.rb
@@ -151,14 +151,14 @@ describe Grack::Auth do
end
it "repeated attempts followed by successful attempt" do
- for n in 0..maxretry do
+ maxretry.times.each do
expect(attempt_login(false)).to eq(401)
end
expect(attempt_login(true)).to eq(200)
expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
- for n in 0..maxretry do
+ maxretry.times.each do
expect(attempt_login(false)).to eq(401)
end
end
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index b6d0433059..b60e23454d 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/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 5d7ff4f612..21254f778d 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -140,28 +140,28 @@ describe Gitlab::ClosingIssueExtractor do
message = "Closes #{reference} and fix #{reference2}"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue])
+ to match_array([issue, other_issue])
end
it 'fetches comma-separated issues references in single line message' do
message = "Closes #{reference}, closes #{reference2}"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue])
+ to match_array([issue, other_issue])
end
it 'fetches comma-separated issues numbers in single line message' do
message = "Closes #{reference}, #{reference2} and #{reference3}"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue, third_issue])
+ to match_array([issue, other_issue, third_issue])
end
it 'fetches issues in multi-line message' do
message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue])
+ to match_array([issue, other_issue])
end
it 'fetches issues in hybrid message' do
@@ -169,7 +169,7 @@ describe Gitlab::ClosingIssueExtractor do
"Also fixing issues #{reference2}, #{reference3} and #4"
expect(subject.closed_by_message(message)).
- to eq([issue, other_issue, third_issue])
+ to match_array([issue, other_issue, third_issue])
end
end
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
new file mode 100644
index 0000000000..7cdebdf209
--- /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 e8208e15e2..8fb432367b 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 fd2e5f6d0e..b5b56a3495 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/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
index 3c6c84a041..e5b8d723fe 100644
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitRangeReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:project) }
+ let(:project) { create(:project, :public) }
let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") }
@@ -75,12 +75,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-commit-range attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-commit-range')
+ expect(link.attr('data-commit-range')).to eq range.to_reference
end
it 'supports an :only_path option' do
@@ -92,59 +100,45 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("See #{reference}")
+ result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit_range]).not_to be_empty
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, namespace: namespace) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
let(:reference) { range.to_reference(project) }
before do
range.project = project2
end
- context 'when user can access reference' do
- before { allow_cross_reference! }
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- it 'links to a valid reference' do
- doc = filter("See #{reference}")
-
- expect(doc.css('a').first.attr('href')).
- to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
- end
-
- it 'links with adjacent text' do
- doc = filter("Fixed (#{reference}.)")
-
- exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
- expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/)
- end
-
- it 'ignores invalid commit IDs on the referenced project' do
- exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
- expect(filter(act).to_html).to eq exp
-
- exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
- expect(filter(act).to_html).to eq exp
- end
-
- it 'adds to the results hash' do
- result = pipeline_result("See #{reference}")
- expect(result[:references][:commit_range]).not_to be_empty
- end
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
+ expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/)
+ end
- expect(filter(act).to_html).to eq exp
- end
+ it 'ignores invalid commit IDs on the referenced project' do
+ exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
+ expect(filter(act).to_html).to eq exp
+
+ exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("See #{reference}")
+ expect(result[:references][:commit_range]).not_to be_empty
end
end
end
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
index 9ed438252b..d080efbf3d 100644
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe CommitReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:project) }
+ let(:project) { create(:project, :public) }
let(:commit) { project.commit }
it 'requires project context' do
@@ -71,12 +71,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("See #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-commit attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-commit')
+ expect(link.attr('data-commit')).to eq commit.id
end
it 'supports an :only_path context' do
@@ -88,53 +96,39 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("See #{reference}")
+ result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit]).not_to be_empty
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, namespace: namespace) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
let(:commit) { project2.commit }
let(:reference) { commit.to_reference(project) }
- context 'when user can access reference' do
- before { allow_cross_reference! }
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- it 'links to a valid reference' do
- doc = filter("See #{reference}")
-
- expect(doc.css('a').first.attr('href')).
- to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
- end
-
- it 'links with adjacent text' do
- doc = filter("Fixed (#{reference}.)")
-
- exp = Regexp.escape(project2.to_reference)
- expect(doc.to_html).to match(/\(#{exp}@#{commit.short_id}<\/a>\.\)/)
- end
-
- it 'ignores invalid commit IDs on the referenced project' do
- exp = act = "Committed #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
- end
-
- it 'adds to the results hash' do
- result = pipeline_result("See #{reference}")
- expect(result[:references][:commit]).not_to be_empty
- end
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ exp = Regexp.escape(project2.to_reference)
+ expect(doc.to_html).to match(/\(#{exp}@#{commit.short_id}<\/a>\.\)/)
+ end
- expect(filter(act).to_html).to eq exp
- end
+ it 'ignores invalid commit IDs on the referenced project' do
+ exp = act = "Committed #{invalidate_reference(reference)}"
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("See #{reference}")
+ expect(result[:references][:commit]).not_to be_empty
end
end
end
diff --git a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
index 4698d6138c..8d4f9e403a 100644
--- a/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
+++ b/spec/lib/gitlab/markdown/cross_project_reference_spec.rb
@@ -2,20 +2,16 @@ require 'spec_helper'
module Gitlab::Markdown
describe CrossProjectReference do
- # context in the html-pipeline sense, not in the rspec sense
- let(:context) do
- {
- current_user: double('user'),
- project: double('project')
- }
- end
-
include described_class
describe '#project_from_ref' do
context 'when no project was referenced' do
it 'returns the project from context' do
- expect(project_from_ref(nil)).to eq context[:project]
+ project = double
+
+ allow(self).to receive(:context).and_return({ project: project })
+
+ expect(project_from_ref(nil)).to eq project
end
end
@@ -26,29 +22,13 @@ module Gitlab::Markdown
end
context 'when referenced project exists' do
- let(:project2) { double('referenced project') }
+ it 'returns the referenced project' do
+ project2 = double('referenced project')
- before do
expect(Project).to receive(:find_with_namespace).
with('cross/reference').and_return(project2)
- end
- context 'and the user has permission to read it' do
- it 'returns the referenced project' do
- expect(self).to receive(:user_can_reference_project?).
- with(project2).and_return(true)
-
- expect(project_from_ref('cross/reference')).to eq project2
- end
- end
-
- context 'and the user does not have permission to read it' do
- it 'returns nil' do
- expect(self).to receive(:user_can_reference_project?).
- with(project2).and_return(false)
-
- expect(project_from_ref('cross/reference')).to be_nil
- end
+ expect(project_from_ref('cross/reference')).to eq project2
end
end
end
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
index 1dd54f5858..94c80ae661 100644
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -8,7 +8,7 @@ module Gitlab::Markdown
IssuesHelper
end
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, :public) }
let(:issue) { create(:issue, project: project) }
it 'requires project context' do
@@ -68,12 +68,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("Issue #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-issue attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-issue')
+ expect(link.attr('data-issue')).to eq issue.id.to_s
end
it 'supports an :only_path context' do
@@ -85,60 +93,46 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Fixed #{reference}")
+ result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:empty_project, namespace: namespace) }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { issue.to_reference(project) }
- context 'when user can access reference' do
- before { allow_cross_reference! }
+ it 'ignores valid references when cross-reference project uses external tracker' do
+ expect_any_instance_of(Project).to receive(:get_issue).
+ with(issue.iid).and_return(nil)
- it 'ignores valid references when cross-reference project uses external tracker' do
- expect_any_instance_of(Project).to receive(:get_issue).
- with(issue.iid).and_return(nil)
-
- exp = act = "Issue #{reference}"
- expect(filter(act).to_html).to eq exp
- end
-
- it 'links to a valid reference' do
- doc = filter("See #{reference}")
-
- expect(doc.css('a').first.attr('href')).
- to eq helper.url_for_issue(issue.iid, project2)
- end
-
- it 'links with adjacent text' do
- doc = filter("Fixed (#{reference}.)")
- expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/)
- end
-
- it 'ignores invalid issue IDs on the referenced project' do
- exp = act = "Fixed #{invalidate_reference(reference)}"
-
- expect(filter(act).to_html).to eq exp
- end
-
- it 'adds to the results hash' do
- result = pipeline_result("Fixed #{reference}")
- expect(result[:references][:issue]).to eq [issue]
- end
+ exp = act = "Issue #{reference}"
+ expect(filter(act).to_html).to eq exp
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ expect(doc.css('a').first.attr('href')).
+ to eq helper.url_for_issue(issue.iid, project2)
+ end
- expect(filter(act).to_html).to eq exp
- end
+ it 'links with adjacent text' do
+ doc = filter("Fixed (#{reference}.)")
+ expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
+
+ it 'ignores invalid issue IDs on the referenced project' do
+ exp = act = "Fixed #{invalidate_reference(reference)}"
+
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Fixed #{reference}")
+ expect(result[:references][:issue]).to eq [issue]
end
end
end
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
index e32089de37..fc21b65a84 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -5,7 +5,7 @@ module Gitlab::Markdown
describe LabelReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, :public) }
let(:label) { create(:label, project: project) }
let(:reference) { label.to_reference }
@@ -25,12 +25,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("Label #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-label attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-label')
+ expect(link.attr('data-label')).to eq label.id.to_s
end
it 'supports an :only_path context' do
@@ -42,7 +50,7 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Label #{reference}")
+ result = reference_pipeline_result("Label #{reference}")
expect(result[:references][:label]).to eq [label]
end
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
index 66616b9336..3ef6cdfff3 100644
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe MergeRequestReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:project) }
+ let(:project) { create(:project, :public) }
let(:merge) { create(:merge_request, source_project: project) }
it 'requires project context' do
@@ -56,12 +56,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("Merge #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-merge-request attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-merge-request')
+ expect(link.attr('data-merge-request')).to eq merge.id.to_s
end
it 'supports an :only_path context' do
@@ -73,53 +81,39 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Merge #{reference}")
+ result = reference_pipeline_result("Merge #{reference}")
expect(result[:references][:merge_request]).to eq [merge]
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, namespace: namespace) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { merge.to_reference(project) }
- context 'when user can access reference' do
- before { allow_cross_reference! }
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- it 'links to a valid reference' do
- doc = filter("See #{reference}")
-
- expect(doc.css('a').first.attr('href')).
- to eq urls.namespace_project_merge_request_url(project2.namespace,
- project, merge)
- end
-
- it 'links with adjacent text' do
- doc = filter("Merge (#{reference}.)")
- expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/)
- end
-
- it 'ignores invalid merge IDs on the referenced project' do
- exp = act = "Merge #{invalidate_reference(reference)}"
-
- expect(filter(act).to_html).to eq exp
- end
-
- it 'adds to the results hash' do
- result = pipeline_result("Merge #{reference}")
- expect(result[:references][:merge_request]).to eq [merge]
- end
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_merge_request_url(project2.namespace,
+ project, merge)
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
+ it 'links with adjacent text' do
+ doc = filter("Merge (#{reference}.)")
+ expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ it 'ignores invalid merge IDs on the referenced project' do
+ exp = act = "Merge #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
- end
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Merge #{reference}")
+ expect(result[:references][:merge_request]).to eq [merge]
end
end
end
diff --git a/spec/lib/gitlab/markdown/redactor_filter_spec.rb b/spec/lib/gitlab/markdown/redactor_filter_spec.rb
new file mode 100644
index 0000000000..eea3f1cf37
--- /dev/null
+++ b/spec/lib/gitlab/markdown/redactor_filter_spec.rb
@@ -0,0 +1,91 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe RedactorFilter do
+ include ActionView::Helpers::UrlHelper
+ include FilterSpecHelper
+
+ it 'ignores non-GFM links' do
+ html = %(See Google)
+ doc = filter(html, current_user: double)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ def reference_link(data)
+ link_to('text', '', class: 'gfm', data: data)
+ end
+
+ context 'with data-project' do
+ it 'removes unpermitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+
+ link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 0
+ end
+
+ it 'allows permitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+ project.team << [user, :master]
+
+ link = reference_link(project: project.id, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'handles invalid Project references' do
+ link = reference_link(project: 12345, reference_filter: Gitlab::Markdown::ReferenceFilter.name)
+
+ expect { filter(link) }.not_to raise_error
+ end
+ end
+
+ context "for user references" do
+
+ context 'with data-group' do
+ it 'removes unpermitted Group references' do
+ user = create(:user)
+ group = create(:group)
+
+ link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 0
+ end
+
+ it 'allows permitted Group references' do
+ user = create(:user)
+ group = create(:group)
+ group.add_developer(user)
+
+ link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ doc = filter(link, current_user: user)
+
+ expect(doc.css('a').length).to eq 1
+ end
+
+ it 'handles invalid Group references' do
+ link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+
+ expect { filter(link) }.not_to raise_error
+ end
+ end
+
+ context 'with data-user' do
+ it 'allows any User reference' do
+ user = create(:user)
+
+ link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ doc = filter(link)
+
+ expect(doc.css('a').length).to eq 1
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb b/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb
new file mode 100644
index 0000000000..4fa473ad19
--- /dev/null
+++ b/spec/lib/gitlab/markdown/reference_gatherer_filter_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe ReferenceGathererFilter do
+ include ActionView::Helpers::UrlHelper
+ include FilterSpecHelper
+
+ def reference_link(data)
+ link_to('text', '', class: 'gfm', data: data)
+ end
+
+ context "for issue references" do
+
+ context 'with data-project' do
+ it 'removes unpermitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+ issue = create(:issue, project: project)
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
+ result = pipeline_result(link, current_user: user)
+
+ expect(result[:references][:issue]).to be_empty
+ end
+
+ it 'allows permitted Project references' do
+ user = create(:user)
+ project = create(:empty_project)
+ issue = create(:issue, project: project)
+ project.team << [user, :master]
+
+ link = reference_link(project: project.id, issue: issue.id, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
+ result = pipeline_result(link, current_user: user)
+
+ expect(result[:references][:issue]).to eq([issue])
+ end
+
+ it 'handles invalid Project references' do
+ link = reference_link(project: 12345, issue: 12345, reference_filter: Gitlab::Markdown::IssueReferenceFilter.name)
+
+ expect { pipeline_result(link) }.not_to raise_error
+ end
+ end
+ end
+
+ context "for user references" do
+
+ context 'with data-group' do
+ it 'removes unpermitted Group references' do
+ user = create(:user)
+ group = create(:group)
+
+ link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ result = pipeline_result(link, current_user: user)
+
+ expect(result[:references][:user]).to be_empty
+ end
+
+ it 'allows permitted Group references' do
+ user = create(:user)
+ group = create(:group)
+ group.add_developer(user)
+
+ link = reference_link(group: group.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ result = pipeline_result(link, current_user: user)
+
+ expect(result[:references][:user]).to eq([user])
+ end
+
+ it 'handles invalid Group references' do
+ link = reference_link(group: 12345, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+
+ expect { pipeline_result(link) }.not_to raise_error
+ end
+ end
+
+ context 'with data-user' do
+ it 'allows any User reference' do
+ user = create(:user)
+
+ link = reference_link(user: user.id, reference_filter: Gitlab::Markdown::UserReferenceFilter.name)
+ result = pipeline_result(link)
+
+ expect(result[:references][:user]).to eq([user])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
index fd3f0d20fa..9d9652dba4 100644
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe SnippetReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, :public) }
let(:snippet) { create(:project_snippet, project: project) }
let(:reference) { snippet.to_reference }
@@ -55,12 +55,20 @@ module Gitlab::Markdown
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
end
- it 'includes a data-project-id attribute' do
+ it 'includes a data-project attribute' do
doc = filter("Snippet #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-project-id')
- expect(link.attr('data-project-id')).to eq project.id.to_s
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-snippet attribute' do
+ doc = filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-snippet')
+ expect(link.attr('data-snippet')).to eq snippet.id.to_s
end
it 'supports an :only_path context' do
@@ -72,52 +80,38 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Snippet #{reference}")
+ result = reference_pipeline_result("Snippet #{reference}")
expect(result[:references][:snippet]).to eq [snippet]
end
end
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:empty_project, namespace: namespace) }
+ let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { snippet.to_reference(project) }
- context 'when user can access reference' do
- before { allow_cross_reference! }
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- it 'links to a valid reference' do
- doc = filter("See #{reference}")
-
- expect(doc.css('a').first.attr('href')).
- to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
- end
-
- it 'links with adjacent text' do
- doc = filter("See (#{reference}.)")
- expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/)
- end
-
- it 'ignores invalid snippet IDs on the referenced project' do
- exp = act = "See #{invalidate_reference(reference)}"
-
- expect(filter(act).to_html).to eq exp
- end
-
- it 'adds to the results hash' do
- result = pipeline_result("Snippet #{reference}")
- expect(result[:references][:snippet]).to eq [snippet]
- end
+ expect(doc.css('a').first.attr('href')).
+ to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
- context 'when user cannot access reference' do
- before { disallow_cross_reference! }
+ it 'links with adjacent text' do
+ doc = filter("See (#{reference}.)")
+ expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/)
+ end
- it 'ignores valid references' do
- exp = act = "See #{reference}"
+ it 'ignores invalid snippet IDs on the referenced project' do
+ exp = act = "See #{invalidate_reference(reference)}"
- expect(filter(act).to_html).to eq exp
- end
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Snippet #{reference}")
+ expect(result[:references][:snippet]).to eq [snippet]
end
end
end
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 0000000000..9ae45a6f55
--- /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/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index b2155fab59..d9e0d7c42d 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -4,7 +4,7 @@ module Gitlab::Markdown
describe UserReferenceFilter do
include FilterSpecHelper
- let(:project) { create(:empty_project) }
+ let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) }
let(:reference) { user.to_reference }
@@ -39,7 +39,7 @@ module Gitlab::Markdown
end
it 'adds to the results hash' do
- result = pipeline_result("Hey #{reference}")
+ result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [project.creator]
end
end
@@ -64,59 +64,40 @@ module Gitlab::Markdown
expect(doc.css('a').length).to eq 1
end
- it 'includes a data-user-id attribute' do
+ it 'includes a data-user attribute' do
doc = filter("Hey #{reference}")
link = doc.css('a').first
- expect(link).to have_attribute('data-user-id')
- expect(link.attr('data-user-id')).to eq user.namespace.owner_id.to_s
+ expect(link).to have_attribute('data-user')
+ expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s
end
it 'adds to the results hash' do
- result = pipeline_result("Hey #{reference}")
+ result = reference_pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [user]
end
end
context 'mentioning a group' do
let(:group) { create(:group) }
- let(:user) { create(:user) }
let(:reference) { group.to_reference }
- context 'that the current user can read' do
- before do
- group.add_developer(user)
- end
-
- it 'links to the Group' do
- doc = filter("Hey #{reference}", current_user: user)
- expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
- end
-
- it 'includes a data-group-id attribute' do
- doc = filter("Hey #{reference}", current_user: user)
- link = doc.css('a').first
-
- expect(link).to have_attribute('data-group-id')
- expect(link.attr('data-group-id')).to eq group.id.to_s
- end
-
- it 'adds to the results hash' do
- result = pipeline_result("Hey #{reference}", current_user: user)
- expect(result[:references][:user]).to eq group.users
- end
+ it 'links to the Group' do
+ doc = filter("Hey #{reference}")
+ expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
end
- context 'that the current user cannot read' do
- it 'ignores references to the Group' do
- doc = filter("Hey #{reference}", current_user: user)
- expect(doc.to_html).to eq "Hey #{reference}"
- end
+ it 'includes a data-group attribute' do
+ doc = filter("Hey #{reference}")
+ link = doc.css('a').first
- it 'does not add to the results hash' do
- result = pipeline_result("Hey #{reference}", current_user: user)
- expect(result[:references][:user]).to eq []
- end
+ expect(link).to have_attribute('data-group')
+ expect(link.attr('data-group')).to eq group.id.to_s
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Hey #{reference}")
+ expect(result[:references][:user]).to eq group.users
end
end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index c0083fc85b..fd3ab1fb7c 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -19,10 +19,6 @@ describe Gitlab::OAuth::User do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
it "finds an existing user based on uid and provider (facebook)" do
- # FIXME (rspeicher): It's unlikely that this test is actually doing anything
- # `auth` is never used and removing it entirely doesn't break the test, so
- # what's it doing?
- auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider')
expect( oauth_user.persisted? ).to be_truthy
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 088e34f050..ad84d2274e 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_bar, :guest]
subject.analyze('@foo, @baduser, @bar, and @offteam')
- expect(subject.users).to eq([@u_foo, @u_bar, @u_offteam])
+ expect(subject.users).to match_array([@u_foo, @u_bar, @u_offteam])
end
it 'ignores user mentions inside specific elements' do
@@ -37,7 +37,7 @@ describe Gitlab::ReferenceExtractor do
> @offteam
})
- expect(subject.users).to eq([])
+ expect(subject.users).to match_array([])
end
it 'accesses valid issue objects' do
@@ -45,7 +45,7 @@ describe Gitlab::ReferenceExtractor do
@i1 = create(:issue, project: project)
subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.")
- expect(subject.issues).to eq([@i0, @i1])
+ expect(subject.issues).to match_array([@i0, @i1])
end
it 'accesses valid merge requests' do
@@ -53,7 +53,7 @@ describe Gitlab::ReferenceExtractor do
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'feature_conflict')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.")
- expect(subject.merge_requests).to eq([@m1, @m0])
+ expect(subject.merge_requests).to match_array([@m1, @m0])
end
it 'accesses valid labels' do
@@ -62,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
@l2 = create(:label)
subject.analyze("~#{@l0.id}, ~999, ~#{@l2.id}, ~#{@l1.id}")
- expect(subject.labels).to eq([@l0, @l1])
+ expect(subject.labels).to match_array([@l0, @l1])
end
it 'accesses valid snippets' do
@@ -71,7 +71,7 @@ describe Gitlab::ReferenceExtractor do
@s2 = create(:project_snippet)
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}")
- expect(subject.snippets).to eq([@s0, @s1])
+ expect(subject.snippets).to match_array([@s0, @s1])
end
it 'accesses valid commits' do
@@ -109,7 +109,7 @@ describe Gitlab::ReferenceExtractor do
subject.analyze("this refers issue #{issue.to_reference(project)}")
extracted = subject.issues
expect(extracted.size).to eq(1)
- expect(extracted).to eq([issue])
+ expect(extracted).to match_array([issue])
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 0000000000..260364a513
--- /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/mailers/ci/notify_spec.rb b/spec/mailers/ci/notify_spec.rb
index 20d8ddcd13..b83fb41603 100644
--- a/spec/mailers/ci/notify_spec.rb
+++ b/spec/mailers/ci/notify_spec.rb
@@ -5,8 +5,7 @@ describe Ci::Notify do
include EmailSpec::Matchers
before do
- @project = FactoryGirl.create :ci_project
- @commit = FactoryGirl.create :ci_commit, project: @project
+ @commit = FactoryGirl.create :ci_commit
@build = FactoryGirl.create :ci_build, commit: @commit
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 2c97a521d9..cb67ec95d5 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -52,6 +52,7 @@ describe Notify do
end
it 'has headers that reference an existing thread' do
+ is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
@@ -399,7 +400,7 @@ describe Notify do
describe 'project was moved' do
let(:project) { create(:project) }
let(:user) { create(:user) }
- subject { Notify.project_was_moved_email(project.id, user.id) }
+ subject { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") }
it_behaves_like 'an email sent from GitLab'
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index 635a6e2518..d45319b25d 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -16,4 +16,16 @@ RSpec.describe AbuseReport, type: :model do
subject { create(:abuse_report) }
it { expect(subject).to be_valid }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:reporter).class_name('User') }
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:reporter) }
+ it { is_expected.to validate_presence_of(:user) }
+ it { is_expected.to validate_presence_of(:message) }
+ it { is_expected.to validate_uniqueness_of(:user_id) }
+ end
end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 8ab72151a6..d80748f23a 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -27,12 +27,12 @@ describe BroadcastMessage do
end
it "should return nil if time not come" do
- broadcast_message = create(:broadcast_message, starts_at: Time.now.tomorrow, ends_at: Time.now + 2.days)
+ create(:broadcast_message, starts_at: Time.now.tomorrow, ends_at: Time.now + 2.days)
expect(BroadcastMessage.current).to be_nil
end
it "should return nil if time has passed" do
- broadcast_message = create(:broadcast_message, starts_at: Time.now - 2.days, ends_at: Time.now.yesterday)
+ create(:broadcast_message, starts_at: Time.now - 2.days, ends_at: Time.now.yesterday)
expect(BroadcastMessage.current).to be_nil
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/build_spec.rb
similarity index 55%
rename from spec/models/ci/build_spec.rb
rename to spec/models/build_spec.rb
index ce80115204..7f5abb83ac 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -27,16 +27,12 @@ require 'spec_helper'
describe Ci::Build do
let(:project) { FactoryGirl.create :ci_project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
+ 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 }
- it { is_expected.to belong_to(:commit) }
- 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
@@ -63,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? }
@@ -177,6 +107,17 @@ describe Ci::Build do
it { is_expected.to include(text) }
it { expect(subject.length).to be >= text.length }
end
+
+ context 'if build.trace hides token' do
+ let(:token) { 'my_secret_token' }
+
+ before do
+ build.project.update_attributes(token: token)
+ build.update_attributes(trace: token)
+ end
+
+ it { is_expected.to_not include(token) }
+ end
end
describe :timeout do
@@ -185,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
{
@@ -224,30 +140,6 @@ describe Ci::Build do
it { is_expected.to eq(options) }
end
- describe :ref do
- subject { build.ref }
-
- it { is_expected.to eq(commit.ref) }
- 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 :before_sha do
- subject { build.before_sha }
-
- it { is_expected.to eq(commit.before_sha) }
- end
-
describe :allow_git_fetch do
subject { build.allow_git_fetch }
@@ -308,13 +200,34 @@ describe Ci::Build do
context 'returns variables' do
subject { build.variables }
- let(:variables) do
+ let(:predefined_variables) do
+ [
+ { key: :CI_BUILD_NAME, value: 'test', public: true },
+ { key: :CI_BUILD_STAGE, value: 'stage', public: true },
+ ]
+ end
+
+ let(:yaml_variables) do
[
{ key: :DB_NAME, value: 'postgres', public: true }
]
end
- it { is_expected.to eq(variables) }
+ before { build.update_attributes(stage: 'stage') }
+
+ it { is_expected.to eq(predefined_variables + yaml_variables) }
+
+ context 'for tag' do
+ let(:tag_variable) do
+ [
+ { key: :CI_BUILD_TAG, value: 'master', public: true }
+ ]
+ end
+
+ before { build.update_attributes(tag: true) }
+
+ it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) }
+ end
context 'and secure variables' do
let(:secure_variables) do
@@ -327,7 +240,7 @@ describe Ci::Build do
build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
- it { is_expected.to eq(variables + secure_variables) }
+ it { is_expected.to eq(predefined_variables + yaml_variables + secure_variables) }
context 'and trigger variables' do
let(:trigger) { FactoryGirl.create :ci_trigger, project: project }
@@ -337,14 +250,154 @@ describe Ci::Build do
{ key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
]
end
+ let(:predefined_trigger_variable) do
+ [
+ { key: :CI_BUILD_TRIGGERED, value: 'true', public: true }
+ ]
+ end
before do
build.trigger_request = trigger_request
end
- it { is_expected.to eq(variables + secure_variables + trigger_variables) }
+ it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
end
end
end
end
+
+ describe :project_recipients do
+ let(:pusher_email) { 'pusher@gitlab.test' }
+ let(:user) { User.new(notification_email: pusher_email) }
+ subject { build.project_recipients }
+
+ before do
+ build.update_attributes(user: user)
+ end
+
+ it 'should return pusher_email as only recipient when no additional recipients are given' do
+ project.update_attributes(email_add_pusher: true,
+ email_recipients: '')
+ is_expected.to eq([pusher_email])
+ end
+
+ it 'should return pusher_email and additional recipients' do
+ project.update_attributes(email_add_pusher: true,
+ email_recipients: 'rec1 rec2')
+ is_expected.to eq(['rec1', 'rec2', pusher_email])
+ end
+
+ it 'should return recipients' do
+ project.update_attributes(email_add_pusher: false,
+ email_recipients: 'rec1 rec2')
+ is_expected.to eq(['rec1', 'rec2'])
+ end
+
+ it 'should return unique recipients only' do
+ project.update_attributes(email_add_pusher: true,
+ email_recipients: "rec1 rec1 #{pusher_email}")
+ is_expected.to eq(['rec1', pusher_email])
+ end
+ end
+
+ describe :can_be_served? do
+ let(:runner) { FactoryGirl.create :ci_specific_runner }
+
+ before { build.project.runners << runner }
+
+ context 'runner without tags' do
+ it 'can handle builds without tags' do
+ expect(build.can_be_served?(runner)).to be_truthy
+ end
+
+ it 'cannot handle build with tags' do
+ build.tag_list = ['aa']
+ expect(build.can_be_served?(runner)).to be_falsey
+ end
+ end
+
+ context 'runner with tags' do
+ before { runner.tag_list = ['bb', 'cc'] }
+
+ it 'can handle builds without tags' do
+ expect(build.can_be_served?(runner)).to be_truthy
+ end
+
+ it 'can handle build with matching tags' do
+ build.tag_list = ['bb']
+ expect(build.can_be_served?(runner)).to be_truthy
+ end
+
+ it 'cannot handle build with not matching tags' do
+ build.tag_list = ['aa']
+ expect(build.can_be_served?(runner)).to be_falsey
+ end
+ end
+ end
+
+ describe :any_runners_online? do
+ subject { build.any_runners_online? }
+
+ context 'when no runners' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'if there are runner' do
+ let(:runner) { FactoryGirl.create :ci_specific_runner }
+
+ before do
+ build.project.runners << runner
+ runner.update_attributes(contacted_at: 1.second.ago)
+ end
+
+ it { is_expected.to be_truthy }
+
+ it 'that is inactive' do
+ runner.update_attributes(active: false)
+ is_expected.to be_falsey
+ end
+
+ it 'that is not online' do
+ runner.update_attributes(contacted_at: nil)
+ is_expected.to be_falsey
+ end
+
+ it 'that cannot handle build' do
+ expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false)
+ is_expected.to be_falsey
+ end
+
+ end
+ end
+
+ describe :show_warning? do
+ subject { build.show_warning? }
+
+ %w(pending).each do |state|
+ context "if commit_status.status is #{state}" do
+ before { build.status = state }
+
+ it { is_expected.to be_truthy }
+
+ context "and there are specific runner" do
+ let(:runner) { FactoryGirl.create :ci_specific_runner, contacted_at: 1.second.ago }
+
+ before do
+ build.project.runners << runner
+ runner.save
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ %w(success failed canceled running).each do |state|
+ context "if commit_status.status is #{state}" do
+ before { build.status = state }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index 586c9dc23a..44dbd083f0 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -19,21 +19,37 @@ require 'spec_helper'
describe Ci::Commit do
let(:project) { FactoryGirl.create :ci_project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
- let(:commit_with_project) { FactoryGirl.create :ci_commit, project: project }
- let(:config_processor) { Ci::GitlabCiYamlProcessor.new(gitlab_ci_yaml) }
+ let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
+ let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
- it { is_expected.to belong_to(: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 :before_sha }
it { is_expected.to validate_presence_of :sha }
- it { is_expected.to validate_presence_of :ref }
- it { is_expected.to validate_presence_of :push_data }
it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
+ describe :ordered do
+ let(:project) { FactoryGirl.create :empty_project }
+
+ it 'returns ordered list of commits' do
+ commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
+ commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
+ expect(project.ci_commits.ordered).to eq([commit2, commit1])
+ end
+
+ it 'returns commits ordered by committed_at and id, with nulls last' do
+ commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
+ commit2 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
+ commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
+ commit4 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
+ expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1])
+ end
+ end
+
describe :last_build do
subject { commit.last_build }
before do
@@ -51,53 +67,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
- end
- end
-
- describe :project_recipients do
-
- context 'always sending notification' do
- it 'should return commit_pusher_email as only recipient when no additional recipients are given' do
- project = FactoryGirl.create :ci_project,
- email_add_pusher: true,
- email_recipients: ''
- commit = FactoryGirl.create :ci_commit, project: project
- expected = 'commit_pusher_email'
- allow(commit).to receive(:push_data) { { user_email: expected } }
- expect(commit.project_recipients).to eq([expected])
- end
-
- it 'should return commit_pusher_email and additional recipients' do
- project = FactoryGirl.create :ci_project,
- email_add_pusher: true,
- email_recipients: 'rec1 rec2'
- commit = FactoryGirl.create :ci_commit, project: project
- expected = 'commit_pusher_email'
- allow(commit).to receive(:push_data) { { user_email: expected } }
- expect(commit.project_recipients).to eq(['rec1', 'rec2', expected])
- end
-
- it 'should return recipients' do
- project = FactoryGirl.create :ci_project,
- email_add_pusher: false,
- email_recipients: 'rec1 rec2'
- commit = FactoryGirl.create :ci_commit, project: project
- expect(commit.project_recipients).to eq(['rec1', 'rec2'])
- end
-
- it 'should return unique recipients only' do
- project = FactoryGirl.create :ci_project,
- email_add_pusher: true,
- email_recipients: 'rec1 rec1 rec2'
- commit = FactoryGirl.create :ci_commit, project: project
- expected = 'rec2'
- allow(commit).to receive(:push_data) { { user_email: expected } }
- expect(commit.project_recipients).to eq(['rec1', 'rec2'])
- end
+ expect(commit.statuses.count(:all)).to eq 3
end
end
@@ -112,23 +87,6 @@ describe Ci::Commit do
end
end
- describe :compare? do
- subject { commit_with_project.compare? }
-
- context 'if commit.before_sha are not nil' do
- it { is_expected.to be_truthy }
- end
- end
-
- describe :short_sha do
- subject { commit.short_before_sha }
-
- it 'has 8 items' do
- expect(subject.size).to eq(8)
- end
- it { expect(commit.before_sha).to start_with(subject) }
- end
-
describe :short_sha do
subject { commit.short_sha }
@@ -138,37 +96,113 @@ describe Ci::Commit do
it { expect(commit.sha).to start_with(subject) }
end
- describe :create_next_builds do
+ describe :stage do
+ subject { commit.stage }
+
before do
- allow(commit).to receive(:config_processor).and_return(config_processor)
+ @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 "creates builds for next type" do
- expect(commit.create_builds).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ it 'returns first running stage' do
+ is_expected.to eq('test')
+ end
- expect(commit.create_next_builds(nil)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(4)
+ context 'first build succeeded' do
+ before do
+ @first.success
+ end
- expect(commit.create_next_builds(nil)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(5)
+ it 'returns last running stage' do
+ is_expected.to eq('deploy')
+ end
+ end
- expect(commit.create_next_builds(nil)).to be_falsey
+ context 'all builds succeeded' do
+ before do
+ @first.success
+ @second.success
+ end
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ 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
- before do
- allow(commit).to receive(:config_processor).and_return(config_processor)
+ let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
+
+ def create_builds(trigger_request = nil)
+ commit.create_builds('master', false, nil, trigger_request)
+ end
+
+ def create_next_builds
+ commit.create_next_builds(commit.builds.order(:id).last)
end
it 'creates builds' do
- expect(commit.create_builds).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ expect(create_builds).to be_truthy
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(2)
+
+ expect(create_next_builds).to be_truthy
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(4)
+
+ expect(create_next_builds).to be_truthy
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(5)
+
+ expect(create_next_builds).to be_falsey
+ end
+
+ context 'for different ref' do
+ def create_develop_builds
+ commit.create_builds('develop', false, nil, nil)
+ end
+
+ it 'creates builds' do
+ expect(create_builds).to be_truthy
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(2)
+
+ expect(create_develop_builds).to be_truthy
+ commit.builds.update_all(status: "success")
+ expect(commit.builds.count(:all)).to eq(4)
+ expect(commit.refs.size).to eq(2)
+ expect(commit.builds.pluck(:name).uniq.size).to eq(2)
+ end
end
context 'for build triggers' do
@@ -176,61 +210,179 @@ describe Ci::Commit do
let(:trigger_request) { FactoryGirl.create :ci_trigger_request, commit: commit, trigger: trigger }
it 'creates builds' do
- expect(commit.create_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ expect(create_builds(trigger_request)).to be_truthy
+ expect(commit.builds.count(:all)).to eq(2)
end
it 'rebuilds commit' do
- expect(commit.create_builds).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ expect(create_builds).to be_truthy
+ expect(commit.builds.count(:all)).to eq(2)
- expect(commit.create_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(4)
+ expect(create_builds(trigger_request)).to be_truthy
+ expect(commit.builds.count(:all)).to eq(4)
end
it 'creates next builds' do
- expect(commit.create_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
+ expect(create_builds(trigger_request)).to be_truthy
+ expect(commit.builds.count(:all)).to eq(2)
+ commit.builds.update_all(status: "success")
- expect(commit.create_next_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(4)
+ expect(create_next_builds).to be_truthy
+ expect(commit.builds.count(:all)).to eq(4)
end
context 'for [ci skip]' do
before do
- commit.push_data[:commits][0][:message] = 'skip this commit [ci skip]'
- commit.save
+ allow(commit).to receive(:git_commit_message) { 'message [ci skip]' }
end
it 'rebuilds commit' do
expect(commit.status).to eq('skipped')
- expect(commit.create_builds(trigger_request)).to be_truthy
- commit.builds.reload
- expect(commit.builds.size).to eq(2)
- expect(commit.status).to eq('pending')
+ expect(create_builds).to be_truthy
+
+ # 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
+
+ context 'properly creates builds when "when" is defined' do
+ let(:yaml) do
+ {
+ stages: ["build", "test", "test_failure", "deploy", "cleanup"],
+ build: {
+ stage: "build",
+ script: "BUILD",
+ },
+ test: {
+ stage: "test",
+ script: "TEST",
+ },
+ test_failure: {
+ stage: "test_failure",
+ script: "ON test failure",
+ when: "on_failure",
+ },
+ deploy: {
+ stage: "deploy",
+ script: "PUBLISH",
+ },
+ cleanup: {
+ stage: "cleanup",
+ script: "TIDY UP",
+ when: "always",
+ }
+ }
+ end
+
+ before do
+ stub_ci_commit_yaml_file(YAML.dump(yaml))
+ end
+
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
+ expect(commit.status).to eq('success')
+ end
+
+ it 'properly creates builds when test fails' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
+ expect(commit.status).to eq('failed')
+ end
+
+ it 'properly creates builds when test and test_failure fails' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
+ expect(commit.status).to eq('failed')
+ end
+
+ it 'properly creates builds when deploy fails' do
+ expect(create_builds).to be_truthy
+ expect(commit.builds.pluck(:name)).to contain_exactly('build')
+ expect(commit.builds.pluck(:status)).to contain_exactly('pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ commit.builds.running_or_pending.each(&:drop)
+
+ expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
+ commit.builds.running_or_pending.each(&:success)
+
+ expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
+ expect(commit.status).to eq('failed')
+ end
+ end
end
describe "#finished_at" do
- let(:project) { FactoryGirl.create :ci_project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
+ let(:commit) { FactoryGirl.create :ci_commit }
it "returns finished_at of latest build" do
build = FactoryGirl.create :ci_build, commit: commit, finished_at: Time.now - 60
- build1 = FactoryGirl.create :ci_build, commit: commit, finished_at: Time.now - 120
+ FactoryGirl.create :ci_build, commit: commit, finished_at: Time.now - 120
expect(commit.finished_at.to_i).to eq(build.finished_at.to_i)
end
it "returns nil if there is no finished build" do
- build = FactoryGirl.create :ci_not_started_build, commit: commit
+ FactoryGirl.create :ci_not_started_build, commit: commit
expect(commit.finished_at).to be_nil
end
@@ -238,7 +390,8 @@ describe Ci::Commit do
describe "coverage" do
let(:project) { FactoryGirl.create :ci_project, coverage_regex: "/.*/" }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
+ let(:gl_project) { FactoryGirl.create :empty_project, gitlab_ci_project: project }
+ let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
it "calculates average when there are two builds with coverage" do
FactoryGirl.create :ci_build, name: "rspec", coverage: 30, commit: commit
diff --git a/spec/models/ci/project_services/hip_chat_message_spec.rb b/spec/models/ci/project_services/hip_chat_message_spec.rb
index 49ac086025..e23d6ae2c2 100644
--- a/spec/models/ci/project_services/hip_chat_message_spec.rb
+++ b/spec/models/ci/project_services/hip_chat_message_spec.rb
@@ -3,72 +3,37 @@ require 'spec_helper'
describe Ci::HipChatMessage do
subject { Ci::HipChatMessage.new(build) }
- let(:project) { FactoryGirl.create(:ci_project) }
+ let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) }
- context "One build" do
- let(:commit) { FactoryGirl.create(:ci_commit_with_one_job, project: project) }
+ let(:build) do
+ commit.builds.first
+ end
- let(:build) do
- commit.create_builds
- commit.builds.first
- end
+ context 'when all matrix builds succeed' do
+ it 'returns a successful message' do
+ commit.create_builds('master', false, nil)
+ commit.builds.update_all(status: "success")
+ commit.reload
- context 'when build succeeds' do
- it 'returns a successful message' do
- build.update(status: "success")
-
- expect( subject.status_color ).to eq 'green'
- expect( subject.notify? ).to be_falsey
- expect( subject.to_s ).to match(/Build '[^']+' #\d+/)
- expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./)
- end
- end
-
- context 'when build fails' do
- it 'returns a failure message' do
- build.update(status: "failed")
-
- expect( subject.status_color ).to eq 'red'
- expect( subject.notify? ).to be_truthy
- expect( subject.to_s ).to match(/Build '[^']+' #\d+/)
- expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./)
- end
+ expect(subject.status_color).to eq 'green'
+ expect(subject.notify?).to be_falsey
+ expect(subject.to_s).to match(/Commit #\d+/)
+ expect(subject.to_s).to match(/Successful in \d+ second\(s\)\./)
end
end
- context "Several builds" do
- let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs, project: project) }
+ context 'when at least one matrix build fails' do
+ it 'returns a failure message' do
+ commit.create_builds('master', false, nil)
+ first_build = commit.builds.first
+ second_build = commit.builds.last
+ first_build.update(status: "success")
+ second_build.update(status: "failed")
- let(:build) do
- commit.builds.first
- end
-
- context 'when all matrix builds succeed' do
- it 'returns a successful message' do
- commit.create_builds
- commit.builds.update_all(status: "success")
- commit.reload
-
- expect( subject.status_color ).to eq 'green'
- expect( subject.notify? ).to be_falsey
- expect( subject.to_s ).to match(/Commit #\d+/)
- expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./)
- end
- end
-
- context 'when at least one matrix build fails' do
- it 'returns a failure message' do
- commit.create_builds
- first_build = commit.builds.first
- second_build = commit.builds.last
- first_build.update(status: "success")
- second_build.update(status: "failed")
-
- expect( subject.status_color ).to eq 'red'
- expect( subject.notify? ).to be_truthy
- expect( subject.to_s ).to match(/Commit #\d+/)
- expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./)
- end
+ expect(subject.status_color).to eq 'red'
+ expect(subject.notify?).to be_truthy
+ expect(subject.to_s).to match(/Commit #\d+/)
+ expect(subject.to_s).to match(/Failed in \d+ second\(s\)\./)
end
end
end
diff --git a/spec/models/ci/project_services/hip_chat_service_spec.rb b/spec/models/ci/project_services/hip_chat_service_spec.rb
index 063d46b84d..d9ccc855ed 100644
--- a/spec/models/ci/project_services/hip_chat_service_spec.rb
+++ b/spec/models/ci/project_services/hip_chat_service_spec.rb
@@ -33,15 +33,14 @@ describe Ci::HipChatService do
describe "Execute" do
let(:service) { Ci::HipChatService.new }
- let(:project) { FactoryGirl.create :ci_project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
+ let(:commit) { FactoryGirl.create :ci_commit }
let(:build) { FactoryGirl.create :ci_build, commit: commit, status: 'failed' }
let(:api_url) { 'https://api.hipchat.com/v2/room/123/notification?auth_token=a1b2c3d4e5f6' }
before do
allow(service).to receive_messages(
- project: project,
- project_id: project.id,
+ project: commit.project,
+ project_id: commit.project_id,
notify_only_broken_builds: false,
hipchat_room: 123,
hipchat_token: 'a1b2c3d4e5f6'
diff --git a/spec/models/ci/mail_service_spec.rb b/spec/models/ci/project_services/mail_service_spec.rb
similarity index 73%
rename from spec/models/ci/mail_service_spec.rb
rename to spec/models/ci/project_services/mail_service_spec.rb
index b5f37b349d..d9b3d34ff1 100644
--- a/spec/models/ci/mail_service_spec.rb
+++ b/spec/models/ci/project_services/mail_service_spec.rb
@@ -29,11 +29,13 @@ describe Ci::MailService do
describe 'Sends email for' do
let(:mail) { Ci::MailService.new }
+ let(:user) { User.new(notification_email: 'git@example.com')}
describe 'failed build' do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true) }
- let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit) }
+ 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) }
before do
allow(mail).to receive_messages(
@@ -54,8 +56,9 @@ describe Ci::MailService do
describe 'successfull build' do
let(:project) { FactoryGirl.create(:ci_project, email_add_pusher: true, email_only_broken_builds: false) }
- let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit) }
+ 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) }
before do
allow(mail).to receive_messages(
@@ -81,8 +84,9 @@ describe Ci::MailService do
email_only_broken_builds: false,
email_recipients: "jeroen@example.com")
end
- let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit) }
+ 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) }
before do
allow(mail).to receive_messages(
@@ -109,8 +113,9 @@ describe Ci::MailService do
email_only_broken_builds: true,
email_recipients: "jeroen@example.com")
end
- let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit) }
+ 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) }
before do
allow(mail).to receive_messages(
@@ -137,8 +142,9 @@ describe Ci::MailService do
email_only_broken_builds: false,
email_recipients: "jeroen@example.com")
end
- let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :success, commit: commit) }
+ 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) }
before do
allow(mail).to receive_messages(
@@ -159,8 +165,9 @@ describe Ci::MailService do
email_only_broken_builds: true,
email_recipients: "jeroen@example.com")
end
- let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
- let(:build) { FactoryGirl.create(:ci_build, status: :failed, commit: commit) }
+ 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) }
before do
allow(mail).to receive_messages(
diff --git a/spec/models/ci/project_services/slack_message_spec.rb b/spec/models/ci/project_services/slack_message_spec.rb
index f533590372..8adda6c86c 100644
--- a/spec/models/ci/project_services/slack_message_spec.rb
+++ b/spec/models/ci/project_services/slack_message_spec.rb
@@ -3,82 +3,41 @@ require 'spec_helper'
describe Ci::SlackMessage do
subject { Ci::SlackMessage.new(commit) }
- let(:project) { FactoryGirl.create :ci_project }
+ let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs) }
- context "One build" do
- let(:commit) { FactoryGirl.create(:ci_commit_with_one_job, project: project) }
+ context 'when all matrix builds succeeded' do
+ let(:color) { 'good' }
- let(:build) do
- commit.create_builds
- commit.builds.first
- end
+ it 'returns a message with success' do
+ commit.create_builds('master', false, nil)
+ commit.builds.update_all(status: "success")
+ commit.reload
- context 'when build succeeded' do
- let(:color) { 'good' }
-
- it 'returns a message with succeeded build' do
- build.update(status: "success")
-
- expect(subject.color).to eq(color)
- expect(subject.fallback).to include('Build')
- expect(subject.fallback).to include("\##{build.id}")
- expect(subject.fallback).to include('succeeded')
- expect(subject.attachments.first[:fields]).to be_empty
- end
- end
-
- context 'when build failed' do
- let(:color) { 'danger' }
-
- it 'returns a message with failed build' do
- build.update(status: "failed")
-
- expect(subject.color).to eq(color)
- expect(subject.fallback).to include('Build')
- expect(subject.fallback).to include("\##{build.id}")
- expect(subject.fallback).to include('failed')
- expect(subject.attachments.first[:fields]).to be_empty
- end
+ expect(subject.color).to eq(color)
+ expect(subject.fallback).to include('Commit')
+ expect(subject.fallback).to include("\##{commit.id}")
+ expect(subject.fallback).to include('succeeded')
+ expect(subject.attachments.first[:fields]).to be_empty
end
end
- context "Several builds" do
- let(:commit) { FactoryGirl.create(:ci_commit_with_two_jobs, project: project) }
+ context 'when one of matrix builds failed' do
+ let(:color) { 'danger' }
- context 'when all matrix builds succeeded' do
- let(:color) { 'good' }
+ it 'returns a message with information about failed build' do
+ commit.create_builds('master', false, nil)
+ first_build = commit.builds.first
+ second_build = commit.builds.last
+ first_build.update(status: "success")
+ second_build.update(status: "failed")
- it 'returns a message with success' do
- commit.create_builds
- commit.builds.update_all(status: "success")
- commit.reload
-
- expect(subject.color).to eq(color)
- expect(subject.fallback).to include('Commit')
- expect(subject.fallback).to include("\##{commit.id}")
- expect(subject.fallback).to include('succeeded')
- expect(subject.attachments.first[:fields]).to be_empty
- end
- end
-
- context 'when one of matrix builds failed' do
- let(:color) { 'danger' }
-
- it 'returns a message with information about failed build' do
- commit.create_builds
- first_build = commit.builds.first
- second_build = commit.builds.last
- first_build.update(status: "success")
- second_build.update(status: "failed")
-
- expect(subject.color).to eq(color)
- expect(subject.fallback).to include('Commit')
- expect(subject.fallback).to include("\##{commit.id}")
- expect(subject.fallback).to include('failed')
- expect(subject.attachments.first[:fields].size).to eq(1)
- expect(subject.attachments.first[:fields].first[:title]).to eq(second_build.name)
- expect(subject.attachments.first[:fields].first[:value]).to include("\##{second_build.id}")
- end
+ expect(subject.color).to eq(color)
+ expect(subject.fallback).to include('Commit')
+ expect(subject.fallback).to include("\##{commit.id}")
+ expect(subject.fallback).to include('failed')
+ expect(subject.attachments.first[:fields].size).to eq(1)
+ expect(subject.attachments.first[:fields].first[:title]).to eq(second_build.name)
+ expect(subject.attachments.first[:fields].first[:value]).to include("\##{second_build.id}")
end
end
end
diff --git a/spec/models/ci/project_services/slack_service_spec.rb b/spec/models/ci/project_services/slack_service_spec.rb
index 0524f47243..1ac7dfe568 100644
--- a/spec/models/ci/project_services/slack_service_spec.rb
+++ b/spec/models/ci/project_services/slack_service_spec.rb
@@ -31,16 +31,15 @@ describe Ci::SlackService do
describe "Execute" do
let(:slack) { Ci::SlackService.new }
- let(:project) { FactoryGirl.create :ci_project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
+ let(:commit) { FactoryGirl.create :ci_commit }
let(:build) { FactoryGirl.create :ci_build, commit: commit, status: 'failed' }
let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
let(:notify_only_broken_builds) { false }
before do
allow(slack).to receive_messages(
- project: project,
- project_id: project.id,
+ project: commit.project,
+ project_id: commit.project_id,
webhook: webhook_url,
notify_only_broken_builds: notify_only_broken_builds
)
diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb
index 261ea69f5b..490c6a6798 100644
--- a/spec/models/ci/project_spec.rb
+++ b/spec/models/ci/project_spec.rb
@@ -28,13 +28,20 @@
require 'spec_helper'
describe Ci::Project do
- subject { FactoryGirl.build :ci_project }
+ let(:gl_project) { FactoryGirl.create :empty_project }
+ let(:project) { FactoryGirl.create :ci_project, gl_project: gl_project }
+ subject { project }
- it { is_expected.to have_many(:commits) }
+ it { is_expected.to have_many(:runner_projects) }
+ it { is_expected.to have_many(:runners) }
+ it { is_expected.to have_many(:web_hooks) }
+ it { is_expected.to have_many(:events) }
+ it { is_expected.to have_many(:variables) }
+ it { is_expected.to have_many(:triggers) }
+ it { is_expected.to have_many(:services) }
- it { is_expected.to validate_presence_of :name }
it { is_expected.to validate_presence_of :timeout }
- it { is_expected.to validate_presence_of :default_ref }
+ it { is_expected.to validate_presence_of :gitlab_id }
describe 'before_validation' do
it 'should set an random token if none provided' do
@@ -48,43 +55,89 @@ describe Ci::Project do
end
end
- describe "ordered_by_last_commit_date" do
- it "returns ordered projects" do
- newest_project = FactoryGirl.create :ci_project
- oldest_project = FactoryGirl.create :ci_project
- project_without_commits = FactoryGirl.create :ci_project
+ describe :name_with_namespace do
+ subject { project.name_with_namespace }
- FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: newest_project
- FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, project: oldest_project
-
- expect(Ci::Project.ordered_by_last_commit_date).to eq([newest_project, oldest_project, project_without_commits])
- end
+ it { is_expected.to eq(project.name) }
+ it { is_expected.to eq(gl_project.name_with_namespace) }
end
- describe 'ordered commits' do
- let(:project) { FactoryGirl.create :ci_project }
+ describe :path_with_namespace do
+ subject { project.path_with_namespace }
- it 'returns ordered list of commits' do
- commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
- commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, project: project
- expect(project.commits).to eq([commit2, commit1])
+ it { is_expected.to eq(project.path) }
+ it { is_expected.to eq(gl_project.path_with_namespace) }
+ end
+
+ describe :path_with_namespace do
+ subject { project.web_url }
+
+ it { is_expected.to eq(gl_project.web_url) }
+ end
+
+ describe :web_url do
+ subject { project.web_url }
+
+ it { is_expected.to eq(project.gitlab_url) }
+ it { is_expected.to eq(gl_project.web_url) }
+ end
+
+ describe :http_url_to_repo do
+ subject { project.http_url_to_repo }
+
+ it { is_expected.to eq(gl_project.http_url_to_repo) }
+ end
+
+ describe :ssh_url_to_repo do
+ subject { project.ssh_url_to_repo }
+
+ it { is_expected.to eq(gl_project.ssh_url_to_repo) }
+ end
+
+ describe :commits do
+ subject { project.commits }
+
+ before do
+ FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: gl_project
end
- it 'returns commits ordered by committed_at and id, with nulls last' do
- commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
- commit2 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
- commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, project: project
- commit4 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
- expect(project.commits).to eq([commit2, commit4, commit3, commit1])
+ it { is_expected.to eq(gl_project.ci_commits) }
+ end
+
+ describe :builds do
+ subject { project.builds }
+
+ before do
+ commit = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: gl_project
+ FactoryGirl.create :ci_build, commit: commit
+ end
+
+ it { is_expected.to eq(gl_project.ci_builds) }
+ end
+
+ describe "ordered_by_last_commit_date" do
+ it "returns ordered projects" do
+ newest_project = FactoryGirl.create :empty_project
+ newest_ci_project = newest_project.ensure_gitlab_ci_project
+ oldest_project = FactoryGirl.create :empty_project
+ oldest_ci_project = oldest_project.ensure_gitlab_ci_project
+ project_without_commits = FactoryGirl.create :empty_project
+ ci_project_without_commits = project_without_commits.ensure_gitlab_ci_project
+
+ FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: newest_project
+ FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: oldest_project
+
+ expect(Ci::Project.ordered_by_last_commit_date).to eq([newest_ci_project, oldest_ci_project, ci_project_without_commits])
end
end
context :valid_project do
- let(:project) { FactoryGirl.create :ci_project }
+ let(:commit) { FactoryGirl.create(:ci_commit) }
context :project_with_commit_and_builds do
+ let(:project) { commit.project }
+
before do
- commit = FactoryGirl.create(:ci_commit, project: project)
FactoryGirl.create(:ci_build, commit: commit)
end
@@ -165,13 +218,6 @@ describe Ci::Project do
it { is_expected.to include(project.gitlab_url[7..-1]) }
end
- describe :search do
- let!(:project) { FactoryGirl.create(:ci_project, name: "foo") }
-
- it { expect(Ci::Project.search('fo')).to include(project) }
- it { expect(Ci::Project.search('bar')).to be_empty }
- end
-
describe :any_runners do
it "there are no runners available" do
project = FactoryGirl.create(:ci_project)
@@ -195,5 +241,18 @@ describe Ci::Project do
FactoryGirl.create(:ci_shared_runner)
expect(project.any_runners?).to be_falsey
end
+
+ it "checks the presence of specific runner" do
+ project = FactoryGirl.create(:ci_project)
+ specific_runner = FactoryGirl.create(:ci_specific_runner)
+ project.runners << specific_runner
+ expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
+ end
+
+ it "checks the presence of shared runner" do
+ project = FactoryGirl.create(:ci_project, shared_runners_enabled: true)
+ shared_runner = FactoryGirl.create(:ci_shared_runner)
+ expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
+ end
end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 757593a7ab..f8a51c29dc 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -32,7 +32,7 @@ describe Ci::Runner do
end
it 'should return the token if the description is an empty string' do
- runner = FactoryGirl.build(:ci_runner, description: '')
+ runner = FactoryGirl.build(:ci_runner, description: '', token: 'token')
expect(runner.display_name).to eq runner.token
end
end
@@ -48,6 +48,71 @@ describe Ci::Runner do
it { expect(shared_runner.only_for?(project)).to be_truthy }
end
+ describe :online do
+ subject { Ci::Runner.online }
+
+ before do
+ @runner1 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.year.ago)
+ @runner2 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago)
+ end
+
+ it { is_expected.to eq([@runner2])}
+ end
+
+ describe :online? do
+ let(:runner) { FactoryGirl.create(:ci_shared_runner) }
+
+ subject { runner.online? }
+
+ context 'never contacted' do
+ before { runner.contacted_at = nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'contacted long time ago time' do
+ before { runner.contacted_at = 1.year.ago }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'contacted 1s ago' do
+ before { runner.contacted_at = 1.second.ago }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe :status do
+ let(:runner) { FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) }
+
+ subject { runner.status }
+
+ context 'never connected' do
+ before { runner.contacted_at = nil }
+
+ it { is_expected.to eq(:not_connected) }
+ end
+
+ context 'contacted 1s ago' do
+ before { runner.contacted_at = 1.second.ago }
+
+ it { is_expected.to eq(:online) }
+ end
+
+ context 'contacted long time ago' do
+ before { runner.contacted_at = 1.year.ago }
+
+ it { is_expected.to eq(:offline) }
+ end
+
+ context 'inactive' do
+ before { runner.active = false }
+
+ it { is_expected.to eq(:paused) }
+ end
+ end
+
describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do
runner = FactoryGirl.create(:ci_specific_runner)
diff --git a/spec/models/ci/service_spec.rb b/spec/models/ci/service_spec.rb
index 2c575056b0..2df70e8888 100644
--- a/spec/models/ci/service_spec.rb
+++ b/spec/models/ci/service_spec.rb
@@ -29,13 +29,12 @@ describe Ci::Service do
end
describe "Testable" do
- let(:project) { FactoryGirl.create :ci_project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
+ let(:commit) { FactoryGirl.create :ci_commit }
let(:build) { FactoryGirl.create :ci_build, commit: commit }
before do
allow(@service).to receive_messages(
- project: project
+ project: commit.project
)
build
@testable = @service.can_test?
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index e303a97e6b..90be932495 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -89,9 +89,9 @@ eos
end
it_behaves_like 'a mentionable' do
- subject { commit }
+ subject { create(:project).commit }
- let(:author) { create(:user, email: commit.author_email) }
+ let(:author) { create(:user, email: subject.author_email) }
let(:backref_text) { "commit #{subject.id}" }
let(:set_mentionable_text) do
->(txt) { allow(subject).to receive(:safe_message).and_return(txt) }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
new file mode 100644
index 0000000000..c96a606fda
--- /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 0000000000..f7ed30f819
--- /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/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 8f706f8934..0f13c4410c 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -68,7 +68,6 @@ describe Issue, "Issuable" do
end
end
-
describe "#to_hook_data" do
let(:hook_data) { issue.to_hook_data(user) }
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 2d6fe00321..6179882e93 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -25,7 +25,7 @@ describe Issue, "Mentionable" do
it 'correctly removes already-mentioned Commits' do
expect(SystemNoteService).not_to receive(:cross_reference)
- issue.create_cross_references!(project, author, [commit2])
+ issue.create_cross_references!(author, [commit2])
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 0000000000..f442fa5fbe
--- /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/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index dae7e399cf..a2dc66fce3 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -22,7 +22,7 @@ describe ProjectHook do
describe '.push_hooks' do
it 'should return hooks for push events only' do
hook = create(:project_hook, push_events: true)
- hook2 = create(:project_hook, push_events: false)
+ create(:project_hook, push_events: false)
expect(ProjectHook.push_hooks).to eq([hook])
end
end
@@ -30,7 +30,7 @@ describe ProjectHook do
describe '.tag_push_hooks' do
it 'should return hooks for tag push events only' do
hook = create(:project_hook, tag_push_events: true)
- hook2 = create(:project_hook, tag_push_events: false)
+ create(:project_hook, tag_push_events: false)
expect(ProjectHook.tag_push_hooks).to eq([hook])
end
end
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 4c8b8910ae..16641c1212 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -39,8 +39,6 @@ describe ServiceHook do
end
it "POSTs the data as JSON" do
- json = @data.to_json
-
@service_hook.execute(@data)
expect(WebMock).to have_requested(:post, @service_hook.url).with(
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook' }
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 23f30881d9..2fdc49f02e 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -60,8 +60,6 @@ describe ProjectHook do
end
it "POSTs the data as JSON" do
- json = @data.to_json
-
@project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, @project_hook.url).with(
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index cf336d8295..c9aa1b063c 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -68,8 +68,45 @@ describe Issue do
end
end
+ describe '#closed_by_merge_requests' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project, state: "opened")}
+ let(:closed_issue) { build(:issue, project: project, state: "closed")}
+
+ let(:mr) do
+ opts = {
+ title: 'Awesome merge_request',
+ description: "Fixes #{issue.to_reference}",
+ source_branch: 'feature',
+ target_branch: 'master'
+ }
+ MergeRequests::CreateService.new(project, project.owner, opts).execute
+ end
+
+ let(:closed_mr) do
+ opts = {
+ title: 'Awesome merge_request 2',
+ description: "Fixes #{issue.to_reference}",
+ source_branch: 'feature',
+ target_branch: 'master',
+ state: 'closed'
+ }
+ MergeRequests::CreateService.new(project, project.owner, opts).execute
+ end
+
+ it 'returns the merge request to close this issue' do
+ allow(mr).to receive(:closes_issue?).with(issue).and_return(true)
+
+ expect(issue.closed_by_merge_requests).to eq([mr])
+ end
+
+ it "returns an empty array when the current issue is closed already" do
+ expect(closed_issue.closed_by_merge_requests).to eq([])
+ end
+ end
+
it_behaves_like 'an editable mentionable' do
- subject { create(:issue, project: project) }
+ subject { create(:issue) }
let(:backref_text) { "issue #{subject.to_reference}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 17a49013d2..6aaf1c036b 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/milestone_spec.rb b/spec/models/milestone_spec.rb
index 36352e1ecc..77c5862732 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -111,8 +111,8 @@ describe Milestone do
describe :is_empty? do
before do
- issue = create :closed_issue, milestone: milestone
- merge_request = create :merge_request, milestone: milestone
+ create :closed_issue, milestone: milestone
+ create :merge_request, milestone: milestone
end
it 'Should return total count of issues and merge requests assigned to milestone' do
@@ -125,7 +125,7 @@ describe Milestone do
milestone = create :milestone
create :closed_issue, milestone: milestone
- issue = create :issue
+ create :issue
end
it 'should be true if milestone active and all nested issues closed' do
@@ -140,4 +140,32 @@ describe Milestone do
end
end
+ describe '#sort_issues' do
+ let(:milestone) { create(:milestone) }
+
+ let(:issue1) { create(:issue, milestone: milestone, position: 1) }
+ let(:issue2) { create(:issue, milestone: milestone, position: 2) }
+ let(:issue3) { create(:issue, milestone: milestone, position: 3) }
+ let(:issue4) { create(:issue, position: 42) }
+
+ it 'sorts the given issues' do
+ milestone.sort_issues([issue3.id, issue2.id, issue1.id])
+
+ issue1.reload
+ issue2.reload
+ issue3.reload
+
+ expect(issue1.position).to eq(3)
+ expect(issue2.position).to eq(2)
+ expect(issue3.position).to eq(1)
+ end
+
+ it 'ignores issues not part of the milestone' do
+ milestone.sort_issues([issue3.id, issue2.id, issue1.id, issue4.id])
+
+ issue4.reload
+
+ expect(issue4.position).to eq(42)
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 3a0b194ba1..75564839dc 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -192,10 +192,9 @@ describe Note do
end
it_behaves_like 'an editable mentionable' do
- subject { create :note, noteable: issue, project: project }
+ subject { create :note, noteable: issue, project: issue.project }
- let(:project) { create(:project) }
- let(:issue) { create :issue, project: project }
+ let(:issue) { create :issue }
let(:backref_text) { issue.gfm_reference }
let(:set_mentionable_text) { ->(txt) { subject.note = txt } }
end
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 0000000000..c34b2487ec
--- /dev/null
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -0,0 +1,94 @@
+# == 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) }
+
+ context "when a password was previously set" do
+ 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
+
+ it "does not reset password if new url is set together with password, even if it's the same password" do
+ @bamboo_service.bamboo_url = 'http://gitlab_edited.com'
+ @bamboo_service.password = 'password'
+ @bamboo_service.save
+ expect(@bamboo_service.password).to eq("password")
+ expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com")
+ end
+
+ it "should reset password if url changed, even if setter called multiple times" do
+ @bamboo_service.bamboo_url = 'http://gitlab1.com'
+ @bamboo_service.bamboo_url = 'http://gitlab1.com'
+ @bamboo_service.save
+ expect(@bamboo_service.password).to be_nil
+ end
+ end
+
+ context "when no password was previously set" do
+ before do
+ @bamboo_service = BambooService.create(
+ project: create(:project),
+ properties: {
+ bamboo_url: 'http://gitlab.com',
+ username: 'mic'
+ }
+ )
+ end
+
+ it "saves password if new url is set together with password" do
+ @bamboo_service.bamboo_url = 'http://gitlab_edited.com'
+ @bamboo_service.password = 'password'
+ @bamboo_service.save
+ expect(@bamboo_service.password).to eq("password")
+ expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com")
+ end
+
+ end
+ end
+end
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
index 8cdd551a0c..842089ebe0 100644
--- a/spec/models/project_services/gitlab_ci_service_spec.rb
+++ b/spec/models/project_services/gitlab_ci_service_spec.rb
@@ -39,8 +39,7 @@ describe GitlabCiService do
end
describe :build_page do
- it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/ci/projects/#{@ci_project.id}/refs/master/commits/2ab7834c")}
- it { expect(@service.build_page("issue#2", 'master')).to eq("http://localhost/ci/projects/#{@ci_project.id}/refs/master/commits/issue%232")}
+ it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://localhost/#{@ci_project.gl_project.path_with_namespace}/commit/2ab7834c/ci")}
end
describe "execute" do
@@ -48,33 +47,11 @@ describe GitlabCiService do
let(:project) { create(:project, name: 'project') }
let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
- it "calls ci_yaml_file" do
- service_hook = double
- expect(@service).to receive(:ci_yaml_file).with(push_sample_data[:checkout_sha])
+ it "calls CreateCommitService" do
+ expect_any_instance_of(Ci::CreateCommitService).to receive(:execute).with(@ci_project, user, push_sample_data)
@service.execute(push_sample_data)
end
end
end
-
- describe "Fork registration" do
- before do
- @old_project = create(:ci_project).gl_project
- @project = create(:empty_project)
- @user = create(:user)
-
- @service = GitlabCiService.new
- allow(@service).to receive_messages(
- service_hook: true,
- project_url: 'http://ci.gitlab.org/projects/2',
- token: 'verySecret',
- project: @old_project
- )
- end
-
- it "creates fork on CI" do
- expect_any_instance_of(Ci::CreateProjectService).to receive(:execute)
- @service.fork_registration(@project, @user)
- end
- end
end
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 65d16beef9..f67d7b3098 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -87,7 +87,7 @@ describe HipchatService do
it "should create a push message" do
message = hipchat.send(:create_push_message, push_sample_data)
- obj_attr = push_sample_data[:object_attributes]
+ push_sample_data[:object_attributes]
branch = push_sample_data[:ref].gsub('refs/heads/', '')
expect(message).to include("#{user.name} pushed to branch " \
"#{branch} of " \
@@ -107,7 +107,7 @@ describe HipchatService do
it "should create a tag push message" do
message = hipchat.send(:create_push_message, push_sample_data)
- obj_attr = push_sample_data[:object_attributes]
+ push_sample_data[:object_attributes]
expect(message).to eq("#{user.name} pushed new tag " \
"test to " \
"#{project_name}\n")
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 0000000000..f26b47a856
--- /dev/null
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -0,0 +1,93 @@
+# == 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) }
+
+ context "when a password was previously set" do
+ 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
+
+ it "does not reset password if new url is set together with password, even if it's the same password" do
+ @teamcity_service.teamcity_url = 'http://gitlab_edited.com'
+ @teamcity_service.password = 'password'
+ @teamcity_service.save
+ expect(@teamcity_service.password).to eq("password")
+ expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com")
+ end
+
+ it "should reset password if url changed, even if setter called multiple times" do
+ @teamcity_service.teamcity_url = 'http://gitlab1.com'
+ @teamcity_service.teamcity_url = 'http://gitlab1.com'
+ @teamcity_service.save
+ expect(@teamcity_service.password).to be_nil
+ end
+ end
+
+ context "when no password was previously set" do
+ before do
+ @teamcity_service = TeamcityService.create(
+ project: create(:project),
+ properties: {
+ teamcity_url: 'http://gitlab.com',
+ username: 'mic'
+ }
+ )
+ end
+
+ it "saves password if new url is set together with password" do
+ @teamcity_service.teamcity_url = 'http://gitlab_edited.com'
+ @teamcity_service.password = 'password'
+ @teamcity_service.save
+ expect(@teamcity_service.password).to eq("password")
+ expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com")
+ end
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 9e7b6f5cb3..f93935ebe3 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -140,7 +140,7 @@ describe Project do
describe 'last_activity_date' do
it 'returns the creation date of the project\'s last event if present' do
- last_activity_event = create(:event, project: project)
+ create(:event, project: project)
expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i)
end
@@ -220,6 +220,7 @@ describe Project do
end
it { expect(Project.find_with_namespace('gitlab/gitlabhq')).to eq(@project) }
+ it { expect(Project.find_with_namespace('GitLab/GitlabHQ')).to eq(@project) }
it { expect(Project.find_with_namespace('gitlab-ci')).to be_nil }
end
end
@@ -404,21 +405,60 @@ describe Project do
describe :ci_commit do
let(:project) { create :project }
- let(:ci_project) { create :ci_project, gl_project: project }
- let(:commit) { create :ci_commit, project: ci_project }
+ let(:commit) { create :ci_commit, gl_project: project }
- before { project.create_gitlab_ci_service(active: true) }
+ before do
+ project.ensure_gitlab_ci_project
+ project.create_gitlab_ci_service(active: true)
+ end
it { expect(project.ci_commit(commit.sha)).to eq(commit) }
end
describe :enable_ci do
let(:project) { create :project }
- let(:user) { create :user }
- before { project.enable_ci(user) }
+ before { project.enable_ci }
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/project_team_spec.rb b/spec/models/project_team_spec.rb
index cc1138490a..26e8fdae47 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -66,4 +66,16 @@ describe ProjectTeam do
it { expect(project.team.member?(guest)).to be_truthy }
end
end
+
+ describe "#human_max_access" do
+ it "return master role" do
+ user = create :user
+ group = create :group
+ group.add_users([user.id], GroupMember::MASTER)
+ project = create(:project, namespace: group)
+ project.team << [user, :guest]
+
+ expect(project.team.human_max_access(user.id)).to eq("Master")
+ end
+ end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index f785203af7..94802dcfb7 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -231,7 +231,7 @@ describe ProjectWiki do
end
def commit_details
- commit = { name: user.name, email: user.email, message: "test commit" }
+ { name: user.name, email: user.email, message: "test commit" }
end
def create_page(name, content)
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index a213ffe6c4..692e5fda3b 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -103,4 +103,125 @@ describe Service do
end
end
end
+
+ describe "{property}_changed?" do
+ let(:service) do
+ BambooService.create(
+ project: create(:project),
+ properties: {
+ bamboo_url: 'http://gitlab.com',
+ username: 'mic',
+ password: "password"
+ }
+ )
+ end
+
+ it "returns false when the property has not been assigned a new value" do
+ service.username = "key_changed"
+ expect(service.bamboo_url_changed?).to be_falsy
+ end
+
+ it "returns true when the property has been assigned a different value" do
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_changed?).to be_truthy
+ end
+
+ it "returns true when the property has been assigned a different value twice" do
+ service.bamboo_url = "http://example.com"
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_changed?).to be_truthy
+ end
+
+ it "returns false when the property has been re-assigned the same value" do
+ service.bamboo_url = 'http://gitlab.com'
+ expect(service.bamboo_url_changed?).to be_falsy
+ end
+
+ it "returns false when the property has been assigned a new value then saved" do
+ service.bamboo_url = 'http://example.com'
+ service.save
+ expect(service.bamboo_url_changed?).to be_falsy
+ end
+ end
+
+ describe "{property}_touched?" do
+ let(:service) do
+ BambooService.create(
+ project: create(:project),
+ properties: {
+ bamboo_url: 'http://gitlab.com',
+ username: 'mic',
+ password: "password"
+ }
+ )
+ end
+
+ it "returns false when the property has not been assigned a new value" do
+ service.username = "key_changed"
+ expect(service.bamboo_url_touched?).to be_falsy
+ end
+
+ it "returns true when the property has been assigned a different value" do
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_touched?).to be_truthy
+ end
+
+ it "returns true when the property has been assigned a different value twice" do
+ service.bamboo_url = "http://example.com"
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_touched?).to be_truthy
+ end
+
+ it "returns true when the property has been re-assigned the same value" do
+ service.bamboo_url = 'http://gitlab.com'
+ expect(service.bamboo_url_touched?).to be_truthy
+ end
+
+ it "returns false when the property has been assigned a new value then saved" do
+ service.bamboo_url = 'http://example.com'
+ service.save
+ expect(service.bamboo_url_changed?).to be_falsy
+ end
+ end
+
+ describe "{property}_was" do
+ let(:service) do
+ BambooService.create(
+ project: create(:project),
+ properties: {
+ bamboo_url: 'http://gitlab.com',
+ username: 'mic',
+ password: "password"
+ }
+ )
+ end
+
+
+ it "returns nil when the property has not been assigned a new value" do
+ service.username = "key_changed"
+ expect(service.bamboo_url_was).to be_nil
+ end
+
+ it "returns the previous value when the property has been assigned a different value" do
+ service.bamboo_url = "http://example.com"
+ expect(service.bamboo_url_was).to eq('http://gitlab.com')
+ end
+
+ it "returns initial value when the property has been re-assigned the same value" do
+ service.bamboo_url = 'http://gitlab.com'
+ expect(service.bamboo_url_was).to eq('http://gitlab.com')
+ end
+
+ it "returns initial value when the property has been assigned multiple values" do
+ service.bamboo_url = "http://example.com"
+ service.bamboo_url = "http://example2.com"
+ expect(service.bamboo_url_was).to eq('http://gitlab.com')
+ end
+
+ it "returns nil when the property has been assigned a new value then saved" do
+ service.bamboo_url = 'http://example.com'
+ service.save
+ expect(service.bamboo_url_was).to be_nil
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 480950859a..c71cfb3ebe 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -85,6 +85,7 @@ describe User do
it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) }
it { is_expected.to have_many(:identities).dependent(:destroy) }
+ it { is_expected.to have_one(:abuse_report) }
end
describe 'validations' do
@@ -227,6 +228,26 @@ describe User do
end
end
+ describe '#recently_sent_password_reset?' do
+ it 'is false when reset_password_sent_at is nil' do
+ user = build_stubbed(:user, reset_password_sent_at: nil)
+
+ expect(user.recently_sent_password_reset?).to eq false
+ end
+
+ it 'is false when sent more than one minute ago' do
+ user = build_stubbed(:user, reset_password_sent_at: 5.minutes.ago)
+
+ expect(user.recently_sent_password_reset?).to eq false
+ end
+
+ it 'is true when sent less than one minute ago' do
+ user = build_stubbed(:user, reset_password_sent_at: Time.now)
+
+ expect(user.recently_sent_password_reset?).to eq true
+ end
+ end
+
describe '#disable_two_factor!' do
it 'clears all 2FA-related fields' do
user = create(:user, :two_factor)
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index dc84a14bb4..d7802d1734 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -196,7 +196,7 @@ describe WikiPage do
end
def commit_details
- commit = { name: user.name, email: user.email, message: "test commit" }
+ { name: user.name, email: user.email, message: "test commit" }
end
def create_page(name, content)
diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb
new file mode 100644
index 0000000000..b9e6dfc15a
--- /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 a1c248c636..49acc3368f 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/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 35b3d3e296..a68c7b1e46 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -379,9 +379,14 @@ describe API::API, api: true do
describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
it "should return comment" do
+ original_count = merge_request.notes.size
+
post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
expect(response.status).to eq(201)
expect(json_response['note']).to eq('My comment')
+ expect(json_response['author']['name']).to eq(user.name)
+ expect(json_response['author']['username']).to eq(user.username)
+ expect(merge_request.notes.size).to eq(original_count + 1)
end
it "should return 400 if note is missing" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 580bbec77d..e9de9e0826 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -606,28 +606,42 @@ describe API::API, api: true do
describe 'DELETE /projects/:id/fork' do
- it "shouldn't available for non admin users" do
+ it "shouldn't be visible to users outside group" do
delete api("/projects/#{project_fork_target.id}/fork", user)
- expect(response.status).to eq(403)
+ expect(response.status).to eq(404)
end
- it 'should make forked project unforked' do
- post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
- project_fork_target.reload
- expect(project_fork_target.forked_from_project).not_to be_nil
- expect(project_fork_target.forked?).to be_truthy
- delete api("/projects/#{project_fork_target.id}/fork", admin)
- expect(response.status).to eq(200)
- project_fork_target.reload
- expect(project_fork_target.forked_from_project).to be_nil
- expect(project_fork_target.forked?).not_to be_truthy
- end
+ context 'when users belong to project group' do
+ let(:project_fork_target) { create(:project, group: create(:group)) }
- it 'should be idempotent if not forked' do
- expect(project_fork_target.forked_from_project).to be_nil
- delete api("/projects/#{project_fork_target.id}/fork", admin)
- expect(response.status).to eq(200)
- expect(project_fork_target.reload.forked_from_project).to be_nil
+ before do
+ project_fork_target.group.add_owner user
+ project_fork_target.group.add_developer user2
+ end
+
+ it 'should be forbidden to non-owner users' do
+ delete api("/projects/#{project_fork_target.id}/fork", user2)
+ expect(response.status).to eq(403)
+ end
+
+ it 'should make forked project unforked' do
+ post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+ project_fork_target.reload
+ expect(project_fork_target.forked_from_project).not_to be_nil
+ expect(project_fork_target.forked?).to be_truthy
+ delete api("/projects/#{project_fork_target.id}/fork", admin)
+ expect(response.status).to eq(200)
+ project_fork_target.reload
+ expect(project_fork_target.forked_from_project).to be_nil
+ expect(project_fork_target.forked?).not_to be_truthy
+ end
+
+ it 'should be idempotent if not forked' do
+ expect(project_fork_target.forked_from_project).to be_nil
+ delete api("/projects/#{project_fork_target.id}/fork", admin)
+ expect(response.status).to eq(200)
+ expect(project_fork_target.reload.forked_from_project).to be_nil
+ end
end
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 09a79553f7..1149f7e798 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -166,24 +166,21 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/)
- expect(response.content_type).to eq(MIME::Types.type_for('file.tar.gz').first.content_type)
+ expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
end
it "should get the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.zip\"/)
- expect(response.content_type).to eq(MIME::Types.type_for('file.zip').first.content_type)
+ expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
end
it "should get the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
- expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/)
- expect(response.content_type).to eq(MIME::Types.type_for('file.tar.bz2').first.content_type)
+ expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
end
it "should return 404 for invalid sha" do
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 9aa60826f2..c0226605a2 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/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index c25d182330..88218a93e1 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -5,10 +5,16 @@ describe Ci::API::API do
let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) }
let(:project) { FactoryGirl.create(:ci_project) }
+ let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
+
+ before do
+ stub_ci_commit_to_return_yaml_file
+ end
describe "Builds API for runners" do
let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") }
let(:shared_project) { FactoryGirl.create(:ci_project, name: "SharedProject") }
+ let(:shared_gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: shared_project) }
before do
FactoryGirl.create :ci_runner_project, project_id: project.id, runner_id: runner.id
@@ -16,8 +22,8 @@ describe Ci::API::API do
describe "POST /builds/register" do
it "should start a build" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds
+ commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
+ commit.create_builds('master', false, nil)
build = commit.builds.first
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -34,7 +40,7 @@ describe Ci::API::API do
end
it "should return 404 error if no builds for specific runner" do
- commit = FactoryGirl.create(:ci_commit, project: shared_project)
+ commit = FactoryGirl.create(:ci_commit, gl_project: shared_gl_project)
FactoryGirl.create(:ci_build, commit: commit, status: 'pending' )
post ci_api("/builds/register"), token: runner.token
@@ -43,7 +49,7 @@ describe Ci::API::API do
end
it "should return 404 error if no builds for shared runner" do
- commit = FactoryGirl.create(:ci_commit, project: project)
+ commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
FactoryGirl.create(:ci_build, commit: commit, status: 'pending' )
post ci_api("/builds/register"), token: shared_runner.token
@@ -52,8 +58,8 @@ describe Ci::API::API do
end
it "returns options" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds
+ commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
+ commit.create_builds('master', false, nil)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
@@ -62,14 +68,16 @@ describe Ci::API::API do
end
it "returns variables" do
- commit = FactoryGirl.create(:ci_commit, project: project)
- commit.create_builds
+ commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
+ commit.create_builds('master', false, nil)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
expect(response.status).to eq(201)
expect(json_response["variables"]).to eq([
+ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
])
@@ -77,16 +85,19 @@ describe Ci::API::API do
it "returns variables for triggers" do
trigger = FactoryGirl.create(:ci_trigger, project: project)
- commit = FactoryGirl.create(:ci_commit, project: project)
+ commit = FactoryGirl.create(:ci_commit, gl_project: gl_project)
trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger)
- commit.create_builds(trigger_request)
+ commit.create_builds('master', false, nil, trigger_request)
project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
expect(response.status).to eq(201)
expect(json_response["variables"]).to eq([
+ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
+ { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
{ "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false },
@@ -95,7 +106,7 @@ describe Ci::API::API do
end
describe "PUT /builds/:id" do
- let(:commit) { FactoryGirl.create(:ci_commit, project: project)}
+ let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project)}
let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) }
it "should update a running build" do
diff --git a/spec/requests/ci/api/commits_spec.rb b/spec/requests/ci/api/commits_spec.rb
index e89b665149..6049135fd1 100644
--- a/spec/requests/ci/api/commits_spec.rb
+++ b/spec/requests/ci/api/commits_spec.rb
@@ -4,7 +4,8 @@ describe Ci::API::API, 'Commits' do
include ApiHelpers
let(:project) { FactoryGirl.create(:ci_project) }
- let(:commit) { FactoryGirl.create(:ci_commit, project: project) }
+ let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
+ let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) }
let(:options) do
{
@@ -43,8 +44,7 @@ describe Ci::API::API, 'Commits' do
"email" => "jordi@softcatala.org",
}
}
- ],
- ci_yaml_file: gitlab_ci_yaml
+ ]
}
end
diff --git a/spec/requests/ci/api/projects_spec.rb b/spec/requests/ci/api/projects_spec.rb
index 409f47fa44..53f7f91cc1 100644
--- a/spec/requests/ci/api/projects_spec.rb
+++ b/spec/requests/ci/api/projects_spec.rb
@@ -134,7 +134,7 @@ describe Ci::API::API do
describe "PUT /projects/:id" do
let!(:project) { FactoryGirl.create(:ci_project) }
- let!(:project_info) { { name: "An updated name!" } }
+ let!(:project_info) { { default_ref: "develop" } }
before do
options.merge!(project_info)
@@ -144,7 +144,7 @@ describe Ci::API::API do
project.gl_project.team << [user, :master]
put ci_api("/projects/#{project.id}"), options
expect(response.status).to eq(200)
- expect(json_response["name"]).to eq(project_info[:name])
+ expect(json_response["default_ref"]).to eq(project_info[:default_ref])
end
it "fails to update a non-existing project" do
@@ -181,12 +181,10 @@ describe Ci::API::API do
end
describe "POST /projects" do
+ let(:gl_project) { FactoryGirl.create :empty_project }
let(:project_info) do
{
- name: "My project",
- gitlab_id: 1,
- path: "testing/testing",
- ssh_url_to_repo: "ssh://example.com/testing/testing.git"
+ gitlab_id: gl_project.id
}
end
@@ -200,7 +198,7 @@ describe Ci::API::API do
it "should create a project with valid data" do
post ci_api("/projects"), options
expect(response.status).to eq(201)
- expect(json_response['name']).to eq(project_info[:name])
+ expect(json_response['name']).to eq(gl_project.name_with_namespace)
end
end
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
index ff6fdbdd6f..93617fc4b3 100644
--- a/spec/requests/ci/api/triggers_spec.rb
+++ b/spec/requests/ci/api/triggers_spec.rb
@@ -5,7 +5,8 @@ describe Ci::API::API do
describe 'POST /projects/:project_id/refs/:ref/trigger' do
let!(:trigger_token) { 'secure token' }
- let!(:project) { FactoryGirl.create(:ci_project) }
+ let!(:gl_project) { FactoryGirl.create(:project) }
+ let!(:project) { FactoryGirl.create(:ci_project, gl_project: gl_project) }
let!(:project2) { FactoryGirl.create(:ci_project) }
let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) }
let(:options) do
@@ -14,6 +15,10 @@ describe Ci::API::API do
}
end
+ before do
+ stub_ci_commit_to_return_yaml_file
+ end
+
context 'Handles errors' do
it 'should return bad request if token is missing' do
post ci_api("/projects/#{project.id}/refs/master/trigger")
@@ -32,15 +37,13 @@ describe Ci::API::API do
end
context 'Have a commit' do
- before do
- @commit = FactoryGirl.create(:ci_commit, project: project)
- end
+ let(:commit) { project.commits.last }
it 'should create builds' do
post ci_api("/projects/#{project.id}/refs/master/trigger"), options
expect(response.status).to eq(201)
- @commit.builds.reload
- expect(@commit.builds.size).to eq(2)
+ commit.builds.reload
+ expect(commit.builds.size).to eq(2)
end
it 'should return bad request with no builds created if there\'s no commit for that ref' do
@@ -69,8 +72,8 @@ describe Ci::API::API do
it 'create trigger request with variables' do
post ci_api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: variables)
expect(response.status).to eq(201)
- @commit.builds.reload
- expect(@commit.builds.first.trigger_request.variables).to eq(variables)
+ commit.builds.reload
+ expect(commit.builds.first.trigger_request.variables).to eq(variables)
end
end
end
diff --git a/spec/requests/ci/builds_spec.rb b/spec/requests/ci/builds_spec.rb
deleted file mode 100644
index 998c386ead..0000000000
--- a/spec/requests/ci/builds_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'spec_helper'
-
-describe "Builds" do
- before do
- @project = FactoryGirl.create :ci_project
- @commit = FactoryGirl.create :ci_commit, project: @project
- @build = FactoryGirl.create :ci_build, commit: @commit
- end
-
- describe "GET /:project/builds/:id/status.json" do
- before do
- get status_ci_project_build_path(@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 fb31767033..0000000000
--- a/spec/requests/ci/commits_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe "Commits" do
- before do
- @project = FactoryGirl.create :ci_project
- @commit = FactoryGirl.create :ci_commit, project: @project
- end
-
- describe "GET /:project/refs/:ref_name/commits/:id/status.json" do
- before do
- get status_ci_project_ref_commits_path(@project, @commit.ref, @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/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb
index 0ec70c51b3..f7a36cd967 100644
--- a/spec/services/archive_repository_service_spec.rb
+++ b/spec/services/archive_repository_service_spec.rb
@@ -6,14 +6,14 @@ describe ArchiveRepositoryService do
describe "#execute" do
it "cleans old archives" do
- expect(project.repository).to receive(:clean_old_archives)
+ expect(RepositoryArchiveCacheWorker).to receive(:perform_async)
subject.execute(timeout: 0.0)
end
context "when the repository doesn't have an archive file path" do
before do
- allow(project.repository).to receive(:archive_file_path).and_return(nil)
+ allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
end
it "raises an error" do
@@ -21,70 +21,5 @@ describe ArchiveRepositoryService do
end
end
- context "when the repository has an archive file path" do
- let(:file_path) { "/archive.zip" }
- let(:pid_file_path) { "/archive.zip.pid" }
-
- before do
- allow(project.repository).to receive(:archive_file_path).and_return(file_path)
- allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path)
- end
-
- context "when the archive file already exists" do
- before do
- allow(File).to receive(:exist?).with(file_path).and_return(true)
- end
-
- it "returns the file path" do
- expect(subject.execute(timeout: 0.0)).to eq(file_path)
- end
- end
-
- context "when the archive file doesn't exist yet" do
- before do
- allow(File).to receive(:exist?).with(file_path).and_return(false)
- allow(File).to receive(:exist?).with(pid_file_path).and_return(true)
- end
-
- context "when the archive pid file doesn't exist yet" do
- before do
- allow(File).to receive(:exist?).with(pid_file_path).and_return(false)
- end
-
- it "queues the RepositoryArchiveWorker" do
- expect(RepositoryArchiveWorker).to receive(:perform_async)
-
- subject.execute(timeout: 0.0)
- end
- end
-
- context "when the archive pid file already exists" do
- it "doesn't queue the RepositoryArchiveWorker" do
- expect(RepositoryArchiveWorker).not_to receive(:perform_async)
-
- subject.execute(timeout: 0.0)
- end
- end
-
- context "when the archive file exists after a little while" do
- before do
- Thread.new do
- sleep 0.1
- allow(File).to receive(:exist?).with(file_path).and_return(true)
- end
- end
-
- it "returns the file path" do
- expect(subject.execute(timeout: 0.2)).to eq(file_path)
- end
- end
-
- context "when the archive file doesn't exist after the timeout" do
- it "returns nil" do
- expect(subject.execute(timeout: 0.0)).to eq(nil)
- end
- end
- end
- end
end
end
diff --git a/spec/services/ci/create_commit_service_spec.rb b/spec/services/ci/create_commit_service_spec.rb
index 84ab0a615d..e3a8fe9681 100644
--- a/spec/services/ci/create_commit_service_spec.rb
+++ b/spec/services/ci/create_commit_service_spec.rb
@@ -4,15 +4,19 @@ module Ci
describe CreateCommitService do
let(:service) { CreateCommitService.new }
let(:project) { FactoryGirl.create(:ci_project) }
+ let(:user) { nil }
+
+ before do
+ stub_ci_commit_to_return_yaml_file
+ end
describe :execute do
context 'valid params' do
let(:commit) do
- service.execute(project,
+ service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
- ci_yaml_file: gitlab_ci_yaml,
commits: [ { message: "Message" } ]
)
end
@@ -26,11 +30,10 @@ module Ci
context "skip tag if there is no build for it" do
it "creates commit if there is appropriate job" do
- result = service.execute(project,
+ result = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
- ci_yaml_file: gitlab_ci_yaml,
commits: [ { message: "Message" } ]
)
expect(result).to be_persisted
@@ -38,12 +41,12 @@ module Ci
it "creates commit if there is no appropriate job but deploy job has right ref setting" do
config = YAML.dump({ deploy: { deploy: "ls", only: ["0_1"] } })
+ stub_ci_commit_yaml_file(config)
- result = service.execute(project,
+ result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
after: '31das312',
- ci_yaml_file: config,
commits: [ { message: "Message" } ]
)
expect(result).to be_persisted
@@ -51,11 +54,11 @@ module Ci
end
it 'fails commits without .gitlab-ci.yml' do
- result = service.execute(project,
+ stub_ci_commit_yaml_file(nil)
+ result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
after: '31das312',
- ci_yaml_file: config,
commits: [ { message: 'Message' } ]
)
expect(result).to be_persisted
@@ -64,41 +67,46 @@ module Ci
end
describe :ci_skip? do
+ let(:message) { "some message[ci skip]" }
+
+ before do
+ allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
+ end
+
it "skips builds creation if there is [ci skip] tag in commit message" do
- commits = [{ message: "some message[ci skip]" }]
- commit = service.execute(project,
+ commits = [{ message: message }]
+ commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
- commits: commits,
- ci_yaml_file: gitlab_ci_yaml
+ commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
end
it "does not skips builds creation if there is no [ci skip] tag in commit message" do
- commits = [{ message: "some message" }]
+ allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { "some message" }
- commit = service.execute(project,
+ commits = [{ message: "some message" }]
+ commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
- commits: commits,
- ci_yaml_file: gitlab_ci_yaml
+ commits: commits
)
expect(commit.builds.first.name).to eq("staging")
end
it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
- commits = [{ message: "some message[ci skip]" }]
- commit = service.execute(project,
+ stub_ci_commit_yaml_file('invalid: file')
+ commits = [{ message: message }]
+ commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
- commits: commits,
- ci_yaml_file: "invalid: file"
+ commits: commits
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
@@ -106,35 +114,36 @@ module Ci
end
it "skips build creation if there are already builds" do
+ allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { gitlab_ci_yaml }
+
commits = [{ message: "message" }]
- commit = service.execute(project,
+ commit = service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
- commits: commits,
- ci_yaml_file: gitlab_ci_yaml
+ commits: commits
)
expect(commit.builds.count(:all)).to eq(2)
- commit = service.execute(project,
+ commit = service.execute(project, user,
ref: 'refs/heads/master',
before: '00000000',
after: '31das312',
- commits: commits,
- ci_yaml_file: gitlab_ci_yaml
+ commits: commits
)
expect(commit.builds.count(:all)).to eq(2)
end
it "creates commit with failed status if yaml is invalid" do
+ stub_ci_commit_yaml_file('invalid: file')
+
commits = [{ message: "some message" }]
- commit = service.execute(project,
+ commit = service.execute(project, user,
ref: 'refs/tags/0_1',
before: '00000000',
after: '31das312',
- commits: commits,
- ci_yaml_file: "invalid: file"
+ commits: commits
)
expect(commit.status).to eq("failed")
diff --git a/spec/services/ci/create_project_service_spec.rb b/spec/services/ci/create_project_service_spec.rb
deleted file mode 100644
index 2de7b0deca..0000000000
--- a/spec/services/ci/create_project_service_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-
-describe Ci::CreateProjectService do
- let(:service) { Ci::CreateProjectService.new }
- let(:current_user) { double.as_null_object }
- let(:project) { FactoryGirl.create :project }
-
- describe :execute do
- context 'valid params' do
- subject { service.execute(current_user, project) }
-
- it { is_expected.to be_kind_of(Ci::Project) }
- it { is_expected.to be_persisted }
- end
-
- context 'without project dump' do
- it 'should raise exception' do
- expect { service.execute(current_user, '', '') }.
- to raise_error(NoMethodError)
- end
- end
-
- context "forking" do
- let(:ci_origin_project) do
- FactoryGirl.create(:ci_project, shared_runners_enabled: true, public: true, allow_git_fetch: true)
- end
-
- subject { service.execute(current_user, project, ci_origin_project) }
-
- it "uses project as a template for settings and jobs" do
- expect(subject.shared_runners_enabled).to be_truthy
- expect(subject.public).to be_truthy
- expect(subject.allow_git_fetch).to be_truthy
- end
- end
- end
-end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index d12cd9773d..fcafae3864 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -2,19 +2,20 @@ require 'spec_helper'
describe Ci::CreateTriggerRequestService do
let(:service) { Ci::CreateTriggerRequestService.new }
- let(:project) { FactoryGirl.create :ci_project }
- let(:trigger) { FactoryGirl.create :ci_trigger, project: project }
+ let(:gl_project) { create(:project) }
+ let(:project) { create(:ci_project, gl_project: gl_project) }
+ let(:trigger) { create(:ci_trigger, project: project) }
+
+ before do
+ stub_ci_commit_to_return_yaml_file
+ end
describe :execute do
context 'valid params' do
subject { service.execute(project, trigger, 'master') }
- before do
- @commit = FactoryGirl.create :ci_commit, project: project
- end
-
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
- it { expect(subject.commit).to eq(@commit) }
+ it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
end
context 'no commit for ref' do
@@ -27,26 +28,11 @@ describe Ci::CreateTriggerRequestService do
subject { service.execute(project, trigger, 'master') }
before do
- FactoryGirl.create :ci_commit_without_jobs, project: project
+ stub_ci_commit_yaml_file('{}')
+ FactoryGirl.create :ci_commit, gl_project: gl_project
end
it { expect(subject).to be_nil }
end
-
- context 'for multiple commits' do
- subject { service.execute(project, trigger, 'master') }
-
- before do
- @commit1 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, project: project
- @commit2 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
- @commit3 = FactoryGirl.create :ci_commit, committed_at: 3.hour.ago, project: project
- end
-
- context 'retries latest one' do
- it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
- it { expect(subject).to be_persisted }
- it { expect(subject.commit).to eq(@commit2) }
- end
- end
end
end
diff --git a/spec/services/ci/event_service_spec.rb b/spec/services/ci/event_service_spec.rb
index 9b330a90ae..1264e17ff5 100644
--- a/spec/services/ci/event_service_spec.rb
+++ b/spec/services/ci/event_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Ci::EventService do
- let(:project) { FactoryGirl.create :ci_project, name: "GitLab / gitlab-shell" }
+ let(:project) { FactoryGirl.create :ci_project }
let(:user) { double(username: "root", id: 1) }
before do
@@ -12,7 +12,7 @@ describe Ci::EventService do
it "creates event" do
Ci::EventService.new.remove_project(user, project)
- expect(Ci::Event.admin.last.description).to eq("Project \"GitLab / gitlab-shell\" has been removed by root")
+ expect(Ci::Event.admin.last.description).to eq("Project \"#{project.name_with_namespace}\" has been removed by root")
end
end
@@ -20,7 +20,7 @@ describe Ci::EventService do
it "creates event" do
Ci::EventService.new.create_project(user, project)
- expect(Ci::Event.admin.last.description).to eq("Project \"GitLab / gitlab-shell\" has been created by root")
+ expect(Ci::Event.admin.last.description).to eq("Project \"#{project.name_with_namespace}\" has been created by root")
end
end
diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb
index 7565eb8f03..d7242d684c 100644
--- a/spec/services/ci/image_for_build_service_spec.rb
+++ b/spec/services/ci/image_for_build_service_spec.rb
@@ -4,7 +4,8 @@ module Ci
describe ImageForBuildService do
let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:ci_project) }
- let(:commit) { FactoryGirl.create(:ci_commit, project: project, ref: 'master') }
+ let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) }
+ let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project, ref: 'master') }
let(:build) { FactoryGirl.create(:ci_build, commit: commit) }
describe :execute do
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb
index 7b5af6c3dd..781764627a 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_build_service_spec.rb
@@ -3,14 +3,14 @@ require 'spec_helper'
module Ci
describe RegisterBuildService do
let!(:service) { RegisterBuildService.new }
- let!(:project) { FactoryGirl.create :ci_project }
- let!(:commit) { FactoryGirl.create :ci_commit, project: project }
- let!(:pending_build) { FactoryGirl.create :ci_build, project: project, commit: commit }
+ let!(:gl_project) { FactoryGirl.create :empty_project }
+ let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
+ let!(:pending_build) { FactoryGirl.create :ci_build, commit: commit }
let!(:shared_runner) { FactoryGirl.create(:ci_runner, is_shared: true) }
let!(:specific_runner) { FactoryGirl.create(:ci_runner, is_shared: false) }
before do
- specific_runner.assign_to(project)
+ specific_runner.assign_to(gl_project.ensure_gitlab_ci_project)
end
describe :execute do
@@ -47,8 +47,7 @@ module Ci
context 'allow shared runners' do
before do
- project.shared_runners_enabled = true
- project.save
+ gl_project.gitlab_ci_project.update(shared_runners_enabled: true)
end
context 'shared runner' do
diff --git a/spec/services/ci/web_hook_service_spec.rb b/spec/services/ci/web_hook_service_spec.rb
index cebdd145e4..aa48fcbcbf 100644
--- a/spec/services/ci/web_hook_service_spec.rb
+++ b/spec/services/ci/web_hook_service_spec.rb
@@ -2,7 +2,8 @@ require 'spec_helper'
describe Ci::WebHookService do
let(:project) { FactoryGirl.create :ci_project }
- let(:commit) { FactoryGirl.create :ci_commit, project: project }
+ 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 }
let(:hook) { FactoryGirl.create :ci_web_hook, project: project }
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index c483060fd7..17015d29e5 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -112,6 +112,14 @@ describe GitPushService do
it { expect(@event.project).to eq(project) }
it { expect(@event.action).to eq(Event::PUSHED) }
it { expect(@event.data).to eq(service.push_data) }
+
+ context "Updates merge requests" do
+ it "when pushing a new branch for the first time" do
+ expect(project).to receive(:update_merge_requests).
+ with(@blankrev, 'newrev', 'refs/heads/master', user)
+ service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ end
+ end
end
describe "Web Hooks" do
@@ -155,7 +163,7 @@ describe GitPushService do
before do
allow(commit).to receive_messages(
- safe_message: "this commit \n mentions ##{issue.id}",
+ safe_message: "this commit \n mentions #{issue.to_reference}",
references: [issue],
author_name: commit_author.name,
author_email: commit_author.email
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 7b564d34d7..7483f51de0 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -35,5 +35,19 @@ describe MergeRequests::MergeService do
expect(note.note).to include 'Status changed to merged'
end
end
+
+ context "error handling" do
+ let(:service) { MergeRequests::MergeService.new(project, user, {}) }
+
+ it 'saves error if there is an exception' do
+ allow(service).to receive(:repository).and_raise("error")
+
+ allow(service).to receive(:execute_hooks)
+
+ service.execute(merge_request, 'Awesome message')
+
+ expect(merge_request.merge_error).to eq("Something went wrong during merge")
+ end
+ end
end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 9516e7936d..227ac995ec 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -106,6 +106,27 @@ describe MergeRequests::RefreshService do
it { expect(@fork_merge_request.notes).to be_empty }
end
+ context 'push new branch that exists in a merge request' do
+ let(:refresh_service) { service.new(@fork_project, @user) }
+
+ it 'refreshes the merge request' do
+ expect(refresh_service).to receive(:execute_hooks).
+ with(@fork_merge_request, 'update')
+ allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev)
+
+ refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master')
+ reload_mrs
+
+ expect(@merge_request.notes).to be_empty
+ expect(@merge_request).to be_open
+
+ notes = @fork_merge_request.notes.reorder(:created_at).map(&:note)
+ expect(notes[0]).to include('Restored source branch `master`')
+ expect(notes[1]).to include('Added 4 commits')
+ expect(@fork_merge_request).to be_open
+ end
+ end
+
def reload_mrs
@merge_request.reload
@fork_merge_request.reload
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 8865335d0d..520140917a 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -427,15 +427,15 @@ describe NotificationService do
should_email(@u_watcher.id)
should_email(@u_participating.id)
should_not_email(@u_disabled.id)
- notification.project_was_moved(project)
+ notification.project_was_moved(project, "gitlab/gitlab")
end
def should_email(user_id)
- expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id)
+ expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab")
end
def should_not_email(user_id)
- expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id)
+ expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab")
end
end
end
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index f12e09c58c..ddee2e62df 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/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 18ab333c1d..65a8c81204 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -43,14 +43,10 @@ describe Projects::ForkService do
end
context 'GitLab CI is enabled' do
- it "calls fork registrator for CI" do
- create(:ci_project, gl_project: @from_project)
- @from_project.build_missing_services
- @from_project.gitlab_ci_service.update_attributes(active: true)
-
- expect_any_instance_of(Ci::CreateProjectService).to receive(:execute)
-
- fork_project(@from_project, @to_user)
+ it "fork and enable CI for fork" do
+ @from_project.enable_ci
+ @to_project = fork_project(@from_project, @to_user)
+ expect(@to_project.gitlab_ci?).to be_truthy
end
end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index bb7da33b12..47755bfc99 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 fa4ff6b01a..1b1a80d1fe 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 48c49e2f71..a31fc1e4b0 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/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 2658576640..a45130bd47 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -242,6 +242,18 @@ describe SystemNoteService do
end
end
+ describe '.change_branch_presence' do
+ subject { described_class.change_branch_presence(noteable, project, author, :source, 'feature', :delete) }
+
+ it_behaves_like 'a system note'
+
+ context 'when source branch deleted' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "Deleted source branch `feature`"
+ end
+ end
+ end
+
describe '.cross_reference' do
subject { described_class.cross_reference(noteable, mentioner, author) }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index dfe855926c..2be13bb3e6 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -14,6 +14,7 @@ require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'shoulda/matchers'
require 'sidekiq/testing/inline'
+require 'benchmark/ips'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
@@ -32,7 +33,7 @@ RSpec.configure do |config|
config.include TestEnv
config.include StubGitlabCalls
config.include StubGitlabData
-
+ config.include BenchmarkMatchers, benchmark: true
config.infer_spec_type_from_file_location!
config.raise_errors_for_deprecations!
@@ -42,4 +43,8 @@ RSpec.configure do |config|
end
end
+FactoryGirl::SyntaxRunner.class_eval do
+ include RSpec::Mocks::ExampleMethods
+end
+
ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index 203117aee7..97e5c270a5 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -29,12 +29,19 @@ module FilterSpecHelper
#
# Returns the Hash
def pipeline_result(body, contexts = {})
- contexts.reverse_merge!(project: project)
+ contexts.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class], contexts)
pipeline.call(body)
end
+ def reference_pipeline_result(body, contexts = {})
+ contexts.reverse_merge!(project: project) if defined?(project)
+
+ pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts)
+ pipeline.call(body)
+ end
+
# Modify a String reference to make it invalid
#
# Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
@@ -55,20 +62,6 @@ module FilterSpecHelper
end
end
- # Stub CrossProjectReference#user_can_reference_project? to return true for
- # the current test
- def allow_cross_reference!
- allow_any_instance_of(described_class).
- to receive(:user_can_reference_project?).and_return(true)
- end
-
- # Stub CrossProjectReference#user_can_reference_project? to return false for
- # the current test
- def disallow_cross_reference!
- allow_any_instance_of(described_class).
- to receive(:user_can_reference_project?).and_return(false)
- end
-
# Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module
def urls
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index 39a6439146..bedc1a7f1d 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -15,18 +15,17 @@ class MarkdownFeature
end
def group
- unless @group
- @group = create(:group)
- @group.add_developer(user)
+ @group ||= create(:group).tap do |group|
+ group.add_developer(user)
end
-
- @group
end
# Direct references ----------------------------------------------------------
def project
- @project ||= create(:project)
+ @project ||= create(:project).tap do |project|
+ project.team << [user, :master]
+ end
end
def issue
@@ -46,12 +45,10 @@ class MarkdownFeature
end
def commit_range
- unless @commit_range
+ @commit_range ||= begin
commit2 = project.commit('HEAD~3')
- @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
+ CommitRange.new("#{commit.id}...#{commit2.id}", project)
end
-
- @commit_range
end
def simple_label
@@ -65,13 +62,12 @@ class MarkdownFeature
# Cross-references -----------------------------------------------------------
def xproject
- unless @xproject
+ @xproject ||= begin
namespace = create(:namespace, name: 'cross-reference')
- @xproject = create(:project, namespace: namespace)
- @xproject.team << [user, :developer]
+ create(:project, namespace: namespace) do |project|
+ project.team << [user, :developer]
+ end
end
-
- @xproject
end
def xissue
@@ -91,12 +87,10 @@ class MarkdownFeature
end
def xcommit_range
- unless @xcommit_range
+ @xcommit_range ||= begin
xcommit2 = xproject.commit('HEAD~2')
- @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
+ CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end
-
- @xcommit_range
end
def raw_markdown
diff --git a/spec/support/matchers/benchmark_matchers.rb b/spec/support/matchers/benchmark_matchers.rb
new file mode 100644
index 0000000000..84f655c211
--- /dev/null
+++ b/spec/support/matchers/benchmark_matchers.rb
@@ -0,0 +1,61 @@
+module BenchmarkMatchers
+ extend RSpec::Matchers::DSL
+
+ def self.included(into)
+ into.extend(ClassMethods)
+ end
+
+ matcher :iterate_per_second do |min_iterations|
+ supports_block_expectations
+
+ match do |block|
+ @max_stddev ||= 30
+
+ @entry = benchmark(&block)
+
+ expect(@entry.ips).to be >= min_iterations
+ expect(@entry.stddev_percentage).to be <= @max_stddev
+ end
+
+ chain :with_maximum_stddev do |value|
+ @max_stddev = value
+ end
+
+ description do
+ "run at least #{min_iterations} iterations per second"
+ end
+
+ failure_message do
+ ips = @entry.ips.round(2)
+ stddev = @entry.stddev_percentage.round(2)
+
+ "expected at least #{min_iterations} iterations per second " \
+ "with a maximum stddev of #{@max_stddev}%, instead of " \
+ "#{ips} iterations per second with a stddev of #{stddev}%"
+ end
+ end
+
+ # Benchmarks the given block and returns a Benchmark::IPS::Report::Entry.
+ def benchmark(&block)
+ report = Benchmark.ips(quiet: true) do |bench|
+ bench.report do
+ instance_eval(&block)
+ end
+ end
+
+ report.entries[0]
+ end
+
+ module ClassMethods
+ # Wraps around rspec's subject method so you can write:
+ #
+ # benchmark_subject { SomeClass.some_method }
+ #
+ # instead of:
+ #
+ # subject { -> { SomeClass.some_method } }
+ def benchmark_subject(&block)
+ subject { block }
+ end
+ end
+end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index e3de0afb44..3bb568f4d4 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -5,7 +5,7 @@
# - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } }
def common_mentionable_setup
- let(:project) { create :project }
+ let(:project) { subject.project }
let(:author) { subject.author }
let(:mentioned_issue) { create(:issue, project: project) }
@@ -50,6 +50,8 @@ def common_mentionable_setup
}
extra_commits.each { |c| commitmap[c.short_id] = c }
+ allow(Project).to receive(:find).and_call_original
+ allow(Project).to receive(:find).with(project.id.to_s).and_return(project)
allow(project.repository).to receive(:commit) { |sha| commitmap[sha] }
set_mentionable_text.call(ref_string)
@@ -65,7 +67,7 @@ shared_examples 'a mentionable' do
it "extracts references from its reference property" do
# De-duplicate and omit itself
- refs = subject.references(project)
+ refs = subject.referenced_mentionables
expect(refs.size).to eq(6)
expect(refs).to include(mentioned_issue)
expect(refs).to include(mentioned_mr)
@@ -84,14 +86,7 @@ shared_examples 'a mentionable' do
with(referenced, subject.local_reference, author)
end
- subject.create_cross_references!(project, author)
- end
-
- it 'detects existing cross-references' do
- SystemNoteService.cross_reference(mentioned_issue, subject.local_reference, author)
-
- expect(subject).to have_mentioned(mentioned_issue)
- expect(subject).not_to have_mentioned(mentioned_mr)
+ subject.create_cross_references!
end
end
@@ -143,6 +138,6 @@ shared_examples 'an editable mentionable' do
end
set_mentionable_text.call(new_text)
- subject.create_new_cross_references!(project, author)
+ subject.create_new_cross_references!(author)
end
end
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
index 4d007ae55e..d1c999cad4 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)/
diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb
index a3e5964618..a4f21e9533 100644
--- a/spec/support/setup_builds_storage.rb
+++ b/spec/support/setup_builds_storage.rb
@@ -10,8 +10,10 @@ RSpec.configure do |config|
end
config.after(:suite) do
- Dir.chdir(builds_path) do
- `ls | grep -v .gitkeep | xargs rm -r`
+ Dir[File.join(builds_path, '*')].each do |path|
+ next if File.basename(path) == '.gitkeep'
+
+ FileUtils.rm_rf(path)
end
end
end
diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb
index 5e6744afda..5b3eb1bfc5 100644
--- a/spec/support/stub_gitlab_calls.rb
+++ b/spec/support/stub_gitlab_calls.rb
@@ -13,6 +13,14 @@ module StubGitlabCalls
allow_any_instance_of(Network).to receive(:projects) { project_hash_array }
end
+ def stub_ci_commit_to_return_yaml_file
+ stub_ci_commit_yaml_file(gitlab_ci_yaml)
+ end
+
+ def stub_ci_commit_yaml_file(ci_yaml)
+ allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { ci_yaml }
+ end
+
private
def gitlab_url
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 3eab74ba98..d12ba25b71 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -9,7 +9,7 @@ module TestEnv
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
- 'fix' => '12d65c8',
+ 'fix' => '48f0be4',
'improve/awesome' => '5937ac0',
'markdown' => '0ed8c6c',
'master' => '5937ac0',
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 2e63e5f36a..3be7dd4e52 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -159,7 +159,7 @@ describe 'gitlab:app namespace rake task' do
end
it "does not contain skipped item" do
- tar_contents, exit_status = Gitlab::Popen.popen(
+ tar_contents, _exit_status = Gitlab::Popen.popen(
%W{tar -tvf #{@backup_tar} db uploads repositories builds}
)
diff --git a/spec/workers/repository_archive_worker_spec.rb b/spec/workers/repository_archive_worker_spec.rb
deleted file mode 100644
index a914d0ac8d..0000000000
--- a/spec/workers/repository_archive_worker_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'spec_helper'
-
-describe RepositoryArchiveWorker do
- let(:project) { create(:project) }
- subject { RepositoryArchiveWorker.new }
-
- before do
- allow(Project).to receive(:find).and_return(project)
- end
-
- describe "#perform" do
- it "cleans old archives" do
- expect(project.repository).to receive(:clean_old_archives)
-
- subject.perform(project.id, "master", "zip")
- end
-
- context "when the repository doesn't have an archive file path" do
- before do
- allow(project.repository).to receive(:archive_file_path).and_return(nil)
- end
-
- it "doesn't archive the repo" do
- expect(project.repository).not_to receive(:archive_repo)
-
- subject.perform(project.id, "master", "zip")
- end
- end
-
- context "when the repository has an archive file path" do
- let(:file_path) { "/archive.zip" }
- let(:pid_file_path) { "/archive.zip.pid" }
-
- before do
- allow(project.repository).to receive(:archive_file_path).and_return(file_path)
- allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path)
- end
-
- context "when the archive file already exists" do
- before do
- allow(File).to receive(:exist?).with(file_path).and_return(true)
- end
-
- it "doesn't archive the repo" do
- expect(project.repository).not_to receive(:archive_repo)
-
- subject.perform(project.id, "master", "zip")
- end
- end
-
- context "when the archive file doesn't exist yet" do
- before do
- allow(File).to receive(:exist?).with(file_path).and_return(false)
- allow(File).to receive(:exist?).with(pid_file_path).and_return(true)
- end
-
- context "when the archive pid file doesn't exist yet" do
- before do
- allow(File).to receive(:exist?).with(pid_file_path).and_return(false)
- end
-
- it "archives the repo" do
- expect(project.repository).to receive(:archive_repo)
-
- subject.perform(project.id, "master", "zip")
- end
- end
-
- context "when the archive pid file already exists" do
- it "doesn't archive the repo" do
- expect(project.repository).not_to receive(:archive_repo)
-
- subject.perform(project.id, "master", "zip")
- end
- end
- end
- end
- end
-end
diff --git a/tmp/.gitkeep b/tmp/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000