Commit e90a5557 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into pipeline-notifications

* upstream/master: (73 commits)
  Stop unauthized users dragging on issue boards
  Capitalize Git
  Add docker-compose environment initialization command
  Improve readability and add specs for label filtering
  Improve label filtering implementation
  Allow the use of params[:name] when filtering labels
  Fix Rubocop offenses in issue move specs
  Add spec in Issues::MoveService to fix label assignment regression
  Fix bug where labels would be assigned to issues that were moved
  Fix `User#to_reference`
  Fix rubocop build error
  Remove redundant class_name and foreign_key overrides
  Enable SingleLinePerSelector in scss-lint
  Escape ref and path for relative links (!6050)
  Add failing test for #21420
  Enable SpaceAfterVariableColon in scss-lint
  Enable SpaceAroundOperator in scss-lint
  Enable trailingWhitespace in scss-lint
  Disable Rails/Output cop since it makes no sense here
  Use File.write instead of File.open + File#write
  ...
parents 02b62474 3b93574a
...@@ -210,7 +210,7 @@ rake brakeman: *exec ...@@ -210,7 +210,7 @@ rake brakeman: *exec
rake flay: *exec rake flay: *exec
license_finder: *exec license_finder: *exec
rake downtime_check: *exec rake downtime_check: *exec
rake ce_to_ee_merge_check: rake ee_compat_check:
<<: *exec <<: *exec
only: only:
- branches - branches
......
...@@ -172,7 +172,7 @@ linters: ...@@ -172,7 +172,7 @@ linters:
# Split selectors onto separate lines after each comma, and have each # Split selectors onto separate lines after each comma, and have each
# individual selector occupy a single line. # individual selector occupy a single line.
SingleLinePerSelector: SingleLinePerSelector:
enabled: false enabled: true
# Commas in lists should be followed by a space. # Commas in lists should be followed by a space.
SpaceAfterComma: SpaceAfterComma:
...@@ -191,7 +191,7 @@ linters: ...@@ -191,7 +191,7 @@ linters:
# Variables should be formatted with a single space separating the colon # Variables should be formatted with a single space separating the colon
# from the variable's value. # from the variable's value.
SpaceAfterVariableColon: SpaceAfterVariableColon:
enabled: false enabled: true
# Variables should be formatted with no space between the name and the # Variables should be formatted with no space between the name and the
# colon. # colon.
...@@ -201,7 +201,7 @@ linters: ...@@ -201,7 +201,7 @@ linters:
# Operators should be formatted with a single space on both sides of an # Operators should be formatted with a single space on both sides of an
# infix operator. # infix operator.
SpaceAroundOperator: SpaceAroundOperator:
enabled: false enabled: true
# Opening braces should be preceded by a single space. # Opening braces should be preceded by a single space.
SpaceBeforeBrace: SpaceBeforeBrace:
...@@ -223,7 +223,7 @@ linters: ...@@ -223,7 +223,7 @@ linters:
# Reports lines containing trailing whitespace. # Reports lines containing trailing whitespace.
TrailingWhitespace: TrailingWhitespace:
enabled: false enabled: true
# Don't write trailing zeros for numeric values with a decimal point. # Don't write trailing zeros for numeric values with a decimal point.
TrailingZero: TrailingZero:
......
...@@ -4,16 +4,30 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -4,16 +4,30 @@ Please view this file on the master branch, on stable branches it's out of date.
- Adds user project membership expired event to clarify why user was removed (Callum Dryden) - Adds user project membership expired event to clarify why user was removed (Callum Dryden)
- Add CI notifications. Who triggered a pipeline would receive an email after the pipeline is succeeded or failed. Users could also update notification settings accordingly. !6342 - Add CI notifications. Who triggered a pipeline would receive an email after the pipeline is succeeded or failed. Users could also update notification settings accordingly. !6342
- Trim leading and trailing whitespace on project_path (Linus Thiel) - Trim leading and trailing whitespace on project_path (Linus Thiel)
- Prevent award emoji via notes for issues/MRs authored by user (barthc)
- Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO)
- Fix extra space on Build sidebar on Firefox !7060
- Fix HipChat notifications rendering (airatshigapov, eisnerd) - Fix HipChat notifications rendering (airatshigapov, eisnerd)
- Add hover to trash icon in notes !7008 (blackst0ne) - Add hover to trash icon in notes !7008 (blackst0ne)
- Escape ref and path for relative links !6050 (winniehell)
- Simpler arguments passed to named_route on toggle_award_url helper method - Simpler arguments passed to named_route on toggle_award_url helper method
- Fix: Backup restore doesn't clear cache - Fix: Backup restore doesn't clear cache
- Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method
- Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens
- Fix documents and comments on Build API `scope` - Fix documents and comments on Build API `scope`
- Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov)
## 8.13.1 (unreleased) ## 8.13.1 (unreleased)
- Fix bug where labels would be assigned to issues that were moved
- Fix error in generating labels - Fix error in generating labels
- Fix reply-by-email not working due to queue name mismatch
- Fixed hidden pipeline graph on commit and MR page !6895
- Expire and build repository cache after project import
- Fix 404 for group pages when GitLab setup uses relative url
- Simpler arguments passed to named_route on toggle_award_url helper method
- Fix unauthorized users dragging on issue boards
- Better handle when no users were selected for adding to group or project. (Linus Thiel)
- Only show register tab if signup enabled.
## 8.13.0 (2016-10-22) ## 8.13.0 (2016-10-22)
- Removes extra line for empty issue description. (!7045) - Removes extra line for empty issue description. (!7045)
...@@ -45,6 +59,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -45,6 +59,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Clarify documentation for Runners API (Gennady Trafimenkov) - Clarify documentation for Runners API (Gennady Trafimenkov)
- The instrumentation for Banzai::Renderer has been restored - The instrumentation for Banzai::Renderer has been restored
- Change user & group landing page routing from /u/:username to /:username - Change user & group landing page routing from /u/:username to /:username
- Fixed issue boards user link when in subdirectory
- Added documentation for .gitattributes files - Added documentation for .gitattributes files
- Move Pipeline Metrics to separate worker - Move Pipeline Metrics to separate worker
- AbstractReferenceFilter caches project_refs on RequestStore when active - AbstractReferenceFilter caches project_refs on RequestStore when active
...@@ -94,6 +109,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -94,6 +109,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add visibility level to project repository - Add visibility level to project repository
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
- Fix showing commits from source project for merge request !6658
- Fix that manual jobs would no longer block jobs in the next stage. !6604 - Fix that manual jobs would no longer block jobs in the next stage. !6604
- Add configurable email subject suffix (Fu Xu) - Add configurable email subject suffix (Fu Xu)
- Use defined colour for a language when available !6748 (nilsding) - Use defined colour for a language when available !6748 (nilsding)
...@@ -388,6 +404,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -388,6 +404,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix inconsistent checkbox alignment (ClemMakesApps) - Fix inconsistent checkbox alignment (ClemMakesApps)
- Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger)
- Adds response mime type to transaction metric action when it's not HTML - Adds response mime type to transaction metric action when it's not HTML
- Fix branch protection API !6215
- Fix hover leading space bug in pipeline graph !5980 - Fix hover leading space bug in pipeline graph !5980
- Avoid conflict with admin labels when importing GitHub labels - Avoid conflict with admin labels when importing GitHub labels
- User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496
......
...@@ -83,14 +83,15 @@ ...@@ -83,14 +83,15 @@
}; };
// Disable button if text field is empty // Disable button if text field is empty
window.disableButtonIfEmptyField = function(field_selector, button_selector) { window.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
event_name = event_name || 'input';
var closest_submit, field; var closest_submit, field;
field = $(field_selector); field = $(field_selector);
closest_submit = field.closest('form').find(button_selector); closest_submit = field.closest('form').find(button_selector);
if (rstrip(field.val()) === "") { if (rstrip(field.val()) === "") {
closest_submit.disable(); closest_submit.disable();
} }
return field.on('input', function() { return field.on(event_name, function() {
if (rstrip($(this).val()) === "") { if (rstrip($(this).val()) === "") {
return closest_submit.disable(); return closest_submit.disable();
} else { } else {
......
...@@ -148,7 +148,7 @@ ...@@ -148,7 +148,7 @@
}; };
Build.prototype.translateSidebar = function(e) { Build.prototype.translateSidebar = function(e) {
var newPosition = this.sidebarTranslationLimits.max - document.body.scrollTop; var newPosition = this.sidebarTranslationLimits.max - (document.body.scrollTop || document.documentElement.scrollTop);
if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min; if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min;
this.$sidebar.css({ this.$sidebar.css({
top: newPosition top: newPosition
......
...@@ -117,6 +117,9 @@ ...@@ -117,6 +117,9 @@
new ZenMode(); new ZenMode();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
break; break;
case 'projects:commit:builds':
new gl.Pipelines();
break;
case 'projects:commits:show': case 'projects:commits:show':
case 'projects:activity': case 'projects:activity':
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
......
...@@ -137,8 +137,11 @@ ...@@ -137,8 +137,11 @@
} }
initValidators () { initValidators () {
// select all non-hidden inputs in form // register selectors here as needed
this.state.inputs = this.form.find(':input:not([type=hidden])').toArray() const validateSelectors = [':text', ':password', '[type=email]']
.map((selector) => `input${selector}`).join(',');
this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag)) .filter((input) => !input.classList.contains(customValidationFlag))
.map((input) => new GlFieldError({ input, formErrors: this })); .map((input) => new GlFieldError({ input, formErrors: this }));
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
$('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow); $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
$('.js-member-update-control').off('change').on('change', this.formSubmit); $('.js-member-update-control').off('change').on('change', this.formSubmit);
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess); $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess);
disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change');
} }
removeRow(e) { removeRow(e) {
......
...@@ -282,6 +282,7 @@ ...@@ -282,6 +282,7 @@
document.querySelector("div#builds").innerHTML = data.html; document.querySelector("div#builds").innerHTML = data.html;
gl.utils.localTimeAgo($('.js-timeago', 'div#builds')); gl.utils.localTimeAgo($('.js-timeago', 'div#builds'));
_this.buildsLoaded = true; _this.buildsLoaded = true;
if (!this.pipelines) this.pipelines = new gl.Pipelines();
return _this.scrollToElement("#builds"); return _this.scrollToElement("#builds");
}; };
})(this) })(this)
......
// This file is based off animate.css 3.5.1, available here: // This file is based off animate.css 3.5.1, available here:
// https://github.com/daneden/animate.css/blob/3.5.1/animate.css // https://github.com/daneden/animate.css/blob/3.5.1/animate.css
// //
// animate.css - http://daneden.me/animate // animate.css - http://daneden.me/animate
// Version - 3.5.1 // Version - 3.5.1
// Licensed under the MIT license - http://opensource.org/licenses/MIT // Licensed under the MIT license - http://opensource.org/licenses/MIT
// //
// Copyright (c) 2016 Daniel Eden // Copyright (c) 2016 Daniel Eden
.animated { .animated {
...@@ -37,7 +37,8 @@ ...@@ -37,7 +37,8 @@
} }
@include keyframes(pulse) { @include keyframes(pulse) {
from, to { from,
to {
@include webkit-prefix(transform, scale3d(1, 1, 1)); @include webkit-prefix(transform, scale3d(1, 1, 1));
} }
......
...@@ -128,7 +128,8 @@ ...@@ -128,7 +128,8 @@
position: relative; position: relative;
.avatar-holder { .avatar-holder {
.avatar, .identicon { .avatar,
.identicon {
margin: 0 auto; margin: 0 auto;
float: none; float: none;
} }
......
...@@ -213,7 +213,8 @@ ...@@ -213,7 +213,8 @@
top: 2px; top: 2px;
} }
svg, .fa { svg,
.fa {
&:not(:last-child) { &:not(:last-child) {
margin-right: 3px; margin-right: 3px;
} }
......
...@@ -143,7 +143,8 @@ li.note { ...@@ -143,7 +143,8 @@ li.note {
} }
} }
.wiki_content code, .readme code { .wiki_content code,
.readme code {
background-color: inherit; background-color: inherit;
} }
...@@ -350,7 +351,8 @@ table { ...@@ -350,7 +351,8 @@ table {
margin-right: 10px; margin-right: 10px;
} }
.alert, .progress { .alert,
.progress {
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
} }
......
...@@ -275,7 +275,8 @@ ...@@ -275,7 +275,8 @@
a { a {
padding-left: 25px; padding-left: 25px;
&.is-indeterminate, &.is-active { &.is-indeterminate,
&.is-active {
&::before { &::before {
position: absolute; position: absolute;
left: 5px; left: 5px;
...@@ -373,7 +374,8 @@ ...@@ -373,7 +374,8 @@
} }
} }
.dropdown-input-field, .default-dropdown-input { .dropdown-input-field,
.default-dropdown-input {
width: 100%; width: 100%;
min-height: 30px; min-height: 30px;
padding: 0 7px; padding: 0 7px;
...@@ -402,7 +404,7 @@ ...@@ -402,7 +404,7 @@
.dropdown-content { .dropdown-content {
max-height: 215px; max-height: 215px;
overflow-y: scroll; overflow-y: auto;
} }
.dropdown-footer { .dropdown-footer {
......
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
margin: 0; margin: 0;
} }
.flash-notice, .flash-alert { .flash-notice,
.flash-alert {
border-radius: $border-radius-default; border-radius: $border-radius-default;
.container-fluid, .container-fluid,
...@@ -30,7 +31,8 @@ ...@@ -30,7 +31,8 @@
&.flash-container-page { &.flash-container-page {
margin-bottom: 0; margin-bottom: 0;
.flash-notice, .flash-alert { .flash-notice,
.flash-alert {
border-radius: 0; border-radius: 0;
} }
} }
......
...@@ -25,7 +25,9 @@ ...@@ -25,7 +25,9 @@
a { a {
color: $color-light; color: $color-light;
&:hover, &:focus, &:active { &:hover,
&:focus,
&:active {
background: $color-dark; background: $color-dark;
} }
......
...@@ -15,7 +15,8 @@ header { ...@@ -15,7 +15,8 @@ header {
margin: 8px 0; margin: 8px 0;
text-align: center; text-align: center;
.tanuki-logo, img { .tanuki-logo,
img {
height: 36px; height: 36px;
} }
} }
...@@ -54,7 +55,9 @@ header { ...@@ -54,7 +55,9 @@ header {
line-height: 28px; line-height: 28px;
text-align: center; text-align: center;
&:hover, &:focus, &:active { &:hover,
&:focus,
&:active {
background-color: $background-color; background-color: $background-color;
} }
...@@ -125,7 +128,8 @@ header { ...@@ -125,7 +128,8 @@ header {
left: -50%; left: -50%;
} }
svg, img { svg,
img {
height: 36px; height: 36px;
} }
...@@ -222,7 +226,8 @@ header { ...@@ -222,7 +226,8 @@ header {
margin: 0; margin: 0;
float: none !important; float: none !important;
.visible-xs, .visable-sm { .visible-xs,
.visable-sm {
display: table-cell !important; display: table-cell !important;
} }
} }
......
...@@ -76,14 +76,16 @@ ...@@ -76,14 +76,16 @@
/** light list with border-bottom between li **/ /** light list with border-bottom between li **/
ul.bordered-list, ul.unstyled-list { ul.bordered-list,
ul.unstyled-list {
@include basic-list; @include basic-list;
&.top-list { &.top-list {
li:first-child { li:first-child {
padding-top: 0; padding-top: 0;
h4, h5 { h4,
h5 {
margin-top: 0; margin-top: 0;
} }
} }
......
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
10%, 80% { 10%, 80% {
fill: $tanuki-red; fill: $tanuki-red;
} }
20%, 90% { 20%, 90% {
fill: lighten($tanuki-red, 25%); fill: lighten($tanuki-red, 25%);
} }
......
...@@ -79,7 +79,8 @@ ...@@ -79,7 +79,8 @@
padding-left: 15px !important; padding-left: 15px !important;
} }
.nav-links, .nav-links { .nav-links,
.nav-links {
li a { li a {
font-size: 14px; font-size: 14px;
padding: 19px 10px; padding: 19px 10px;
...@@ -99,18 +100,21 @@ ...@@ -99,18 +100,21 @@
@media (max-width: $screen-sm-max) { @media (max-width: $screen-sm-max) {
.issues-filters { .issues-filters {
.milestone-filter, .labels-filter { .milestone-filter,
.labels-filter {
display: none; display: none;
} }
} }
.page-title { .page-title {
.note-created-ago, .new-issue-link { .note-created-ago,
.new-issue-link {
display: none; display: none;
} }
} }
.issue_edited_ago, .note_edited_ago { .issue_edited_ago,
.note_edited_ago {
display: none; display: none;
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
padding: 15px; padding: 15px;
.form-actions { .form-actions {
margin: -$gl-padding+1; margin: -$gl-padding + 1;
margin-top: 15px; margin-top: 15px;
} }
......
...@@ -54,7 +54,9 @@ ...@@ -54,7 +54,9 @@
color: #959494; color: #959494;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
&:hover, &:active, &:focus { &:hover,
&:active,
&:focus {
text-decoration: none; text-decoration: none;
outline: none; outline: none;
} }
...@@ -211,7 +213,11 @@ ...@@ -211,7 +213,11 @@
padding-bottom: 0; padding-bottom: 0;
width: 100%; width: 100%;
.btn, form, .dropdown, .dropdown-menu-toggle, .form-control { .btn,
form,
.dropdown,
.dropdown-menu-toggle,
.form-control {
margin: 0 0 10px; margin: 0 0 10px;
display: block; display: block;
width: 100%; width: 100%;
...@@ -245,7 +251,8 @@ ...@@ -245,7 +251,8 @@
} }
&.adjust { &.adjust {
.nav-text, .nav-controls { .nav-text,
.nav-controls {
width: auto; width: auto;
} }
} }
...@@ -309,13 +316,15 @@ ...@@ -309,13 +316,15 @@
padding-top: 10px; padding-top: 10px;
} }
a, i { a,
i {
color: $layout-link-gray; color: $layout-link-gray;
} }
&.active { &.active {
a, i { a,
i {
color: $black; color: $black;
} }
...@@ -328,7 +337,8 @@ ...@@ -328,7 +337,8 @@
} }
&:hover { &:hover {
a, i { a,
i {
color: $black; color: $black;
} }
} }
......
...@@ -3,7 +3,8 @@ ...@@ -3,7 +3,8 @@
width: 100% !important; width: 100% !important;
} }
.select2-container, .select2-container.select2-drop-above { .select2-container,
.select2-container.select2-drop-above {
.select2-choice { .select2-choice {
background: #fff; background: #fff;
border-color: $input-border; border-color: $input-border;
...@@ -71,7 +72,8 @@ ...@@ -71,7 +72,8 @@
} }
.select2-container-active { .select2-container-active {
.select2-choice, .select2-choices { .select2-choice,
.select2-choices {
box-shadow: none; box-shadow: none;
} }
} }
......
...@@ -23,7 +23,8 @@ table { ...@@ -23,7 +23,8 @@ table {
} }
tr { tr {
td, th { td,
th {
padding: 10px $gl-padding; padding: 10px $gl-padding;
line-height: 20px; line-height: 20px;
vertical-align: middle; vertical-align: middle;
......
...@@ -126,7 +126,8 @@ ...@@ -126,7 +126,8 @@
box-shadow: none; box-shadow: none;
.panel-body { .panel-body {
form, pre { form,
pre {
margin: 0; margin: 0;
} }
......
...@@ -16,21 +16,21 @@ ...@@ -16,21 +16,21 @@
// $gray-light: lighten($gray-base, 46.7%) // #777 // $gray-light: lighten($gray-base, 46.7%) // #777
// $gray-lighter: lighten($gray-base, 93.5%) // #eee // $gray-lighter: lighten($gray-base, 93.5%) // #eee
$brand-primary: $gl-primary; $brand-primary: $gl-primary;
$brand-success: $gl-success; $brand-success: $gl-success;
$brand-info: $gl-info; $brand-info: $gl-info;
$brand-warning: $gl-warning; $brand-warning: $gl-warning;
$brand-danger: $gl-danger; $brand-danger: $gl-danger;
$border-radius-base: 3px !default; $border-radius-base: 3px !default;
$border-radius-large: 3px !default; $border-radius-large: 3px !default;
$border-radius-small: 3px !default; $border-radius-small: 3px !default;
//== Scaffolding //== Scaffolding
// //
$text-color: $gl-text-color; $text-color: $gl-text-color;
$link-color: $gl-link-color; $link-color: $gl-link-color;
//== Typography //== Typography
...@@ -38,112 +38,112 @@ $link-color: $gl-link-color; ...@@ -38,112 +38,112 @@ $link-color: $gl-link-color;
//## Font, line-height, and color for body text, headings, and more. //## Font, line-height, and color for body text, headings, and more.
$font-family-sans-serif: $regular_font; $font-family-sans-serif: $regular_font;
$font-family-monospace: $monospace_font; $font-family-monospace: $monospace_font;
$font-size-base: $gl-font-size; $font-size-base: $gl-font-size;
//== Components //== Components
// //
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
$padding-base-vertical: $gl-vert-padding; $padding-base-vertical: $gl-vert-padding;
$padding-base-horizontal: $gl-padding; $padding-base-horizontal: $gl-padding;
$component-active-color: #fff; $component-active-color: #fff;
$component-active-bg: $brand-info; $component-active-bg: $brand-info;
//== Forms //== Forms
// //
//## //##
$input-color: $text-color; $input-color: $text-color;
$input-border: $border-color; $input-border: $border-color;
$input-border-focus: $focus-border-color; $input-border-focus: $focus-border-color;
$legend-color: $text-color; $legend-color: $text-color;
//== Pagination //== Pagination
// //
//## //##
$pagination-color: $gl-gray; $pagination-color: $gl-gray;
$pagination-bg: #fff; $pagination-bg: #fff;
$pagination-border: $border-color; $pagination-border: $border-color;
$pagination-hover-color: $gl-gray; $pagination-hover-color: $gl-gray;
$pagination-hover-bg: $row-hover; $pagination-hover-bg: $row-hover;
$pagination-hover-border: $border-color; $pagination-hover-border: $border-color;
$pagination-active-color: $blue-dark; $pagination-active-color: $blue-dark;
$pagination-active-bg: #fff; $pagination-active-bg: #fff;
$pagination-active-border: $border-color; $pagination-active-border: $border-color;
$pagination-disabled-color: #cdcdcd; $pagination-disabled-color: #cdcdcd;
$pagination-disabled-bg: $background-color; $pagination-disabled-bg: $background-color;
$pagination-disabled-border: $border-color; $pagination-disabled-border: $border-color;
//== Form states and alerts //== Form states and alerts
// //
//## Define colors for form feedback states and, by default, alerts. //## Define colors for form feedback states and, by default, alerts.
$state-success-text: #fff; $state-success-text: #fff;
$state-success-bg: $brand-success; $state-success-bg: $brand-success;
$state-success-border: $brand-success; $state-success-border: $brand-success;
$state-info-text: #fff; $state-info-text: #fff;
$state-info-bg: $brand-info; $state-info-bg: $brand-info;
$state-info-border: $brand-info; $state-info-border: $brand-info;
$state-warning-text: #fff; $state-warning-text: #fff;
$state-warning-bg: $brand-warning; $state-warning-bg: $brand-warning;
$state-warning-border: $brand-warning; $state-warning-border: $brand-warning;
$state-danger-text: #fff; $state-danger-text: #fff;
$state-danger-bg: $brand-danger; $state-danger-bg: $brand-danger;
$state-danger-border: $brand-danger; $state-danger-border: $brand-danger;
//== Alerts //== Alerts
// //
//## Define alert colors, border radius, and padding. //## Define alert colors, border radius, and padding.
$alert-border-radius: 0; $alert-border-radius: 0;
//== Panels //== Panels
// //
//## //##
$panel-border-radius: 2px; $panel-border-radius: 2px;
$panel-default-text: $text-color; $panel-default-text: $text-color;
$panel-default-border: $border-color; $panel-default-border: $border-color;
$panel-default-heading-bg: $background-color; $panel-default-heading-bg: $background-color;
$panel-footer-bg: $background-color; $panel-footer-bg: $background-color;
$panel-inner-border: $border-color; $panel-inner-border: $border-color;
//== Wells //== Wells
// //
//## //##
$well-bg: $gray-light; $well-bg: $gray-light;
$well-border: #eee; $well-border: #eee;
//== Code //== Code
// //
//## //##
$code-color: #c7254e; $code-color: #c7254e;
$code-bg: #f9f2f4; $code-bg: #f9f2f4;
$kbd-color: #fff; $kbd-color: #fff;
$kbd-bg: #333; $kbd-bg: #333;
//== Buttons //== Buttons
// //
//## //##
$btn-default-color: $gl-text-color; $btn-default-color: $gl-text-color;
$btn-default-bg: #fff; $btn-default-bg: #fff;
$btn-default-border: #e7e9ed; $btn-default-border: #e7e9ed;
//== Nav //== Nav
// //
...@@ -153,8 +153,8 @@ $nav-link-padding: 13px $gl-padding; ...@@ -153,8 +153,8 @@ $nav-link-padding: 13px $gl-padding;
//== Code //== Code
// //
//## //##
$pre-bg: $background-color !default; $pre-bg: $background-color !default;
$pre-color: $gl-gray !default; $pre-color: $gl-gray !default;
$pre-border-color: $border-color; $pre-border-color: $border-color;
$table-bg-accent: $background-color; $table-bg-accent: $background-color;
...@@ -131,12 +131,14 @@ ...@@ -131,12 +131,14 @@
font-weight: inherit; font-weight: inherit;
} }
ul, ol { ul,
ol {
padding: 0; padding: 0;
margin: 3px 0 3px 28px !important; margin: 3px 0 3px 28px !important;
} }
ul:dir(rtl), ol:dir(rtl) { ul:dir(rtl),
ol:dir(rtl) {
margin: 3px 28px 3px 0 !important; margin: 3px 28px 3px 0 !important;
} }
...@@ -144,7 +146,8 @@ ...@@ -144,7 +146,8 @@
line-height: 1.6em; line-height: 1.6em;
} }
a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] { a[href*="/uploads/"],
a[href*="storage.googleapis.com/google-code-attachments/"] {
&:before { &:before {
margin-right: 4px; margin-right: 4px;
...@@ -167,7 +170,12 @@ ...@@ -167,7 +170,12 @@
} }
/* Link to current header. */ /* Link to current header. */
h1, h2, h3, h4, h5, h6 { h1,
h2,
h3,
h4,
h5,
h6 {
position: relative; position: relative;
a.anchor { a.anchor {
...@@ -215,7 +223,12 @@ body { ...@@ -215,7 +223,12 @@ body {
margin: 12px 7px; margin: 12px 7px;
} }
h1, h2, h3, h4, h5, h6 { h1,
h2,
h3,
h4,
h5,
h6 {
color: $gl-title-color; color: $gl-title-color;
font-weight: 600; font-weight: 600;
} }
...@@ -273,7 +286,10 @@ a > code { ...@@ -273,7 +286,10 @@ a > code {
text-decoration: line-through; text-decoration: line-through;
} }
h1, h2, h3, h4 { h1,
h2,
h3,
h4 {
small { small {
color: $gl-gray; color: $gl-gray;
} }
......
...@@ -84,39 +84,39 @@ $warning-message-border: #f0e2bb; ...@@ -84,39 +84,39 @@ $warning-message-border: #f0e2bb;
/* /*
* UI elements * UI elements
*/ */
$border-color: #e5e5e5; $border-color: #e5e5e5;
$focus-border-color: #3aabf0; $focus-border-color: #3aabf0;
$table-border-color: #f0f0f0; $table-border-color: #f0f0f0;
$background-color: $gray-light; $background-color: $gray-light;
$dark-background-color: #f5f5f5; $dark-background-color: #f5f5f5;
$table-text-gray: #8f8f8f; $table-text-gray: #8f8f8f;
/* /*
* Text * Text
*/ */
$gl-font-size: 15px; $gl-font-size: 15px;
$gl-title-color: #333; $gl-title-color: #333;
$gl-text-color: #5c5c5c; $gl-text-color: #5c5c5c;
$gl-text-color-light: #8c8c8c; $gl-text-color-light: #8c8c8c;
$gl-text-green: #4a2; $gl-text-green: #4a2;
$gl-text-red: #d12f19; $gl-text-red: #d12f19;
$gl-text-orange: #d90; $gl-text-orange: #d90;
$gl-link-color: #3084bb; $gl-link-color: #3084bb;
$gl-dark-link-color: #333; $gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f; $gl-placeholder-color: #8f8f8f;
$gl-icon-color: $gl-placeholder-color; $gl-icon-color: $gl-placeholder-color;
$gl-grayish-blue: #7f8fa4; $gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color; $gl-gray: $gl-text-color;
$gl-gray-dark: #313236; $gl-gray-dark: #313236;
$gl-gray-light: $gl-placeholder-color; $gl-gray-light: $gl-placeholder-color;
$gl-header-color: #4c4e54; $gl-header-color: #4c4e54;
/* /*
* Lists * Lists
*/ */
$list-font-size: $gl-font-size; $list-font-size: $gl-font-size;
$list-title-color: $gl-title-color; $list-title-color: $gl-title-color;
$list-text-color: $gl-text-color; $list-text-color: $gl-text-color;
$list-text-height: 42px; $list-text-height: 42px;
/* /*
......
/* https://github.com/MozMorris/tomorrow-pygments */ /* https://github.com/MozMorris/tomorrow-pygments */
.code.dark { .code.dark {
// Line numbers // Line numbers
.line-numbers, .diff-line-num { .line-numbers,
.diff-line-num {
background-color: #1d1f21; background-color: #1d1f21;
} }
.diff-line-num, .diff-line-num a { .diff-line-num,
.diff-line-num a {
color: rgba(255, 255, 255, 0.3); color: rgba(255, 255, 255, 0.3);
} }
// Code itself // Code itself
pre.code, .diff-line-num { pre.code,
.diff-line-num {
border-color: #666; border-color: #666;
} }
&, pre.code, .line_holder .line_content { &,
pre.code,
.line_holder .line_content {
background-color: #1d1f21; background-color: #1d1f21;
color: #c5c8c6; color: #c5c8c6;
} }
...@@ -31,11 +36,13 @@ ...@@ -31,11 +36,13 @@
border-color: darken(#557, 15%); border-color: darken(#557, 15%);
} }
.diff-line-num.new, .line_content.new { .diff-line-num.new,
.line_content.new {
@include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080); @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
} }
.diff-line-num.old, .line_content.old { .diff-line-num.old,
.line_content.old {
@include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.25), #808080); @include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.25), #808080);
} }
......
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */
.code.monokai { .code.monokai {
// Line numbers // Line numbers
.line-numbers, .diff-line-num { .line-numbers,
.diff-line-num {
background-color: #272822; background-color: #272822;
} }
.diff-line-num, .diff-line-num a { .diff-line-num,
.diff-line-num a {
color: rgba(255, 255, 255, 0.3); color: rgba(255, 255, 255, 0.3);
} }
// Code itself // Code itself
pre.code, .diff-line-num { pre.code,
.diff-line-num {
border-color: #555; border-color: #555;
} }
&, pre.code, .line_holder .line_content { &,
pre.code,
.line_holder .line_content {
background-color: #272822; background-color: #272822;
color: #f8f8f2; color: #f8f8f2;
} }
...@@ -31,11 +36,13 @@ ...@@ -31,11 +36,13 @@
border-color: darken(#49483e, 15%); border-color: darken(#49483e, 15%);
} }
.diff-line-num.new, .line_content.new { .diff-line-num.new,
.line_content.new {
@include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080); @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
} }
.diff-line-num.old, .line_content.old { .diff-line-num.old,
.line_content.old {
@include diff_background(rgba(254, 147, 140, 0.15), rgba(254, 147, 140, 0.2), #808080); @include diff_background(rgba(254, 147, 140, 0.15), rgba(254, 147, 140, 0.2), #808080);
} }
......
/* https://gist.github.com/qguv/7936275 */ /* https://gist.github.com/qguv/7936275 */
.code.solarized-dark { .code.solarized-dark {
// Line numbers // Line numbers
.line-numbers, .diff-line-num { .line-numbers,
.diff-line-num {
background-color: #002b36; background-color: #002b36;
} }
.diff-line-num, .diff-line-num a { .diff-line-num,
.diff-line-num a {
color: rgba(255, 255, 255, 0.3); color: rgba(255, 255, 255, 0.3);
} }
// Code itself // Code itself
pre.code, .diff-line-num { pre.code,
.diff-line-num {
border-color: #113b46; border-color: #113b46;
} }
&, pre.code, .line_holder .line_content { &,
pre.code,
.line_holder .line_content {
background-color: #002b36; background-color: #002b36;
color: #93a1a1; color: #93a1a1;
} }
...@@ -31,11 +36,13 @@ ...@@ -31,11 +36,13 @@
border-color: darken(#174652, 15%); border-color: darken(#174652, 15%);
} }
.diff-line-num.new, .line_content.new { .diff-line-num.new,
.line_content.new {
@include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46); @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
} }
.diff-line-num.old, .line_content.old { .diff-line-num.old,
.line_content.old {
@include diff_background(rgba(220, 50, 47, 0.3), rgba(220, 50, 47, 0.25), #113b46); @include diff_background(rgba(220, 50, 47, 0.3), rgba(220, 50, 47, 0.25), #113b46);
} }
......
...@@ -7,20 +7,25 @@ ...@@ -7,20 +7,25 @@
.code.solarized-light { .code.solarized-light {
// Line numbers // Line numbers
.line-numbers, .diff-line-num { .line-numbers,
.diff-line-num {
background-color: #fdf6e3; background-color: #fdf6e3;
} }
.diff-line-num, .diff-line-num a { .diff-line-num,
.diff-line-num a {
color: $black-transparent; color: $black-transparent;
} }
// Code itself // Code itself
pre.code, .diff-line-num { pre.code,
.diff-line-num {
border-color: #c5d0d4; border-color: #c5d0d4;
} }
&, pre.code, .line_holder .line_content { &,
pre.code,
.line_holder .line_content {
background-color: #fdf6e3; background-color: #fdf6e3;
color: #586e75; color: #586e75;
} }
...@@ -37,11 +42,13 @@ ...@@ -37,11 +42,13 @@
border-color: darken(#ddd8c5, 15%); border-color: darken(#ddd8c5, 15%);
} }
.diff-line-num.new, .line_content.new { .diff-line-num.new,
.line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4); @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
} }
.diff-line-num.old, .line_content.old { .diff-line-num.old,
.line_content.old {
@include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.25), #c5d0d4); @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.25), #c5d0d4);
} }
......
...@@ -7,20 +7,25 @@ ...@@ -7,20 +7,25 @@
.code.white { .code.white {
// Line numbers // Line numbers
.line-numbers, .diff-line-num { .line-numbers,
.diff-line-num {
background-color: $background-color; background-color: $background-color;
} }
.diff-line-num, .diff-line-num a { .diff-line-num,
.diff-line-num a {
color: $black-transparent; color: $black-transparent;
} }
// Code itself // Code itself
pre.code, .diff-line-num { pre.code,
.diff-line-num {
border-color: $table-border-gray; border-color: $table-border-gray;
} }
&, pre.code, .line_holder .line_content { &,
pre.code,
.line_holder .line_content {
background-color: #fff; background-color: #fff;
color: #333; color: #333;
} }
......
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
// Styles defined here are embedded directly into the resulting email HTML via // Styles defined here are embedded directly into the resulting email HTML via
// the `premailer` gem. // the `premailer` gem.
$body-background-color: #363636; $body-background-color: #363636;
$message-background-color: #fafafa; $message-background-color: #fafafa;
$header-color: #6b4fbb; $header-color: #6b4fbb;
$body-color: #444; $body-color: #444;
$cta-color: #e14329; $cta-color: #e14329;
$footer-link-color: #7e7e7e; $footer-link-color: #7e7e7e;
$font-family: Helvetica, Arial, sans-serif; $font-family: Helvetica, Arial, sans-serif;
......
...@@ -56,7 +56,8 @@ ...@@ -56,7 +56,8 @@
padding: 10px; padding: 10px;
text-align: center; text-align: center;
> div, p { > div,
p {
display: inline; display: inline;
margin: 0; margin: 0;
......
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
border-color: $border-color; border-color: $border-color;
} }
th, td { th,
td {
padding: 10px $gl-padding; padding: 10px $gl-padding;
} }
......
...@@ -2,14 +2,16 @@ ...@@ -2,14 +2,16 @@
display: block; display: block;
} }
.commit-author, .commit-committer { .commit-author,
.commit-committer {
display: block; display: block;
color: #999; color: #999;
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
} }
.commit-author strong, .commit-committer strong { .commit-author strong,
.commit-committer strong {
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
......
...@@ -63,7 +63,8 @@ ...@@ -63,7 +63,8 @@
display: inline-block; display: inline-block;
} }
.btn-clipboard, .btn-transparent { .btn-clipboard,
.btn-transparent {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
} }
...@@ -162,7 +163,8 @@ ...@@ -162,7 +163,8 @@
.branch-commit { .branch-commit {
color: $gl-gray; color: $gl-gray;
.commit-id, .commit-row-message { .commit-id,
.commit-row-message {
color: $gl-gray; color: $gl-gray;
} }
} }
......
...@@ -2,7 +2,12 @@ ...@@ -2,7 +2,12 @@
margin-bottom: 20px; margin-bottom: 20px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
> h1, h2, h3, h4, h5, h6 { > h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 400; font-weight: 400;
} }
...@@ -10,7 +15,8 @@ ...@@ -10,7 +15,8 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
ul, ol { ul,
ol {
padding-left: 0; padding-left: 0;
} }
......
...@@ -9,15 +9,15 @@ ...@@ -9,15 +9,15 @@
padding: 24px 0; padding: 24px 0;
border-bottom: none; border-bottom: none;
position: relative; position: relative;
@media (max-width: $screen-sm-min) { @media (max-width: $screen-sm-min) {
padding: 6px 0 24px; padding: 6px 0 24px;
} }
} }
.column { .column {
text-align: center; text-align: center;
@media (max-width: $screen-sm-min) { @media (max-width: $screen-sm-min) {
padding: 15px 0; padding: 15px 0;
} }
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
&:last-child { &:last-child {
text-align: right; text-align: right;
@media (max-width: $screen-sm-min) { @media (max-width: $screen-sm-min) {
text-align: center; text-align: center;
} }
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
.bordered-box { .bordered-box {
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: $border-radius-default; border-radius: $border-radius-default;
} }
.content-list { .content-list {
...@@ -73,10 +73,10 @@ ...@@ -73,10 +73,10 @@
font-weight: 600; font-weight: 600;
color: $gl-title-color; color: $gl-title-color;
} }
&.text { &.text {
color: $layout-link-gray; color: $layout-link-gray;
&.value-col { &.value-col {
color: $gl-title-color; color: $gl-title-color;
} }
...@@ -108,13 +108,13 @@ ...@@ -108,13 +108,13 @@
.svg-container { .svg-container {
text-align: center; text-align: center;
svg { svg {
width: 136px; width: 136px;
height: 136px; height: 136px;
} }
} }
.inner-content { .inner-content {
@media (max-width: $screen-sm-min) { @media (max-width: $screen-sm-min) {
padding: 0 28px; padding: 0 28px;
......
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
color: #5c5d5e; color: #5c5d5e;
} }
.issue_created_ago, .author_link { .issue_created_ago,
.author_link {
white-space: nowrap; white-space: nowrap;
} }
} }
......
...@@ -124,7 +124,8 @@ ...@@ -124,7 +124,8 @@
} }
} }
.old_line, .new_line { .old_line,
.new_line {
margin: 0; margin: 0;
padding: 0; padding: 0;
border: none; border: none;
...@@ -222,12 +223,12 @@ ...@@ -222,12 +223,12 @@
top: 13px; top: 13px;
right: 7px; right: 7px;
} }
.frame { .frame {
top: 0; top: 0;
right: 0; right: 0;
position: absolute; position: absolute;
&.deleted { &.deleted {
margin: 0; margin: 0;
display: block; display: block;
...@@ -281,7 +282,8 @@ ...@@ -281,7 +282,8 @@
position: relative; position: relative;
} }
.frame.added, .frame.deleted { .frame.added,
.frame.deleted {
position: absolute; position: absolute;
display: block; display: block;
top: 0; top: 0;
...@@ -347,7 +349,8 @@ ...@@ -347,7 +349,8 @@
text-align: center; text-align: center;
background: #eee; background: #eee;
ul, li { ul,
li {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
......
...@@ -91,7 +91,9 @@ ...@@ -91,7 +91,9 @@
} }
} }
.gitignore-selector, .license-selector, .gitlab-ci-yml-selector { .gitignore-selector,
.license-selector,
.gitlab-ci-yml-selector {
.dropdown { .dropdown {
line-height: 21px; line-height: 21px;
} }
......
...@@ -37,10 +37,10 @@ ...@@ -37,10 +37,10 @@
.branch-name { .branch-name {
color: $gl-dark-link-color; color: $gl-dark-link-color;
} }
.stop-env-link { .stop-env-link {
color: $table-text-gray; color: $table-text-gray;
.stop-env-icon { .stop-env-icon {
font-size: 14px; font-size: 14px;
} }
...@@ -48,11 +48,11 @@ ...@@ -48,11 +48,11 @@
.deployment { .deployment {
.build-column { .build-column {
.build-link { .build-link {
color: $gl-dark-link-color; color: $gl-dark-link-color;
} }
.avatar { .avatar {
float: none; float: none;
} }
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
max-width: 400px; max-width: 400px;
margin: 0 auto; margin: 0 auto;
h1, h2, h3 { h1,
h2,
h3 {
text-align: center; text-align: center;
} }
......
...@@ -142,7 +142,7 @@ ...@@ -142,7 +142,7 @@
.event-last-push { .event-last-push {
overflow: auto; overflow: auto;
width: 100%; width: 100%;
.event-last-push-text { .event-last-push-text {
@include str-truncated(100%); @include str-truncated(100%);
padding: 4px 0; padding: 4px 0;
......
...@@ -43,7 +43,8 @@ ul.related-merge-requests > li { ...@@ -43,7 +43,8 @@ ul.related-merge-requests > li {
} }
} }
.merge-requests-title, .related-branches-title { .merge-requests-title,
.related-branches-title {
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
} }
......
...@@ -41,7 +41,8 @@ ...@@ -41,7 +41,8 @@
font-size: 13px; font-size: 13px;
} }
.login-box, .omniauth-container { .login-box,
.omniauth-container {
box-shadow: 0 0 0 1px $border-color; box-shadow: 0 0 0 1px $border-color;
border-bottom-right-radius: 2px; border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px; border-bottom-left-radius: 2px;
...@@ -198,7 +199,8 @@ ...@@ -198,7 +199,8 @@
.form-control { .form-control {
&:active, &:focus { &:active,
&:focus {
background-color: #fff; background-color: #fff;
} }
} }
...@@ -261,7 +263,8 @@ ...@@ -261,7 +263,8 @@
position: relative; position: relative;
} }
.footer-container, hr.footer-fixed { .footer-container,
hr.footer-fixed {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
...@@ -286,6 +289,13 @@ ...@@ -286,6 +289,13 @@
.new_user { .new_user {
position: relative; position: relative;
padding-bottom: 35px; padding-bottom: 35px;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
.forgot-password {
float: none !important;
margin-top: 5px;
}
}
} }
.move-submit-down { .move-submit-down {
......
...@@ -101,7 +101,8 @@ $colors: ( ...@@ -101,7 +101,8 @@ $colors: (
@mixin color-scheme($color) { @mixin color-scheme($color) {
.header.line_content, .diff-line-num { .header.line_content,
.diff-line-num {
&.origin { &.origin {
background-color: map-get($colors, #{$color}_header_origin_neutral); background-color: map-get($colors, #{$color}_header_origin_neutral);
border-color: map-get($colors, #{$color}_header_origin_neutral); border-color: map-get($colors, #{$color}_header_origin_neutral);
...@@ -254,7 +255,7 @@ $colors: ( ...@@ -254,7 +255,7 @@ $colors: (
border-top: solid 2px $border-green-extra-light; border-top: solid 2px $border-green-extra-light;
} }
} }
.editor { .editor {
pre { pre {
height: 350px; height: 350px;
......
...@@ -50,7 +50,8 @@ ...@@ -50,7 +50,8 @@
} }
} }
.issues-sortable-list, .merge_requests-sortable-list { .issues-sortable-list,
.merge_requests-sortable-list {
.issuable-detail { .issuable-detail {
display: block; display: block;
margin-top: 7px; margin-top: 7px;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.diff-file .diff-content { .diff-file .diff-content {
tr.line_holder:hover > td .line_note_link { tr.line_holder:hover > td .line_note_link {
opacity: 1.0; opacity: 1.0;
filter: alpha(opacity=100); filter: alpha(opacity = 100);
} }
} }
...@@ -24,7 +24,8 @@ ...@@ -24,7 +24,8 @@
display: none; display: none;
} }
.new-note, .note-edit-form { .new-note,
.note-edit-form {
.note-form-actions { .note-form-actions {
margin-top: $gl-padding; margin-top: $gl-padding;
} }
......
...@@ -28,7 +28,8 @@ ul.notes { ...@@ -28,7 +28,8 @@ ul.notes {
} }
} }
.note-created-ago, .note-updated-at { .note-created-ago,
.note-updated-at {
white-space: nowrap; white-space: nowrap;
} }
...@@ -458,7 +459,7 @@ ul.notes { ...@@ -458,7 +459,7 @@ ul.notes {
.discussion-next-btn { .discussion-next-btn {
svg { svg {
margin: 0; margin: 0;
path { path {
fill: $gray-darkest; fill: $gray-darkest;
} }
......
...@@ -248,7 +248,8 @@ ...@@ -248,7 +248,8 @@
font-size: 14px; font-size: 14px;
} }
svg, .fa { svg,
.fa {
margin-right: 0; margin-right: 0;
} }
} }
...@@ -529,7 +530,8 @@ ...@@ -529,7 +530,8 @@
// Connect each build (except for first) with curved lines // Connect each build (except for first) with curved lines
&:not(:first-child) { &:not(:first-child) {
&::after, &::before { &::after,
&::before {
content: ''; content: '';
top: -49px; top: -49px;
position: absolute; position: absolute;
...@@ -555,7 +557,8 @@ ...@@ -555,7 +557,8 @@
// Connect second build to first build with smaller curved line // Connect second build to first build with smaller curved line
&:nth-child(2) { &:nth-child(2) {
&::after, &::before { &::after,
&::before {
height: 29px; height: 29px;
top: -9px; top: -9px;
} }
...@@ -570,7 +573,8 @@ ...@@ -570,7 +573,8 @@
.build { .build {
// Remove right connecting horizontal line from first build in last stage // Remove right connecting horizontal line from first build in last stage
&:first-child { &:first-child {
&::after, &::before { &::after,
&::before {
border: none; border: none;
} }
} }
......
...@@ -253,7 +253,8 @@ ...@@ -253,7 +253,8 @@
} }
table.u2f-registrations { table.u2f-registrations {
th:not(:last-child), td:not(:last-child) { th:not(:last-child),
td:not(:last-child) {
border-right: solid 1px transparent; border-right: solid 1px transparent;
} }
} }
\ No newline at end of file
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
} }
} }
.no-ssh-key-message, .project-limit-message { .no-ssh-key-message,
.project-limit-message {
background-color: #f28d35; background-color: #f28d35;
margin-bottom: 0; margin-bottom: 0;
} }
...@@ -385,7 +386,8 @@ a.deploy-project-label { ...@@ -385,7 +386,8 @@ a.deploy-project-label {
text-align: center; text-align: center;
width: 169px; width: 169px;
&:hover, &.forked { &:hover,
&.forked {
background-color: $row-hover; background-color: $row-hover;
border-color: $row-hover-border; border-color: $row-hover-border;
} }
...@@ -734,7 +736,8 @@ pre.light-well { ...@@ -734,7 +736,8 @@ pre.light-well {
.table-bordered { .table-bordered {
border-radius: 1px; border-radius: 1px;
th:not(:last-child), td:not(:last-child) { th:not(:last-child),
td:not(:last-child) {
border-right: solid 1px transparent; border-right: solid 1px transparent;
} }
} }
...@@ -757,7 +760,8 @@ pre.light-well { ...@@ -757,7 +760,8 @@ pre.light-well {
} }
} }
.project-refs-form .dropdown-menu, .dropdown-menu-projects { .project-refs-form .dropdown-menu,
.dropdown-menu-projects {
width: 300px; width: 300px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
......
...@@ -65,7 +65,8 @@ ...@@ -65,7 +65,8 @@
.search-input-wrap { .search-input-wrap {
width: 100%; width: 100%;
.search-icon, .clear-icon { .search-icon,
.clear-icon {
position: absolute; position: absolute;
right: 5px; right: 5px;
top: 0; top: 0;
...@@ -185,7 +186,8 @@ ...@@ -185,7 +186,8 @@
padding-right: $gl-padding + 15px; padding-right: $gl-padding + 15px;
} }
.btn-search, .btn-new { .btn-search,
.btn-new {
width: 100%; width: 100%;
margin-top: 5px; margin-top: 5px;
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
.ci-status-icon-success_with_warning { .ci-status-icon-success_with_warning {
color: $gl-warning; color: $gl-warning;
} }
.ci-status-icon-running { .ci-status-icon-running {
color: $blue-normal; color: $blue-normal;
} }
......
...@@ -23,17 +23,18 @@ ...@@ -23,17 +23,18 @@
border-bottom: 1px solid $table-border-gray; border-bottom: 1px solid $table-border-gray;
border-top: 1px solid $table-border-gray; border-top: 1px solid $table-border-gray;
td, th { td,
th {
line-height: 21px; line-height: 21px;
} }
.last-commit { .last-commit {
@include str-truncated(506px); @include str-truncated(506px);
@media (min-width: $screen-sm-max) and (max-width: $screen-md-max) { @media (min-width: $screen-sm-max) and (max-width: $screen-md-max) {
@include str-truncated(450px); @include str-truncated(450px);
} }
} }
.commit-history-link-spacer { .commit-history-link-spacer {
...@@ -74,7 +75,8 @@ ...@@ -74,7 +75,8 @@
max-width: 320px; max-width: 320px;
vertical-align: middle; vertical-align: middle;
i, a { i,
a {
color: $gl-dark-link-color; color: $gl-dark-link-color;
} }
......
.wiki h1, .wiki h2, .wiki h3, .wiki h4, .wiki h5, .wiki h6 {margin-top: 17px; } .wiki h1,
.wiki h1 {font-size: 30px;} .wiki h2,
.wiki h2 {font-size: 22px;} .wiki h3,
.wiki h3 {font-size: 18px; font-weight: bold; } .wiki h4,
.wiki h5,
.wiki h6 {
margin-top: 17px;
}
.wiki h1 {
font-size: 30px;
}
.wiki h2 {
font-size: 22px;
}
.wiki h3 {
font-size: 18px;
font-weight: bold;
}
header, header,
nav, nav,
......
...@@ -21,6 +21,10 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -21,6 +21,10 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def create def create
if params[:user_ids].blank?
return redirect_to(group_group_members_path(@group), alert: 'No users specified.')
end
@group.add_users( @group.add_users(
params[:user_ids].split(','), params[:user_ids].split(','),
params[:access_level], params[:access_level],
......
...@@ -2,8 +2,8 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -2,8 +2,8 @@ class Import::GitlabProjectsController < Import::BaseController
before_action :verify_gitlab_project_import_enabled before_action :verify_gitlab_project_import_enabled
def new def new
@namespace_id = project_params[:namespace_id] @namespace = Namespace.find(project_params[:namespace_id])
@namespace_name = Namespace.find(project_params[:namespace_id]).name return render_404 unless current_user.can?(:create_projects, @namespace)
@path = project_params[:path] @path = project_params[:path]
end end
......
...@@ -25,6 +25,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -25,6 +25,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end end
def create def create
if params[:user_ids].blank?
return redirect_to(namespace_project_project_members_path(@project.namespace, @project), alert: 'No users or groups specified.')
end
@project.team.add_users( @project.team.add_users(
params[:user_ids].split(','), params[:user_ids].split(','),
params[:access_level], params[:access_level],
...@@ -32,7 +36,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -32,7 +36,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
current_user: current_user current_user: current_user
) )
redirect_to namespace_project_project_members_path(@project.namespace, @project) redirect_to namespace_project_project_members_path(@project.namespace, @project), notice: 'Users were successfully added.'
end end
def update def update
......
...@@ -35,8 +35,10 @@ class LabelsFinder < UnionFinder ...@@ -35,8 +35,10 @@ class LabelsFinder < UnionFinder
end end
def with_title(items) def with_title(items)
items = items.where(title: title) if title return items if title.nil?
items return items.none if title.blank?
items.where(title: title)
end end
def group_id def group_id
...@@ -52,7 +54,7 @@ class LabelsFinder < UnionFinder ...@@ -52,7 +54,7 @@ class LabelsFinder < UnionFinder
end end
def title def title
params[:title].presence || params[:name].presence params[:title] || params[:name]
end end
def project def project
......
...@@ -5,7 +5,7 @@ module BoardsHelper ...@@ -5,7 +5,7 @@ module BoardsHelper
{ {
endpoint: namespace_project_boards_path(@project.namespace, @project), endpoint: namespace_project_boards_path(@project.namespace, @project),
board_id: board.id, board_id: board.id,
disabled: !can?(current_user, :admin_list, @project), disabled: "#{!can?(current_user, :admin_list, @project)}",
issue_link_base: namespace_project_issues_path(@project.namespace, @project) issue_link_base: namespace_project_issues_path(@project.namespace, @project)
} }
end end
......
...@@ -3,8 +3,8 @@ module Ci ...@@ -3,8 +3,8 @@ module Ci
include TokenAuthenticatable include TokenAuthenticatable
include AfterCommitQueue include AfterCommitQueue
belongs_to :runner, class_name: 'Ci::Runner' belongs_to :runner
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User' belongs_to :erased_by, class_name: 'User'
serialize :options serialize :options
......
...@@ -7,12 +7,12 @@ module Ci ...@@ -7,12 +7,12 @@ module Ci
self.table_name = 'ci_commits' self.table_name = 'ci_commits'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, foreign_key: :gl_project_id
belongs_to :user belongs_to :user
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
validates_presence_of :sha, unless: :importing? validates_presence_of :sha, unless: :importing?
validates_presence_of :ref, unless: :importing? validates_presence_of :ref, unless: :importing?
......
...@@ -6,9 +6,9 @@ module Ci ...@@ -6,9 +6,9 @@ module Ci
AVAILABLE_SCOPES = %w[specific shared active paused online] AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active run_untagged locked] FORM_EDITABLE = %i[description tag_list active run_untagged locked]
has_many :builds, class_name: 'Ci::Build' has_many :builds
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' has_many :runner_projects, dependent: :destroy
has_many :projects, through: :runner_projects, class_name: '::Project', foreign_key: :gl_project_id has_many :projects, through: :runner_projects, foreign_key: :gl_project_id
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build' has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
......
...@@ -2,8 +2,8 @@ module Ci ...@@ -2,8 +2,8 @@ module Ci
class RunnerProject < ActiveRecord::Base class RunnerProject < ActiveRecord::Base
extend Ci::Model extend Ci::Model
belongs_to :runner, class_name: 'Ci::Runner' belongs_to :runner
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, foreign_key: :gl_project_id
validates_uniqueness_of :runner_id, scope: :gl_project_id validates_uniqueness_of :runner_id, scope: :gl_project_id
end end
......
...@@ -4,8 +4,8 @@ module Ci ...@@ -4,8 +4,8 @@ module Ci
acts_as_paranoid acts_as_paranoid
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, foreign_key: :gl_project_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' has_many :trigger_requests, dependent: :destroy
validates_presence_of :token validates_presence_of :token
validates_uniqueness_of :token validates_uniqueness_of :token
......
...@@ -2,9 +2,9 @@ module Ci ...@@ -2,9 +2,9 @@ module Ci
class TriggerRequest < ActiveRecord::Base class TriggerRequest < ActiveRecord::Base
extend Ci::Model extend Ci::Model
belongs_to :trigger, class_name: 'Ci::Trigger' belongs_to :trigger
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id belongs_to :pipeline, foreign_key: :commit_id
has_many :builds, class_name: 'Ci::Build' has_many :builds
serialize :variables serialize :variables
......
...@@ -2,7 +2,7 @@ module Ci ...@@ -2,7 +2,7 @@ module Ci
class Variable < ActiveRecord::Base class Variable < ActiveRecord::Base
extend Ci::Model extend Ci::Model
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, foreign_key: :gl_project_id
validates_uniqueness_of :key, scope: :gl_project_id validates_uniqueness_of :key, scope: :gl_project_id
validates :key, validates :key,
......
...@@ -5,7 +5,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -5,7 +5,7 @@ class CommitStatus < ActiveRecord::Base
self.table_name = 'ci_builds' self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, foreign_key: :gl_project_id
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
belongs_to :user belongs_to :user
......
module ProtectedBranchAccess module ProtectedBranchAccess
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do
scope :master, -> { where(access_level: Gitlab::Access::MASTER) }
scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) }
end
def humanize def humanize
self.class.human_access_levels[self.access_level] self.class.human_access_levels[self.access_level]
end end
......
...@@ -7,10 +7,8 @@ class Email < ActiveRecord::Base ...@@ -7,10 +7,8 @@ class Email < ActiveRecord::Base
validates :email, presence: true, uniqueness: true, email: true validates :email, presence: true, uniqueness: true, email: true
validate :unique_email, if: ->(email) { email.email_changed? } validate :unique_email, if: ->(email) { email.email_changed? }
before_validation :cleanup_email def email=(value)
write_attribute(:email, value.downcase.strip)
def cleanup_email
self.email = self.email.downcase.strip
end end
def unique_email def unique_email
......
...@@ -6,7 +6,7 @@ class Group < Namespace ...@@ -6,7 +6,7 @@ class Group < Namespace
include AccessRequestable include AccessRequestable
include Referable include Referable
has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source
alias_method :members, :group_members alias_method :members, :group_members
has_many :users, through: :group_members has_many :users, through: :group_members
has_many :owners, has_many :owners,
...@@ -68,7 +68,7 @@ class Group < Namespace ...@@ -68,7 +68,7 @@ class Group < Namespace
end end
def web_url def web_url
Gitlab::Routing.url_helpers.group_canonical_url(self) Gitlab::Routing.url_helpers.group_url(self)
end end
def human_name def human_name
......
class GroupMember < Member class GroupMember < Member
SOURCE_TYPE = 'Namespace' SOURCE_TYPE = 'Namespace'
belongs_to :group, class_name: 'Group', foreign_key: 'source_id' belongs_to :group, foreign_key: 'source_id'
# Make sure group member points only to group as it source # Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE default_value_for :source_type, SOURCE_TYPE
......
...@@ -3,7 +3,7 @@ class ProjectMember < Member ...@@ -3,7 +3,7 @@ class ProjectMember < Member
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
belongs_to :project, class_name: 'Project', foreign_key: 'source_id' belongs_to :project, foreign_key: 'source_id'
# Make sure project member points only to project as it source # Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE default_value_for :source_type, SOURCE_TYPE
......
...@@ -6,8 +6,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -6,8 +6,8 @@ class MergeRequest < ActiveRecord::Base
include Taskable include Taskable
include Importable include Importable
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :target_project, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" belongs_to :source_project, class_name: "Project"
belongs_to :merge_user, class_name: "User" belongs_to :merge_user, class_name: "User"
has_many :merge_request_diffs, dependent: :destroy has_many :merge_request_diffs, dependent: :destroy
......
...@@ -299,8 +299,10 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -299,8 +299,10 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def keep_around_commits def keep_around_commits
repository.keep_around(start_commit_sha) [repository, merge_request.source_project.repository].each do |repo|
repository.keep_around(head_commit_sha) repo.keep_around(start_commit_sha)
repository.keep_around(base_commit_sha) repo.keep_around(head_commit_sha)
repo.keep_around(base_commit_sha)
end
end end
end end
...@@ -63,11 +63,11 @@ class Project < ActiveRecord::Base ...@@ -63,11 +63,11 @@ class Project < ActiveRecord::Base
alias_attribute :title, :name alias_attribute :title, :name
# Relations # Relations
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' belongs_to :creator, class_name: 'User'
belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id' belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
belongs_to :namespace belongs_to :namespace
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event'
has_many :boards, before_add: :validate_board_limit, dependent: :destroy has_many :boards, before_add: :validate_board_limit, dependent: :destroy
# Project services # Project services
...@@ -116,7 +116,7 @@ class Project < ActiveRecord::Base ...@@ -116,7 +116,7 @@ class Project < ActiveRecord::Base
has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
has_many :protected_branches, dependent: :destroy has_many :protected_branches, dependent: :destroy
has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember' has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source
alias_method :members, :project_members alias_method :members, :project_members
has_many :users, through: :project_members has_many :users, through: :project_members
...@@ -137,7 +137,7 @@ class Project < ActiveRecord::Base ...@@ -137,7 +137,7 @@ class Project < ActiveRecord::Base
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :project_feature, dependent: :destroy has_one :project_feature, dependent: :destroy
has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id has_many :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id
......
...@@ -419,6 +419,17 @@ class Repository ...@@ -419,6 +419,17 @@ class Repository
@exists = nil @exists = nil
end end
# expire cache that doesn't depend on repository data (when expiring)
def expire_content_cache
expire_tags_cache
expire_tag_count_cache
expire_branches_cache
expire_branch_count_cache
expire_root_ref_cache
expire_emptiness_caches
expire_exists_cache
end
# Runs code after a repository has been created. # Runs code after a repository has been created.
def after_create def after_create
expire_exists_cache expire_exists_cache
...@@ -434,14 +445,7 @@ class Repository ...@@ -434,14 +445,7 @@ class Repository
expire_cache if exists? expire_cache if exists?
# expire cache that don't depend on repository data (when expiring) expire_content_cache
expire_tags_cache
expire_tag_count_cache
expire_branches_cache
expire_branch_count_cache
expire_root_ref_cache
expire_emptiness_caches
expire_exists_cache
repository_event(:remove_repository) repository_event(:remove_repository)
end end
...@@ -473,14 +477,13 @@ class Repository ...@@ -473,14 +477,13 @@ class Repository
end end
def before_import def before_import
expire_emptiness_caches expire_content_cache
expire_exists_cache
end end
# Runs code after a repository has been forked/imported. # Runs code after a repository has been forked/imported.
def after_import def after_import
expire_emptiness_caches expire_content_cache
expire_exists_cache build_cache
end end
# Runs code after a new commit has been pushed. # Runs code after a new commit has been pushed.
......
...@@ -47,7 +47,7 @@ class User < ActiveRecord::Base ...@@ -47,7 +47,7 @@ class User < ActiveRecord::Base
# #
# Namespace for personal projects # Namespace for personal projects
has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace" has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id
# Profile # Profile
has_many :keys, dependent: :destroy has_many :keys, dependent: :destroy
...@@ -66,17 +66,17 @@ class User < ActiveRecord::Base ...@@ -66,17 +66,17 @@ class User < ActiveRecord::Base
# Projects # Projects
has_many :groups_projects, through: :groups, source: :projects has_many :groups_projects, through: :groups, source: :projects
has_many :personal_projects, through: :namespace, source: :projects has_many :personal_projects, through: :namespace, source: :projects
has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, class_name: 'ProjectMember' has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy
has_many :projects, through: :project_members has_many :projects, through: :project_members
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :users_star_projects, dependent: :destroy has_many :users_star_projects, dependent: :destroy
has_many :starred_projects, through: :users_star_projects, source: :project has_many :starred_projects, through: :users_star_projects, source: :project
has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" has_many :snippets, dependent: :destroy, foreign_key: :author_id
has_many :issues, dependent: :destroy, foreign_key: :author_id has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" has_many :events, dependent: :destroy, foreign_key: :author_id
has_many :subscriptions, dependent: :destroy has_many :subscriptions, dependent: :destroy
has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
...@@ -309,7 +309,7 @@ class User < ActiveRecord::Base ...@@ -309,7 +309,7 @@ class User < ActiveRecord::Base
username username
end end
def to_reference(_from_project = nil) def to_reference(_from_project = nil, _target_project = nil)
"#{self.class.reference_prefix}#{username}" "#{self.class.reference_prefix}#{username}"
end end
......
...@@ -7,8 +7,10 @@ module Notes ...@@ -7,8 +7,10 @@ module Notes
if note.award_emoji? if note.award_emoji?
noteable = note.noteable noteable = note.noteable
todo_service.new_award_emoji(noteable, current_user) if noteable.user_can_award?(current_user, note.award_emoji_name)
return noteable.create_award_emoji(note.award_emoji_name, current_user) todo_service.new_award_emoji(noteable, current_user)
return noteable.create_award_emoji(note.award_emoji_name, current_user)
end
end end
# We execute commands (extracted from `params[:note]`) on the noteable # We execute commands (extracted from `params[:note]`) on the noteable
......
# The protected branches API still uses the `developers_can_push` and `developers_can_merge`
# flags for backward compatibility, and so performs translation between that format and the
# internal data model (separate access levels). The translation code is non-trivial, and so
# lives in this service.
module ProtectedBranches
class ApiCreateService < BaseService
def execute
push_access_level =
if params.delete(:developers_can_push)
Gitlab::Access::DEVELOPER
else
Gitlab::Access::MASTER
end
merge_access_level =
if params.delete(:developers_can_merge)
Gitlab::Access::DEVELOPER
else
Gitlab::Access::MASTER
end
@params.merge!(push_access_levels_attributes: [{ access_level: push_access_level }],
merge_access_levels_attributes: [{ access_level: merge_access_level }])
service = ProtectedBranches::CreateService.new(@project, @current_user, @params)
service.execute
end
end
end
# The protected branches API still uses the `developers_can_push` and `developers_can_merge`
# flags for backward compatibility, and so performs translation between that format and the
# internal data model (separate access levels). The translation code is non-trivial, and so
# lives in this service.
module ProtectedBranches
class ApiUpdateService < BaseService
def execute(protected_branch)
@developers_can_push = params.delete(:developers_can_push)
@developers_can_merge = params.delete(:developers_can_merge)
@protected_branch = protected_branch
protected_branch.transaction do
delete_redundant_access_levels
case @developers_can_push
when true
params.merge!(push_access_levels_attributes: [{ access_level: Gitlab::Access::DEVELOPER }])
when false
params.merge!(push_access_levels_attributes: [{ access_level: Gitlab::Access::MASTER }])
end
case @developers_can_merge
when true
params.merge!(merge_access_levels_attributes: [{ access_level: Gitlab::Access::DEVELOPER }])
when false
params.merge!(merge_access_levels_attributes: [{ access_level: Gitlab::Access::MASTER }])
end
service = ProtectedBranches::UpdateService.new(@project, @current_user, @params)
service.execute(protected_branch)
end
end
private
def delete_redundant_access_levels
unless @developers_can_merge.nil?
@protected_branch.merge_access_levels.destroy_all
end
unless @developers_can_push.nil?
@protected_branch.push_access_levels.destroy_all
end
end
end
end
...@@ -12,5 +12,5 @@ ...@@ -12,5 +12,5 @@
%label{for: "user_remember_me"} %label{for: "user_remember_me"}
= f.check_box :remember_me = f.check_box :remember_me
%span Remember me %span Remember me
.pull-right .pull-right.forgot-password
= link_to "Forgot your password?", new_password_path(resource_name) = link_to "Forgot your password?", new_password_path(resource_name)
%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'} %ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'}
%li.active{ role: 'presentation' } %li.active{ role: 'presentation' }
%a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab'} Sign in %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab'} Sign in
%li{ role: 'presentation'} - if signin_enabled? && signup_enabled?
%a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab'} Register %li{ role: 'presentation'}
%a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab'} Register
...@@ -9,12 +9,12 @@ ...@@ -9,12 +9,12 @@
%p %p
Project will be imported as Project will be imported as
%strong %strong
#{@namespace_name}/#{@path} #{@namespace.name}/#{@path}
%p %p
To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here. To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here.
.form-group .form-group
= hidden_field_tag :namespace_id, @namespace_id = hidden_field_tag :namespace_id, @namespace.id
= hidden_field_tag :path, @path = hidden_field_tag :path, @path
= label_tag :file, class: 'control-label' do = label_tag :file, class: 'control-label' do
%span GitLab project export %span GitLab project export
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
":title" => "label.description", ":title" => "label.description",
data: { container: 'body' } } data: { container: 'body' } }
{{ label.title }} {{ label.title }}
%a.has-tooltip{ ":href" => "'/' + issue.assignee.username", %a.has-tooltip{ ":href" => "'#{root_path}' + issue.assignee.username",
":title" => "'Assigned to ' + issue.assignee.name", ":title" => "'Assigned to ' + issue.assignee.name",
"v-if" => "issue.assignee", "v-if" => "issue.assignee",
data: { container: 'body' } } data: { container: 'body' } }
......
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
Git Large File Storage Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
- if Gitlab.config.lfs.enabled && current_user.admin? - if Gitlab.config.registry.enabled
.form-group .form-group
.checkbox .checkbox
= f.label :container_registry_enabled do = f.label :container_registry_enabled do
......
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
Most recent commits displayed first Most recent commits displayed first
%ol#commits-list.list-unstyled %ol#commits-list.list-unstyled
= render "projects/commits/commits", project: @merge_request.project = render "projects/commits/commits", project: @merge_request.source_project
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
:delivery_options: :delivery_options:
:redis_url: <%= config[:redis_url].to_json %> :redis_url: <%= config[:redis_url].to_json %>
:namespace: <%= Gitlab::Redis::SIDEKIQ_NAMESPACE %> :namespace: <%= Gitlab::Redis::SIDEKIQ_NAMESPACE %>
:queue: incoming_email :queue: email_receiver
:worker: EmailReceiverWorker :worker: EmailReceiverWorker
:arbitration_method: redis :arbitration_method: redis
......
...@@ -12,26 +12,23 @@ constraints(GroupUrlConstrainer.new) do ...@@ -12,26 +12,23 @@ constraints(GroupUrlConstrainer.new) do
end end
end end
scope constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } do resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } do
resources :groups, except: [:show] do member do
member do get :issues
get :issues get :merge_requests
get :merge_requests get :projects
get :projects get :activity
get :activity end
end
scope module: :groups do scope module: :groups do
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
post :resend_invite, on: :member post :resend_invite, on: :member
delete :leave, on: :collection delete :leave, on: :collection
end end
resource :avatar, only: [:destroy] resource :avatar, only: [:destroy]
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create] resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
resources :labels, except: [:show], constraints: { id: /\d+/ } resources :labels, except: [:show], constraints: { id: /\d+/ }
end
end end
get 'groups/:id' => 'groups#show', as: :group_canonical
end end
require 'json'
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class MigrateMailroomQueueFromDefault < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
DOWNTIME_REASON = <<-EOF
Moving Sidekiq jobs from queues requires Sidekiq to be stopped. Not stopping
Sidekiq will result in the loss of jobs that are scheduled after this
migration completes.
EOF
disable_ddl_transaction!
# Jobs for which the queue names have been changed (e.g. multiple workers
# using the same non-default queue).
#
# The keys are the old queue names, the values the jobs to move and their new
# queue names.
RENAMED_QUEUES = {
incoming_email: {
'EmailReceiverWorker' => :email_receiver
}
}
def up
Sidekiq.redis do |redis|
RENAMED_QUEUES.each do |queue, jobs|
migrate_from_queue(redis, queue, jobs)
end
end
end
def down
Sidekiq.redis do |redis|
RENAMED_QUEUES.each do |dest_queue, jobs|
jobs.each do |worker, from_queue|
migrate_from_queue(redis, from_queue, worker => dest_queue)
end
end
end
end
def migrate_from_queue(redis, queue, job_mapping)
while job = redis.lpop("queue:#{queue}")
payload = JSON.load(job)
new_queue = job_mapping[payload['class']]
# If we have no target queue to migrate to we're probably dealing with
# some ancient job for which the worker no longer exists. In that case
# there's no sane option we can take, other than just dropping the job.
next unless new_queue
payload['queue'] = new_queue
redis.lpush("queue:#{new_queue}", JSON.dump(payload))
end
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20161019213545) do ActiveRecord::Schema.define(version: 20161024042317) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
......
...@@ -61,6 +61,7 @@ executing commands in the following snippet. ...@@ -61,6 +61,7 @@ executing commands in the following snippet.
```bash ```bash
git clone https://github.com/koding/koding.git git clone https://github.com/koding/koding.git
cd koding cd koding
docker-compose -f docker-compose-init.yml run init
docker-compose up docker-compose up
``` ```
......
...@@ -37,7 +37,7 @@ graphs/dashboards. ...@@ -37,7 +37,7 @@ graphs/dashboards.
GitLab provides built-in tools to aid the process of improving performance: GitLab provides built-in tools to aid the process of improving performance:
* [Sherlock](profiling.md#sherlock) * [Sherlock](profiling.md#sherlock)
* [GitLab Performance Monitoring](../monitoring/performance/monitoring.md) * [GitLab Performance Monitoring](../administration/monitoring/performance/monitoring.md)
* [Request Profiling](../administration/monitoring/performance/request_profiling.md) * [Request Profiling](../administration/monitoring/performance/request_profiling.md)
GitLab employees can use GitLab.com's performance monitoring systems located at GitLab employees can use GitLab.com's performance monitoring systems located at
......
This document was moved to [administration/monitoring/performance/gitlab_configuration](../administration/monitoring/performance/gitlab_configuration.md). This document was moved to [administration/monitoring/performance/gitlab_configuration](../../administration/monitoring/performance/gitlab_configuration.md).
This document was moved to [administration/monitoring/performance/influxdb_configuration](../administration/monitoring/performance/influxdb_configuration.md). This document was moved to [administration/monitoring/performance/influxdb_configuration](../../administration/monitoring/performance/influxdb_configuration.md).
This document was moved to [administration/monitoring/performance/influxdb_schema](../administration/monitoring/performance/influxdb_schema.md). This document was moved to [administration/monitoring/performance/influxdb_schema](../../administration/monitoring/performance/influxdb_schema.md).
This document was moved to [administration/monitoring/performance/introduction](../administration/monitoring/performance/introduction.md). This document was moved to [administration/monitoring/performance/introduction](../../administration/monitoring/performance/introduction.md).
...@@ -30,6 +30,10 @@ Use this if you've installed GitLab from source: ...@@ -30,6 +30,10 @@ Use this if you've installed GitLab from source:
``` ```
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
``` ```
If you are running GitLab within a Docker container, you can run the backup from the host:
```
docker -t exec <container name> gitlab-rake gitlab:backup:create
```
You can specify that portions of the application data be skipped using the You can specify that portions of the application data be skipped using the
environment variable `SKIP`. You can skip: environment variable `SKIP`. You can skip:
......
...@@ -254,6 +254,12 @@ test: ...@@ -254,6 +254,12 @@ test:
This will make GitLab CI initialize (fetch) and update (checkout) all your This will make GitLab CI initialize (fetch) and update (checkout) all your
submodules recursively. submodules recursively.
If Git does not use the newly added relative URLs but still uses your old URLs,
you might need to add `git submodule sync --recursive` to your `.gitlab-ci.yml`,
prior to running `git submodule update --init --recursive`. This transfers the
changes from your `.gitmodules` file into the `.git` folder, which is kept by
runners between runs.
In case your environment or your Docker image doesn't have Git installed, In case your environment or your Docker image doesn't have Git installed,
you have to either ask your Administrator or install the missing dependency you have to either ask your Administrator or install the missing dependency
yourself: yourself:
......
...@@ -54,43 +54,25 @@ module API ...@@ -54,43 +54,25 @@ module API
not_found!('Branch') unless @branch not_found!('Branch') unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name) protected_branch = user_project.protected_branches.find_by(name: @branch.name)
developers_can_merge = to_boolean(params[:developers_can_merge])
developers_can_push = to_boolean(params[:developers_can_push])
protected_branch_params = { protected_branch_params = {
name: @branch.name name: @branch.name,
developers_can_push: to_boolean(params[:developers_can_push]),
developers_can_merge: to_boolean(params[:developers_can_merge])
} }
# If `developers_can_merge` is switched off, _all_ `DEVELOPER` service_args = [user_project, current_user, protected_branch_params]
# merge_access_levels need to be deleted.
if developers_can_merge == false
protected_branch.merge_access_levels.where(access_level: Gitlab::Access::DEVELOPER).destroy_all
end
# If `developers_can_push` is switched off, _all_ `DEVELOPER` protected_branch = if protected_branch
# push_access_levels need to be deleted. ProtectedBranches::ApiUpdateService.new(*service_args).execute(protected_branch)
if developers_can_push == false else
protected_branch.push_access_levels.where(access_level: Gitlab::Access::DEVELOPER).destroy_all ProtectedBranches::ApiCreateService.new(*service_args).execute
end end
protected_branch_params.merge!( if protected_branch.valid?
merge_access_levels_attributes: [{ present @branch, with: Entities::RepoBranch, project: user_project
access_level: developers_can_merge ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
}],
push_access_levels_attributes: [{
access_level: developers_can_push ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
}]
)
if protected_branch
service = ProtectedBranches::UpdateService.new(user_project, current_user, protected_branch_params)
service.execute(protected_branch)
else else
service = ProtectedBranches::CreateService.new(user_project, current_user, protected_branch_params) render_api_error!(protected_branch.errors.full_messages, 422)
service.execute
end end
present @branch, with: Entities::RepoBranch, project: user_project
end end
# Unprotect a single branch # Unprotect a single branch
...@@ -123,7 +105,7 @@ module API ...@@ -123,7 +105,7 @@ module API
post ":id/repository/branches" do post ":id/repository/branches" do
authorize_push_project authorize_push_project
result = CreateBranchService.new(user_project, current_user). result = CreateBranchService.new(user_project, current_user).
execute(params[:branch_name], params[:ref]) execute(params[:branch_name], params[:ref])
if result[:status] == :success if result[:status] == :success
present result[:branch], present result[:branch],
...@@ -142,10 +124,10 @@ module API ...@@ -142,10 +124,10 @@ module API
# Example Request: # Example Request:
# DELETE /projects/:id/repository/branches/:branch # DELETE /projects/:id/repository/branches/:branch
delete ":id/repository/branches/:branch", delete ":id/repository/branches/:branch",
requirements: { branch: /.+/ } do requirements: { branch: /.+/ } do
authorize_push_project authorize_push_project
result = DeleteBranchService.new(user_project, current_user). result = DeleteBranchService.new(user_project, current_user).
execute(params[:branch]) execute(params[:branch])
if result[:status] == :success if result[:status] == :success
{ {
......
...@@ -3,15 +3,32 @@ module API ...@@ -3,15 +3,32 @@ module API
class Builds < Grape::API class Builds < Grape::API
before { authenticate! } before { authenticate! }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do resource :projects do
# Get a project builds helpers do
# params :optional_scope do
# Parameters: optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
# id (required) - The ID of a project values: ['pending', 'running', 'failed', 'success', 'canceled'],
# scope (optional) - The scope of builds to show (one or array of: created, pending, running, failed, success, canceled, skipped; coerce_with: ->(scope) {
# if none provided showing all builds) if scope.is_a?(String)
# Example Request: [scope]
# GET /projects/:id/builds elsif scope.is_a?(Hashie::Mash)
scope.values
else
['unknown']
end
}
end
end
desc 'Get a project builds' do
success Entities::Build
end
params do
use :optional_scope
end
get ':id/builds' do get ':id/builds' do
builds = user_project.builds.order('id DESC') builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope]) builds = filter_builds(builds, params[:scope])
...@@ -20,15 +37,13 @@ module API ...@@ -20,15 +37,13 @@ module API
user_can_download_artifacts: can?(current_user, :read_build, user_project) user_can_download_artifacts: can?(current_user, :read_build, user_project)
end end
# Get builds for a specific commit of a project desc 'Get builds for a specific commit of a project' do
# success Entities::Build
# Parameters: end
# id (required) - The ID of a project params do
# sha (required) - The SHA id of a commit requires :sha, type: String, desc: 'The SHA id of a commit'
# scope (optional) - The scope of builds to show (one or array of: created, pending, running, failed, success, canceled, skipped; use :optional_scope
# if none provided showing all builds) end
# Example Request:
# GET /projects/:id/repository/commits/:sha/builds
get ':id/repository/commits/:sha/builds' do get ':id/repository/commits/:sha/builds' do
authorize_read_builds! authorize_read_builds!
...@@ -42,13 +57,12 @@ module API ...@@ -42,13 +57,12 @@ module API
user_can_download_artifacts: can?(current_user, :read_build, user_project) user_can_download_artifacts: can?(current_user, :read_build, user_project)
end end
# Get a specific build of a project desc 'Get a specific build of a project' do
# success Entities::Build
# Parameters: end
# id (required) - The ID of a project params do
# build_id (required) - The ID of a build requires :build_id, type: Integer, desc: 'The ID of a build'
# Example Request: end
# GET /projects/:id/builds/:build_id
get ':id/builds/:build_id' do get ':id/builds/:build_id' do
authorize_read_builds! authorize_read_builds!
...@@ -58,13 +72,12 @@ module API ...@@ -58,13 +72,12 @@ module API
user_can_download_artifacts: can?(current_user, :read_build, user_project) user_can_download_artifacts: can?(current_user, :read_build, user_project)
end end
# Download the artifacts file from build desc 'Download the artifacts file from build' do
# detail 'This feature was introduced in GitLab 8.5'
# Parameters: end
# id (required) - The ID of a build params do
# token (required) - The build authorization token requires :build_id, type: Integer, desc: 'The ID of a build'
# Example Request: end
# GET /projects/:id/builds/:build_id/artifacts
get ':id/builds/:build_id/artifacts' do get ':id/builds/:build_id/artifacts' do
authorize_read_builds! authorize_read_builds!
...@@ -73,14 +86,13 @@ module API ...@@ -73,14 +86,13 @@ module API
present_artifacts!(build.artifacts_file) present_artifacts!(build.artifacts_file)
end end
# Download the artifacts file from ref_name and job desc 'Download the artifacts file from build' do
# detail 'This feature was introduced in GitLab 8.10'
# Parameters: end
# id (required) - The ID of a project params do
# ref_name (required) - The ref from repository requires :ref_name, type: String, desc: 'The ref from repository'
# job (required) - The name for the build requires :job, type: String, desc: 'The name for the build'
# Example Request: end
# GET /projects/:id/builds/artifacts/:ref_name/download?job=name
get ':id/builds/artifacts/:ref_name/download', get ':id/builds/artifacts/:ref_name/download',
requirements: { ref_name: /.+/ } do requirements: { ref_name: /.+/ } do
authorize_read_builds! authorize_read_builds!
...@@ -91,17 +103,13 @@ module API ...@@ -91,17 +103,13 @@ module API
present_artifacts!(latest_build.artifacts_file) present_artifacts!(latest_build.artifacts_file)
end end
# Get a trace of a specific build of a project
#
# Parameters:
# id (required) - The ID of a project
# build_id (required) - The ID of a build
# Example Request:
# GET /projects/:id/build/:build_id/trace
#
# TODO: We should use `present_file!` and leave this implementation for backward compatibility (when build trace # TODO: We should use `present_file!` and leave this implementation for backward compatibility (when build trace
# is saved in the DB instead of file). But before that, we need to consider how to replace the value of # is saved in the DB instead of file). But before that, we need to consider how to replace the value of
# `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse. # `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
desc 'Get a trace of a specific build of a project'
params do
requires :build_id, type: Integer, desc: 'The ID of a build'
end
get ':id/builds/:build_id/trace' do get ':id/builds/:build_id/trace' do
authorize_read_builds! authorize_read_builds!
...@@ -115,13 +123,12 @@ module API ...@@ -115,13 +123,12 @@ module API
body trace body trace
end end
# Cancel a specific build of a project desc 'Cancel a specific build of a project' do
# success Entities::Build
# parameters: end
# id (required) - the id of a project params do
# build_id (required) - the id of a build requires :build_id, type: Integer, desc: 'The ID of a build'
# example request: end
# post /projects/:id/build/:build_id/cancel
post ':id/builds/:build_id/cancel' do post ':id/builds/:build_id/cancel' do
authorize_update_builds! authorize_update_builds!
...@@ -133,13 +140,12 @@ module API ...@@ -133,13 +140,12 @@ module API
user_can_download_artifacts: can?(current_user, :read_build, user_project) user_can_download_artifacts: can?(current_user, :read_build, user_project)
end end
# Retry a specific build of a project desc 'Retry a specific build of a project' do
# success Entities::Build
# parameters: end
# id (required) - the id of a project params do
# build_id (required) - the id of a build requires :build_id, type: Integer, desc: 'The ID of a build'
# example request: end
# post /projects/:id/build/:build_id/retry
post ':id/builds/:build_id/retry' do post ':id/builds/:build_id/retry' do
authorize_update_builds! authorize_update_builds!
...@@ -152,13 +158,12 @@ module API ...@@ -152,13 +158,12 @@ module API
user_can_download_artifacts: can?(current_user, :read_build, user_project) user_can_download_artifacts: can?(current_user, :read_build, user_project)
end end
# Erase build (remove artifacts and build trace) desc 'Erase build (remove artifacts and build trace)' do
# success Entities::Build
# Parameters: end
# id (required) - the id of a project params do
# build_id (required) - the id of a build requires :build_id, type: Integer, desc: 'The ID of a build'
# example Request: end
# post /projects/:id/build/:build_id/erase
post ':id/builds/:build_id/erase' do post ':id/builds/:build_id/erase' do
authorize_update_builds! authorize_update_builds!
...@@ -170,13 +175,12 @@ module API ...@@ -170,13 +175,12 @@ module API
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project) user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
end end
# Keep the artifacts to prevent them from being deleted desc 'Keep the artifacts to prevent them from being deleted' do
# success Entities::Build
# Parameters: end
# id (required) - the id of a project params do
# build_id (required) - The ID of a build requires :build_id, type: Integer, desc: 'The ID of a build'
# Example Request: end
# POST /projects/:id/builds/:build_id/artifacts/keep
post ':id/builds/:build_id/artifacts/keep' do post ':id/builds/:build_id/artifacts/keep' do
authorize_update_builds! authorize_update_builds!
...@@ -235,14 +239,6 @@ module API ...@@ -235,14 +239,6 @@ module API
return builds if scope.nil? || scope.empty? return builds if scope.nil? || scope.empty?
available_statuses = ::CommitStatus::AVAILABLE_STATUSES available_statuses = ::CommitStatus::AVAILABLE_STATUSES
scope =
if scope.is_a?(String)
[scope]
elsif scope.is_a?(Hashie::Mash)
scope.values
else
['unknown']
end
unknown = scope - available_statuses unknown = scope - available_statuses
render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty? render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty?
......
...@@ -19,6 +19,7 @@ module API ...@@ -19,6 +19,7 @@ module API
optional :until, type: String, desc: 'Only commits before or in this date will be returned' optional :until, type: String, desc: 'Only commits before or in this date will be returned'
optional :page, type: Integer, default: 0, desc: 'The page for pagination' optional :page, type: Integer, default: 0, desc: 'The page for pagination'
optional :per_page, type: Integer, default: 20, desc: 'The number of results per page' optional :per_page, type: Integer, default: 20, desc: 'The number of results per page'
optional :path, type: String, desc: 'The file path'
end end
get ":id/repository/commits" do get ":id/repository/commits" do
# TODO remove the next line for 9.0, use DateTime type in the params block # TODO remove the next line for 9.0, use DateTime type in the params block
...@@ -28,6 +29,7 @@ module API ...@@ -28,6 +29,7 @@ module API
offset = params[:page] * params[:per_page] offset = params[:page] * params[:per_page]
commits = user_project.repository.commits(ref, commits = user_project.repository.commits(ref,
path: params[:path],
limit: params[:per_page], limit: params[:per_page],
offset: offset, offset: offset,
after: params[:since], after: params[:since],
......
...@@ -3,37 +3,32 @@ module API ...@@ -3,37 +3,32 @@ module API
class Labels < Grape::API class Labels < Grape::API
before { authenticate! } before { authenticate! }
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do resource :projects do
# Get all labels of the project desc 'Get all labels of the project' do
# success Entities::Label
# Parameters: end
# id (required) - The ID of a project
# Example Request:
# GET /projects/:id/labels
get ':id/labels' do get ':id/labels' do
present available_labels, with: Entities::Label, current_user: current_user present available_labels, with: Entities::Label, current_user: current_user
end end
# Creates a new label desc 'Create a new label' do
# success Entities::Label
# Parameters: end
# id (required) - The ID of a project params do
# name (required) - The name of the label to be created requires :name, type: String, desc: 'The name of the label to be created'
# color (required) - Color of the label given in 6-digit hex requires :color, type: String, desc: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)"
# notation with leading '#' sign (e.g. #FFAABB) optional :description, type: String, desc: 'The description of label to be created'
# description (optional) - The description of label to be created end
# Example Request:
# POST /projects/:id/labels
post ':id/labels' do post ':id/labels' do
authorize! :admin_label, user_project authorize! :admin_label, user_project
required_attributes! [:name, :color]
attrs = attributes_for_keys [:name, :color, :description]
label = user_project.find_label(attrs[:name])
label = user_project.find_label(params[:name])
conflict!('Label already exists') if label conflict!('Label already exists') if label
label = user_project.labels.create(attrs) label = user_project.labels.create(declared(params, include_parent_namespaces: false).to_h)
if label.valid? if label.valid?
present label, with: Entities::Label, current_user: current_user present label, with: Entities::Label, current_user: current_user
...@@ -42,54 +37,44 @@ module API ...@@ -42,54 +37,44 @@ module API
end end
end end
# Deletes an existing label desc 'Delete an existing label' do
# success Entities::Label
# Parameters: end
# id (required) - The ID of a project params do
# name (required) - The name of the label to be deleted requires :name, type: String, desc: 'The name of the label to be deleted'
# end
# Example Request:
# DELETE /projects/:id/labels
delete ':id/labels' do delete ':id/labels' do
authorize! :admin_label, user_project authorize! :admin_label, user_project
required_attributes! [:name]
label = user_project.find_label(params[:name]) label = user_project.find_label(params[:name])
not_found!('Label') unless label not_found!('Label') unless label
label.destroy present label.destroy, with: Entities::Label, current_user: current_user
end end
# Updates an existing label. At least one optional parameter is required. desc 'Update an existing label. At least one optional parameter is required.' do
# success Entities::Label
# Parameters: end
# id (required) - The ID of a project params do
# name (required) - The name of the label to be deleted requires :name, type: String, desc: 'The name of the label to be updated'
# new_name (optional) - The new name of the label optional :new_name, type: String, desc: 'The new name of the label'
# color (optional) - Color of the label given in 6-digit hex optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)"
# notation with leading '#' sign (e.g. #FFAABB) optional :description, type: String, desc: 'The new description of label'
# description (optional) - The description of label to be created at_least_one_of :new_name, :color, :description
# Example Request: end
# PUT /projects/:id/labels
put ':id/labels' do put ':id/labels' do
authorize! :admin_label, user_project authorize! :admin_label, user_project
required_attributes! [:name]
label = user_project.find_label(params[:name]) label = user_project.find_label(params[:name])
not_found!('Label not found') unless label not_found!('Label not found') unless label
attrs = attributes_for_keys [:new_name, :color, :description] update_params = declared(params,
include_parent_namespaces: false,
if attrs.empty? include_missing: false).to_h
render_api_error!('Required parameters "new_name" or "color" ' \
'missing',
400)
end
# Rename new name to the actual label attribute name # Rename new name to the actual label attribute name
attrs[:name] = attrs.delete(:new_name) if attrs.key?(:new_name) update_params['name'] = update_params.delete('new_name') if update_params.key?('new_name')
if label.update(attrs) if label.update(update_params)
present label, with: Entities::Label, current_user: current_user present label, with: Entities::Label, current_user: current_user
else else
render_validation_error!(label) render_validation_error!(label)
......
...@@ -52,8 +52,8 @@ module Banzai ...@@ -52,8 +52,8 @@ module Banzai
relative_url_root, relative_url_root,
context[:project].path_with_namespace, context[:project].path_with_namespace,
uri_type(file_path), uri_type(file_path),
ref, Addressable::URI.escape(ref),
file_path Addressable::URI.escape(file_path)
].compact.join('/').squeeze('/').chomp('/') ].compact.join('/').squeeze('/').chomp('/')
uri uri
......
class NamespaceUrlConstrainer class NamespaceUrlConstrainer
def matches?(request) def matches?(request)
id = request.path.sub(/\A\/+/, '').split('/').first.sub(/.atom\z/, '') id = request.path
id = id.sub(/\A#{relative_url_root}/, '') if relative_url_root
id = id.sub(/\A\/+/, '').split('/').first
id = id.sub(/.atom\z/, '') if id
if id =~ Gitlab::Regex.namespace_regex if id =~ Gitlab::Regex.namespace_regex
find_resource(id) find_resource(id)
...@@ -10,4 +13,12 @@ class NamespaceUrlConstrainer ...@@ -10,4 +13,12 @@ class NamespaceUrlConstrainer
def find_resource(id) def find_resource(id)
Namespace.find_by_path(id) Namespace.find_by_path(id)
end end
private
def relative_url_root
if defined?(Gitlab::Application.config.relative_url_root)
Gitlab::Application.config.relative_url_root
end
end
end end
# rubocop: disable Rails/Output
module Gitlab
# Checks if a set of migrations requires downtime or not.
class EeCompatCheck
EE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
attr_reader :ce_branch, :check_dir, :ce_repo
def initialize(branch:, check_dir:, ce_repo: nil)
@ce_branch = branch
@check_dir = check_dir
@ce_repo = ce_repo || 'https://gitlab.com/gitlab-org/gitlab-ce.git'
end
def check
ensure_ee_repo
delete_patches
generate_patch(ce_branch, ce_patch_full_path)
Dir.chdir(check_dir) do
step("In the #{check_dir} directory")
step("Pulling latest master", %w[git pull --ff-only origin master])
status = catch(:halt_check) do
ce_branch_compat_check!
delete_ee_branch_locally
ee_branch_presence_check!
ee_branch_compat_check!
end
delete_ee_branch_locally
delete_patches
if status.nil?
true
else
false
end
end
end
private
def ensure_ee_repo
if Dir.exist?(check_dir)
step("#{check_dir} already exists")
else
cmd = %W[git clone --branch master --single-branch --depth 1 #{EE_REPO} #{check_dir}]
step("Cloning #{EE_REPO} into #{check_dir}", cmd)
end
end
def ce_branch_compat_check!
cmd = %W[git apply --check #{ce_patch_full_path}]
status = step("Checking if #{ce_patch_name} applies cleanly to EE/master", cmd)
if status.zero?
puts ce_applies_cleanly_msg(ce_branch)
throw(:halt_check)
end
end
def ee_branch_presence_check!
status = step("Fetching origin/#{ee_branch}", %W[git fetch origin #{ee_branch}])
unless status.zero?
puts
puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
throw(:halt_check, :ko)
end
end
def ee_branch_compat_check!
step("Checking out origin/#{ee_branch}", %W[git checkout -b #{ee_branch} FETCH_HEAD])
generate_patch(ee_branch, ee_patch_full_path)
cmd = %W[git apply --check #{ee_patch_full_path}]
status = step("Checking if #{ee_patch_name} applies cleanly to EE/master", cmd)
unless status.zero?
puts
puts ee_branch_doesnt_apply_cleanly_msg
throw(:halt_check, :ko)
end
puts
puts ee_applies_cleanly_msg
end
def generate_patch(branch, filepath)
FileUtils.rm(filepath, force: true)
depth = 0
loop do
depth += 10
step("Fetching origin/master", %W[git fetch origin master --depth=#{depth}])
status = step("Finding merge base with master", %W[git merge-base FETCH_HEAD #{branch}])
break if status.zero? || depth > 500
end
raise "#{branch} is too far behind master, please rebase it!" if depth > 500
step("Generating the patch against master")
output, status = Gitlab::Popen.popen(%w[git format-patch FETCH_HEAD --stdout])
throw(:halt_check, :ko) unless status.zero?
File.write(filepath, output)
throw(:halt_check, :ko) unless File.exist?(filepath)
end
def delete_ee_branch_locally
command(%w[git checkout master])
step("Deleting the local #{ee_branch} branch", %W[git branch -D #{ee_branch}])
end
def delete_patches
step("Deleting #{ce_patch_full_path}")
FileUtils.rm(ce_patch_full_path, force: true)
step("Deleting #{ee_patch_full_path}")
FileUtils.rm(ee_patch_full_path, force: true)
end
def ce_patch_name
@ce_patch_name ||= "#{ce_branch}.patch"
end
def ce_patch_full_path
@ce_patch_full_path ||= File.expand_path(ce_patch_name, check_dir)
end
def ee_branch
@ee_branch ||= "#{ce_branch}-ee"
end
def ee_patch_name
@ee_patch_name ||= "#{ee_branch}.patch"
end
def ee_patch_full_path
@ee_patch_full_path ||= File.expand_path(ee_patch_name, check_dir)
end
def step(desc, cmd = nil)
puts "\n=> #{desc}\n"
if cmd
puts "\n$ #{cmd.join(' ')}"
command(cmd)
end
end
def command(cmd)
output, status = Gitlab::Popen.popen(cmd)
puts output
status
end
def ce_applies_cleanly_msg(ce_branch)
<<-MSG.strip_heredoc
=================================================================
🎉 Congratulations!! 🎉
The #{ce_branch} branch applies cleanly to EE/master!
Much ❤️!!
=================================================================\n
MSG
end
def ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg
<<-MSG.strip_heredoc
=================================================================
💥 Oh no! 💥
The #{ce_branch} branch does not apply cleanly to the current
EE/master, and no #{ee_branch} branch was found in the EE repository.
Please create a #{ee_branch} branch that includes changes from
#{ce_branch} but also specific changes than can be applied cleanly
to EE/master.
There are different ways to create such branch:
1. Create a new branch based on the CE branch and rebase it on top of EE/master
# In the EE repo
$ git fetch #{ce_repo} #{ce_branch}
$ git checkout -b #{ee_branch} FETCH_HEAD
# You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit
# before rebasing to limit the conflicts-resolving steps during the rebase
$ git fetch origin
$ git rebase origin/master
At this point you will likely have conflicts.
Solve them, and continue/finish the rebase.
You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE".
2. Create a new branch from master and cherry-pick your CE commits
# In the EE repo
$ git fetch origin
$ git checkout -b #{ee_branch} FETCH_HEAD
$ git fetch #{ce_repo} #{ce_branch}
$ git cherry-pick SHA # Repeat for all the commits you want to pick
You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit.
Don't forget to push your branch to #{EE_REPO}:
# In the EE repo
$ git push origin #{ee_branch}
You can then retry this failed build, and hopefully it should pass.
Stay 💪 !
=================================================================\n
MSG
end
def ee_branch_doesnt_apply_cleanly_msg
<<-MSG.strip_heredoc
=================================================================
💥 Oh no! 💥
The #{ce_branch} does not apply cleanly to the current
EE/master, and even though a #{ee_branch} branch exists in the EE
repository, it does not apply cleanly either to EE/master!
Please update the #{ee_branch}, push it again to #{EE_REPO}, and
retry this build.
Stay 💪 !
=================================================================\n
MSG
end
def ee_applies_cleanly_msg
<<-MSG.strip_heredoc
=================================================================
🎉 Congratulations!! 🎉
The #{ee_branch} branch applies cleanly to EE/master!
Much ❤️!!
=================================================================\n
MSG
end
end
end
desc 'Checks if the branch would apply cleanly to EE' desc 'Checks if the branch would apply cleanly to EE'
task ce_to_ee_merge_check: :environment do task ee_compat_check: :environment do
Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke Rake::Task['gitlab:dev:ee_compat_check'].invoke
end end
namespace :gitlab do namespace :gitlab do
namespace :dev do namespace :dev do
desc 'Checks if the branch would apply cleanly to EE' desc 'Checks if the branch would apply cleanly to EE'
task ce_to_ee_merge_check: :environment do task ee_compat_check: :environment do
return if defined?(Gitlab::License) return if defined?(Gitlab::License)
return unless ENV['CI'] return unless ENV['CI']
ce_repo = ENV['CI_BUILD_REPO'] success =
ce_branch = ENV['CI_BUILD_REF_NAME'] Gitlab::EeCompatCheck.new(
branch: ENV['CI_BUILD_REF_NAME'],
ee_repo = 'https://gitlab.com/gitlab-org/gitlab-ee.git' check_dir: File.expand_path('ee-compat-check', __dir__),
ee_branch = "#{ce_branch}-ee" ce_repo: ENV['CI_BUILD_REPO']
ee_dir = 'gitlab-ee-merge-check' ).check
puts "\n=> Cloning #{ee_repo} into #{ee_dir}\n" if success
`git clone #{ee_repo} #{ee_dir} --depth 1` exit 0
Dir.chdir(ee_dir) do else
puts "\n => Fetching #{ce_repo}/#{ce_branch}\n" exit 1
`git fetch #{ce_repo} #{ce_branch} --depth 1`
# Try to merge the current tested branch to EE/master...
puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n"
`git merge FETCH_HEAD`
exit 0 if $?.success?
# Check if the <branch>-ee branch exists...
puts "\n => Check if #{ee_repo}/#{ee_branch} exists\n"
`git rev-parse --verify #{ee_branch}`
# The <branch>-ee doesn't exist
unless $?.success?
puts
puts <<-MSG.strip_heredoc
=================================================================
The #{ce_branch} branch cannot be merged without conflicts to the
current EE/master, and no #{ee_branch} branch was detected in
the EE repository.
Please create a #{ee_branch} branch that includes changes from
#{ce_branch} but also specific changes than can be applied cleanly
to EE/master.
You can create this branch as follows:
1. In the EE repo:
$ git fetch origin
$ git fetch #{ce_repo} #{ce_branch}
$ git checkout -b #{ee_branch} FETCH_HEAD
$ git rebase origin/master
2. At this point you will likely have conflicts, solve them, and
continue/finish the rebase. Note: You can squash the CE commits
before rebasing.
3. You can squash all the original #{ce_branch} commits into a
single "Port of #{ce_branch} to EE".
4. Push your branch to #{ee_repo}:
$ git push origin #{ee_branch}
=================================================================\n
MSG
exit 1
end
# Try to merge the <branch>-ee branch to EE/master...
puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n"
`git merge #{ee_branch} master`
# The <branch>-ee cannot be merged cleanly to EE/master...
unless $?.success?
puts
puts <<-MSG.strip_heredoc
=================================================================
The #{ce_branch} branch cannot be merged without conflicts to
EE/master, and even though the #{ee_branch} branch exists in the EE
repository, it cannot be merged without conflicts to EE/master.
Please update the #{ee_branch}, push it again to #{ee_repo}, and
retry this job.
=================================================================\n
MSG
exit 2
end
puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n"
`git merge FETCH_HEAD`
exit 0 if $?.success?
# The <branch>-ee can be merged cleanly to EE/master, but <branch> still
# cannot be merged cleanly to EE/master...
puts
puts <<-MSG.strip_heredoc
=================================================================
The #{ce_branch} branch cannot be merged without conflicts to EE, and
even though the #{ee_branch} branch exists in the EE repository and
applies cleanly to EE/master, it doesn't prevent conflicts when
merging #{ce_branch} into EE.
We may be in a complex situation here.
=================================================================\n
MSG
exit 3
end end
end end
end end
......
...@@ -13,6 +13,49 @@ describe Groups::GroupMembersController do ...@@ -13,6 +13,49 @@ describe Groups::GroupMembersController do
end end
end end
describe 'POST create' do
let(:group_user) { create(:user) }
before { sign_in(user) }
context 'when user does not have enough rights' do
before { group.add_developer(user) }
it 'returns 403' do
post :create, group_id: group,
user_ids: group_user.id,
access_level: Gitlab::Access::GUEST
expect(response).to have_http_status(403)
expect(group.users).not_to include group_user
end
end
context 'when user has enough rights' do
before { group.add_owner(user) }
it 'adds user to members' do
post :create, group_id: group,
user_ids: group_user.id,
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'Users were successfully added.'
expect(response).to redirect_to(group_group_members_path(group))
expect(group.users).to include group_user
end
it 'adds no user to members' do
post :create, group_id: group,
user_ids: '',
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'No users specified.'
expect(response).to redirect_to(group_group_members_path(group))
expect(group.users).not_to include group_user
end
end
end
describe 'DELETE destroy' do describe 'DELETE destroy' do
let(:member) { create(:group_member, :developer, group: group) } let(:member) { create(:group_member, :developer, group: group) }
......
...@@ -13,6 +13,54 @@ describe Projects::ProjectMembersController do ...@@ -13,6 +13,54 @@ describe Projects::ProjectMembersController do
end end
end end
describe 'POST create' do
context 'when users are added' do
let(:project_user) { create(:user) }
before { sign_in(user) }
context 'when user does not have enough rights' do
before { project.team << [user, :developer] }
it 'returns 404' do
post :create, namespace_id: project.namespace,
project_id: project,
user_ids: project_user.id,
access_level: Gitlab::Access::GUEST
expect(response).to have_http_status(404)
expect(project.users).not_to include project_user
end
end
context 'when user has enough rights' do
before { project.team << [user, :master] }
it 'adds user to members' do
post :create, namespace_id: project.namespace,
project_id: project,
user_ids: project_user.id,
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'Users were successfully added.'
expect(response).to redirect_to(namespace_project_project_members_path(project.namespace, project))
expect(project.users).to include project_user
end
it 'adds no user to members' do
post :create, namespace_id: project.namespace,
project_id: project,
user_ids: '',
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'No users or groups specified.'
expect(response).to redirect_to(namespace_project_project_members_path(project.namespace, project))
expect(project.users).not_to include project_user
end
end
end
end
describe 'DELETE destroy' do describe 'DELETE destroy' do
let(:member) { create(:project_member, :developer, project: project) } let(:member) { create(:project_member, :developer, project: project) }
......
...@@ -624,6 +624,10 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -624,6 +624,10 @@ describe 'Issue Boards', feature: true, js: true do
it 'does not show create new list' do it 'does not show create new list' do
expect(page).not_to have_selector('.js-new-board-list') expect(page).not_to have_selector('.js-new-board-list')
end end
it 'does not allow dragging' do
expect(page).not_to have_selector('.user-can-drag')
end
end end
context 'as guest user' do context 'as guest user' do
......
...@@ -215,4 +215,69 @@ feature 'Login', feature: true do ...@@ -215,4 +215,69 @@ feature 'Login', feature: true do
end end
end end
end end
describe 'UI tabs and panes' do
context 'when no defaults are changed' do
it 'correctly renders tabs and panes' do
ensure_tab_pane_correctness
end
end
context 'when signup is disabled' do
before do
stub_application_setting(signup_enabled: false)
end
it 'correctly renders tabs and panes' do
ensure_tab_pane_correctness
end
end
context 'when ldap is enabled' do
before do
visit new_user_session_path
allow(page).to receive(:form_based_providers).and_return([:ldapmain])
allow(page).to receive(:ldap_enabled).and_return(true)
end
it 'correctly renders tabs and panes' do
ensure_tab_pane_correctness(false)
end
end
context 'when crowd is enabled' do
before do
visit new_user_session_path
allow(page).to receive(:form_based_providers).and_return([:crowd])
allow(page).to receive(:crowd_enabled?).and_return(true)
end
it 'correctly renders tabs and panes' do
ensure_tab_pane_correctness(false)
end
end
def ensure_tab_pane_correctness(visit_path = true)
if visit_path
visit new_user_session_path
end
ensure_tab_pane_counts
ensure_one_active_tab
ensure_one_active_pane
end
def ensure_tab_pane_counts
tabs_count = page.all('[role="tab"]').size
expect(page).to have_selector('[role="tabpanel"]', count: tabs_count)
end
def ensure_one_active_tab
expect(page).to have_selector('.nav-tabs > li.active', count: 1)
end
def ensure_one_active_pane
expect(page).to have_selector('.tab-pane.active', count: 1)
end
end
end end
...@@ -38,6 +38,14 @@ describe LabelsFinder do ...@@ -38,6 +38,14 @@ describe LabelsFinder do
expect(finder.execute).to eq [group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4] expect(finder.execute).to eq [group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4]
end end
it 'returns labels available if nil title is supplied' do
group_2.add_developer(user)
# params[:title] will return `nil` regardless whether it is specified
finder = described_class.new(user, title: nil)
expect(finder.execute).to eq [group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4]
end
end end
context 'filtering by group_id' do context 'filtering by group_id' do
...@@ -64,6 +72,30 @@ describe LabelsFinder do ...@@ -64,6 +72,30 @@ describe LabelsFinder do
expect(finder.execute).to eq [group_label_2] expect(finder.execute).to eq [group_label_2]
end end
it 'returns label with title alias' do
finder = described_class.new(user, name: 'Group Label 2')
expect(finder.execute).to eq [group_label_2]
end
it 'returns no labels if empty title is supplied' do
finder = described_class.new(user, title: [])
expect(finder.execute).to be_empty
end
it 'returns no labels if blank title is supplied' do
finder = described_class.new(user, title: '')
expect(finder.execute).to be_empty
end
it 'returns no labels if empty name is supplied' do
finder = described_class.new(user, name: [])
expect(finder.execute).to be_empty
end
end end
end end
end end
...@@ -11,12 +11,12 @@ ...@@ -11,12 +11,12 @@
this.fieldErrors = new global.GlFieldErrors($form); this.fieldErrors = new global.GlFieldErrors($form);
}); });
it('should properly initialize the form', function() { it('should select the correct input elements', function() {
expect(this.$form).toBeDefined(); expect(this.$form).toBeDefined();
expect(this.$form.length).toBe(1); expect(this.$form.length).toBe(1);
expect(this.fieldErrors).toBeDefined(); expect(this.fieldErrors).toBeDefined();
const inputs = this.fieldErrors.state.inputs; const inputs = this.fieldErrors.state.inputs;
expect(inputs.length).toBe(5); expect(inputs.length).toBe(4);
}); });
it('should ignore elements with custom error handling', function() { it('should ignore elements with custom error handling', function() {
......
...@@ -50,14 +50,6 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do ...@@ -50,14 +50,6 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
end end
end end
shared_examples :relative_to_requested do
it 'rebuilds URL relative to the requested path' do
doc = filter(link('users.md'))
expect(doc.at_css('a')['href']).
to eq "/#{project_path}/blob/#{ref}/doc/api/users.md"
end
end
context 'with a project_wiki' do context 'with a project_wiki' do
let(:project_wiki) { double('ProjectWiki') } let(:project_wiki) { double('ProjectWiki') }
include_examples :preserve_unchanged include_examples :preserve_unchanged
...@@ -188,12 +180,38 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do ...@@ -188,12 +180,38 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
context 'when requested path is a file in the repo' do context 'when requested path is a file in the repo' do
let(:requested_path) { 'doc/api/README.md' } let(:requested_path) { 'doc/api/README.md' }
include_examples :relative_to_requested it 'rebuilds URL relative to the containing directory' do
doc = filter(link('users.md'))
expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md"
end
end end
context 'when requested path is a directory in the repo' do context 'when requested path is a directory in the repo' do
let(:requested_path) { 'doc/api' } let(:requested_path) { 'doc/api/' }
include_examples :relative_to_requested it 'rebuilds URL relative to the directory' do
doc = filter(link('users.md'))
expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md"
end
end
context 'when ref name contains percent sign' do
let(:ref) { '100%branch' }
let(:commit) { project.commit('1b12f15a11fc6e62177bef08f47bc7b5ce50b141') }
let(:requested_path) { 'foo/bar/' }
it 'correctly escapes the ref' do
doc = filter(link('.gitkeep'))
expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/foo/bar/.gitkeep"
end
end
context 'when requested path is a directory with space in the repo' do
let(:ref) { 'master' }
let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') }
let(:requested_path) { 'with space/' }
it 'does not escape the space twice' do
doc = filter(link('README.md'))
expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/with%20space/README.md"
end
end end
end end
......
...@@ -17,6 +17,16 @@ describe NamespaceUrlConstrainer, lib: true do ...@@ -17,6 +17,16 @@ describe NamespaceUrlConstrainer, lib: true do
it { expect(subject.matches?(request '/g/gitlab')).to be_falsey } it { expect(subject.matches?(request '/g/gitlab')).to be_falsey }
it { expect(subject.matches?(request '/.gitlab')).to be_falsey } it { expect(subject.matches?(request '/.gitlab')).to be_falsey }
end end
context 'relative url' do
before do
allow(Gitlab::Application.config).to receive(:relative_url_root) { '/gitlab' }
end
it { expect(subject.matches?(request '/gitlab/gitlab')).to be_truthy }
it { expect(subject.matches?(request '/gitlab/gitlab-ce')).to be_falsey }
it { expect(subject.matches?(request '/gitlab/')).to be_falsey }
end
end end
def request(path) def request(path)
......
require 'spec_helper' require 'spec_helper'
require 'email_spec' require 'email_spec'
require 'mailers/shared/notify'
describe Notify do describe Notify do
include EmailSpec::Matchers include EmailSpec::Matchers
......
require 'spec_helper' require 'spec_helper'
require 'email_spec' require 'email_spec'
require 'mailers/shared/notify'
describe Notify, "merge request notifications" do describe Notify, "merge request notifications" do
include EmailSpec::Matchers include EmailSpec::Matchers
......
require 'spec_helper' require 'spec_helper'
require 'email_spec' require 'email_spec'
require 'mailers/shared/notify'
describe Notify do describe Notify do
include EmailSpec::Matchers include EmailSpec::Matchers
......
require 'spec_helper' require 'spec_helper'
require 'email_spec' require 'email_spec'
require 'mailers/shared/notify'
describe Notify do describe Notify do
include EmailSpec::Helpers include EmailSpec::Helpers
......
...@@ -6,4 +6,9 @@ describe Email, models: true do ...@@ -6,4 +6,9 @@ describe Email, models: true do
subject { build(:email) } subject { build(:email) }
end end
end end
it 'normalize email value' do
expect(described_class.new(email: ' inFO@exAMPLe.com ').email)
.to eq 'info@example.com'
end
end end
...@@ -265,10 +265,4 @@ describe Group, models: true do ...@@ -265,10 +265,4 @@ describe Group, models: true do
members members
end end
describe '#web_url' do
it 'returns the canonical URL' do
expect(group.web_url).to include("groups/#{group.name}")
end
end
end end
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe ProjectMember, models: true do describe ProjectMember, models: true do
describe 'associations' do describe 'associations' do
it { is_expected.to belong_to(:project).class_name('Project').with_foreign_key(:source_id) } it { is_expected.to belong_to(:project).with_foreign_key(:source_id) }
end end
describe 'validations' do describe 'validations' do
......
...@@ -6,8 +6,8 @@ describe MergeRequest, models: true do ...@@ -6,8 +6,8 @@ describe MergeRequest, models: true do
subject { create(:merge_request) } subject { create(:merge_request) }
describe 'associations' do describe 'associations' do
it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } it { is_expected.to belong_to(:target_project).class_name('Project') }
it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } it { is_expected.to belong_to(:source_project).class_name('Project') }
it { is_expected.to belong_to(:merge_user).class_name("User") } it { is_expected.to belong_to(:merge_user).class_name("User") }
it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) } it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) }
end end
...@@ -1286,7 +1286,8 @@ describe MergeRequest, models: true do ...@@ -1286,7 +1286,8 @@ describe MergeRequest, models: true do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) }
let(:merge_request) do
let!(:merge_request) do
create(:closed_merge_request, create(:closed_merge_request,
source_project: fork_project, source_project: fork_project,
target_project: project) target_project: project)
......
...@@ -1146,28 +1146,17 @@ describe Repository, models: true do ...@@ -1146,28 +1146,17 @@ describe Repository, models: true do
end end
describe '#before_import' do describe '#before_import' do
it 'flushes the emptiness cachess' do it 'flushes the repository caches' do
expect(repository).to receive(:expire_emptiness_caches) expect(repository).to receive(:expire_content_cache)
repository.before_import
end
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
repository.before_import repository.before_import
end end
end end
describe '#after_import' do describe '#after_import' do
it 'flushes the emptiness cachess' do it 'flushes and builds the cache' do
expect(repository).to receive(:expire_emptiness_caches) expect(repository).to receive(:expire_content_cache)
expect(repository).to receive(:build_cache)
repository.after_import
end
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
repository.after_import repository.after_import
end end
......
...@@ -15,11 +15,11 @@ describe User, models: true do ...@@ -15,11 +15,11 @@ describe User, models: true do
describe 'associations' do describe 'associations' do
it { is_expected.to have_one(:namespace) } it { is_expected.to have_one(:namespace) }
it { is_expected.to have_many(:snippets).class_name('Snippet').dependent(:destroy) } it { is_expected.to have_many(:snippets).dependent(:destroy) }
it { is_expected.to have_many(:project_members).dependent(:destroy) } it { is_expected.to have_many(:project_members).dependent(:destroy) }
it { is_expected.to have_many(:groups) } it { is_expected.to have_many(:groups) }
it { is_expected.to have_many(:keys).dependent(:destroy) } it { is_expected.to have_many(:keys).dependent(:destroy) }
it { is_expected.to have_many(:events).class_name('Event').dependent(:destroy) } it { is_expected.to have_many(:events).dependent(:destroy) }
it { is_expected.to have_many(:recent_events).class_name('Event') } it { is_expected.to have_many(:recent_events).class_name('Event') }
it { is_expected.to have_many(:issues).dependent(:destroy) } it { is_expected.to have_many(:issues).dependent(:destroy) }
it { is_expected.to have_many(:notes).dependent(:destroy) } it { is_expected.to have_many(:notes).dependent(:destroy) }
......
...@@ -48,92 +48,154 @@ describe API::API, api: true do ...@@ -48,92 +48,154 @@ describe API::API, api: true do
end end
describe 'PUT /projects/:id/repository/branches/:branch/protect' do describe 'PUT /projects/:id/repository/branches/:branch/protect' do
it 'protects a single branch' do context "when a protected branch doesn't already exist" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) it 'protects a single branch' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name) expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha) expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true) expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false) expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(false) expect(json_response['developers_can_merge']).to eq(false)
end end
it 'protects a single branch and developers can push' do it 'protects a single branch and developers can push' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
developers_can_push: true developers_can_push: true
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name) expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha) expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true) expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(true) expect(json_response['developers_can_push']).to eq(true)
expect(json_response['developers_can_merge']).to eq(false) expect(json_response['developers_can_merge']).to eq(false)
end end
it 'protects a single branch and developers can merge' do it 'protects a single branch and developers can merge' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
developers_can_merge: true developers_can_merge: true
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name) expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha) expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true) expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false) expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(true) expect(json_response['developers_can_merge']).to eq(true)
end end
it 'protects a single branch and developers can push and merge' do it 'protects a single branch and developers can push and merge' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
developers_can_push: true, developers_can_merge: true developers_can_push: true, developers_can_merge: true
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name) expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha) expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true) expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(true) expect(json_response['developers_can_push']).to eq(true)
expect(json_response['developers_can_merge']).to eq(true) expect(json_response['developers_can_merge']).to eq(true)
end end
it 'protects a single branch and developers cannot push and merge' do it 'protects a single branch and developers cannot push and merge' do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
developers_can_push: 'tru', developers_can_merge: 'tr' developers_can_push: 'tru', developers_can_merge: 'tr'
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name) expect(json_response['name']).to eq(branch_name)
expect(json_response['commit']['id']).to eq(branch_sha) expect(json_response['commit']['id']).to eq(branch_sha)
expect(json_response['protected']).to eq(true) expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false) expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(false) expect(json_response['developers_can_merge']).to eq(false)
end
end end
context 'on a protected branch' do context 'for an existing protected branch' do
let(:protected_branch) { 'foo' }
before do before do
project.repository.add_branch(user, protected_branch, 'master') project.repository.add_branch(user, protected_branch.name, 'master')
create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: protected_branch)
end end
it 'updates that a developer can push' do context "when developers can push and merge" do
put api("/projects/#{project.id}/repository/branches/#{protected_branch}/protect", user), let(:protected_branch) { create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: 'protected_branch') }
developers_can_push: false, developers_can_merge: false
it 'updates that a developer cannot push or merge' do
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
developers_can_push: false, developers_can_merge: false
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(protected_branch.name)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(false)
end
it "doesn't result in 0 access levels when 'developers_can_push' is switched off" do
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
developers_can_push: false
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(protected_branch.name)
expect(protected_branch.reload.push_access_levels.first).to be_present
expect(protected_branch.reload.push_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
end
it "doesn't result in 0 access levels when 'developers_can_merge' is switched off" do
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
developers_can_merge: false
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(protected_branch.name)
expect(protected_branch.reload.merge_access_levels.first).to be_present
expect(protected_branch.reload.merge_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
end
end
context "when developers cannot push or merge" do
let(:protected_branch) { create(:protected_branch, project: project, name: 'protected_branch') }
it 'updates that a developer can push and merge' do
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
developers_can_push: true, developers_can_merge: true
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(protected_branch.name)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(true)
expect(json_response['developers_can_merge']).to eq(true)
end
end
end
context "multiple API calls" do
it "returns success when `protect` is called twice" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(protected_branch) expect(json_response['name']).to eq(branch_name)
expect(json_response['protected']).to eq(true) expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false) expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(false) expect(json_response['developers_can_merge']).to eq(false)
end end
it 'does not update that a developer can push' do it "returns success when `protect` is called twice with `developers_can_push` turned on" do
put api("/projects/#{project.id}/repository/branches/#{protected_branch}/protect", user), put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true
developers_can_push: 'foobar', developers_can_merge: 'foo' put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['name']).to eq(protected_branch) expect(json_response['name']).to eq(branch_name)
expect(json_response['protected']).to eq(true) expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(true) expect(json_response['developers_can_push']).to eq(true)
expect(json_response['developers_can_merge']).to eq(false)
end
it "returns success when `protect` is called twice with `developers_can_merge` turned on" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_merge: true
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_merge: true
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(branch_name)
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(true) expect(json_response['developers_can_merge']).to eq(true)
end end
end end
...@@ -147,12 +209,6 @@ describe API::API, api: true do ...@@ -147,12 +209,6 @@ describe API::API, api: true do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user2) put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user2)
expect(response).to have_http_status(403) expect(response).to have_http_status(403)
end end
it "returns success when protect branch again" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
expect(response).to have_http_status(200)
end
end end
describe "PUT /projects/:id/repository/branches/:branch/unprotect" do describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
......
...@@ -72,6 +72,17 @@ describe API::API, api: true do ...@@ -72,6 +72,17 @@ describe API::API, api: true do
expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format" expect(json_response['message']).to include "\"since\" must be a timestamp in ISO 8601 format"
end end
end end
context "path optional parameter" do
it "returns project commits matching provided path parameter" do
path = 'files/ruby/popen.rb'
get api("/projects/#{project.id}/repository/commits?path=#{path}", user)
expect(json_response.size).to eq(3)
expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
end
end
end end
describe "Create a commit with multiple files and actions" do describe "Create a commit with multiple files and actions" do
......
...@@ -159,14 +159,14 @@ describe API::API, api: true do ...@@ -159,14 +159,14 @@ describe API::API, api: true do
it 'returns 400 if no label name given' do it 'returns 400 if no label name given' do
put api("/projects/#{project.id}/labels", user), new_name: 'label2' put api("/projects/#{project.id}/labels", user), new_name: 'label2'
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 (Bad request) "name" not given') expect(json_response['error']).to eq('name is missing')
end end
it 'returns 400 if no new parameters given' do it 'returns 400 if no new parameters given' do
put api("/projects/#{project.id}/labels", user), name: 'label1' put api("/projects/#{project.id}/labels", user), name: 'label1'
expect(response).to have_http_status(400) expect(response).to have_http_status(400)
expect(json_response['message']).to eq('Required parameters '\ expect(json_response['error']).to eq('new_name, color, description are missing, '\
'"new_name" or "color" missing') 'at least one parameter must be provided')
end end
it 'returns 400 for invalid name' do it 'returns 400 for invalid name' do
......
...@@ -221,12 +221,23 @@ describe API::API, api: true do ...@@ -221,12 +221,23 @@ describe API::API, api: true do
end end
end end
context 'when the user is posting an award emoji' do context 'when the user is posting an award emoji on an issue created by someone else' do
let(:issue2) { create(:issue, project: project) }
it 'returns an award emoji' do it 'returns an award emoji' do
post api("/projects/#{project.id}/issues/#{issue2.id}/notes", user), body: ':+1:'
expect(response).to have_http_status(201)
expect(json_response['awardable_id']).to eq issue2.id
end
end
context 'when the user is posting an award emoji on his/her own issue' do
it 'creates a new issue note' do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: ':+1:' post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: ':+1:'
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['awardable_id']).to eq issue.id expect(json_response['body']).to eq(':+1:')
end end
end end
end end
......
...@@ -23,14 +23,15 @@ describe Issues::MoveService, services: true do ...@@ -23,14 +23,15 @@ describe Issues::MoveService, services: true do
old_project.team << [user, :reporter] old_project.team << [user, :reporter]
new_project.team << [user, :reporter] new_project.team << [user, :reporter]
['label1', 'label2'].each do |label| labels = Array.new(2) { |x| "label%d" % (x + 1) }
labels.each do |label|
old_issue.labels << create(:label, old_issue.labels << create(:label,
project_id: old_project.id, project_id: old_project.id,
title: label) title: label)
end
new_project.labels << create(:label, title: 'label1') new_project.labels << create(:label, title: label)
new_project.labels << create(:label, title: 'label2') end
end end
end end
...@@ -207,10 +208,10 @@ describe Issues::MoveService, services: true do ...@@ -207,10 +208,10 @@ describe Issues::MoveService, services: true do
end end
end end
describe 'rewritting references' do describe 'rewriting references' do
include_context 'issue move executed' include_context 'issue move executed'
context 'issue reference' do context 'issue references' do
let(:another_issue) { create(:issue, project: old_project) } let(:another_issue) { create(:issue, project: old_project) }
let(:description) { "Some description #{another_issue.to_reference}" } let(:description) { "Some description #{another_issue.to_reference}" }
...@@ -219,6 +220,16 @@ describe Issues::MoveService, services: true do ...@@ -219,6 +220,16 @@ describe Issues::MoveService, services: true do
.to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}" .to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}"
end end
end end
context "user references" do
let(:another_issue) { create(:issue, project: old_project) }
let(:description) { "Some description #{user.to_reference}" }
it "doesn't throw any errors for issues containing user references" do
expect(new_issue.description)
.to eq "Some description #{user.to_reference}"
end
end
end end
context 'moving to same project' do context 'moving to same project' do
...@@ -277,5 +288,25 @@ describe Issues::MoveService, services: true do ...@@ -277,5 +288,25 @@ describe Issues::MoveService, services: true do
it { expect { move }.to raise_error(StandardError, /permissions/) } it { expect { move }.to raise_error(StandardError, /permissions/) }
end end
end end
context 'movable issue with no assigned labels' do
before do
old_project.team << [user, :reporter]
new_project.team << [user, :reporter]
labels = Array.new(2) { |x| "label%d" % (x + 1) }
labels.each do |label|
new_project.labels << create(:label, title: label)
end
end
include_context 'issue move executed'
it 'does not assign labels to new issue' do
expected_label_titles = new_issue.reload.labels.map(&:title)
expect(expected_label_titles.size).to eq 0
end
end
end end
end end
...@@ -17,9 +17,9 @@ module Select2Helper ...@@ -17,9 +17,9 @@ module Select2Helper
selector = options.fetch(:from) selector = options.fetch(:from)
if options[:multiple] if options[:multiple]
execute_script("$('#{selector}').select2('val', ['#{value}'], true);") execute_script("$('#{selector}').select2('val', ['#{value}']).trigger('change');")
else else
execute_script("$('#{selector}').select2('val', '#{value}', true);") execute_script("$('#{selector}').select2('val', '#{value}').trigger('change');")
end end
end end
end end
...@@ -9,6 +9,7 @@ describe 'gitlab:app namespace rake task' do ...@@ -9,6 +9,7 @@ describe 'gitlab:app namespace rake task' do
Rake.application.rake_require 'tasks/gitlab/backup' Rake.application.rake_require 'tasks/gitlab/backup'
Rake.application.rake_require 'tasks/gitlab/shell' Rake.application.rake_require 'tasks/gitlab/shell'
Rake.application.rake_require 'tasks/gitlab/db' Rake.application.rake_require 'tasks/gitlab/db'
Rake.application.rake_require 'tasks/cache'
# empty task as env is already loaded # empty task as env is already loaded
Rake::Task.define_task :environment Rake::Task.define_task :environment
......
require 'spec_helper'
describe 'projects/merge_requests/show/_commits.html.haml' do
include Devise::Test::ControllerHelpers
let(:user) { create(:user) }
let(:target_project) { create(:project) }
let(:source_project) do
create(:project, forked_from_project: target_project)
end
let(:merge_request) do
create(:merge_request, :simple,
source_project: source_project,
target_project: target_project,
author: user)
end
before do
controller.prepend_view_path('app/views/projects')
assign(:merge_request, merge_request)
assign(:commits, merge_request.commits)
end
it 'shows commits from source project' do
render
commit = source_project.commit(merge_request.source_branch)
href = namespace_project_commit_path(
source_project.namespace,
source_project,
commit)
expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment