`, and ``.
+// $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace
+// $font-family-base: $font-family-sans-serif
+
+$font-size-base: $gl-font-size;
+// $font-size-large: ceil(($font-size-base * 1.25)) // ~18px
+// $font-size-small: ceil(($font-size-base * 0.85)) // ~12px
+
+// $font-size-h1: floor(($font-size-base * 2.6)) // ~36px
+// $font-size-h2: floor(($font-size-base * 2.15)) // ~30px
+// $font-size-h3: ceil(($font-size-base * 1.7)) // ~24px
+// $font-size-h4: ceil(($font-size-base * 1.25)) // ~18px
+// $font-size-h5: $font-size-base
+// $font-size-h6: ceil(($font-size-base * 0.85)) // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+// $line-height-base: 1.428571429 // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+// $line-height-computed: floor(($font-size-base * $line-height-base)) // ~20px
+
+//** By default, this inherits from the ``.
+// $headings-font-family: inherit
+// $headings-font-weight: 500
+// $headings-line-height: 1.1
+// $headings-color: inherit
+
+
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+//** Load fonts from this directory.
+
+// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path.
+// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths.
+// $icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/")
+
+//** File name for all font files.
+// $icon-font-name: "glyphicons-halflings-regular"
+//** Element ID within SVG icon file.
+// $icon-font-svg-id: "glyphicons_halflingsregular"
+
+
+//== Components
+//
+//## 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: 6px;
+$padding-base-horizontal: 14px;
+
+// $padding-large-vertical: 10px
+// $padding-large-horizontal: 16px
+
+// $padding-small-vertical: 5px
+// $padding-small-horizontal: 10px
+
+// $padding-xs-vertical: 1px
+// $padding-xs-horizontal: 5px
+
+// $line-height-large: 1.3333333 // extra decimals for Win 8.1 Chrome
+// $line-height-small: 1.5
+
+// $border-radius-base: 4px
+// $border-radius-large: 6px
+// $border-radius-small: 3px
+
+//** Global color for active items (e.g., navs or dropdowns).
+// $component-active-color: #fff
+//** Global background color for active items (e.g., navs or dropdowns).
+// $component-active-bg: $brand-primary
+
+//** Width of the `border` for generating carets that indicator dropdowns.
+// $caret-width-base: 4px
+//** Carets increase slightly in size for larger components.
+// $caret-width-large: 5px
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for ``s and ` `s.
+// $table-cell-padding: 8px
+//** Padding for cells in `.table-condensed`.
+// $table-condensed-cell-padding: 5px
+
+//** Default background color used for all tables.
+// $table-bg: transparent
+//** Background color used for `.table-striped`.
+// $table-bg-accent: #f9f9f9
+//** Background color used for `.table-hover`.
+// $table-bg-hover: #f5f5f5
+// $table-bg-active: $table-bg-hover
+
+//** Border color for table and cell borders.
+// $table-border-color: #ddd
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+// $btn-font-weight: normal
+
+// $btn-default-color: #333
+// $btn-default-bg: #fff
+// $btn-default-border: #ccc
+
+// $btn-primary-color: #fff
+// $btn-primary-bg: $brand-primary
+// $btn-primary-border: darken($btn-primary-bg, 5%)
+
+// $btn-success-color: #fff
+// $btn-success-bg: $brand-success
+// $btn-success-border: darken($btn-success-bg, 5%)
+
+// $btn-info-color: #fff
+// $btn-info-bg: $brand-info
+// $btn-info-border: darken($btn-info-bg, 5%)
+
+// $btn-warning-color: #fff
+// $btn-warning-bg: $brand-warning
+// $btn-warning-border: darken($btn-warning-bg, 5%)
+
+// $btn-danger-color: #fff
+// $btn-danger-bg: $brand-danger
+// $btn-danger-border: darken($btn-danger-bg, 5%)
+
+// $btn-link-disabled-color: $gray-light
+
+
+//== Forms
+//
+//##
+
+//** ` ` background color
+// $input-bg: #fff
+//** ` ` background color
+// $input-bg-disabled: $gray-lighter
+
+//** Text color for ` `s
+$input-color: $text-color;
+//** ` ` border color
+$input-border: #dce4ec;
+
+// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on ``s in some browsers, due to the limited stylability of ``s in CSS.
+// $input-border-radius: $border-radius-base
+//** Large `.form-control` border radius
+// $input-border-radius-large: $border-radius-large
+//** Small `.form-control` border radius
+// $input-border-radius-small: $border-radius-small
+
+//** Border color for inputs on focus
+$input-border-focus: $brand-info;
+
+//** Placeholder text color
+// $input-color-placeholder: #999
+
+//** Default `.form-control` height
+// $input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2)
+//** Large `.form-control` height
+// $input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2)
+//** Small `.form-control` height
+// $input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2)
+
+$legend-color: $text-color;
+// $legend-border-color: #e5e5e5
+
+//** Background color for textual input addons
+// $input-group-addon-bg: $gray-lighter
+//** Border color for textual input addons
+// $input-group-addon-border-color: $input-border
+
+//** Disabled cursor for form controls and buttons.
+// $cursor-disabled: not-allowed
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+// $dropdown-bg: #fff
+//** Dropdown menu `border-color`.
+// $dropdown-border: rgba(0,0,0,.15)
+//** Dropdown menu `border-color` **for IE8**.
+// $dropdown-fallback-border: #ccc
+//** Divider color for between dropdown items.
+// $dropdown-divider-bg: #e5e5e5
+
+//** Dropdown link text color.
+// $dropdown-link-color: $gray-dark
+//** Hover color for dropdown links.
+// $dropdown-link-hover-color: darken($gray-dark, 5%)
+//** Hover background for dropdown links.
+// $dropdown-link-hover-bg: #f5f5f5
+
+//** Active dropdown menu item text color.
+// $dropdown-link-active-color: $component-active-color
+//** Active dropdown menu item background color.
+// $dropdown-link-active-bg: $component-active-bg
+
+//** Disabled dropdown menu item background color.
+// $dropdown-link-disabled-color: $gray-light
+
+//** Text color for headers within dropdown menus.
+// $dropdown-header-color: $gray-light
+
+//** Deprecated `$dropdown-caret-color` as of v3.1.0
+// $dropdown-caret-color: #000
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+// $zindex-navbar: 1000
+// $zindex-dropdown: 1000
+// $zindex-popover: 1060
+// $zindex-tooltip: 1070
+// $zindex-navbar-fixed: 1030
+// $zindex-modal: 1040
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `$screen-xs` as of v3.0.1
+// $screen-xs: 480px
+//** Deprecated `$screen-xs-min` as of v3.2.0
+// $screen-xs-min: $screen-xs
+//** Deprecated `$screen-phone` as of v3.0.1
+// $screen-phone: $screen-xs-min
+
+// Small screen / tablet
+//** Deprecated `$screen-sm` as of v3.0.1
+// $screen-sm: 768px
+// $screen-sm-min: $screen-sm
+//** Deprecated `$screen-tablet` as of v3.0.1
+// $screen-tablet: $screen-sm-min
+
+// Medium screen / desktop
+//** Deprecated `$screen-md` as of v3.0.1
+// $screen-md: 992px
+// $screen-md-min: $screen-md
+//** Deprecated `$screen-desktop` as of v3.0.1
+// $screen-desktop: $screen-md-min
+
+// Large screen / wide desktop
+//** Deprecated `$screen-lg` as of v3.0.1
+// $screen-lg: 1200px
+// $screen-lg-min: $screen-lg
+//** Deprecated `$screen-lg-desktop` as of v3.0.1
+// $screen-lg-desktop: $screen-lg-min
+
+// So media queries don't overlap when required, provide a maximum
+// $screen-xs-max: ($screen-sm-min - 1)
+// $screen-sm-max: ($screen-md-min - 1)
+// $screen-md-max: ($screen-lg-min - 1)
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+// $grid-columns: 12
+//** Padding between columns. Gets divided in half for the left and right.
+// $grid-gutter-width: 30px
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+// $grid-float-breakpoint: $screen-sm-min
+//** Point at which the navbar begins collapsing.
+// $grid-float-breakpoint-max: ($grid-float-breakpoint - 1)
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+// $container-tablet: (720px + $grid-gutter-width)
+//** For `$screen-sm-min` and up.
+// $container-sm: $container-tablet
+
+// Medium screen / desktop
+// $container-desktop: (940px + $grid-gutter-width)
+//** For `$screen-md-min` and up.
+// $container-md: $container-desktop
+
+// Large screen / wide desktop
+// $container-large-desktop: (1140px + $grid-gutter-width)
+//** For `$screen-lg-min` and up.
+// $container-lg: $container-large-desktop
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+// $navbar-height: 50px
+// $navbar-margin-bottom: $line-height-computed
+// $navbar-border-radius: $border-radius-base
+// $navbar-padding-horizontal: floor(($grid-gutter-width / 2))
+// $navbar-padding-vertical: (($navbar-height - $line-height-computed) / 2)
+// $navbar-collapse-max-height: 340px
+
+// $navbar-default-color: #777
+// $navbar-default-bg: #f8f8f8
+// $navbar-default-border: darken($navbar-default-bg, 6.5%)
+
+// Navbar links
+// $navbar-default-link-color: #777
+// $navbar-default-link-hover-color: #333
+// $navbar-default-link-hover-bg: transparent
+// $navbar-default-link-active-color: #555
+// $navbar-default-link-active-bg: darken($navbar-default-bg, 6.5%)
+// $navbar-default-link-disabled-color: #ccc
+// $navbar-default-link-disabled-bg: transparent
+
+// Navbar brand label
+// $navbar-default-brand-color: $navbar-default-link-color
+// $navbar-default-brand-hover-color: darken($navbar-default-brand-color, 10%)
+// $navbar-default-brand-hover-bg: transparent
+
+// Navbar toggle
+// $navbar-default-toggle-hover-bg: #ddd
+// $navbar-default-toggle-icon-bar-bg: #888
+// $navbar-default-toggle-border-color: #ddd
+
+
+// Inverted navbar
+// Reset inverted navbar basics
+// $navbar-inverse-color: lighten($gray-light, 15%)
+// $navbar-inverse-bg: #222
+// $navbar-inverse-border: darken($navbar-inverse-bg, 10%)
+
+// Inverted navbar links
+// $navbar-inverse-link-color: lighten($gray-light, 15%)
+// $navbar-inverse-link-hover-color: #fff
+// $navbar-inverse-link-hover-bg: transparent
+// $navbar-inverse-link-active-color: $navbar-inverse-link-hover-color
+// $navbar-inverse-link-active-bg: darken($navbar-inverse-bg, 10%)
+// $navbar-inverse-link-disabled-color: #444
+// $navbar-inverse-link-disabled-bg: transparent
+
+// Inverted navbar brand label
+// $navbar-inverse-brand-color: $navbar-inverse-link-color
+// $navbar-inverse-brand-hover-color: #fff
+// $navbar-inverse-brand-hover-bg: transparent
+
+// Inverted navbar toggle
+// $navbar-inverse-toggle-hover-bg: #333
+// $navbar-inverse-toggle-icon-bar-bg: #fff
+// $navbar-inverse-toggle-border-color: #333
+
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+// $nav-link-padding: 10px 15px
+// $nav-link-hover-bg: $gray-lighter
+
+// $nav-disabled-link-color: $gray-light
+// $nav-disabled-link-hover-color: $gray-light
+
+//== Tabs
+// $nav-tabs-border-color: #ddd
+
+// $nav-tabs-link-hover-border-color: $gray-lighter
+
+// $nav-tabs-active-link-hover-bg: $body-bg
+// $nav-tabs-active-link-hover-color: $gray
+// $nav-tabs-active-link-hover-border-color: #ddd
+
+// $nav-tabs-justified-link-border-color: #ddd
+// $nav-tabs-justified-active-link-border-color: $body-bg
+
+//== Pills
+// $nav-pills-border-radius: $border-radius-base
+// $nav-pills-active-link-hover-bg: $component-active-bg
+// $nav-pills-active-link-hover-color: $component-active-color
+
+
+//== Pagination
+//
+//##
+
+$pagination-color: #fff;
+$pagination-bg: $brand-success;
+$pagination-border: transparent;
+
+$pagination-hover-color: #fff;
+$pagination-hover-bg: darken($brand-success, 15%);
+$pagination-hover-border: transparent;
+
+$pagination-active-color: #fff;
+$pagination-active-bg: darken($brand-success, 15%);
+$pagination-active-border: transparent;
+
+$pagination-disabled-color: #b4bcc2;
+$pagination-disabled-bg: lighten($brand-success, 15%);
+$pagination-disabled-border: transparent;
+
+
+//== Pager
+//
+//##
+
+// $pager-bg: $pagination-bg
+// $pager-border: $pagination-border
+// $pager-border-radius: 15px
+
+// $pager-hover-bg: $pagination-hover-bg
+
+// $pager-active-bg: $pagination-active-bg
+// $pager-active-color: $pagination-active-color
+
+// $pager-disabled-color: $pagination-disabled-color
+
+
+//== Jumbotron
+//
+//##
+
+// $jumbotron-padding: 30px
+// $jumbotron-color: inherit
+// $jumbotron-bg: $gray-lighter
+// $jumbotron-heading-color: inherit
+// $jumbotron-font-size: ceil(($font-size-base * 1.5))
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+
+$state-success-text: #fff;
+$state-success-bg: $brand-success;
+$state-success-border: $brand-success;
+
+$state-info-text: #fff;
+$state-info-bg: $brand-info;
+$state-info-border: $brand-info;
+
+$state-warning-text: #fff;
+$state-warning-bg: $brand-warning;
+$state-warning-border: $brand-warning;
+
+$state-danger-text: #fff;
+$state-danger-bg: $brand-danger;
+$state-danger-border: $brand-danger;
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+// $tooltip-max-width: 200px
+//** Tooltip text color
+// $tooltip-color: #fff
+//** Tooltip background color
+// $tooltip-bg: #000
+// $tooltip-opacity: .9
+
+//** Tooltip arrow width
+// $tooltip-arrow-width: 5px
+//** Tooltip arrow color
+// $tooltip-arrow-color: $tooltip-bg
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+// $popover-bg: #fff
+//** Popover maximum width
+// $popover-max-width: 276px
+//** Popover border color
+// $popover-border-color: rgba(0,0,0,.2)
+//** Popover fallback border color
+// $popover-fallback-border-color: #ccc
+
+//** Popover title background color
+// $popover-title-bg: darken($popover-bg, 3%)
+
+//** Popover arrow width
+// $popover-arrow-width: 10px
+//** Popover arrow color
+// $popover-arrow-color: $popover-bg
+
+//** Popover outer arrow width
+// $popover-arrow-outer-width: ($popover-arrow-width + 1)
+//** Popover outer arrow color
+// $popover-arrow-outer-color: fade_in($popover-border-color, 0.05)
+//** Popover outer arrow fallback color
+// $popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%)
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+// $label-default-bg: $gray-light
+//** Primary label background color
+// $label-primary-bg: $brand-primary
+//** Success label background color
+// $label-success-bg: $brand-success
+//** Info label background color
+// $label-info-bg: $brand-info
+//** Warning label background color
+// $label-warning-bg: $brand-warning
+//** Danger label background color
+// $label-danger-bg: $brand-danger
+
+//** Default label text color
+// $label-color: #fff
+//** Default text color of a linked label
+// $label-link-hover-color: #fff
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+// $modal-inner-padding: 15px
+
+//** Padding applied to the modal title
+// $modal-title-padding: 15px
+//** Modal title line-height
+// $modal-title-line-height: $line-height-base
+
+//** Background color of modal content area
+// $modal-content-bg: #fff
+//** Modal content border color
+// $modal-content-border-color: rgba(0,0,0,.2)
+//** Modal content border color **for IE8**
+// $modal-content-fallback-border-color: #999
+
+//** Modal backdrop background color
+// $modal-backdrop-bg: #000
+//** Modal backdrop opacity
+// $modal-backdrop-opacity: .5
+//** Modal header border color
+// $modal-header-border-color: #e5e5e5
+//** Modal footer border color
+// $modal-footer-border-color: $modal-header-border-color
+
+// $modal-lg: 900px
+// $modal-md: 600px
+// $modal-sm: 300px
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+// $alert-padding: 15px
+$alert-border-radius: 0;
+// $alert-link-font-weight: bold
+
+// $alert-success-bg: $state-success-bg
+// $alert-success-text: $state-success-text
+// $alert-success-border: $state-success-border
+
+// $alert-info-bg: $state-info-bg
+// $alert-info-text: $state-info-text
+// $alert-info-border: $state-info-border
+
+// $alert-warning-bg: $state-warning-bg
+// $alert-warning-text: $state-warning-text
+// $alert-warning-border: $state-warning-border
+
+// $alert-danger-bg: $state-danger-bg
+// $alert-danger-text: $state-danger-text
+// $alert-danger-border: $state-danger-border
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+// $progress-bg: #f5f5f5
+//** Progress bar text color
+// $progress-bar-color: #fff
+//** Variable for setting rounded corners on progress bar.
+// $progress-border-radius: $border-radius-base
+
+//** Default progress bar color
+// $progress-bar-bg: $brand-primary
+//** Success progress bar color
+// $progress-bar-success-bg: $brand-success
+//** Warning progress bar color
+// $progress-bar-warning-bg: $brand-warning
+//** Danger progress bar color
+// $progress-bar-danger-bg: $brand-danger
+//** Info progress bar color
+// $progress-bar-info-bg: $brand-info
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+// $list-group-bg: #fff
+//** `.list-group-item` border color
+// $list-group-border: #ddd
+//** List group border radius
+// $list-group-border-radius: $border-radius-base
+
+//** Background color of single list items on hover
+// $list-group-hover-bg: #f5f5f5
+//** Text color of active list items
+// $list-group-active-color: $component-active-color
+//** Background color of active list items
+// $list-group-active-bg: $component-active-bg
+//** Border color of active list elements
+// $list-group-active-border: $list-group-active-bg
+//** Text color for content within active list items
+// $list-group-active-text-color: lighten($list-group-active-bg, 40%)
+
+//** Text color of disabled list items
+// $list-group-disabled-color: $gray-light
+//** Background color of disabled list items
+// $list-group-disabled-bg: $gray-lighter
+//** Text color for content within disabled list items
+// $list-group-disabled-text-color: $list-group-disabled-color
+
+// $list-group-link-color: #555
+// $list-group-link-hover-color: $list-group-link-color
+// $list-group-link-heading-color: #333
+
+
+//== Panels
+//
+//##
+
+// $panel-bg: #fff
+// $panel-body-padding: 15px
+// $panel-heading-padding: 10px 15px
+// $panel-footer-padding: $panel-heading-padding
+$panel-border-radius: 0;
+
+//** Border color for elements within panels
+// $panel-inner-border: #ddd
+// $panel-footer-bg: #f5f5f5
+
+$panel-default-text: $text-color;
+$panel-default-border: $border-color;
+$panel-default-heading-bg: $background-color;
+
+// $panel-primary-text: #fff
+// $panel-primary-border: $brand-primary
+// $panel-primary-heading-bg: $brand-primary
+
+// $panel-success-text: $state-success-text
+// $panel-success-border: $state-success-border
+// $panel-success-heading-bg: $state-success-bg
+
+// $panel-info-text: $state-info-text
+// $panel-info-border: $state-info-border
+// $panel-info-heading-bg: $state-info-bg
+
+// $panel-warning-text: $state-warning-text
+// $panel-warning-border: $state-warning-border
+// $panel-warning-heading-bg: $state-warning-bg
+
+// $panel-danger-text: $state-danger-text
+// $panel-danger-border: $state-danger-border
+// $panel-danger-heading-bg: $state-danger-bg
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+// $thumbnail-padding: 4px
+//** Thumbnail background color
+// $thumbnail-bg: $body-bg
+//** Thumbnail border color
+// $thumbnail-border: #ddd
+//** Thumbnail border radius
+// $thumbnail-border-radius: $border-radius-base
+
+//** Custom text color for thumbnail captions
+// $thumbnail-caption-color: $text-color
+//** Padding around the thumbnail caption
+// $thumbnail-caption-padding: 9px
+
+
+//== Wells
+//
+//##
+
+$well-bg: $gray-lighter;
+$well-border: transparent;
+
+
+//== Badges
+//
+//##
+
+// $badge-color: #fff
+//** Linked badge text color on hover
+// $badge-link-hover-color: #fff
+// $badge-bg: $gray-light
+
+//** Badge text color in active nav link
+// $badge-active-color: $link-color
+//** Badge background color in active nav link
+// $badge-active-bg: #fff
+
+// $badge-font-weight: bold
+// $badge-line-height: 1
+// $badge-border-radius: 10px
+
+
+//== Breadcrumbs
+//
+//##
+
+// $breadcrumb-padding-vertical: 8px
+// $breadcrumb-padding-horizontal: 15px
+//** Breadcrumb background color
+// $breadcrumb-bg: #f5f5f5
+//** Breadcrumb text color
+// $breadcrumb-color: #ccc
+//** Text color of current page in the breadcrumb
+// $breadcrumb-active-color: $gray-light
+//** Textual separator for between breadcrumb elements
+// $breadcrumb-separator: "/"
+
+
+//== Carousel
+//
+//##
+
+// $carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6)
+
+// $carousel-control-color: #fff
+// $carousel-control-width: 15%
+// $carousel-control-opacity: .5
+// $carousel-control-font-size: 20px
+
+// $carousel-indicator-active-bg: #fff
+// $carousel-indicator-border-color: #fff
+
+// $carousel-caption-color: #fff
+
+
+//== Close
+//
+//##
+
+// $close-font-weight: bold
+// $close-color: #000
+// $close-text-shadow: 0 1px 0 #fff
+
+
+//== Code
+//
+//##
+
+$code-color: #c7254e;
+$code-bg: #f9f2f4;
+
+$kbd-color: #fff;
+$kbd-bg: #333;
+
+$pre-bg: $gray-lighter;
+$pre-color: $text-color;
+$pre-border-color: #ccc;
+// $pre-scrollable-max-height: 340px
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+// $component-offset-horizontal: 180px
+//** Text muted color
+// $text-muted: $gray-light
+//** Abbreviations and acronyms border color
+// $abbr-border-color: $gray-light
+//** Headings small color
+$headings-small-color: $gray-dark;
+//** Blockquote small color
+// $blockquote-small-color: $gray-light
+//** Blockquote font size
+// $blockquote-font-size: ($font-size-base * 1.25)
+//** Blockquote border color
+// $blockquote-border-color: $gray-lighter
+//** Page header border color
+// $page-header-border-color: $gray-lighter
+//** Width of horizontal description list titles
+// $dl-horizontal-offset: $component-offset-horizontal
+//** Horizontal line color.
+// $hr-border: $gray-lighter
diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/base/layout.scss
similarity index 69%
rename from app/assets/stylesheets/main/layout.scss
rename to app/assets/stylesheets/base/layout.scss
index 2800feb81f..62c11b0636 100644
--- a/app/assets/stylesheets/main/layout.scss
+++ b/app/assets/stylesheets/base/layout.scss
@@ -2,10 +2,10 @@ html {
overflow-y: scroll;
&.touch .tooltip { display: none !important; }
-}
-body {
- padding-bottom: 20px;
+ body {
+ padding-top: 46px;
+ }
}
.container {
@@ -17,3 +17,6 @@ body {
margin: 0 0;
}
+.navless-container {
+ margin-top: 30px;
+}
diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/base/mixins.scss
similarity index 84%
rename from app/assets/stylesheets/main/mixins.scss
rename to app/assets/stylesheets/base/mixins.scss
index 7f607fc4e8..ccba65e3fd 100644
--- a/app/assets/stylesheets/main/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -58,8 +58,8 @@
}
@mixin md-typography {
- font-size: 14px;
- line-height: 1.6;
+ font-size: 15px;
+ line-height: 1.5;
img {
max-width: 100%;
@@ -69,7 +69,12 @@
margin-top: 0;
}
- code { padding: 0 4px; }
+ code {
+ font-family: $monospace_font;
+ white-space: pre;
+ word-wrap: normal;
+ padding: 0;
+ }
h1 {
margin-top: 45px;
@@ -93,7 +98,7 @@
blockquote p {
color: #888;
- font-size: 14px;
+ font-size: 15px;
line-height: 1.5;
}
@@ -116,14 +121,6 @@
}
}
-@mixin page-title {
- color: #333;
- line-height: 1.5;
- font-weight: normal;
- margin-top: 0px;
- margin-bottom: 10px;
-}
-
@mixin str-truncated($max_width: 82%) {
display: inline-block;
overflow: hidden;
@@ -132,14 +129,3 @@
white-space: nowrap;
max-width: $max_width;
}
-
-@mixin panel-colored {
- border: none;
- background: $box_bg;
- @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
-
- .panel-heading {
- font-weight: bold;
- background-color: $box_bg;
- }
-}
diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss
new file mode 100644
index 0000000000..4e2c64aa13
--- /dev/null
+++ b/app/assets/stylesheets/base/variables.scss
@@ -0,0 +1,38 @@
+$style_color: #474D57;
+$hover: #FFF3EB;
+$gl-link-color: #446e9b;
+$nprogress-color: #c0392b;
+$gl-font-size: 14px;
+$list-font-size: 15px;
+$sidebar_width: 230px;
+$avatar_radius: 50%;
+$code_font_size: 13px;
+$code_line_height: 1.5;
+$border-color: #dce4ec;
+$background-color: #ECF0F1;
+
+/*
+ * State colors:
+ */
+$gl-success: #019875;
+$gl-danger: #d9534f;
+$gl-primary: #446e9b;
+$gl-info: #029ACF;
+$gl-warning: #EB9532;
+
+$gl-primary: #2C3E50;
+$gl-success: #18BC9C;
+$gl-info: #3498DB;
+$gl-warning: #F39C12;
+$gl-danger: #E74C3C;
+/*
+ * Commit Diff Colors
+ */
+$added: #63c363;
+$deleted: #f77;
+
+/*
+ * Fonts
+ */
+$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
+$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss
index be4c4d07f1..469f4f296a 100644
--- a/app/assets/stylesheets/behaviors.scss
+++ b/app/assets/stylesheets/behaviors.scss
@@ -1,12 +1,22 @@
// Details
//--------
-.js-details-container .content { display: none; }
-.js-details-container .content.hide { display: block; }
-.js-details-container.open .content { display: block; }
-.js-details-container.open .content.hide { display: none; }
+.js-details-container {
+ .content {
+ display: none;
+ &.hide { display: block; }
+ }
+ &.open .content {
+ display: block;
+ &.hide { display: none; }
+ }
+}
// Toggle between two states.
-.js-toggler-container .turn-on { display: block; }
-.js-toggler-container .turn-off { display: none; }
-.js-toggler-container.on .turn-on { display: none; }
-.js-toggler-container.on .turn-off { display: block; }
+.js-toggler-container {
+ .turn-on { display: block; }
+ .turn-off { display: none; }
+ &.on {
+ .turn-on { display: none; }
+ .turn-off { display: block; }
+ }
+}
diff --git a/app/assets/stylesheets/generic/avatar.scss b/app/assets/stylesheets/generic/avatar.scss
index 4f038b977e..8595887c3b 100644
--- a/app/assets/stylesheets/generic/avatar.scss
+++ b/app/assets/stylesheets/generic/avatar.scss
@@ -2,15 +2,21 @@
float: left;
margin-right: 12px;
width: 40px;
- padding: 1px;
- @include border-radius(4px);
+ height: 40px;
+ padding: 0;
+ @include border-radius($avatar_radius);
&.avatar-inline {
float: none;
- margin-left: 3px;
+ margin-left: 4px;
+ margin-bottom: 2px;
- &.s16 { margin-right: 2px; }
- &.s24 { margin-right: 2px; }
+ &.s16 { margin-right: 4px; }
+ &.s24 { margin-right: 4px; }
+ }
+
+ &.group-avatar, &.project-avatar, &.avatar-tile {
+ @include border-radius(0px);
}
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
@@ -21,3 +27,16 @@
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; }
}
+
+.identicon {
+ text-align: center;
+ vertical-align: top;
+
+ &.s16 { font-size: 12px; line-height: 1.33; }
+ &.s24 { font-size: 14px; line-height: 1.8; }
+ &.s26 { font-size: 20px; line-height: 1.33; }
+ &.s32 { font-size: 22px; line-height: 32px; }
+ &.s60 { font-size: 32px; line-height: 60px; }
+ &.s90 { font-size: 36px; line-height: 90px; }
+ &.s160 { font-size: 96px; line-height: 1.33; }
+}
diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss
index d098f1ecaa..cd6bf64c0a 100644
--- a/app/assets/stylesheets/generic/buttons.scss
+++ b/app/assets/stylesheets/generic/buttons.scss
@@ -1,127 +1,15 @@
.btn {
- display: inline-block;
- margin-bottom: 0;
- font-weight: normal;
- text-align: center;
- vertical-align: middle;
- cursor: pointer;
- background-image: none;
- border: $btn-border;
- white-space: nowrap;
- padding: 6px 12px;
- font-size: 13px;
- line-height: 18px;
- border-radius: 4px;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- -o-user-select: none;
- user-select: none;
- color: #444444;
- background-color: #fff;
- text-shadow: none;
-
- &.hover,
- &:hover {
- color: #444444;
- text-decoration: none;
- background-color: #ebebeb;
- border-color: #adadad;
- }
-
- &.focus,
- &:focus {
- color: #444444;
- text-decoration: none;
- outline: thin dotted #333;
- outline: 5px auto -webkit-focus-ring-color;
- outline-offset: -2px;
- }
-
- &.active,
- &:active {
- outline: 0;
- background-image: none;
- -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
- box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
- }
-
- &.disabled,
- &[disabled] {
- cursor: not-allowed;
- pointer-events: none;
- opacity: 0.65;
- filter: alpha(opacity=65);
- -webkit-box-shadow: none;
- box-shadow: none;
- }
-
- &.btn-primary {
- color: #ffffff;
- background-color: $bg_primary;
- border-color: $border_primary;
-
- &.hover,
- &:hover,
- &.disabled,
- &[disabled] {
- color: #ffffff;
- }
- }
-
- &.btn-success {
- color: #ffffff;
- background-color: $bg_success;
- border-color: $border_success;
-
-
- &.hover,
- &:hover,
- &.disabled,
- &[disabled] {
- color: #ffffff;
- }
- }
-
- &.btn-danger {
- color: #ffffff;
- background-color: $bg_danger;
- border-color: $border_danger;
-
-
- &.hover,
- &:hover,
- &.disabled,
- &[disabled] {
- color: #ffffff;
- }
- }
-
- &.btn-warning {
- color: #ffffff;
- background-color: $bg_warning;
- border-color: $border_warning;
-
-
- &.hover,
- &:hover,
- &.disabled,
- &[disabled] {
- color: #ffffff;
- }
- }
+ @extend .btn-default;
&.btn-new {
@extend .btn-success;
}
&.btn-create {
- @extend .wide;
@extend .btn-success;
}
&.btn-save {
- @extend .wide;
@extend .btn-primary;
}
@@ -133,34 +21,17 @@
float: right;
}
- &.wide {
- padding-left: 20px;
- padding-right: 20px;
- }
-
- &.btn-small {
- padding: 2px 10px;
- font-size: 12px;
- }
-
- &.btn-tiny {
- font-size: 11px;
- padding: 2px 6px;
- line-height: 16px;
- margin: 2px;
- }
-
&.btn-close {
- color: $bg_danger;
- border-color: $border_danger;
+ color: $gl-danger;
+ border-color: $gl-danger;
&:hover {
color: #B94A48;
}
}
&.btn-reopen {
- color: $bg_success;
- border-color: $border_success;
+ color: $gl-success;
+ border-color: $gl-success;
&:hover {
color: #468847;
}
@@ -173,6 +44,14 @@
margin-right: 0px;
}
}
+
+ &.btn-save {
+ @extend .btn-primary;
+ }
+
+ &.btn-new, &.btn-create {
+ @extend .btn-success;
+ }
}
.btn-block {
@@ -193,6 +72,3 @@
}
}
}
-
-.btn-group-small > .btn { @extend .btn.btn-small; }
-.btn-group-tiny > .btn { @extend .btn.btn-tiny; }
diff --git a/app/assets/stylesheets/generic/calendar.scss b/app/assets/stylesheets/generic/calendar.scss
new file mode 100644
index 0000000000..9483b26164
--- /dev/null
+++ b/app/assets/stylesheets/generic/calendar.scss
@@ -0,0 +1,95 @@
+.calendar_onclick_placeholder {
+ padding: 0 0 2px 0;
+}
+
+.calendar_commit_activity {
+ padding: 5px 0 0;
+}
+
+.calendar_onclick_second {
+ font-size: 14px;
+ display: block;
+}
+
+.calendar_onclick_hr {
+ padding: 0;
+ margin: 10px 0;
+}
+
+.calendar_commit_date {
+ color: #999;
+}
+
+.calendar_activity_summary {
+ font-size: 14px;
+}
+
+/**
+* This overwrites the default values of the cal-heatmap gem
+*/
+.calendar {
+ .qi {
+ background-color: #999;
+ fill: #fff;
+ }
+
+ .q1 {
+ background-color: #dae289;
+ fill: #ededed;
+ }
+
+ .q2 {
+ background-color: #cedb9c;
+ fill: #ACD5F2;
+ }
+
+ .q3 {
+ background-color: #b5cf6b;
+ fill: #7FA8D1;
+ }
+
+ .q4 {
+ background-color: #637939;
+ fill: #49729B;
+ }
+
+ .q5 {
+ background-color: #3b6427;
+ fill: #254E77;
+ }
+
+ .domain-background {
+ fill: none;
+ shape-rendering: crispedges;
+ }
+
+ .ch-tooltip {
+ position: absolute;
+ display: none;
+ margin-top: 22px;
+ margin-left: 1px;
+ font-size: 13px;
+ padding: 3px;
+ font-weight: 550;
+ background-color: #222;
+ span {
+ position: absolute;
+ width: 200px;
+ text-align: center;
+ visibility: hidden;
+ border-radius: 10px;
+ &:after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -8px;
+ width: 0;
+ height: 0;
+ border-top: 8px solid #000000;
+ border-right: 8px solid transparent;
+ border-left: 8px solid transparent;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index cd2f4e45e3..876eea72e8 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -24,7 +24,7 @@
.slead {
color: #666;
- font-size: 14px;
+ font-size: 15px;
margin-bottom: 12px;
font-weight: normal;
line-height: 24px;
@@ -54,9 +54,14 @@ pre {
text-shadow: none;
}
+.dropdown-menu-align-right {
+ left: auto;
+ right: 0px;
+}
+
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
- background: $bg_primary;
+ background: $gl-primary;
color: #FFF
}
@@ -66,7 +71,7 @@ pre {
/** FLASH message **/
.author_link {
- color: $link_color;
+ color: $gl-link-color;
}
.help li { color:$style_color; }
@@ -162,7 +167,7 @@ li.note {
background-color: inherit;
}
-.team_member_show {
+.project_member_show {
td:first-child {
color: #aaa;
}
@@ -207,24 +212,16 @@ li.note {
}
}
-.no-ssh-key-message {
- padding: 10px 0;
- background: #C67;
- margin: 0;
- color: #FFF;
- margin-top: -1px;
+.browser-alert {
+ padding: 10px;
text-align: center;
-
+ background: #C67;
+ color: #fff;
+ font-weight: bold;
a {
color: #fff;
text-decoration: underline;
}
-
- .links-xs {
- text-align: center;
- font-size: 16px;
- padding: 5px;
- }
}
.warning_message {
@@ -281,10 +278,6 @@ img.emoji {
height: 220px;
}
-.navless-container {
- margin-top: 20px;
-}
-
.description-block {
@extend .light-well;
@extend .light;
@@ -300,27 +293,21 @@ table {
.dashboard-intro-icon {
float: left;
+ text-align: center;
font-size: 32px;
color: #AAA;
- padding: 5px 0;
- width: 50px;
- min-height: 100px;
+ width: 60px;
}
-.broadcast-message {
- padding: 10px;
- text-align: center;
- background: #555;
- color: #BBB;
-}
-
-.broadcast-message-preview {
- @extend .broadcast-message;
- margin-bottom: 20px;
+.dashboard-intro-text {
+ display: inline-block;
+ margin-left: -60px;
+ padding-left: 60px;
+ width: 100%;
}
.btn-sign-in {
- margin-top: 7px;
+ margin-top: 5px;
text-shadow: none;
}
@@ -330,16 +317,19 @@ table {
}
}
-@media (max-width: $screen-xs-max) {
- .container .content { margin-top: 20px; }
-}
-
.wiki .highlight, .note-body .highlight {
margin-bottom: 9px;
}
-.footer-links a {
- margin-right: 15px;
+.wiki .code {
+ overflow-x: auto;
+}
+
+.footer-links {
+ margin-bottom: 20px;
+ a {
+ margin-right: 15px;
+ }
}
.search_box {
@@ -359,3 +349,9 @@ table {
.task-status {
margin-left: 10px;
}
+
+#nprogress .spinner {
+ top: auto !important;
+ bottom: 20px !important;
+ left: 20px !important;
+}
diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/generic/files.scss
index e2b0ef0c5e..91220a856a 100644
--- a/app/assets/stylesheets/generic/files.scss
+++ b/app/assets/stylesheets/generic/files.scss
@@ -3,7 +3,7 @@
*
*/
.file-holder {
- border: 1px solid #CCC;
+ border: 1px solid $border-color;
margin-bottom: 1em;
table {
@@ -11,38 +11,33 @@
}
.file-title {
- background: #EEE;
- border-bottom: 1px solid #CCC;
+ position: relative;
+ background: $background-color;
+ border-bottom: 1px solid $border-color;
text-shadow: 0 1px 1px #fff;
margin: 0;
text-align: left;
padding: 10px 15px;
- .options {
+ .file-actions {
float: right;
- margin-top: -3px;
+ position: absolute;
+ top: 5px;
+ right: 15px;
+
+ .btn {
+ padding: 0px 10px;
+ font-size: 13px;
+ line-height: 28px;
+ }
}
.left-options {
margin-top: -3px;
}
-
- .file_name {
- font-weight: bold;
- padding-left: 3px;
- font-size: 14px;
-
- small {
- color: #888;
- font-size: 13px;
- font-weight: normal;
- padding-left: 10px;
- }
- }
}
.file-content {
background: #fff;
- font-size: 11px;
&.image_file {
background: #eee;
@@ -54,8 +49,6 @@
}
&.wiki {
- font-size: 14px;
- line-height: 1.6;
padding: 25px;
.highlight {
@@ -122,7 +115,7 @@
ol {
margin-left: 40px;
padding: 10px 0;
- border-left: 1px solid #CCC;
+ border-left: 1px solid $border-color;
margin-bottom: 0;
background: white;
li {
diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss
index e8b23090b0..19bc11086e 100644
--- a/app/assets/stylesheets/generic/forms.scss
+++ b/app/assets/stylesheets/generic/forms.scss
@@ -29,9 +29,14 @@ fieldset legend {
padding: 17px 20px 18px;
margin-top: 18px;
margin-bottom: 18px;
- background-color: whitesmoke;
+ background-color: #ecf0f1;
border-top: 1px solid #e5e5e5;
- padding-left: 17%;
+}
+
+@media (min-width: $screen-sm-min) {
+ .form-actions {
+ padding-left: 17%;
+ }
}
label {
@@ -88,139 +93,7 @@ label {
@include box-shadow(none);
}
-.issuable-description {
+.issuable-description,
+.wiki-content {
margin-top: 35px;
}
-
-.zennable {
- position: relative;
-
- input {
- display: none;
- }
-
- .collapse {
- display: none;
- opacity: 0.5;
-
- &:before {
- content: '\f066';
- font-family: FontAwesome;
- color: #000;
- font-size: 28px;
- position: relative;
- padding: 30px 40px 0 0;
- }
-
- &:hover {
- opacity: 0.8;
- }
- }
-
- .expand {
- opacity: 0.5;
-
- &:before {
- content: '\f065';
- font-family: FontAwesome;
- color: #000;
- font-size: 14px;
- line-height: 14px;
- padding-right: 20px;
- position: relative;
- vertical-align: middle;
- }
-
- &:hover {
- opacity: 0.8;
- }
- }
-
- input:checked ~ .zen-backdrop .expand {
- display: none;
- }
-
- input:checked ~ .zen-backdrop .collapse {
- display: block;
- position: absolute;
- top: 0;
- }
-
- label {
- position: absolute;
- top: -26px;
- right: 0;
- font-variant: small-caps;
- text-transform: uppercase;
- font-size: 10px;
- padding: 4px;
- font-weight: 500;
- letter-spacing: 1px;
-
- &:before {
- display: inline-block;
- width: 10px;
- height: 14px;
- }
- }
-
- input:checked ~ .zen-backdrop {
- background-color: white;
- position: fixed;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 1031;
-
- textarea {
- border: none;
- box-shadow: none;
- border-radius: 0;
- color: #000;
- font-size: 20px;
- line-height: 26px;
- padding: 30px;
- display: block;
- outline: none;
- resize: none;
- height: 100vh;
- max-width: 900px;
- margin: 0 auto;
- }
- }
-
- .zen-backdrop textarea::-webkit-input-placeholder {
- color: white;
- }
-
- .zen-backdrop textarea:-moz-placeholder {
- color: white;
- }
-
- .zen-backdrop textarea::-moz-placeholder {
- color: white;
- }
-
- .zen-backdrop textarea:-ms-input-placeholder {
- color: white;
- }
-
- input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder {
- color: #999;
- }
-
- input:checked ~ .zen-backdrop textarea:-moz-placeholder {
- color: #999;
- opacity: 1;
- }
-
- input:checked ~ .zen-backdrop textarea::-moz-placeholder {
- color: #999;
- opacity: 1;
- }
-
- input:checked ~ .zen-backdrop textarea:-ms-input-placeholder {
- color: #999;
- }
-}
diff --git a/app/assets/stylesheets/generic/gfm.scss b/app/assets/stylesheets/generic/gfm.scss
index e257f05361..8fac5e534f 100644
--- a/app/assets/stylesheets/generic/gfm.scss
+++ b/app/assets/stylesheets/generic/gfm.scss
@@ -3,7 +3,8 @@
*/
.issue-form, .merge-request-form, .wiki-form {
.description {
- height: 20em;
+ height: 16em;
+ border-top-left-radius: 0;
}
}
@@ -17,4 +18,4 @@
.description {
height: 14em;
}
-}
\ No newline at end of file
+}
diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/generic/highlight.scss
index 4110bddf4f..2e13ee842e 100644
--- a/app/assets/stylesheets/generic/highlight.scss
+++ b/app/assets/stylesheets/generic/highlight.scss
@@ -1,4 +1,4 @@
-.highlighted-data {
+.file-content.code {
border: none;
box-shadow: none;
margin: 0px;
@@ -10,11 +10,16 @@
border: none;
border-radius: 0;
font-family: $monospace_font;
- font-size: 12px !important;
- line-height: 16px !important;
+ font-size: $code_font_size !important;
+ line-height: $code_line_height !important;
margin: 0;
+ overflow: auto;
+ overflow-y: hidden;
+ white-space: pre;
+ word-wrap: normal;
code {
+ font-family: $monospace_font;
white-space: pre;
word-wrap: normal;
padding: 0;
@@ -25,10 +30,6 @@
}
}
- .hljs {
- padding: 0;
- }
-
.line-numbers {
padding: 10px;
text-align: right;
@@ -37,8 +38,8 @@
a {
font-family: $monospace_font;
display: block;
- font-size: 12px !important;
- line-height: 16px !important;
+ font-size: $code_font_size !important;
+ line-height: $code_line_height !important;
white-space: nowrap;
i {
@@ -51,14 +52,19 @@
}
}
}
+}
- .highlight {
- overflow: auto;
- overflow-y: hidden;
+.note-text .code {
+ border: none;
+ box-shadow: none;
+ background: $background-color;
+ padding: 1em;
+ overflow-x: auto;
- pre {
- white-space: pre;
- word-wrap: normal;
- }
+ code {
+ font-family: $monospace_font;
+ white-space: pre;
+ word-wrap: normal;
+ padding: 0;
}
}
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss
index 94149594e2..9558f241b7 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/generic/issue_box.scss
@@ -1,123 +1,32 @@
/**
- * Issue box:
- * Huge block (one per page) for storing title, descripion and other information.
+ * Issue box for showing Open/Closed state:
* Used for Issue#show page, MergeRequest#show page etc
*
- * CLasses:
- * .issue-box - Regular box
*/
.issue-box {
- color: #555;
- margin:20px 0;
- background: $box_bg;
- @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
+ display: inline-block;
+ padding: 7px 13px;
+ font-weight: normal;
+ margin-right: 5px;
&.issue-box-closed {
- .state {
- background-color: #F3CECE;
- border-color: $border_danger;
- }
- .state-label {
- background-color: $bg_danger;
- color: #FFF;
- }
+ background-color: $gl-danger;
+ color: #FFF;
}
&.issue-box-merged {
- .state {
- background-color: #B7CEE7;
- border-color: $border_primary;
- }
- .state-label {
- background-color: $bg_primary;
- color: #FFF;
- }
+ background-color: $gl-primary;
+ color: #FFF;
}
&.issue-box-open {
- .state {
- background-color: #D6F1D7;
- border-color: $bg_success;
- }
- .state-label {
- background-color: $bg_success;
- color: #FFF;
- }
+ background-color: $gl-success;
+ color: #FFF;
}
&.issue-box-expired {
- .state {
- background-color: #EEE9B3;
- border-color: #faebcc;
- }
- .state-label {
- background: #cea61b;
- color: #FFF;
- }
- }
-
- .control-group {
- margin-bottom: 0;
- }
-
- .state {
- background-color: #f9f9f9;
- }
-
- .title {
- font-size: 28px;
- font-weight: normal;
- line-height: 1.5;
- margin: 0;
- color: #333;
- padding: 10px 15px;
- }
-
- .context {
- border: none;
- border-top: 1px solid #eee;
- padding: 10px 15px;
-
- // Reset text align for children
- .text-right > * { text-align: left; }
-
- @media (max-width: $screen-xs-max) {
- // Don't right align on mobile
- .text-right { text-align: left; }
-
- .row .col-md-6 {
- padding-top: 5px;
- }
- }
- }
-
- .description {
- padding: 0 15px 10px 15px;
-
- code {
- white-space: pre-wrap;
- }
- }
-
- .title, .context, .description {
- .clearfix {
- margin: 0;
- }
- }
-
- .state-label {
- font-size: 14px;
- float: left;
- font-weight: bold;
- padding: 10px 15px;
- }
-
- .creator {
- float: right;
- padding: 10px 15px;
- a {
- text-decoration: underline;
- }
+ background: #cea61b;
+ color: #FFF;
}
}
diff --git a/app/assets/stylesheets/generic/jquery.scss b/app/assets/stylesheets/generic/jquery.scss
index bfbbc7d25e..871b808bad 100644
--- a/app/assets/stylesheets/generic/jquery.scss
+++ b/app/assets/stylesheets/generic/jquery.scss
@@ -41,8 +41,8 @@
}
.ui-state-active {
- border: 1px solid $bg_primary;
- background: $bg_primary;
+ border: 1px solid $gl-primary;
+ background: $gl-primary;
color: #FFF;
}
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss
index 2653bfbf83..5950885c42 100644
--- a/app/assets/stylesheets/generic/lists.scss
+++ b/app/assets/stylesheets/generic/lists.scss
@@ -69,12 +69,11 @@
}
.well-title {
- font-size: 14px;
+ font-size: $list-font-size;
line-height: 18px;
}
.row_title {
- font-weight: 500;
color: #444;
&:hover {
color: #444;
diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss
index fbfa72c5e5..eb39b6bb7e 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/generic/markdown_area.scss
@@ -20,6 +20,7 @@
opacity: 0;
font-size: 50px;
transition: opacity 200ms ease-in-out;
+ pointer-events: none;
}
.div-dropzone-spinner {
@@ -50,3 +51,37 @@
margin-bottom: 0;
transition: opacity 200ms ease-in-out;
}
+
+.md-preview-holder {
+ background: #FFF;
+ border: 1px solid #ddd;
+ min-height: 100px;
+ padding: 5px;
+ box-shadow: none;
+}
+
+.new_note,
+.edit_note,
+.issuable-description,
+.milestone-description,
+.wiki-content,
+.merge-request-form {
+ .nav-tabs {
+ margin-bottom: 0;
+ border: none;
+
+ li a,
+ li.active a {
+ border: 1px solid #DDD;
+ }
+ }
+}
+
+.markdown-area {
+ background: #FFF;
+ border: 1px solid #ddd;
+ min-height: 100px;
+ padding: 5px;
+ box-shadow: none;
+ width: 100%;
+}
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
new file mode 100644
index 0000000000..1b0e056216
--- /dev/null
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -0,0 +1,76 @@
+/** Common mobile (screen XS, SM) styles **/
+@media (max-width: $screen-xs-max) {
+ .container .content {
+ margin-top: 20px;
+ }
+
+ .nav.nav-tabs > li > a {
+ padding: 10px;
+ font-size: 12px;
+ margin-right: 3px;
+
+ .badge {
+ display: none;
+ }
+ }
+
+ .issues-filters,
+ .dash-projects-filters,
+ .check-all-holder {
+ display: none;
+ }
+
+ .rss-btn {
+ display: none !important;
+ }
+
+ .project-home-panel {
+ .star-fork-buttons {
+ padding-top: 10px;
+ padding-right: 15px;
+ }
+ }
+
+ .project-home-links {
+ display: none;
+ }
+}
+
+@media (max-width: $screen-sm-max) {
+ .issues-filters {
+ .milestone-filter, .labels-filter {
+ display: none;
+ }
+ }
+
+ .page-title {
+ .note_created_ago, .new-issue-link {
+ display: none;
+ }
+ }
+
+ .issue_edited_ago, .note_edited_ago {
+ display: none;
+ }
+
+ aside {
+ display: none;
+ }
+
+ .show-aside {
+ display: block !important;
+ }
+}
+
+.show-aside {
+ display: none;
+ position: fixed;
+ right: 0px;
+ top: 30%;
+ padding: 5px 15px;
+ background: #EEE;
+ font-size: 20px;
+ color: #777;
+ z-index: 100;
+ @include box-shadow(0 1px 2px #DDD);
+}
diff --git a/app/assets/stylesheets/generic/nav_sidebar.scss b/app/assets/stylesheets/generic/nav_sidebar.scss
new file mode 100644
index 0000000000..c14f12284d
--- /dev/null
+++ b/app/assets/stylesheets/generic/nav_sidebar.scss
@@ -0,0 +1,195 @@
+.page-with-sidebar {
+ background: #F5F5F5;
+
+ .sidebar-wrapper {
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100%;
+ border-right: 1px solid #EAEAEA;
+ }
+}
+
+.sidebar-wrapper {
+ z-index: 99;
+ background: #F5F5F5;
+}
+
+.content-wrapper {
+ width: 100%;
+ padding: 15px;
+ background: #FFF;
+}
+
+.nav-sidebar {
+ margin: 0;
+ list-style: none;
+
+ &.navbar-collapse {
+ padding: 0px !important;
+ }
+}
+
+.nav-sidebar li a .count {
+ float: right;
+ background: #eee;
+ padding: 0px 8px;
+ @include border-radius(6px);
+}
+
+.nav-sidebar li {
+ &.active a {
+ color: #333;
+ background: #FFF !important;
+ font-weight: bold;
+ border: 1px solid #EEE;
+ border-right: 1px solid transparent;
+ border-left: 3px solid $style_color;
+
+ &.no-highlight {
+ background: none !important;
+ border: none;
+ }
+
+ i {
+ color: #444;
+ }
+ }
+}
+
+.nav-sidebar li {
+ &.separate-item {
+ border-top: 1px solid #ddd;
+ padding-top: 10px;
+ margin-top: 10px;
+ }
+
+ a {
+ color: #555;
+ display: block;
+ text-decoration: none;
+ padding: 8px 15px;
+ font-size: 13px;
+ line-height: 20px;
+ text-shadow: 0 1px 2px #FFF;
+ padding-left: 20px;
+
+ &:hover {
+ text-decoration: none;
+ color: #333;
+ background: #EEE;
+ }
+
+ &:active, &:focus {
+ text-decoration: none;
+ }
+
+ i {
+ width: 20px;
+ color: #888;
+ margin-right: 23px;
+ }
+ }
+}
+
+.sidebar-subnav {
+ margin-left: 0px;
+ padding-left: 0px;
+
+ li {
+ list-style: none;
+ }
+}
+
+@mixin expanded-sidebar {
+ padding-left: $sidebar_width;
+
+ .sidebar-wrapper {
+ width: $sidebar_width;
+
+ .nav-sidebar {
+ margin-top: 29px;
+ position: fixed;
+ top: 45px;
+ width: $sidebar_width;
+ }
+ }
+
+ .content-wrapper {
+ padding: 20px;
+ }
+}
+
+@mixin folded-sidebar {
+ padding-left: 50px;
+
+ .sidebar-wrapper {
+ width: 52px;
+
+ .nav-sidebar {
+ margin-top: 29px;
+ position: fixed;
+ top: 45px;
+ width: 52px;
+
+ li a {
+ padding-left: 18px;
+ font-size: 14px;
+ padding: 8px 15px;
+ text-align: center;
+
+
+ & > span {
+ display: none;
+ }
+ }
+ }
+
+ .collapse-nav a {
+ left: 0px;
+ padding: 7px 23px 3px 22px;
+ }
+ }
+}
+
+.collapse-nav a {
+ position: fixed;
+ top: 46px;
+ padding: 5px 13px 5px 13px;
+ left: 197px;
+ font-size: 13px;
+ background: #EEE;
+ color: black;
+ border-left: 1px solid rgba(0,0,0,0.035);
+ border-right: 1px solid rgba(0,0,0,0.035);
+}
+
+.collapse-nav a:hover {
+ text-decoration: none;
+ color: #333;
+ background: #eaeaea;
+}
+
+@media (max-width: $screen-md-max) {
+ .page-sidebar-collapsed {
+ @include folded-sidebar;
+ }
+
+ .page-sidebar-expanded {
+ @include folded-sidebar;
+ }
+
+ .collapse-nav {
+ display: none;
+ }
+}
+
+@media(min-width: $screen-md-max) {
+ .page-sidebar-collapsed {
+ @include folded-sidebar;
+ }
+
+ .page-sidebar-expanded {
+ @include expanded-sidebar;
+ }
+}
diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss
index e0f508d269..c13a685a52 100644
--- a/app/assets/stylesheets/generic/selects.scss
+++ b/app/assets/stylesheets/generic/selects.scss
@@ -2,10 +2,9 @@
.select2-container, .select2-container.select2-drop-above {
.select2-choice {
background: #FFF;
- border-color: #BBB;
- padding: 6px 12px;
- font-size: 13px;
- line-height: 18px;
+ border-color: #CCC;
+ padding: 6px 14px;
+ line-height: 1.42857143;
height: auto;
.select2-arrow {
@@ -20,7 +19,7 @@
}
.select2-container-multi .select2-choices .select2-search-field input {
- padding: 6px 12px;
+ padding: 8px 14px;
font-size: 13px;
line-height: 18px;
height: auto;
@@ -42,7 +41,7 @@
.select2-results {
max-height: 350px;
.select2-highlighted {
- background: $bg_primary;
+ background: $gl-primary;
}
}
}
@@ -116,6 +115,18 @@ select {
}
}
+.group-result {
+ .group-image {
+ float: left;
+ }
+ .group-name {
+ font-weight: bold;
+ }
+ .group-path {
+ color: #999;
+ }
+}
+
.user-result {
.user-image {
float: left;
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
deleted file mode 100644
index f6311ef74e..0000000000
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ /dev/null
@@ -1,46 +0,0 @@
-.ui.sidebar {
- z-index: 1000 !important;
- background: #fff;
- padding: 10px;
- width: 285px;
-}
-
-.ui.right.sidebar {
- border-left: 1px solid #e1e1e1;
- border-right: 0;
-}
-
-.sidebar-expand-button {
- cursor: pointer;
- transition: all 0.4s;
- -moz-transition: all 0.4s;
- -webkit-transition: all 0.4s;
-}
-
-.fixed.sidebar-expand-button {
- background: #f9f9f9;
- color: #555;
- padding: 9px 12px 6px 14px;
- border: 1px solid #E1E1E1;
- border-right: 0;
- position: fixed;
- top: 108px;
- right: 0px;
- margin-right: 0;
- &:hover {
- background: #ddd;
- color: #333;
- padding-right: 25px;
- }
-}
-
-.btn.btn-default.sidebar-expand-button {
- margin-left: 12px;
- display: inline-block !important;
-}
-
-@media (min-width: 767px) {
-.btn.btn-default.sidebar-expand-button {
- display: none!important;
- }
-}
diff --git a/app/assets/stylesheets/generic/tables.scss b/app/assets/stylesheets/generic/tables.scss
new file mode 100644
index 0000000000..a66e45577d
--- /dev/null
+++ b/app/assets/stylesheets/generic/tables.scss
@@ -0,0 +1,20 @@
+table {
+ &.table {
+ tr {
+ td, th {
+ padding: 8px 10px;
+ line-height: 20px;
+ vertical-align: middle;
+ }
+ th {
+ font-weight: normal;
+ font-size: 15px;
+ border-bottom: 1px solid $border-color !important;
+ }
+ td {
+ border-color: #F1F1F1 !important;
+ border-bottom: 1px solid;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss
index f29cf25fa4..f92a79f7a5 100644
--- a/app/assets/stylesheets/generic/timeline.scss
+++ b/app/assets/stylesheets/generic/timeline.scss
@@ -42,7 +42,7 @@
background: #fff;
color: #737881;
float: left;
- @include border-radius(40px);
+ @include border-radius($avatar_radius);
@include box-shadow(0 0 0 3px #EEE);
overflow: hidden;
@@ -58,6 +58,10 @@
padding: 10px 15px;
margin-left: 60px;
+ img {
+ max-width: 100%;
+ }
+
&:after {
content: '';
display: block;
@@ -74,4 +78,57 @@
}
}
}
+
+ .system-note .timeline-entry-inner {
+ .timeline-icon {
+ background: none;
+ margin-left: 12px;
+ margin-top: 0;
+ @include box-shadow(none);
+
+ span {
+ margin: 0 2px;
+ font-size: 16px;
+ color: #eeeeee;
+ }
+ }
+
+ .timeline-content {
+ background: none;
+ margin-left: 45px;
+ padding: 0px 15px;
+
+ &:after { border: 0; }
+
+ .note-header {
+ span { font-size: 12px; }
+
+ .avatar {
+ margin-right: 5px;
+ }
+ }
+
+ .note-text {
+ font-size: 12px;
+ margin-left: 20px;
+ }
+ }
+ }
+}
+
+@media (max-width: $screen-xs-max) {
+ .timeline {
+ &:before {
+ background: none;
+ }
+ .timeline-entry .timeline-entry-inner {
+ .timeline-icon {
+ display: none;
+ }
+
+ .timeline-content {
+ margin-left: 0;
+ }
+ }
+ }
}
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index 385a627b4b..4d940ee6b2 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -2,24 +2,12 @@
* Headers
*
*/
-h1.page-title {
- @include page-title;
- font-size: 28px;
-}
-
-h2.page-title {
- @include page-title;
- font-size: 24px;
-}
-
-h3.page-title {
- @include page-title;
- font-size: 22px;
-}
-
-h6 {
- color: #888;
- text-transform: uppercase;
+.page-title {
+ margin-top: 0px;
+ color: #333;
+ line-height: 1.5;
+ font-weight: normal;
+ margin-bottom: 5px;
}
/** CODE **/
@@ -32,52 +20,6 @@ pre {
}
}
-/**
- * Links
- *
- */
-a {
- outline: none;
- color: $link_color;
- &:hover {
- text-decoration: underline;
- color: $link_hover_color;
- }
-
- &:focus {
- text-decoration: underline;
- }
-
- &.darken {
- color: $style_color;
- }
-
- &.lined {
- text-decoration: underline;
- &:hover { text-decoration: underline; }
- }
-
- &.gray {
- color: gray;
- }
-
- &.supp_diff_link {
- text-align: center;
- padding: 20px 0;
- background: #f1f1f1;
- width: 100%;
- float: left;
- }
-
- &.neib {
- margin-right: 15px;
- }
-}
-
-a:focus {
- outline: none;
-}
-
.monospace {
font-family: $monospace_font;
}
@@ -128,3 +70,7 @@ a:focus {
textarea.js-gfm-input {
font-family: $monospace_font;
}
+
+.strikethrough {
+ text-decoration: line-through;
+}
diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss
new file mode 100644
index 0000000000..26afc21a6a
--- /dev/null
+++ b/app/assets/stylesheets/generic/zen.scss
@@ -0,0 +1,98 @@
+.zennable {
+ position: relative;
+
+ input {
+ display: none;
+ }
+
+ .zen-enter-link {
+ color: #888;
+ position: absolute;
+ top: -26px;
+ right: 4px;
+ }
+
+ .zen-leave-link {
+ display: none;
+ color: #888;
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ padding: 5px;
+ font-size: 36px;
+
+ &:hover {
+ color: #111;
+ }
+ }
+
+ input:checked ~ .zen-backdrop .zen-enter-link {
+ display: none;
+ }
+
+ input:checked ~ .zen-backdrop .zen-leave-link {
+ display: block;
+ position: absolute;
+ top: 0;
+ }
+
+ input:checked ~ .zen-backdrop {
+ background-color: white;
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 1031;
+
+ textarea {
+ border: none;
+ box-shadow: none;
+ border-radius: 0;
+ color: #000;
+ font-size: 20px;
+ line-height: 26px;
+ padding: 30px;
+ display: block;
+ outline: none;
+ resize: none;
+ height: 100vh;
+ max-width: 900px;
+ margin: 0 auto;
+ }
+ }
+
+ .zen-backdrop textarea::-webkit-input-placeholder {
+ color: white;
+ }
+
+ .zen-backdrop textarea:-moz-placeholder {
+ color: white;
+ }
+
+ .zen-backdrop textarea::-moz-placeholder {
+ color: white;
+ }
+
+ .zen-backdrop textarea:-ms-input-placeholder {
+ color: white;
+ }
+
+ input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder {
+ color: #999;
+ }
+
+ input:checked ~ .zen-backdrop textarea:-moz-placeholder {
+ color: #999;
+ opacity: 1;
+ }
+
+ input:checked ~ .zen-backdrop textarea::-moz-placeholder {
+ color: #999;
+ opacity: 1;
+ }
+
+ input:checked ~ .zen-backdrop textarea:-ms-input-placeholder {
+ color: #999;
+ }
+}
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index ca51da3fdd..c8cb18ec35 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,199 +1,88 @@
-.dark {
- background-color: #232323;
+/* https://github.com/MozMorris/tomorrow-pygments */
+pre.code.highlight.dark,
+.code.dark {
- .line.hll {
- background: #558;
- }
-
- .highlight{
- border-left: 1px solid #444;
- }
-
- .no-highlight {
- color: #DDD;
- }
+ background-color: #1d1f21;
+ color: #c5c8c6;
+ pre.code,
+ .line-numbers,
.line-numbers a {
- color: #666;
+ background-color: #1d1f21 !important;
+ color: #c5c8c6 !important;
}
- pre {
- background-color: #232323;
+ pre.code {
+ border-left: 1px solid #666;
}
- .hljs {
- display: block;
- background: #232323;
- color: #E6E1DC;
+ // highlight line via anchor
+ pre .hll {
+ background-color: #557 !important;
}
- .hljs-comment,
- .hljs-template_comment,
- .hljs-javadoc,
- .hljs-shebang {
- color: #BC9458;
- font-style: italic;
- }
-
- .hljs-keyword,
- .ruby .hljs-function .hljs-keyword,
- .hljs-request,
- .hljs-status,
- .nginx .hljs-title,
- .method,
- .hljs-list .hljs-title {
- color: #C26230;
- }
-
- .hljs-string,
- .hljs-number,
- .hljs-regexp,
- .hljs-tag .hljs-value,
- .hljs-cdata,
- .hljs-filter .hljs-argument,
- .hljs-attr_selector,
- .apache .hljs-cbracket,
- .hljs-date,
- .tex .hljs-command,
- .markdown .hljs-link_label {
- color: #A5C261;
- }
-
- .hljs-subst {
- color: #519F50;
- }
-
- .hljs-tag,
- .hljs-tag .hljs-keyword,
- .hljs-tag .hljs-title,
- .hljs-doctype,
- .hljs-sub .hljs-identifier,
- .hljs-pi,
- .input_number {
- color: #E8BF6A;
- }
-
- .hljs-identifier {
- color: #D0D0FF;
- }
-
- .hljs-class .hljs-title,
- .haskell .hljs-type,
- .smalltalk .hljs-class,
- .hljs-javadoctag,
- .hljs-yardoctag,
- .hljs-phpdoc {
- text-decoration: none;
- }
-
- .hljs-constant {
- color: #DA4939;
- }
-
-
- .hljs-symbol,
- .hljs-built_in,
- .ruby .hljs-symbol .hljs-string,
- .ruby .hljs-symbol .hljs-identifier,
- .markdown .hljs-link_url,
- .hljs-attribute {
- color: #6D9CBE;
- }
-
- .markdown .hljs-link_url {
- text-decoration: underline;
- }
-
-
-
- .hljs-params,
- .hljs-variable,
- .clojure .hljs-attribute {
- color: #D0D0FF;
- }
-
- .css .hljs-tag,
- .hljs-rules .hljs-property,
- .hljs-pseudo,
- .tex .hljs-special {
- color: #CDA869;
- }
-
- .css .hljs-class {
- color: #9B703F;
- }
-
- .hljs-rules .hljs-keyword {
- color: #C5AF75;
- }
-
- .hljs-rules .hljs-value {
- color: #CF6A4C;
- }
-
- .css .hljs-id {
- color: #8B98AB;
- }
-
- .hljs-annotation,
- .apache .hljs-sqbracket,
- .nginx .hljs-built_in {
- color: #9B859D;
- }
-
- .hljs-preprocessor,
- .hljs-preprocessor *,
- .hljs-pragma {
- color: #8996A8 !important;
- }
-
- .hljs-hexcolor,
- .css .hljs-value .hljs-number {
- color: #A5C261;
- }
-
- .hljs-title,
- .hljs-decorator,
- .css .hljs-function {
- color: #FFC66D;
- }
-
- .diff .hljs-header,
- .hljs-chunk {
- background-color: #2F33AB;
- color: #E6E1DC;
- display: inline-block;
- width: 100%;
- }
-
- .diff .hljs-change {
- background-color: #4A410D;
- color: #F8F8F8;
- display: inline-block;
- width: 100%;
- }
-
- .hljs-addition {
- background-color: #144212;
- color: #E6E1DC;
- display: inline-block;
- width: 100%;
- }
-
- .hljs-deletion {
- background-color: #600;
- color: #E6E1DC;
- display: inline-block;
- width: 100%;
- }
-
- .coffeescript .javascript,
- .javascript .xml,
- .tex .hljs-formula,
- .xml .javascript,
- .xml .vbscript,
- .xml .css,
- .xml .hljs-cdata {
- opacity: 0.7;
- }
+ .hll { background-color: #373b41 }
+ .c { color: #969896 } /* Comment */
+ .err { color: #cc6666 } /* Error */
+ .k { color: #b294bb } /* Keyword */
+ .l { color: #de935f } /* Literal */
+ .n { color: #c5c8c6 } /* Name */
+ .o { color: #8abeb7 } /* Operator */
+ .p { color: #c5c8c6 } /* Punctuation */
+ .cm { color: #969896 } /* Comment.Multiline */
+ .cp { color: #969896 } /* Comment.Preproc */
+ .c1 { color: #969896 } /* Comment.Single */
+ .cs { color: #969896 } /* Comment.Special */
+ .gd { color: #cc6666 } /* Generic.Deleted */
+ .ge { font-style: italic } /* Generic.Emph */
+ .gh { color: #c5c8c6; font-weight: bold } /* Generic.Heading */
+ .gi { color: #b5bd68 } /* Generic.Inserted */
+ .gp { color: #969896; font-weight: bold } /* Generic.Prompt */
+ .gs { font-weight: bold } /* Generic.Strong */
+ .gu { color: #8abeb7; font-weight: bold } /* Generic.Subheading */
+ .kc { color: #b294bb } /* Keyword.Constant */
+ .kd { color: #b294bb } /* Keyword.Declaration */
+ .kn { color: #8abeb7 } /* Keyword.Namespace */
+ .kp { color: #b294bb } /* Keyword.Pseudo */
+ .kr { color: #b294bb } /* Keyword.Reserved */
+ .kt { color: #f0c674 } /* Keyword.Type */
+ .ld { color: #b5bd68 } /* Literal.Date */
+ .m { color: #de935f } /* Literal.Number */
+ .s { color: #b5bd68 } /* Literal.String */
+ .na { color: #81a2be } /* Name.Attribute */
+ .nb { color: #c5c8c6 } /* Name.Builtin */
+ .nc { color: #f0c674 } /* Name.Class */
+ .no { color: #cc6666 } /* Name.Constant */
+ .nd { color: #8abeb7 } /* Name.Decorator */
+ .ni { color: #c5c8c6 } /* Name.Entity */
+ .ne { color: #cc6666 } /* Name.Exception */
+ .nf { color: #81a2be } /* Name.Function */
+ .nl { color: #c5c8c6 } /* Name.Label */
+ .nn { color: #f0c674 } /* Name.Namespace */
+ .nx { color: #81a2be } /* Name.Other */
+ .py { color: #c5c8c6 } /* Name.Property */
+ .nt { color: #8abeb7 } /* Name.Tag */
+ .nv { color: #cc6666 } /* Name.Variable */
+ .ow { color: #8abeb7 } /* Operator.Word */
+ .w { color: #c5c8c6 } /* Text.Whitespace */
+ .mf { color: #de935f } /* Literal.Number.Float */
+ .mh { color: #de935f } /* Literal.Number.Hex */
+ .mi { color: #de935f } /* Literal.Number.Integer */
+ .mo { color: #de935f } /* Literal.Number.Oct */
+ .sb { color: #b5bd68 } /* Literal.String.Backtick */
+ .sc { color: #c5c8c6 } /* Literal.String.Char */
+ .sd { color: #969896 } /* Literal.String.Doc */
+ .s2 { color: #b5bd68 } /* Literal.String.Double */
+ .se { color: #de935f } /* Literal.String.Escape */
+ .sh { color: #b5bd68 } /* Literal.String.Heredoc */
+ .si { color: #de935f } /* Literal.String.Interpol */
+ .sx { color: #b5bd68 } /* Literal.String.Other */
+ .sr { color: #b5bd68 } /* Literal.String.Regex */
+ .s1 { color: #b5bd68 } /* Literal.String.Single */
+ .ss { color: #b5bd68 } /* Literal.String.Symbol */
+ .bp { color: #c5c8c6 } /* Name.Builtin.Pseudo */
+ .vc { color: #cc6666 } /* Name.Variable.Class */
+ .vg { color: #cc6666 } /* Name.Variable.Global */
+ .vi { color: #cc6666 } /* Name.Variable.Instance */
+ .il { color: #de935f } /* Literal.Number.Integer.Long */
}
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 36bc5df2f4..001e8b3102 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -1,148 +1,88 @@
-.monokai {
- background-color: #272822;
+/* https://github.com/richleland/pygments-css/blob/master/monokai.css */
+pre.code.monokai,
+.code.monokai {
- .highlight{
- border-left: 1px solid #444;
- }
-
- .line.hll {
- background: #558;
- }
-
- .no-highlight {
- color: #DDD;
- }
+ background: #272822;
+ color: #f8f8f2;
+ pre.highlight,
+ .line-numbers,
.line-numbers a {
- color: #666;
+ background:#272822 !important;
+ color:#f8f8f2 !important;
}
- pre {
- background-color: #272822;
- color: #f8f8f2;
+ pre.code {
+ border-left: 1px solid #555;
}
- .hljs {
- display: block;
- background: #272822;
+ // highlight line via anchor
+ pre .hll {
+ background-color: #49483e !important;
}
- .hljs-tag,
- .hljs-tag .hljs-title,
- .hljs-keyword,
- .hljs-literal,
- .hljs-strong,
- .hljs-change,
- .hljs-winutils,
- .hljs-flow,
- .lisp .hljs-title,
- .clojure .hljs-built_in,
- .nginx .hljs-title,
- .tex .hljs-special {
- color: #F92672;
- }
+ .hll { background-color: #49483e }
+ .c { color: #75715e } /* Comment */
+ .err { color: #960050; background-color: #1e0010 } /* Error */
+ .k { color: #66d9ef } /* Keyword */
+ .l { color: #ae81ff } /* Literal */
+ .n { color: #f8f8f2 } /* Name */
+ .o { color: #f92672 } /* Operator */
+ .p { color: #f8f8f2 } /* Punctuation */
+ .cm { color: #75715e } /* Comment.Multiline */
+ .cp { color: #75715e } /* Comment.Preproc */
+ .c1 { color: #75715e } /* Comment.Single */
+ .cs { color: #75715e } /* Comment.Special */
+ .ge { font-style: italic } /* Generic.Emph */
+ .gs { font-weight: bold } /* Generic.Strong */
+ .kc { color: #66d9ef } /* Keyword.Constant */
+ .kd { color: #66d9ef } /* Keyword.Declaration */
+ .kn { color: #f92672 } /* Keyword.Namespace */
+ .kp { color: #66d9ef } /* Keyword.Pseudo */
+ .kr { color: #66d9ef } /* Keyword.Reserved */
+ .kt { color: #66d9ef } /* Keyword.Type */
+ .ld { color: #e6db74 } /* Literal.Date */
+ .m { color: #ae81ff } /* Literal.Number */
+ .s { color: #e6db74 } /* Literal.String */
+ .na { color: #a6e22e } /* Name.Attribute */
+ .nb { color: #f8f8f2 } /* Name.Builtin */
+ .nc { color: #a6e22e } /* Name.Class */
+ .no { color: #66d9ef } /* Name.Constant */
+ .nd { color: #a6e22e } /* Name.Decorator */
+ .ni { color: #f8f8f2 } /* Name.Entity */
+ .ne { color: #a6e22e } /* Name.Exception */
+ .nf { color: #a6e22e } /* Name.Function */
+ .nl { color: #f8f8f2 } /* Name.Label */
+ .nn { color: #f8f8f2 } /* Name.Namespace */
+ .nx { color: #a6e22e } /* Name.Other */
+ .py { color: #f8f8f2 } /* Name.Property */
+ .nt { color: #f92672 } /* Name.Tag */
+ .nv { color: #f8f8f2 } /* Name.Variable */
+ .ow { color: #f92672 } /* Operator.Word */
+ .w { color: #f8f8f2 } /* Text.Whitespace */
+ .mf { color: #ae81ff } /* Literal.Number.Float */
+ .mh { color: #ae81ff } /* Literal.Number.Hex */
+ .mi { color: #ae81ff } /* Literal.Number.Integer */
+ .mo { color: #ae81ff } /* Literal.Number.Oct */
+ .sb { color: #e6db74 } /* Literal.String.Backtick */
+ .sc { color: #e6db74 } /* Literal.String.Char */
+ .sd { color: #e6db74 } /* Literal.String.Doc */
+ .s2 { color: #e6db74 } /* Literal.String.Double */
+ .se { color: #ae81ff } /* Literal.String.Escape */
+ .sh { color: #e6db74 } /* Literal.String.Heredoc */
+ .si { color: #e6db74 } /* Literal.String.Interpol */
+ .sx { color: #e6db74 } /* Literal.String.Other */
+ .sr { color: #e6db74 } /* Literal.String.Regex */
+ .s1 { color: #e6db74 } /* Literal.String.Single */
+ .ss { color: #e6db74 } /* Literal.String.Symbol */
+ .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
+ .vc { color: #f8f8f2 } /* Name.Variable.Class */
+ .vg { color: #f8f8f2 } /* Name.Variable.Global */
+ .vi { color: #f8f8f2 } /* Name.Variable.Instance */
+ .il { color: #ae81ff } /* Literal.Number.Integer.Long */
- .hljs {
- color: #DDD;
- }
-
- .hljs .hljs-constant,
- .asciidoc .hljs-code {
- color: #66D9EF;
- }
-
- .hljs-code,
- .hljs-class .hljs-title,
- .hljs-header {
- color: white;
- }
-
- .hljs-link_label,
- .hljs-attribute,
- .hljs-symbol,
- .hljs-symbol .hljs-string,
- .hljs-value,
- .hljs-regexp {
- color: #BF79DB;
- }
-
- .hljs-link_url,
- .hljs-tag .hljs-value,
- .hljs-string,
- .hljs-bullet,
- .hljs-subst,
- .hljs-title,
- .hljs-emphasis,
- .haskell .hljs-type,
- .hljs-preprocessor,
- .hljs-pragma,
- .ruby .hljs-class .hljs-parent,
- .hljs-built_in,
- .sql .hljs-aggregate,
- .django .hljs-template_tag,
- .django .hljs-variable,
- .smalltalk .hljs-class,
- .hljs-javadoc,
- .django .hljs-filter .hljs-argument,
- .smalltalk .hljs-localvars,
- .smalltalk .hljs-array,
- .hljs-attr_selector,
- .hljs-pseudo,
- .hljs-addition,
- .hljs-stream,
- .hljs-envvar,
- .apache .hljs-tag,
- .apache .hljs-cbracket,
- .tex .hljs-command,
- .hljs-prompt {
- color: #A6E22E;
- }
-
- .hljs-comment,
- .java .hljs-annotation,
- .smartquote,
- .hljs-blockquote,
- .hljs-horizontal_rule,
- .python .hljs-decorator,
- .hljs-template_comment,
- .hljs-pi,
- .hljs-doctype,
- .hljs-deletion,
- .hljs-shebang,
- .apache .hljs-sqbracket,
- .tex .hljs-formula {
- color: #75715E;
- }
-
- .hljs-keyword,
- .hljs-literal,
- .css .hljs-id,
- .hljs-phpdoc,
- .hljs-title,
- .hljs-header,
- .haskell .hljs-type,
- .vbscript .hljs-built_in,
- .sql .hljs-aggregate,
- .rsl .hljs-built_in,
- .smalltalk .hljs-class,
- .diff .hljs-header,
- .hljs-chunk,
- .hljs-winutils,
- .bash .hljs-variable,
- .apache .hljs-tag,
- .tex .hljs-special,
- .hljs-request,
- .hljs-status {
- font-weight: bold;
- }
-
- .coffeescript .javascript,
- .javascript .xml,
- .tex .hljs-formula,
- .xml .javascript,
- .xml .vbscript,
- .xml .css,
- .xml .hljs-cdata {
- opacity: 0.5;
- }
+ .gh { } /* Generic Heading & Diff Header */
+ .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */
+ .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */
+ .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */
}
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index b9bec22518..f5b827e7c0 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -1,125 +1,110 @@
-.solarized-dark {
- background-color: #002B36;
+/* https://gist.github.com/qguv/7936275 */
+pre.code.highlight.solarized-dark,
+.code.solarized-dark {
- .highlight{
+ background-color: #002b36;
+ color: #93a1a1;
+
+ pre.code,
+ .line-numbers,
+ .line-numbers a {
+ background-color: #002b36 !important;
+ color: #93a1a1 !important;
+ }
+
+ pre.code {
border-left: 1px solid #113b46;
}
- .line.hll {
- background: #000;
+ // highlight line via anchor
+ pre .hll {
+ background-color: #174652 !important;
}
- .no-highlight {
- color: #DDD;
- }
+ /* Solarized Dark
- pre {
- background-color: #002B36;
- color: #eee;
- }
+ For use with Jekyll and Pygments
- .line-numbers a {
- color: #666;
- }
+ http://ethanschoonover.com/solarized
- .hljs {
- display: block;
- background: #002b36;
- color: #839496;
- }
+ SOLARIZED HEX ROLE
+ --------- -------- ------------------------------------------
+ base03 #002b36 background
+ base01 #586e75 comments / secondary content
+ base1 #93a1a1 body text / default code / primary content
+ orange #cb4b16 constants
+ red #dc322f regex, special keywords
+ blue #268bd2 reserved keywords
+ cyan #2aa198 strings, numbers
+ green #859900 operators, other keywords
+ */
- .hljs-comment,
- .hljs-template_comment,
- .diff .hljs-header,
- .hljs-doctype,
- .hljs-pi,
- .lisp .hljs-string,
- .hljs-javadoc {
- color: #586e75;
- }
-
- /* Solarized Green */
- .hljs-keyword,
- .hljs-winutils,
- .method,
- .hljs-addition,
- .css .hljs-tag,
- .hljs-request,
- .hljs-status,
- .nginx .hljs-title {
- color: #859900;
- }
-
- /* Solarized Cyan */
- .hljs-number,
- .hljs-command,
- .hljs-string,
- .hljs-tag .hljs-value,
- .hljs-rules .hljs-value,
- .hljs-phpdoc,
- .tex .hljs-formula,
- .hljs-regexp,
- .hljs-hexcolor,
- .hljs-link_url {
- color: #2aa198;
- }
-
- /* Solarized Blue */
- .hljs-title,
- .hljs-localvars,
- .hljs-chunk,
- .hljs-decorator,
- .hljs-built_in,
- .hljs-identifier,
- .vhdl .hljs-literal,
- .hljs-id,
- .css .hljs-function {
- color: #268bd2;
- }
-
- /* Solarized Yellow */
- .hljs-attribute,
- .hljs-variable,
- .lisp .hljs-body,
- .smalltalk .hljs-number,
- .hljs-constant,
- .hljs-class .hljs-title,
- .hljs-parent,
- .haskell .hljs-type,
- .hljs-link_reference {
- color: #b58900;
- }
-
- /* Solarized Orange */
- .hljs-preprocessor,
- .hljs-preprocessor .hljs-keyword,
- .hljs-pragma,
- .hljs-shebang,
- .hljs-symbol,
- .hljs-symbol .hljs-string,
- .diff .hljs-change,
- .hljs-special,
- .hljs-attr_selector,
- .hljs-subst,
- .hljs-cdata,
- .clojure .hljs-title,
- .css .hljs-pseudo,
- .hljs-header {
- color: #cb4b16;
- }
-
- /* Solarized Red */
- .hljs-deletion,
- .hljs-important {
- color: #dc322f;
- }
-
- /* Solarized Violet */
- .hljs-link_label {
- color: #6c71c4;
- }
-
- .tex .hljs-formula {
- background: #073642;
- }
+ .c { color: #586e75 } /* Comment */
+ .err { color: #93a1a1 } /* Error */
+ .g { color: #93a1a1 } /* Generic */
+ .k { color: #859900 } /* Keyword */
+ .l { color: #93a1a1 } /* Literal */
+ .n { color: #93a1a1 } /* Name */
+ .o { color: #859900 } /* Operator */
+ .x { color: #cb4b16 } /* Other */
+ .p { color: #93a1a1 } /* Punctuation */
+ .cm { color: #586e75 } /* Comment.Multiline */
+ .cp { color: #859900 } /* Comment.Preproc */
+ .c1 { color: #586e75 } /* Comment.Single */
+ .cs { color: #859900 } /* Comment.Special */
+ .gd { color: #2aa198 } /* Generic.Deleted */
+ .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */
+ .gr { color: #dc322f } /* Generic.Error */
+ .gh { color: #cb4b16 } /* Generic.Heading */
+ .gi { color: #859900 } /* Generic.Inserted */
+ .go { color: #93a1a1 } /* Generic.Output */
+ .gp { color: #93a1a1 } /* Generic.Prompt */
+ .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */
+ .gu { color: #cb4b16 } /* Generic.Subheading */
+ .gt { color: #93a1a1 } /* Generic.Traceback */
+ .kc { color: #cb4b16 } /* Keyword.Constant */
+ .kd { color: #268bd2 } /* Keyword.Declaration */
+ .kn { color: #859900 } /* Keyword.Namespace */
+ .kp { color: #859900 } /* Keyword.Pseudo */
+ .kr { color: #268bd2 } /* Keyword.Reserved */
+ .kt { color: #dc322f } /* Keyword.Type */
+ .ld { color: #93a1a1 } /* Literal.Date */
+ .m { color: #2aa198 } /* Literal.Number */
+ .s { color: #2aa198 } /* Literal.String */
+ .na { color: #93a1a1 } /* Name.Attribute */
+ .nb { color: #B58900 } /* Name.Builtin */
+ .nc { color: #268bd2 } /* Name.Class */
+ .no { color: #cb4b16 } /* Name.Constant */
+ .nd { color: #268bd2 } /* Name.Decorator */
+ .ni { color: #cb4b16 } /* Name.Entity */
+ .ne { color: #cb4b16 } /* Name.Exception */
+ .nf { color: #268bd2 } /* Name.Function */
+ .nl { color: #93a1a1 } /* Name.Label */
+ .nn { color: #93a1a1 } /* Name.Namespace */
+ .nx { color: #93a1a1 } /* Name.Other */
+ .py { color: #93a1a1 } /* Name.Property */
+ .nt { color: #268bd2 } /* Name.Tag */
+ .nv { color: #268bd2 } /* Name.Variable */
+ .ow { color: #859900 } /* Operator.Word */
+ .w { color: #93a1a1 } /* Text.Whitespace */
+ .mf { color: #2aa198 } /* Literal.Number.Float */
+ .mh { color: #2aa198 } /* Literal.Number.Hex */
+ .mi { color: #2aa198 } /* Literal.Number.Integer */
+ .mo { color: #2aa198 } /* Literal.Number.Oct */
+ .sb { color: #586e75 } /* Literal.String.Backtick */
+ .sc { color: #2aa198 } /* Literal.String.Char */
+ .sd { color: #93a1a1 } /* Literal.String.Doc */
+ .s2 { color: #2aa198 } /* Literal.String.Double */
+ .se { color: #cb4b16 } /* Literal.String.Escape */
+ .sh { color: #93a1a1 } /* Literal.String.Heredoc */
+ .si { color: #2aa198 } /* Literal.String.Interpol */
+ .sx { color: #2aa198 } /* Literal.String.Other */
+ .sr { color: #dc322f } /* Literal.String.Regex */
+ .s1 { color: #2aa198 } /* Literal.String.Single */
+ .ss { color: #2aa198 } /* Literal.String.Symbol */
+ .bp { color: #268bd2 } /* Name.Builtin.Pseudo */
+ .vc { color: #268bd2 } /* Name.Variable.Class */
+ .vg { color: #268bd2 } /* Name.Variable.Global */
+ .vi { color: #268bd2 } /* Name.Variable.Instance */
+ .il { color: #2aa198 } /* Literal.Number.Integer.Long */
}
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
new file mode 100644
index 0000000000..6b44c00c30
--- /dev/null
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -0,0 +1,110 @@
+/* https://gist.github.com/qguv/7936275 */
+pre.code.highlight.solarized-light,
+.code.solarized-light {
+
+ background-color: #fdf6e3;
+ color: #586e75;
+
+ pre.code,
+ .line-numbers,
+ .line-numbers a {
+ background-color: #fdf6e3 !important;
+ color: #586e75 !important;
+ }
+
+ pre.code {
+ border-left: 1px solid #c5d0d4;
+ }
+
+ // highlight line via anchor
+ pre .hll {
+ background-color: #ddd8c5 !important;
+ }
+
+ /* Solarized Light
+
+ For use with Jekyll and Pygments
+
+ http://ethanschoonover.com/solarized
+
+ SOLARIZED HEX ROLE
+ --------- -------- ------------------------------------------
+ base01 #586e75 body text / default code / primary content
+ base1 #93a1a1 comments / secondary content
+ base3 #fdf6e3 background
+ orange #cb4b16 constants
+ red #dc322f regex, special keywords
+ blue #268bd2 reserved keywords
+ cyan #2aa198 strings, numbers
+ green #859900 operators, other keywords
+ */
+
+ .c { color: #93a1a1 } /* Comment */
+ .err { color: #586e75 } /* Error */
+ .g { color: #586e75 } /* Generic */
+ .k { color: #859900 } /* Keyword */
+ .l { color: #586e75 } /* Literal */
+ .n { color: #586e75 } /* Name */
+ .o { color: #859900 } /* Operator */
+ .x { color: #cb4b16 } /* Other */
+ .p { color: #586e75 } /* Punctuation */
+ .cm { color: #93a1a1 } /* Comment.Multiline */
+ .cp { color: #859900 } /* Comment.Preproc */
+ .c1 { color: #93a1a1 } /* Comment.Single */
+ .cs { color: #859900 } /* Comment.Special */
+ .gd { color: #2aa198 } /* Generic.Deleted */
+ .ge { color: #586e75; font-style: italic } /* Generic.Emph */
+ .gr { color: #dc322f } /* Generic.Error */
+ .gh { color: #cb4b16 } /* Generic.Heading */
+ .gi { color: #859900 } /* Generic.Inserted */
+ .go { color: #586e75 } /* Generic.Output */
+ .gp { color: #586e75 } /* Generic.Prompt */
+ .gs { color: #586e75; font-weight: bold } /* Generic.Strong */
+ .gu { color: #cb4b16 } /* Generic.Subheading */
+ .gt { color: #586e75 } /* Generic.Traceback */
+ .kc { color: #cb4b16 } /* Keyword.Constant */
+ .kd { color: #268bd2 } /* Keyword.Declaration */
+ .kn { color: #859900 } /* Keyword.Namespace */
+ .kp { color: #859900 } /* Keyword.Pseudo */
+ .kr { color: #268bd2 } /* Keyword.Reserved */
+ .kt { color: #dc322f } /* Keyword.Type */
+ .ld { color: #586e75 } /* Literal.Date */
+ .m { color: #2aa198 } /* Literal.Number */
+ .s { color: #2aa198 } /* Literal.String */
+ .na { color: #586e75 } /* Name.Attribute */
+ .nb { color: #B58900 } /* Name.Builtin */
+ .nc { color: #268bd2 } /* Name.Class */
+ .no { color: #cb4b16 } /* Name.Constant */
+ .nd { color: #268bd2 } /* Name.Decorator */
+ .ni { color: #cb4b16 } /* Name.Entity */
+ .ne { color: #cb4b16 } /* Name.Exception */
+ .nf { color: #268bd2 } /* Name.Function */
+ .nl { color: #586e75 } /* Name.Label */
+ .nn { color: #586e75 } /* Name.Namespace */
+ .nx { color: #586e75 } /* Name.Other */
+ .py { color: #586e75 } /* Name.Property */
+ .nt { color: #268bd2 } /* Name.Tag */
+ .nv { color: #268bd2 } /* Name.Variable */
+ .ow { color: #859900 } /* Operator.Word */
+ .w { color: #586e75 } /* Text.Whitespace */
+ .mf { color: #2aa198 } /* Literal.Number.Float */
+ .mh { color: #2aa198 } /* Literal.Number.Hex */
+ .mi { color: #2aa198 } /* Literal.Number.Integer */
+ .mo { color: #2aa198 } /* Literal.Number.Oct */
+ .sb { color: #93a1a1 } /* Literal.String.Backtick */
+ .sc { color: #2aa198 } /* Literal.String.Char */
+ .sd { color: #586e75 } /* Literal.String.Doc */
+ .s2 { color: #2aa198 } /* Literal.String.Double */
+ .se { color: #cb4b16 } /* Literal.String.Escape */
+ .sh { color: #586e75 } /* Literal.String.Heredoc */
+ .si { color: #2aa198 } /* Literal.String.Interpol */
+ .sx { color: #2aa198 } /* Literal.String.Other */
+ .sr { color: #dc322f } /* Literal.String.Regex */
+ .s1 { color: #2aa198 } /* Literal.String.Single */
+ .ss { color: #2aa198 } /* Literal.String.Symbol */
+ .bp { color: #268bd2 } /* Name.Builtin.Pseudo */
+ .vc { color: #268bd2 } /* Name.Variable.Class */
+ .vg { color: #268bd2 } /* Name.Variable.Global */
+ .vi { color: #268bd2 } /* Name.Variable.Instance */
+ .il { color: #2aa198 } /* Literal.Number.Integer.Long */
+}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 815cf367ae..a52ffc971d 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,188 +1,87 @@
-.white {
- .line.hll {
- background: #FFA;
- }
+/* https://github.com/aahan/pygments-github-style */
+pre.code.highlight.white,
+.code.white {
- pre {
- background-color: #fff;
- color: #333;
- }
-
- .hljs {
- background: #FFF;
- }
+ background-color: #fff;
+ color: #333;
+ pre.highlight,
+ .line-numbers,
.line-numbers a {
- color: #999;
+ background-color: #fff !important;
+ color: #333 !important;
}
- .hljs {
- display: block;
- background: #fff; color: black;
+ pre.code {
+ border-left: 1px solid #bbb;
}
- .hljs-comment,
- .hljs-template_comment,
- .hljs-javadoc,
- .hljs-comment * {
- color: #006a00;
+ // highlight line via anchor
+ pre .hll {
+ background-color: #f8eec7 !important;
}
- .hljs-keyword,
- .hljs-literal,
- .nginx .hljs-title {
- color: #aa0d91;
- }
- .method,
- .hljs-list .hljs-title,
- .hljs-tag .hljs-title,
- .setting .hljs-value,
- .hljs-winutils,
- .tex .hljs-command,
- .http .hljs-title,
- .hljs-request,
- .hljs-status {
- color: #008;
- }
-
- .hljs-envvar,
- .tex .hljs-special {
- color: #660;
- }
-
- .hljs-string {
- color: #c41a16;
- }
- .hljs-tag .hljs-value,
- .hljs-cdata,
- .hljs-filter .hljs-argument,
- .hljs-attr_selector,
- .apache .hljs-cbracket,
- .hljs-date,
- .hljs-regexp {
- color: #080;
- }
-
- .hljs-sub .hljs-identifier,
- .hljs-pi,
- .hljs-tag,
- .hljs-tag .hljs-keyword,
- .hljs-decorator,
- .ini .hljs-title,
- .hljs-shebang,
- .hljs-prompt,
- .hljs-hexcolor,
- .hljs-rules .hljs-value,
- .hljs-symbol,
- .hljs-symbol .hljs-string,
- .hljs-number,
- .css .hljs-function,
- .clojure .hljs-title,
- .clojure .hljs-built_in,
- .hljs-function .hljs-title,
- .coffeescript .hljs-attribute {
- color: #1c00cf;
- }
-
- .hljs-class .hljs-title,
- .haskell .hljs-type,
- .smalltalk .hljs-class,
- .hljs-javadoctag,
- .hljs-yardoctag,
- .hljs-phpdoc,
- .hljs-typename,
- .hljs-tag .hljs-attribute,
- .hljs-doctype,
- .hljs-class .hljs-id,
- .hljs-built_in,
- .setting,
- .hljs-params,
- .clojure .hljs-attribute {
- color: #5c2699;
- }
-
- .hljs-variable {
- color: #3f6e74;
- }
- .css .hljs-tag,
- .hljs-rules .hljs-property,
- .hljs-pseudo,
- .hljs-subst {
- color: #000;
- }
-
- .css .hljs-class,
- .css .hljs-id {
- color: #9B703F;
- }
-
- .hljs-value .hljs-important {
- color: #ff7700;
- font-weight: bold;
- }
-
- .hljs-rules .hljs-keyword {
- color: #C5AF75;
- }
-
- .hljs-annotation,
- .apache .hljs-sqbracket,
- .nginx .hljs-built_in {
- color: #9B859D;
- }
-
- .hljs-preprocessor,
- .hljs-preprocessor *,
- .hljs-pragma {
- color: #643820;
- }
-
- .tex .hljs-formula {
- background-color: #EEE;
- font-style: italic;
- }
-
- .diff .hljs-header,
- .hljs-chunk {
- color: #808080;
- font-weight: bold;
- }
-
- .diff .hljs-change {
- background-color: #BCCFF9;
- }
-
- .hljs-addition {
- background-color: #BAEEBA;
- }
-
- .hljs-deletion {
- background-color: #FFC8BD;
- }
-
- .hljs-comment .hljs-yardoctag {
- font-weight: bold;
- }
-
- .method .hljs-id {
- color: #000;
- }
-}
-
-.shadow {
- @include box-shadow(0 5px 15px #000);
-}
-
-.file-content {
- &.code .white {
- .highlight {
- border-left: 1px solid #eee;
- }
- }
-
- &.wiki .white {
- .highlight, pre, .hljs {
- background: #F9F9F9;
- }
- }
+ .hll { background-color: #f8f8f8 }
+ .c { color: #999988; font-style: italic; }
+ .err { color: #a61717; background-color: #e3d2d2; }
+ .k { font-weight: bold; }
+ .o { font-weight: bold; }
+ .cm { color: #999988; font-style: italic; }
+ .cp { color: #999999; font-weight: bold; }
+ .c1 { color: #999988; font-style: italic; }
+ .cs { color: #999999; font-weight: bold; font-style: italic; }
+ .gd { color: #000000; background-color: #ffdddd; }
+ .gd .x { color: #000000; background-color: #ffaaaa; }
+ .ge { font-style: italic; }
+ .gr { color: #aa0000; }
+ .gh { color: #999999; }
+ .gi { color: #000000; background-color: #ddffdd; }
+ .gi .x { color: #000000; background-color: #aaffaa; }
+ .go { color: #888888; }
+ .gp { color: #555555; }
+ .gs { font-weight: bold; }
+ .gu { color: #800080; font-weight: bold; }
+ .gt { color: #aa0000; }
+ .kc { font-weight: bold; }
+ .kd { font-weight: bold; }
+ .kn { font-weight: bold; }
+ .kp { font-weight: bold; }
+ .kr { font-weight: bold; }
+ .kt { color: #445588; font-weight: bold; }
+ .m { color: #009999; }
+ .s { color: #dd1144; }
+ .n { color: #333333; }
+ .na { color: teal; }
+ .nb { color: #0086b3; }
+ .nc { color: #445588; font-weight: bold; }
+ .no { color: teal; }
+ .ni { color: purple; }
+ .ne { color: #990000; font-weight: bold; }
+ .nf { color: #990000; font-weight: bold; }
+ .nn { color: #555555; }
+ .nt { color: navy; }
+ .nv { color: teal; }
+ .ow { font-weight: bold; }
+ .w { color: #bbbbbb; }
+ .mf { color: #009999; }
+ .mh { color: #009999; }
+ .mi { color: #009999; }
+ .mo { color: #009999; }
+ .sb { color: #dd1144; }
+ .sc { color: #dd1144; }
+ .sd { color: #dd1144; }
+ .s2 { color: #dd1144; }
+ .se { color: #dd1144; }
+ .sh { color: #dd1144; }
+ .si { color: #dd1144; }
+ .sx { color: #dd1144; }
+ .sr { color: #009926; }
+ .s1 { color: #dd1144; }
+ .ss { color: #990073; }
+ .bp { color: #999999; }
+ .vc { color: teal; }
+ .vg { color: teal; }
+ .vi { color: teal; }
+ .il { color: #009999; }
+ .gc { color: #999; background-color: #EAF2F5; }
}
diff --git a/app/assets/stylesheets/main/fonts.scss b/app/assets/stylesheets/main/fonts.scss
deleted file mode 100644
index d90274a0db..0000000000
--- a/app/assets/stylesheets/main/fonts.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-/** Typo **/
-$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
-$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss
deleted file mode 100644
index c71984a566..0000000000
--- a/app/assets/stylesheets/main/variables.scss
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * General Colors
- */
-$style_color: #474D57;
-$hover: #FFECDB;
-$box_bg: #F9F9F9;
-
-/*
- * Link colors
- */
-$link_color: #446e9b;
-$link_hover_color: darken($link-color, 10%);
-
-$btn-border: 1px solid #ccc;
-
-/*
- * Success colors (green)
- */
-$border_success: #019875;
-$bg_success: #019875;
-
-/*
- * Danger colors (red)
- */
-$border_danger: #d43f3a;
-$bg_danger: #d9534f;
-
-/*
- * Primary colors (blue)
- */
-$border_primary: #446e9b;
-$bg_primary: #446e9b;
-
-/*
- * Warning colors (yellow)
- */
-$bg_warning: #EB9532;
-$border_warning: #EB9532;
-
-/**
- * Commit Diff Colors
- */
-$added: #63c363;
-$deleted: #f77;
-
-/**
- *
- */
-$nprogress-color: #3498db;
diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/pages/admin.scss
similarity index 78%
rename from app/assets/stylesheets/sections/admin.scss
rename to app/assets/stylesheets/pages/admin.scss
index a51deee797..144852e787 100644
--- a/app/assets/stylesheets/sections/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -50,3 +50,14 @@
line-height: 2;
}
}
+
+.broadcast-message {
+ @extend .alert-warning;
+ padding: 10px;
+ text-align: center;
+}
+
+.broadcast-message-preview {
+ @extend .broadcast-message;
+ margin-bottom: 20px;
+}
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
new file mode 100644
index 0000000000..e7125c0399
--- /dev/null
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -0,0 +1,123 @@
+.commit-title{
+ display: block;
+}
+
+.commit-title{
+ margin-bottom: 10px;
+}
+
+.commit-author, .commit-committer{
+ display: block;
+ color: #999;
+ font-weight: normal;
+ font-style: italic;
+}
+
+.commit-author strong, .commit-committer strong{
+ font-weight: bold;
+ font-style: normal;
+}
+
+.commit-description {
+ background: none;
+ border: none;
+ margin: 0;
+ padding: 0;
+ margin-top: 10px;
+}
+
+.commit-stat-summary {
+ color: #666;
+ font-size: 14px;
+ font-weight: normal;
+ padding: 3px 0;
+ margin-bottom: 10px;
+}
+
+.commit-info-row {
+ margin-bottom: 10px;
+ .avatar {
+ @extend .avatar-inline;
+ }
+ .commit-committer-link,
+ .commit-author-link {
+ color: #444;
+ font-weight: bold;
+ }
+}
+
+.commit-box {
+ margin: 10px 0;
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ padding: 20px 0;
+
+ .commit-title {
+ margin: 0;
+ }
+
+ .commit-description {
+ margin-top: 15px;
+ }
+}
+
+.file-stats a {
+ color: $style_color;
+}
+
+.file-stats {
+ .new-file {
+ a {
+ color: #090;
+ }
+ i {
+ color: #1BCF00;
+ }
+ }
+ .renamed-file {
+ i {
+ color: #FE9300;
+ }
+ }
+ .deleted-file {
+ a {
+ color: #B00;
+ }
+ i {
+ color: #EE0000;
+ }
+ }
+ .edit-file{
+ i{
+ color: #555;
+ }
+ }
+}
+
+/*
+ * Commit message textarea for web editor and
+ * custom merge request message
+ */
+.commit-message-container {
+ background-color: $body-bg;
+ position: relative;
+ font-family: $monospace_font;
+ $left: 12px;
+ .max-width-marker {
+ width: 72ch;
+ color: rgba(0, 0, 0, 0.0);
+ font-family: inherit;
+ left: $left;
+ height: 100%;
+ border-right: 1px solid mix($input-border, white);
+ position: absolute;
+ z-index: 1;
+ }
+ > textarea {
+ background-color: rgba(0, 0, 0, 0.0);
+ font-family: inherit;
+ padding-left: $left;
+ position: relative;
+ z-index: 2;
+ }
+}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
new file mode 100644
index 0000000000..84361e1548
--- /dev/null
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -0,0 +1,118 @@
+.commits-compare-switch{
+ @extend .btn;
+ background: image-url("switch_icon.png") no-repeat center center;
+ text-indent: -9999px;
+ float: left;
+ margin-right: 9px;
+}
+
+.lists-separator {
+ margin: 10px 0;
+ border-color: #DDD;
+}
+
+.commits-row {
+ ul {
+ margin: 0;
+
+ li.commit {
+ padding: 8px 0;
+ }
+ }
+
+ .commits-row-date {
+ font-size: 15px;
+ line-height: 20px;
+ margin-bottom: 5px;
+ }
+}
+
+.commits-feed-holder {
+ float: right;
+
+ .btn {
+ padding: 4px 12px;
+ }
+}
+
+li.commit {
+ .commit-row-title {
+ font-size: $list-font-size;
+ line-height: 20px;
+ margin-bottom: 2px;
+
+ .notes_count {
+ float: right;
+ margin-right: 10px;
+ }
+
+ .commit_short_id {
+ min-width: 65px;
+ font-family: $monospace_font;
+ }
+
+ .str-truncated {
+ max-width: 70%;
+ }
+
+ .commit-row-message {
+ color: #444;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ .text-expander {
+ background: #eee;
+ color: #555;
+ padding: 0 5px;
+ cursor: pointer;
+ margin-left: 4px;
+ &:hover {
+ background-color: #ddd;
+ }
+ }
+ }
+
+ .commit-row-description {
+ font-size: 14px;
+ border-left: 1px solid #EEE;
+ padding: 10px 15px;
+ margin: 5px 0 10px 5px;
+ background: #f9f9f9;
+ display: none;
+
+ pre {
+ border: none;
+ background: inherit;
+ padding: 0;
+ margin: 0;
+ }
+ }
+
+ .commit-row-info {
+ color: #777;
+ line-height: 24px;
+ font-size: 13px;
+
+ a {
+ color: #777;
+ }
+
+ .committed_ago {
+ display: inline-block;
+ }
+ }
+
+ &.inline-commit {
+ .commit-row-title {
+ font-size: 13px;
+ }
+
+ .committed_ago {
+ float: right;
+ @extend .cgray;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
similarity index 66%
rename from app/assets/stylesheets/sections/dashboard.scss
rename to app/assets/stylesheets/pages/dashboard.scss
index d181d83e85..5a543a852c 100644
--- a/app/assets/stylesheets/sections/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -23,42 +23,6 @@
}
}
-.dashboard {
- .dash-filter {
- width: 205px;
- float: left;
- height: inherit;
- }
-}
-
-@media (max-width: 1200px) {
- .dashboard .dash-filter {
- width: 140px;
- }
-}
-
-.dash-sidebar-tabs {
- margin-bottom: 2px;
- border: none;
- margin: 0 !important;
-
- li {
- &.active {
- a {
- background-color: #EEE;
- border-bottom: 1px solid #EEE !important;
- &:hover {
- background: #eee;
- }
- }
- }
-
- a {
- border-color: #DDD !important;
- }
- }
-}
-
.project-row, .group-row {
padding: 0 !important;
font-size: 14px;
@@ -98,7 +62,6 @@
margin-left: 10px;
float: left;
margin-right: 15px;
- font-size: 20px;
margin-bottom: 15px;
i {
@@ -106,8 +69,34 @@
}
}
+.dash-project-avatar {
+ float: left;
+
+ .avatar {
+ margin-top: -8px;
+ margin-left: -15px;
+ @include border-radius(0px);
+ }
+ .identicon {
+ line-height: 40px;
+ }
+}
+
.dash-project-access-icon {
float: left;
- margin-right: 3px;
+ margin-right: 5px;
width: 16px;
}
+
+.dash-new-project {
+ background: $gl-success;
+ border: 1px solid $gl-success;
+
+ a {
+ color: #FFF;
+ }
+}
+
+.dash-list .str-truncated {
+ max-width: 72%;
+}
diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/pages/diff.scss
similarity index 88%
rename from app/assets/stylesheets/sections/diff.scss
rename to app/assets/stylesheets/pages/diff.scss
index 758f15c801..5a9f93dc03 100644
--- a/app/assets/stylesheets/sections/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1,24 +1,30 @@
.diff-file {
- border: 1px solid #CCC;
+ border: 1px solid $border-color;
margin-bottom: 1em;
.diff-header {
- @extend .clearfix;
- background: #EEE;
- border-bottom: 1px solid #CCC;
- padding: 5px 5px 5px 10px;
+ position: relative;
+ background: $background-color;
+ border-bottom: 1px solid $border-color;
+ padding: 10px 15px;
color: #555;
+ z-index: 10;
> span {
+ @include str-truncated(65%);
font-family: $monospace_font;
- line-height: 2;
}
.diff-btn-group {
float: right;
+ position: absolute;
+ top: 5px;
+ right: 15px;
.btn {
- background-color: #FFF;
+ padding: 0px 10px;
+ font-size: 13px;
+ line-height: 28px;
}
}
@@ -37,15 +43,15 @@
overflow-y: hidden;
background: #FFF;
color: #333;
- font-size: 12px;
+ font-size: $code_font_size;
.old {
span.idiff {
- background-color: #F99;
+ background-color: #f8cbcb;
}
}
.new {
span.idiff {
- background-color: #8F8;
+ background-color: #a6f3a6;
}
}
.unfold {
@@ -64,8 +70,8 @@
margin: 0px;
padding: 0px;
td {
- line-height: 18px;
- font-size: 12px;
+ line-height: $code_line_height;
+ font-size: $code_font_size;
}
}
@@ -84,9 +90,9 @@
padding: 0px;
border: none;
background: #F5F5F5;
- color: #666;
+ color: rgba(0,0,0,0.3);
padding: 0px 5px;
- border-right: 1px solid #ccc;
+ border-right: 1px solid $border-color;
text-align: right;
min-width: 35px;
max-width: 50px;
@@ -96,7 +102,7 @@
float: left;
width: 35px;
font-weight: normal;
- color: #666;
+ color: rgba(0,0,0,0.3);
&:hover {
text-decoration: underline;
}
@@ -114,13 +120,13 @@
.line_holder {
&.old .old_line,
&.old .new_line {
- background: #FCC;
- border-color: #E7BABA;
+ background: #ffdddd;
+ border-color: #f1c0c0;
}
&.new .old_line,
&.new .new_line {
- background: #CFC;
- border-color: #B9ECB9;
+ background: #dbffdb;
+ border-color: #c1e9c1;
}
}
.line_content {
@@ -129,13 +135,13 @@
padding: 0px 0.5em;
border: none;
&.new {
- background: #CFD;
+ background: #eaffea;
}
&.old {
- background: #FDD;
+ background: #ffecec;
}
&.matched {
- color: #ccc;
+ color: $border-color;
background: #fafafa;
}
&.parallel {
diff --git a/app/assets/stylesheets/sections/editor.scss b/app/assets/stylesheets/pages/editor.scss
similarity index 56%
rename from app/assets/stylesheets/sections/editor.scss
rename to app/assets/stylesheets/pages/editor.scss
index f62f46ee16..851f126318 100644
--- a/app/assets/stylesheets/sections/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -16,8 +16,6 @@
}
}
.commit-button-annotation {
- @extend .alert;
- @extend .alert-info;
display: inline-block;
margin: 0;
padding: 2px;
@@ -31,4 +29,26 @@
margin: 5px 8px 0 8px;
}
}
+
+ .file-title {
+ @extend .monospace;
+ font-size: 14px;
+ padding: 5px;
+ }
+
+ .editor-ref {
+ background: #f5f5f5;
+ padding: 11px 15px;
+ border-right: 1px solid #CCC;
+ display: inline-block;
+ margin: -5px -5px;
+ margin-right: 10px;
+ }
+
+ .editor-file-name {
+ .new-file-name {
+ display: inline-block;
+ width: 200px;
+ }
+ }
}
diff --git a/app/assets/stylesheets/sections/errors.scss b/app/assets/stylesheets/pages/errors.scss
similarity index 100%
rename from app/assets/stylesheets/sections/errors.scss
rename to app/assets/stylesheets/pages/errors.scss
diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/pages/events.scss
similarity index 73%
rename from app/assets/stylesheets/sections/events.scss
rename to app/assets/stylesheets/pages/events.scss
index 656aa5b18a..3e9e36e477 100644
--- a/app/assets/stylesheets/sections/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -46,34 +46,37 @@
border-bottom: 1px solid #eee;
.event-title {
@include str-truncated(72%);
- color: #333;
- font-weight: normal;
+ font-weight: 500;
font-size: 14px;
.author_name {
color: #333;
}
}
.event-body {
+ font-size: 13px;
margin-left: 35px;
- margin-right: 100px;
+ margin-right: 80px;
+ color: #777;
- .event-info {
- color: #666;
- }
.event-note {
- color: #666;
margin-top: 5px;
+ word-wrap: break-word;
.md {
font-size: 13px;
+
+ iframe.twitter-share-button {
+ vertical-align: bottom;
+ }
}
pre {
border: none;
background: #f9f9f9;
border-radius: 0;
- color: #666;
+ color: #777;
margin: 0 20px;
+ overflow: hidden;
}
.note-image-attach {
@@ -120,7 +123,6 @@
padding: 3px;
padding-left: 0;
border: none;
- color: #666;
.commit-row-title {
font-size: 12px;
}
@@ -144,49 +146,49 @@
}
}
-/**
- * Event filter
- *
- */
-.event_filter {
- position: absolute;
- width: 40px;
- margin-left: -55px;
-
- .filter_icon {
- a {
- text-align:center;
- background: $bg_primary;
- margin-bottom: 10px;
- float: left;
- padding: 9px 6px;
- font-size: 18px;
- width: 40px;
- color: #FFF;
- @include border-radius(3px);
- }
-
- &.inactive {
- a {
- color: #DDD;
- background: #f9f9f9;
- }
- }
- }
-}
/*
* Last push widget
*/
.event-last-push {
+ overflow: auto;
.event-last-push-text {
- @include str-truncated(75%);
+ @include str-truncated(100%);
+ float:left;
+ margin-right: -150px;
+ padding-right: 150px;
line-height: 24px;
}
}
@media (max-width: $screen-xs-max) {
- .event-item .event-title {
- @include str-truncated(65%);
+ .event-item {
+ .event-title {
+ white-space: normal;
+ overflow: visible;
+ max-width: 100%;
+ }
+ .avatar {
+ display: none;
+ }
+
+ .event-body {
+ margin: 0;
+ border-left: 2px solid #DDD;
+ padding-left: 10px;
+ }
+
+ .event-item-timestamp {
+ display: none;
+ }
+ }
+}
+
+.event_filter {
+ li a {
+ font-size: 13px;
+ padding: 5px 10px;
+ background: rgba(0,0,0,0.045);
+ margin-left: 4px;
}
}
diff --git a/app/assets/stylesheets/sections/explore.scss b/app/assets/stylesheets/pages/explore.scss
similarity index 100%
rename from app/assets/stylesheets/sections/explore.scss
rename to app/assets/stylesheets/pages/explore.scss
diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/pages/graph.scss
similarity index 84%
rename from app/assets/stylesheets/sections/graph.scss
rename to app/assets/stylesheets/pages/graph.scss
index 3d878d1e52..c3b10d144e 100644
--- a/app/assets/stylesheets/sections/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -1,11 +1,11 @@
.project-network {
- border: 1px solid #CCC;
+ border: 1px solid $border-color;
.controls {
color: #888;
font-size: 14px;
padding: 5px;
- border-bottom: 1px solid #bbb;
+ border-bottom: 1px solid $border-color;
background: #EEE;
}
diff --git a/app/assets/stylesheets/sections/groups.scss b/app/assets/stylesheets/pages/groups.scss
similarity index 100%
rename from app/assets/stylesheets/sections/groups.scss
rename to app/assets/stylesheets/pages/groups.scss
diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/pages/header.scss
similarity index 60%
rename from app/assets/stylesheets/sections/header.scss
rename to app/assets/stylesheets/pages/header.scss
index e0e0d60c38..26b4d04106 100644
--- a/app/assets/stylesheets/sections/header.scss
+++ b/app/assets/stylesheets/pages/header.scss
@@ -4,9 +4,11 @@
*/
header {
&.navbar-gitlab {
+ z-index: 100;
margin-bottom: 0;
min-height: 40px;
border: none;
+ width: 100%;
.navbar-inner {
filter: none;
@@ -52,13 +54,12 @@ header {
border-width: 0;
font-size: 18px;
- .app_logo { margin-left: -15px; }
-
.title {
@include str-truncated(70%);
}
.navbar-collapse {
+ margin-top: 47px;
padding-right: 0;
padding-left: 0;
}
@@ -83,7 +84,10 @@ header {
}
}
- z-index: 10;
+ .container {
+ width: 100% !important;
+ padding: 0px;
+ }
/**
*
@@ -96,18 +100,14 @@ header {
a {
float: left;
- padding: 0px;
- margin: 0 6px;
+ padding: 5px 0;
+ height: 46px;
+ width: 52px;
+ text-align: center;
- h1 {
- margin: 0;
- background: image-url('logo-black.png') no-repeat center center;
- background-size: 32px;
- float: left;
- height: 46px;
- width: 40px;
- @include header-font;
- text-indent: -9999px;
+ img {
+ width: 36px;
+ height: 36px;
}
}
&:hover {
@@ -130,13 +130,13 @@ header {
}
.profile-pic {
- position: relative;
- top: -1px;
- padding-right: 0px !important;
+ padding: 0px !important;
+ width: 46px;
+ height: 46px;
+ margin-left: 5px;
img {
- width: 26px;
- height: 26px;
- @include border-radius(4px);
+ width: 46px;
+ height: 46px;
}
}
@@ -169,83 +169,6 @@ header {
@include transition(all 0.15s ease-in 0s);
}
}
-
-
- /*
- * Dark header
- *
- */
- &.header-dark {
- &.navbar-gitlab {
- .navbar-inner {
- background: #708090;
- border-bottom: 1px solid #AAA;
-
- .navbar-toggle { color: #fff; }
-
- .nav > li > a {
- color: #AAA;
-
- &:hover, &:focus, &:active {
- background: none;
- color: #FFF;
- }
- }
- }
- }
-
- .turbolink-spinner {
- color: #FFF;
- }
-
- .search {
- .search-input {
- background-color: #D2D5DA;
- background-color: rgba(255, 255, 255, 0.5);
- border: 1px solid #AAA;
-
- &:focus {
- background-color: white;
- }
- }
- }
- .search-input::-webkit-input-placeholder {
- color: #666;
- }
- .app_logo {
- a {
- h1 {
- background: image-url('logo-white.png') no-repeat center center;
- background-size: 32px;
- color: #fff;
- }
- }
- }
- .title {
- a {
- color: #FFF;
- &:hover {
- text-decoration: underline;
- }
- }
- color: #fff;
- }
- }
-
- .app_logo {
- .separator {
- margin-left: 0;
- margin-right: 0;
- }
- }
-
- .separator {
- float: left;
- height: 46px;
- width: 2px;
- margin-left: 10px;
- margin-right: 10px;
- }
}
.search .search-input {
diff --git a/app/assets/stylesheets/sections/help.scss b/app/assets/stylesheets/pages/help.scss
similarity index 95%
rename from app/assets/stylesheets/sections/help.scss
rename to app/assets/stylesheets/pages/help.scss
index 07c62f98c3..6da7a2511a 100644
--- a/app/assets/stylesheets/sections/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -12,7 +12,6 @@
color: #888;
a {
- font-size: 14px;
margin-right: 3px;
}
}
@@ -29,7 +28,6 @@
th {
padding-top: 15px;
- font-size: 14px;
line-height: 1.5;
color: #333;
text-align: left
diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss
new file mode 100644
index 0000000000..3df4bb84bd
--- /dev/null
+++ b/app/assets/stylesheets/pages/import.scss
@@ -0,0 +1,18 @@
+i.icon-gitorious {
+ display: inline-block;
+ background-position: 0px 0px;
+ background-size: contain;
+ background-repeat: no-repeat;
+}
+
+i.icon-gitorious-small {
+ background-image: image-url('gitorious-logo-blue.png');
+ width: 13px;
+ height: 13px;
+}
+
+i.icon-gitorious-big {
+ background-image: image-url('gitorious-logo-black.png');
+ width: 18px;
+ height: 18px;
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
new file mode 100644
index 0000000000..d8d1233885
--- /dev/null
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -0,0 +1,41 @@
+@media (max-width: $screen-sm-max) {
+ .issuable-affix {
+ margin-top: 20px;
+ }
+}
+
+@media (max-width: $screen-md-max) {
+ .issuable-affix {
+ position: static;
+ }
+}
+
+@media (min-width: $screen-md-max) {
+ .issuable-affix {
+ &.affix-top {
+ position: static;
+ }
+
+ &.affix {
+ position: fixed;
+ top: 70px;
+ width: 220px;
+ }
+ }
+}
+
+.issuable-context-title {
+ font-size: 15px;
+ line-height: 1.4;
+ margin-bottom: 5px;
+
+ .avatar {
+ margin-left: 0;
+ }
+
+ label {
+ color: #666;
+ font-weight: normal;
+ margin-right: 4px;
+ }
+}
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/pages/issues.scss
similarity index 81%
rename from app/assets/stylesheets/sections/issues.scss
rename to app/assets/stylesheets/pages/issues.scss
index a7fa715d2e..4ea34cc1da 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -5,11 +5,13 @@
.issue-title {
margin-bottom: 5px;
- font-size: 14px;
+ font-size: $list-font-size;
+ font-weight: bold;
}
.issue-info {
color: #999;
+ font-size: 13px;
}
.issue-check {
@@ -39,7 +41,7 @@
}
.check-all-holder {
- height: 32px;
+ height: 36px;
float: left;
margin-right: 12px;
padding: 6px 15px;
@@ -75,7 +77,7 @@
}
.participants {
- margin-bottom: 10px;
+ margin-bottom: 20px;
}
.issues_bulk_update {
@@ -94,8 +96,15 @@
}
}
-.issue-show-labels .color-label {
- padding: 6px 10px;
+.issue-show-labels {
+ a {
+ margin-right: 5px;
+ margin-bottom: 5px;
+ display: inline-block;
+ .color-label {
+ padding: 6px 10px;
+ }
+ }
}
form.edit-issue {
@@ -151,4 +160,23 @@ form.edit-issue {
}
}
}
+
+ .issue {
+ &:hover .issue-actions {
+ display: none !important;
+ }
+
+ .issue-updated-at {
+ display: none;
+ }
+ }
+}
+
+h2.issue-title {
+ margin-top: 0;
+ font-weight: bold;
+}
+
+.context .select2-container {
+ width: 100% !important;
}
diff --git a/app/assets/stylesheets/sections/labels.scss b/app/assets/stylesheets/pages/labels.scss
similarity index 100%
rename from app/assets/stylesheets/sections/labels.scss
rename to app/assets/stylesheets/pages/labels.scss
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
new file mode 100644
index 0000000000..d366300511
--- /dev/null
+++ b/app/assets/stylesheets/pages/login.scss
@@ -0,0 +1,115 @@
+/* Login Page */
+.login-page {
+ .container {
+ max-width: 960px;
+ }
+
+ .navbar-gitlab .container {
+ max-width: none;
+ }
+
+ .brand-holder {
+ font-size: 18px;
+ line-height: 1.5;
+
+ p {
+ color: #888;
+ }
+
+ h1:first-child {
+ font-weight: normal;
+ margin-bottom: 30px;
+ }
+
+ img {
+ max-width: 100%;
+ margin-bottom: 30px;
+ }
+
+ a {
+ font-weight: bold;
+ }
+ }
+
+ .login-box{
+ background: #fafafa;
+ border-radius: 10px;
+ box-shadow: 0 0px 2px #CCC;
+ padding: 15px;
+
+ .login-heading h3 {
+ font-weight: 300;
+ line-height: 1.5;
+ margin: 0 0 10px 0;
+ }
+
+ .login-footer {
+ margin-top: 10px;
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ a.forgot {
+ float: right;
+ padding-top: 6px
+ }
+
+ .nav .active a {
+ background: transparent;
+ }
+ }
+
+ .form-control {
+ font-size: 14px;
+ padding: 10px 8px;
+ width: 100%;
+ height: auto;
+
+ &.top {
+ @include border-radius(5px 5px 0 0);
+ margin-bottom: 0px;
+ }
+
+ &.bottom {
+ @include border-radius(0 0 5px 5px);
+ border-top: 0;
+ margin-bottom: 20px;
+ }
+
+ &.middle {
+ border-top: 0;
+ margin-bottom:0px;
+ @include border-radius(0);
+ }
+
+ &:active, &:focus {
+ background-color: #FFF;
+ }
+ }
+
+ .devise-errors {
+ h2 {
+ margin-top: 0;
+ font-size: 14px;
+ color: #a00;
+ }
+ }
+
+ .remember-me {
+ margin-top: -10px;
+
+ label {
+ font-weight: normal;
+ }
+ }
+}
+
+@media (max-width: $screen-xs-max) {
+ .login-page {
+ .col-sm-5.pull-right {
+ float: none !important;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
similarity index 58%
rename from app/assets/stylesheets/sections/merge_requests.scss
rename to app/assets/stylesheets/pages/merge_requests.scss
index c8d0cac292..d41e34caba 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -11,25 +11,36 @@
}
}
- .accept-group {
- label {
- margin: 5px;
+ .accept-merge-holder {
+ .accept-action {
+ display: inline-block;
+ }
+
+ .accept-control {
+ display: inline-block;
+ margin: 0;
margin-left: 20px;
+ padding: 10px 0;
+ line-height: 20px;
+ font-weight: bold;
+
+ .remove_source_checkbox {
+ margin: 0;
+ font-weight: bold;
+ }
}
}
}
-.merge-request .merge-request-tabs{
- border-bottom: 2px solid $border_primary;
- margin: 20px 0;
+@media(min-width: $screen-sm-max) {
+ .merge-request .merge-request-tabs{
+ margin: 20px 0;
- li {
- a {
- padding: 15px 40px;
- font-size: 14px;
- margin-bottom: -2px;
- border-bottom: 2px solid $border_primary;
- @include border-radius(0px);
+ li {
+ a {
+ padding: 15px 40px;
+ font-size: 14px;
+ }
}
}
}
@@ -73,11 +84,13 @@
.merge-request-title {
margin-bottom: 5px;
- font-size: 14px;
+ font-size: $list-font-size;
+ font-weight: bold;
}
.merge-request-info {
color: #999;
+ font-size: 13px;
.merge-request-labels {
display: inline-block;
@@ -104,38 +117,48 @@
}
.mr-state-widget {
- background: $box_bg;
+ font-size: 13px;
+ background: #F9F9F9;
margin-bottom: 20px;
+ color: #666;
+ border: 1px solid #EEE;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.ci_widget {
padding: 10px 15px;
font-size: 15px;
- border-bottom: 1px dashed #AAA;
+ border-bottom: 1px solid #BBB;
+ color: #777;
+ background-color: #F5F5F5;
&.ci-success {
- color: $bg_success;
- border-color: $border_success;
+ color: $gl-success;
+ border-color: $gl-success;
+ background-color: #F1FAF1;
}
&.ci-pending {
color: #548;
border-color: #548;
+ background-color: #F4F1FA;
}
&.ci-running {
- color: $bg_warning;
- border-color: $border_warning;
+ color: $gl-warning;
+ border-color: $gl-warning;
+ background-color: #FAF5F1;
}
&.ci-failed {
- color: $bg_danger;
- border-color: $border_danger;
+ color: $gl-danger;
+ border-color: $gl-danger;
+ background-color: #FAF1F1;
}
&.ci-error {
- color: $bg_danger;
- border-color: $border_danger;
+ color: $gl-danger;
+ border-color: $gl-danger;
+ background-color: #FAF1F1;
}
}
@@ -143,7 +166,7 @@
padding: 10px 15px;
h4 {
- margin-top: 0px;
+ font-weight: normal;
}
p:last-child {
@@ -161,10 +184,13 @@
}
}
-.merge-request-show-labels .label {
- padding: 6px 10px;
-}
-
-.mr-commits .commit {
- padding: 10px 15px;
+.merge-request-show-labels {
+ a {
+ margin-right: 5px;
+ margin-bottom: 5px;
+ display: inline-block;
+ .color-label {
+ padding: 6px 10px;
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
new file mode 100644
index 0000000000..15e3948e40
--- /dev/null
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -0,0 +1,9 @@
+.issues-sortable-list .str-truncated {
+ max-width: 90%;
+}
+
+li.milestone {
+ h4 {
+ font-weight: bold;
+ }
+}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
new file mode 100644
index 0000000000..a052203078
--- /dev/null
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -0,0 +1,175 @@
+/**
+ * Note Form
+ */
+
+.comment-btn {
+ @extend .btn-create;
+}
+.reply-btn {
+ @extend .btn-primary;
+}
+.diff-file .diff-content {
+ tr.line_holder:hover {
+ &> td.line_content {
+ background: $hover !important;
+ border-color: darken($hover, 10%) !important;
+ }
+ &> td.new_line,
+ &> td.old_line {
+ background: darken($hover, 4%) !important;
+ border-color: darken($hover, 10%) !important;
+ }
+ }
+
+ tr.line_holder:hover > td .line_note_link {
+ opacity: 1.0;
+ filter: alpha(opacity=100);
+ }
+}
+.diff-file,
+.discussion {
+ .new_note {
+ margin: 0;
+ border: none;
+ }
+}
+.new_note {
+ display: none;
+}
+
+.new_note, .edit_note {
+ .buttons {
+ float: left;
+ margin-top: 8px;
+ }
+ .clearfix {
+ margin-bottom: 0;
+ }
+
+ .note-preview-holder {
+ > p {
+ overflow-x: auto;
+ }
+ }
+
+ img {
+ max-width: 100%;
+ }
+
+ .note_text {
+ width: 100%;
+ }
+}
+
+/* loading indicator */
+.notes-busy {
+ margin: 18px;
+}
+
+.note-image-attach {
+ @extend .col-md-4;
+ @extend .thumbnail;
+ margin-left: 45px;
+ float: none;
+}
+
+.common-note-form {
+ margin: 0;
+ background: #F9F9F9;
+ padding: 5px;
+ border: 1px solid #DDD;
+}
+
+.note-form-actions {
+ background: #F9F9F9;
+ height: 45px;
+
+ .note-form-option {
+ margin-top: 8px;
+ margin-left: 30px;
+ @extend .pull-left;
+ }
+
+ .js-notify-commit-author {
+ float: left;
+ }
+
+ .write-preview-btn {
+ // makes the "absolute" position for links relative to this
+ position: relative;
+
+ // preview/edit buttons
+ > a {
+ position: absolute;
+ right: 5px;
+ top: 8px;
+ }
+ }
+}
+
+.note-edit-form {
+ display: none;
+ font-size: 13px;
+
+ .form-actions {
+ padding-left: 20px;
+
+ .btn-save {
+ float: left;
+ }
+
+ .note-form-option {
+ float: left;
+ padding: 2px 0 0 25px;
+ }
+ }
+}
+
+.js-note-attachment-delete {
+ display: none;
+}
+
+.parallel-comment {
+ padding: 6px;
+}
+
+.error-alert > .alert {
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+.discussion-body,
+.diff-file {
+ .notes .note {
+ border-color: #ddd;
+ padding: 10px 15px;
+ }
+
+ .discussion-reply-holder {
+ background: #f9f9f9;
+ padding: 10px 15px;
+ border-top: 1px solid #DDD;
+ }
+}
+
+.discussion-notes-count {
+ font-size: 16px;
+}
+
+.edit_note {
+ .markdown-area {
+ min-height: 140px;
+ }
+ .note-form-actions {
+ background: transparent;
+ }
+}
+
+.comment-hints {
+ color: #999;
+ background: #FFF;
+ padding: 5px;
+ margin-top: -11px;
+ border: 1px solid #DDD;
+ font-size: 13px;
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
new file mode 100644
index 0000000000..70505dc430
--- /dev/null
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -0,0 +1,213 @@
+/**
+ * Notes
+ */
+
+@-webkit-keyframes targe3-note {
+ from { background:#fffff0; }
+ 50% { background:#ffffd3; }
+ to { background:#fffff0; }
+}
+
+ul.notes {
+ display: block;
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+
+ .discussion-header,
+ .note-header {
+ @extend .cgray;
+ padding-bottom: 15px;
+
+ a:hover {
+ text-decoration: none;
+ }
+
+ .avatar {
+ float: left;
+ margin-right: 10px;
+ }
+
+ .discussion-last-update,
+ .note-last-update {
+ &:before {
+ content: "\00b7";
+ }
+ font-size: 13px;
+ }
+ .author {
+ color: #333;
+ font-weight: bold;
+ &:hover {
+ color: $gl-link-color;
+ }
+ }
+ .author-username {
+ }
+ }
+
+ .discussion {
+ overflow: hidden;
+ display: block;
+ position:relative;
+ }
+
+ .note {
+ display: block;
+ position:relative;
+ .note-body {
+ overflow: auto;
+ .note-text {
+ overflow: auto;
+ word-wrap: break-word;
+ @include md-typography;
+
+ a[href*="/uploads/"] {
+ &:before {
+ margin-right: 4px;
+
+ font: normal normal normal 14px/1 FontAwesome;
+ font-size: inherit;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ content: "\f0c6";
+ }
+
+ &:hover:before {
+ text-decoration: none;
+ }
+ }
+ }
+ }
+ .note-header {
+ padding-bottom: 3px;
+ }
+
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+}
+
+// Diff code in discussion view
+.discussion-body .diff-file .line_content {
+ white-space: pre-wrap;
+}
+
+.diff-file .notes_holder {
+ font-size: 13px;
+ line-height: 18px;
+ font-family: $regular_font;
+
+ td {
+ border: 1px solid #ddd;
+ border-left: none;
+
+ &.notes_line {
+ text-align: center;
+ padding: 10px 0;
+ background: #FFF;
+ }
+ &.notes_line2 {
+ text-align: center;
+ padding: 10px 0;
+ border-left: 1px solid #ddd !important;
+ }
+ &.notes_content {
+ background-color: #fff;
+ border-width: 1px 0;
+ padding-top: 0;
+ vertical-align: top;
+ &.parallel{
+ border-width: 1px;
+ }
+ }
+ }
+}
+
+/**
+ * Actions for Discussions/Notes
+ */
+
+.discussion,
+.note {
+ &.note:hover {
+ .note-actions { display: block; }
+ }
+ .discussion-header:hover {
+ .discussion-actions { display: block; }
+ }
+
+ .discussion-actions,
+ .note-actions {
+ display: none;
+ float: right;
+
+ [class~="fa"] {
+ font-size: 16px;
+ line-height: 16px;
+ vertical-align: middle;
+ }
+
+ a {
+ @extend .cgray;
+
+ &:hover {
+ &.danger { @extend .cred; }
+ }
+ }
+ }
+}
+.diff-file .note .note-actions {
+ right: 0;
+ top: 0;
+}
+
+
+/**
+ * Line note button on the side of diffs
+ */
+
+.diff-file tr.line_holder {
+ @mixin show-add-diff-note {
+ filter: alpha(opacity=100);
+ opacity: 1.0;
+ }
+
+ .add-diff-note {
+ margin-top: -4px;
+ @include border-radius(40px);
+ background: #FFF;
+ padding: 4px;
+ font-size: 16px;
+ color: $gl-link-color;
+ margin-left: -60px;
+ position: absolute;
+ z-index: 10;
+ width: 32px;
+
+ transition: all 0.2s ease;
+
+ // "hide" it by default
+ opacity: 0.0;
+ filter: alpha(opacity=0);
+
+ &:hover {
+ width: 38px;
+ font-size: 20px;
+ background: $gl-info;
+ color: #FFF;
+ @include show-add-diff-note;
+ }
+ }
+
+ // "show" the icon also if we just hover somewhere over the line
+ &:hover > td {
+ background: $hover !important;
+
+ .add-diff-note {
+ @include show-add-diff-note;
+ }
+ }
+}
+
diff --git a/app/assets/stylesheets/sections/notifications.scss b/app/assets/stylesheets/pages/notifications.scss
similarity index 75%
rename from app/assets/stylesheets/sections/notifications.scss
rename to app/assets/stylesheets/pages/notifications.scss
index f11c5dff4a..cc273f5522 100644
--- a/app/assets/stylesheets/sections/notifications.scss
+++ b/app/assets/stylesheets/pages/notifications.scss
@@ -10,13 +10,13 @@
}
.ns-part {
- color: $bg_primary;
+ color: $gl-primary;
}
.ns-watch {
- color: $bg_success;
+ color: $gl-success;
}
.ns-mute {
- color: $bg_danger;
+ color: $gl-danger;
}
diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/pages/profile.scss
similarity index 93%
rename from app/assets/stylesheets/sections/profile.scss
rename to app/assets/stylesheets/pages/profile.scss
index 086875582f..81afe05162 100644
--- a/app/assets/stylesheets/sections/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -80,6 +80,10 @@
&.violet {
background: #548;
}
+
+ &.blue {
+ background: #2980b9;
+ }
}
}
}
@@ -102,12 +106,3 @@
}
}
}
-
-.profile-groups-avatars {
- margin: 0 5px 10px 0;
-
- img {
- width: 50px;
- height: 50px;
- }
-}
diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/pages/projects.scss
similarity index 81%
rename from app/assets/stylesheets/sections/projects.scss
rename to app/assets/stylesheets/pages/projects.scss
index b4ee5ccc8d..7475595167 100644
--- a/app/assets/stylesheets/sections/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -16,6 +16,8 @@
.project-home-panel {
margin-bottom: 15px;
+ position: relative;
+ padding-left: 85px;
&.empty-project {
border-bottom: 0px;
@@ -23,6 +25,21 @@
margin-bottom: 0px;
}
+ .project-identicon-holder {
+ position: absolute;
+ left: 0;
+
+ .avatar {
+ width: 70px;
+ height: 70px;
+ }
+
+ .identicon {
+ font-size: 45px;
+ line-height: 1.6;
+ }
+ }
+
.project-home-dropdown {
margin-left: 10px;
float: right;
@@ -82,23 +99,6 @@
margin-right: 45px;
}
- .btn,
- .form-control {
- border: 1px solid #E1E1E1;
- box-shadow: none;
- padding: 6px 9px;
- }
-
- .btn {
- background: none;
- color: $link_color;
-
- &.active {
- color: #333;
- font-weight: bold;
- }
- }
-
.form-control {
cursor: auto;
@extend .monospace;
@@ -156,7 +156,7 @@ ul.nav.nav-projects-tabs {
}
}
-.team_member_row form {
+.project_member_row form {
margin: 0px;
}
@@ -248,15 +248,15 @@ ul.nav.nav-projects-tabs {
}
.vs-public {
- color: $bg_primary;
+ color: $gl-primary;
}
.vs-internal {
- color: $bg_warning;
+ color: $gl-warning;
}
.vs-private {
- color: $bg_success;
+ color: $gl-success;
}
.breadcrumb.repo-breadcrumb {
@@ -270,3 +270,39 @@ ul.nav.nav-projects-tabs {
color: #999;
}
}
+
+.fork-namespaces {
+ .thumbnail {
+
+ &.fork-exists-thumbnail {
+ border-color: #EEE;
+
+ .caption {
+ color: #999;
+ }
+ }
+
+ &.fork-thumbnail {
+ border-color: #AAA;
+
+ &:hover {
+ background-color: $hover;
+ }
+ }
+
+ a {
+ text-decoration: none;
+ }
+ }
+}
+
+table.table.protected-branches-list tr.no-border {
+ th, td {
+ border: 0;
+ }
+}
+
+.project-import .btn {
+ float: left;
+ margin-right: 10px;
+}
diff --git a/app/assets/stylesheets/sections/search.scss b/app/assets/stylesheets/pages/search.scss
similarity index 100%
rename from app/assets/stylesheets/sections/search.scss
rename to app/assets/stylesheets/pages/search.scss
diff --git a/app/assets/stylesheets/sections/snippets.scss b/app/assets/stylesheets/pages/snippets.scss
similarity index 100%
rename from app/assets/stylesheets/sections/snippets.scss
rename to app/assets/stylesheets/pages/snippets.scss
diff --git a/app/assets/stylesheets/sections/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
similarity index 100%
rename from app/assets/stylesheets/sections/stat_graph.scss
rename to app/assets/stylesheets/pages/stat_graph.scss
diff --git a/app/assets/stylesheets/sections/themes.scss b/app/assets/stylesheets/pages/themes.scss
similarity index 100%
rename from app/assets/stylesheets/sections/themes.scss
rename to app/assets/stylesheets/pages/themes.scss
diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/pages/tree.scss
similarity index 81%
rename from app/assets/stylesheets/sections/tree.scss
rename to app/assets/stylesheets/pages/tree.scss
index 678a6cd716..ce02cdb165 100644
--- a/app/assets/stylesheets/sections/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -17,19 +17,6 @@
@include border-radius(0);
tr {
- td, th {
- padding: 8px 10px;
- line-height: 20px;
- }
- th {
- font-weight: normal;
- font-size: 15px;
- border-bottom: 1px solid #CCC !important;
- }
- td {
- border-color: #F1F1F1 !important;
- border-bottom: 1px solid;
- }
&:hover {
td {
background: $hover;
@@ -52,14 +39,9 @@
.tree-item-file-name {
max-width: 320px;
vertical-align: middle;
- a {
- &:hover {
- color: $link_hover_color;
- }
- }
- i {
- color: $bg_primary;
+ i, a {
+ color: $gl-link-color;
}
img {
@@ -79,13 +61,18 @@
.tree_author {
padding-right: 8px;
+
+ .commit-author-name {
+ color: gray;
+ }
}
.tree_commit {
color: gray;
.tree-commit-link {
- color: #444;
+ color: gray;
+
&:hover {
text-decoration: underline;
}
@@ -111,6 +98,11 @@
background: #f1f1f1;
border-left: 1px solid #DDD;
}
+ td.lines {
+ code {
+ font-family: $monospace_font;
+ }
+ }
}
}
@@ -128,13 +120,13 @@
}
.readme-holder {
- border-top: 1px dashed #CCC;
- padding-top: 10px;
-
.readme-file-title {
font-size: 14px;
+ font-weight: bold;
margin-bottom: 20px;
color: #777;
+ border-bottom: 1px solid #DDD;
+ padding: 10px 0;
}
}
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
new file mode 100644
index 0000000000..277afa1db9
--- /dev/null
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -0,0 +1,9 @@
+.gitlab-ui-dev-kit {
+ > h2 {
+ font-size: 27px;
+ border-bottom: 1px solid #CCC;
+ color: #666;
+ margin: 30px 0;
+ font-weight: bold;
+ }
+}
diff --git a/app/assets/stylesheets/sections/votes.scss b/app/assets/stylesheets/pages/votes.scss
similarity index 80%
rename from app/assets/stylesheets/sections/votes.scss
rename to app/assets/stylesheets/pages/votes.scss
index d683e33e1f..ba0a519dca 100644
--- a/app/assets/stylesheets/sections/votes.scss
+++ b/app/assets/stylesheets/pages/votes.scss
@@ -37,13 +37,3 @@
margin: 0 8px;
}
-.votes-holder {
- float: right;
- width: 250px;
-
- @media (max-width: $screen-xs-max) {
- width: 100%;
- margin-top: 5px;
- margin-bottom: 10px;
- }
-}
diff --git a/app/assets/stylesheets/sections/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
similarity index 100%
rename from app/assets/stylesheets/sections/wiki.scss
rename to app/assets/stylesheets/pages/wiki.scss
diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss
deleted file mode 100644
index 684e8377a7..0000000000
--- a/app/assets/stylesheets/sections/commits.scss
+++ /dev/null
@@ -1,249 +0,0 @@
-/**
- * Commit file
- */
-.commit-committer-link,
-.commit-author-link {
- font-size: 13px;
- color: #555;
- &:hover {
- color: #999;
- }
-}
-
-/** COMMIT BLOCK **/
-.commit-title{
- display: block;
-}
-.commit-title{
- margin-bottom: 10px;
-}
-.commit-author, .commit-committer{
- display: block;
- color: #999;
- font-weight: normal;
- font-style: italic;
-}
-.commit-author strong, .commit-committer strong{
- font-weight: bold;
- font-style: normal;
-}
-
-
-.file-stats a {
- color: $style_color;
-}
-
-.file-stats {
- .new-file {
- a {
- color: #090;
- }
- i {
- color: #1BCF00;
- }
- }
- .renamed-file {
- i {
- color: #FE9300;
- }
- }
- .deleted-file {
- a {
- color: #B00;
- }
- i {
- color: #EE0000;
- }
- }
- .edit-file{
- i{
- color: #555;
- }
- }
-}
-
-.label_commit {
- @include border-radius(4px);
- padding: 2px 4px;
- font-size: 13px;
- background: #474D57;
- color: #fff;
- font-family: $monospace_font;
-}
-
-
-.commits-compare-switch{
- background: image-url("switch_icon.png") no-repeat center center;
- width: 32px;
- height: 32px;
- text-indent: -9999px;
- float: left;
- margin-right: 9px;
- border: 1px solid #DDD;
- @include border-radius(4px);
- padding: 4px;
- background-color: #EEE;
-}
-
-.commit-description {
- background: none;
- border: none;
- margin: 0;
- padding: 0;
- margin-top: 10px;
-}
-
-.commit-box {
- margin: 10px 0;
- border-top: 1px solid #ddd;
- border-bottom: 1px solid #ddd;
- padding: 20px 0;
-
- .commit-title {
- margin: 0;
- font-size: 20px;
- }
-
- .commit-description {
- margin-top: 15px;
- }
-}
-
-
-.commit-stat-summary {
- color: #666;
- font-size: 14px;
- font-weight: normal;
- padding: 10px 0;
-}
-
-.commit-info-row {
- margin-bottom: 10px;
- .avatar {
- @extend .avatar-inline;
- }
- .commit-committer-link,
- .commit-author-link {
- color: #444;
- font-weight: bold;
- }
-}
-
-.lists-separator {
- margin: 10px 0;
- border-top: 1px dashed #CCC;
-}
-
-/**
- * COMMIT ROW
- */
-li.commit {
- .commit-row-title {
- font-size: 14px;
- margin-bottom: 2px;
-
- .notes_count {
- float: right;
- margin-right: 10px;
- }
-
- .commit_short_id {
- min-width: 65px;
- font-family: $monospace_font;
- }
-
- .str-truncated {
- max-width: 70%;
- }
-
- .commit-row-message {
- color: #333;
- font-weight: 500;
- &:hover {
- color: #444;
- text-decoration: underline;
- }
- }
-
- .text-expander {
- background: #eee;
- color: #555;
- padding: 0 5px;
- cursor: pointer;
- margin-left: 4px;
- &:hover {
- background-color: #ddd;
- }
- }
- }
-
- .commit-row-description {
- font-size: 14px;
- border-left: 1px solid #EEE;
- padding: 10px 15px;
- margin: 5px 0 10px 5px;
- background: #f9f9f9;
- display: none;
-
- pre {
- border: none;
- background: inherit;
- padding: 0;
- margin: 0;
- }
- }
-
- .commit-row-info {
- color: #777;
-
- a {
- color: #777;
- }
-
- .committed_ago {
- float: right;
- }
- }
-
- &.inline-commit {
- .commit-row-title {
- font-size: 13px;
- }
-
- .committed_ago {
- float: right;
- @extend .cgray;
- }
- }
-}
-
-.commits-feed-holder {
- float: right;
- .btn {
- padding: 4px 12px;
- }
-}
-
-.commit-message-container {
- background-color: $body-bg;
- position: relative;
- font-family: $monospace_font;
- $left: 12px;
- .max-width-marker {
- width: 72ch;
- color: rgba(0, 0, 0, 0.0);
- font-family: inherit;
- left: $left;
- height: 100%;
- border-right: 1px solid mix($input-border, white);
- position: absolute;
- z-index: 1;
- }
- > textarea {
- background-color: rgba(0, 0, 0, 0.0);
- font-family: inherit;
- padding-left: $left;
- position: relative;
- z-index: 2;
- }
-}
diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss
deleted file mode 100644
index 1bcb1f6d68..0000000000
--- a/app/assets/stylesheets/sections/login.scss
+++ /dev/null
@@ -1,86 +0,0 @@
-/* Login Page */
-.login-page {
- h1 {
- font-size: 3em;
- font-weight: 200;
- }
-
- .login-box{
- padding: 0 15px;
-
- .login-heading h3 {
- font-weight: 300;
- line-height: 2;
- }
-
- .login-footer {
- margin-top: 10px;
- }
-
- .btn {
- padding: 12px !important;
- @extend .btn-block;
- }
- }
-
- .brand-image {
- img {
- max-width: 100%;
- margin-bottom: 20px;
- }
-
- &.default-brand-image {
- margin: 0 80px;
- }
- }
-
- .login-logo {
- margin: 10px 0 30px 0;
- display: block;
- }
-
- .form-control {
- background-color: #F5F5F5;
- font-size: 16px;
- padding: 14px 10px;
- width: 100%;
- height: auto;
-
- &.top {
- @include border-radius(5px 5px 0 0);
- margin-bottom: 0px;
- }
-
- &.bottom {
- @include border-radius(0 0 5px 5px);
- border-top: 0;
- margin-bottom: 20px;
- }
-
- &.middle {
- border-top: 0;
- margin-bottom:0px;
- @include border-radius(0);
- }
-
- &:active, &:focus {
- background-color: #FFF;
- }
- }
-
- .login-box a.forgot {
- float: right;
- padding-top: 6px
- }
-
- .devise-errors {
- h2 {
- font-size: 14px;
- color: #a00;
- }
- }
-
- .brand-holder {
- border-right: 1px solid #EEE;
- }
-}
diff --git a/app/assets/stylesheets/sections/milestone.scss b/app/assets/stylesheets/sections/milestone.scss
deleted file mode 100644
index d20391e38f..0000000000
--- a/app/assets/stylesheets/sections/milestone.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.issues-sortable-list .str-truncated {
- max-width: 70%;
-}
diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss
deleted file mode 100644
index 31c0a0835d..0000000000
--- a/app/assets/stylesheets/sections/nav.scss
+++ /dev/null
@@ -1,96 +0,0 @@
-.main-nav {
- background: #f5f5f5;
- margin: 20px 0;
- margin-top: 0;
- padding-top: 4px;
- border-bottom: 1px solid #E9E9E9;
-
- ul {
- padding: 0;
- margin: auto;
- .count {
- font-weight: normal;
- display: inline-block;
- height: 15px;
- padding: 1px 6px;
- height: auto;
- font-size: 0.82em;
- line-height: 14px;
- text-align: center;
- color: #777;
- background: #eee;
- @include border-radius(8px);
- }
- .label {
- background: $hover;
- text-shadow: none;
- color: $style_color;
- }
- li {
- list-style-type: none;
- margin: 0;
- display: table-cell;
- width: 1%;
- &.active {
- a {
- color: $link_color;
- font-weight: bold;
- border-bottom: 3px solid $link_color;
- }
- }
-
- &:hover {
- a {
- color: $link_hover_color;
- border-bottom: 3px solid $link_hover_color;
- }
- }
- }
- a {
- display: block;
- text-align: center;
- font-weight: bold;
- height: 42px;
- line-height: 39px;
- color: #777;
- text-shadow: 0 1px 1px white;
- text-decoration: none;
- overflow: hidden;
- margin-bottom: -1px;
- }
- }
-
- @media (max-width: $screen-xs-max) {
- font-size: 18px;
- margin: 0;
-
- max-height: none;
-
- &, .container {
- padding: 0;
- border-top: 0;
- }
-
- ul {
- height: auto;
-
- li {
- display: list-item;
- width: auto;
- padding: 5px 0;
-
- &.active {
- background-color: $link_hover_color;
-
- a {
- color: #fff;
- font-weight: normal;
- text-shadow: none;
-
- &:after { display: none; }
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
deleted file mode 100644
index 7eb42fddad..0000000000
--- a/app/assets/stylesheets/sections/notes.scss
+++ /dev/null
@@ -1,357 +0,0 @@
-/**
- * Notes
- */
-
-@-webkit-keyframes targe3-note {
- from { background:#fffff0; }
- 50% { background:#ffffd3; }
- to { background:#fffff0; }
-}
-
-ul.notes {
- display: block;
- list-style: none;
- margin: 0px;
- padding: 0px;
-
- .discussion-header,
- .note-header {
- @extend .cgray;
- padding-bottom: 15px;
-
- a:hover {
- text-decoration: none;
- }
-
- .avatar {
- float: left;
- margin-right: 10px;
- }
-
- .discussion-last-update,
- .note-last-update {
- &:before {
- content: "\00b7";
- }
- font-size: 13px;
- }
- .author {
- color: #555;
- font-weight: bold;
- font-size: 14px;
- &:hover {
- color: $link_hover_color;
- }
- }
- }
-
- .discussion {
- overflow: hidden;
- display: block;
- position:relative;
- }
-
- .note {
- display: block;
- position:relative;
- .attachment {
- font-size: 14px;
- }
- .note-body {
- @include md-typography;
- }
- .note-header {
- padding-bottom: 3px;
- }
-
- &:last-child {
- border-bottom: none;
- }
- }
-}
-
-.diff-file .notes_holder {
- font-size: 13px;
- line-height: 18px;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-
- td {
- border: 1px solid #ddd;
- border-left: none;
-
- &.notes_line {
- text-align: center;
- padding: 10px 0;
- background: #FFF;
- }
- &.notes_line2 {
- text-align: center;
- padding: 10px 0;
- border-left: 1px solid #ddd !important;
- }
- &.notes_content {
- background-color: #fff;
- border-width: 1px 0;
- padding-top: 0;
- vertical-align: top;
- &.parallel{
- border-width: 1px;
- }
- }
- }
-}
-
-/**
- * Actions for Discussions/Notes
- */
-
-.discussion,
-.note {
- &.note:hover {
- .note-actions { display: block; }
- }
- .discussion-header:hover {
- .discussion-actions { display: block; }
- }
-
- .discussion-actions,
- .note-actions {
- display: none;
- float: right;
-
- [class~="fa"] {
- font-size: 16px;
- line-height: 16px;
- vertical-align: middle;
- }
-
- a {
- @extend .cgray;
-
- &:hover {
- color: $link_hover_color;
- &.danger { @extend .cred; }
- }
- }
- }
-}
-.diff-file .note .note-actions {
- right: 0;
- top: 0;
-}
-
-
-/**
- * Line note button on the side of diffs
- */
-
-.diff-file tr.line_holder {
- @mixin show-add-diff-note {
- filter: alpha(opacity=100);
- opacity: 1.0;
- }
-
- .add-diff-note {
- background: image-url("diff_note_add.png") no-repeat left 0;
- border: none;
- height: 22px;
- margin-left: -65px;
- position: absolute;
- width: 22px;
- z-index: 10;
-
- // "hide" it by default
- opacity: 0.0;
- filter: alpha(opacity=0);
-
- &:hover {
- @include show-add-diff-note;
- }
- }
-
- // "show" the icon also if we just hover somewhere over the line
- &:hover > td {
- background: $hover !important;
-
- .add-diff-note {
- @include show-add-diff-note;
- }
- }
-}
-
-/**
- * Note Form
- */
-
-.comment-btn {
- @extend .btn-create;
-}
-.reply-btn {
- @extend .btn-primary;
-}
-.diff-file .diff-content {
- tr.line_holder:hover {
- &> td.line_content {
- background: $hover !important;
- border-color: darken($hover, 10%) !important;
- }
- &> td.new_line,
- &> td.old_line {
- background: darken($hover, 4%) !important;
- border-color: darken($hover, 10%) !important;
- }
- }
-
- tr.line_holder:hover > td .line_note_link {
- opacity: 1.0;
- filter: alpha(opacity=100);
- }
-}
-.diff-file,
-.discussion {
- .new_note {
- margin: 0;
- border: none;
- }
-}
-.new_note {
- display: none;
- .buttons {
- float: left;
- margin-top: 8px;
- }
- .clearfix {
- margin-bottom: 0;
- }
-
- .note-preview-holder,
- .note_text {
- background: #FFF;
- border: 1px solid #ddd;
- min-height: 100px;
- padding: 5px;
- font-size: 14px;
- box-shadow: none;
- }
-
- .note-preview-holder {
- > p {
- overflow-x: auto;
- }
- }
-
- .note_text {
- width: 100%;
- }
- .nav-tabs {
- margin-bottom: 0;
- border: none;
-
- li a,
- li.active a {
- border: 1px solid #DDD;
- }
- }
-}
-
-/* loading indicator */
-.notes-busy {
- margin: 18px;
-}
-
-.note-image-attach {
- @extend .col-md-4;
- @extend .thumbnail;
- margin-left: 45px;
- float: none;
-}
-
-.common-note-form {
- margin: 0;
- background: #F9F9F9;
- padding: 5px;
- border: 1px solid #DDD;
-}
-
-.note-form-actions {
- background: #F9F9F9;
- height: 45px;
-
- .note-form-option {
- margin-top: 8px;
- margin-left: 30px;
- @extend .pull-left;
- }
-
- .js-notify-commit-author {
- float: left;
- }
-
- .write-preview-btn {
- // makes the "absolute" position for links relative to this
- position: relative;
-
- // preview/edit buttons
- > a {
- position: absolute;
- right: 5px;
- top: 8px;
- }
- }
-}
-
-.note-edit-form {
- display: none;
-
- .note_text {
- border: 1px solid #DDD;
- box-shadow: none;
- font-size: 14px;
- height: 80px;
- width: 100%;
- }
-
- .form-actions {
- padding-left: 20px;
-
- .btn-save {
- float: left;
- }
-
- .note-form-option {
- float: left;
- padding: 2px 0 0 25px;
- }
- }
-}
-
-.js-note-attachment-delete {
- display: none;
-}
-
-.parallel-comment {
- padding: 6px;
-}
-
-.error-alert > .alert {
- margin-top: 5px;
- margin-bottom: 5px;
-}
-
-.discussion-body,
-.diff-file {
- .notes .note {
- border-color: #ddd;
- padding: 10px 15px;
- }
-
- .discussion-reply-holder {
- background: #f9f9f9;
- padding: 10px 15px;
- border-top: 1px solid #DDD;
- }
-}
-
-.discussion-notes-count {
- font-size: 16px;
-}
diff --git a/app/assets/stylesheets/themes/dark-theme.scss b/app/assets/stylesheets/themes/dark-theme.scss
new file mode 100644
index 0000000000..b7b22a8724
--- /dev/null
+++ b/app/assets/stylesheets/themes/dark-theme.scss
@@ -0,0 +1,63 @@
+@mixin dark-theme($color-light, $color, $color-darker, $color-dark) {
+ header {
+ &.navbar-gitlab {
+ .navbar-inner {
+ background: $color;
+
+ .navbar-toggle {
+ color: #FFF;
+ }
+
+ .app_logo, .navbar-toggle {
+ &:hover {
+ background-color: $color-darker;
+ }
+ }
+
+ .app_logo {
+ background-color: $color-dark;
+ }
+
+ .title {
+ color: #FFF;
+
+ a {
+ color: #FFF;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .search {
+ .search-input {
+ background-color: $color-light;
+ background-color: rgba(255, 255, 255, 0.5);
+ border: 1px solid $color-light;
+
+ &:focus {
+ background-color: white;
+ }
+ }
+ }
+
+ .search-input::-webkit-input-placeholder {
+ color: #666;
+ }
+
+ .nav > li > a {
+ color: $color-light;
+
+ &:hover, &:focus, &:active {
+ background: none;
+ color: #FFF;
+ }
+ }
+
+ .search-input {
+ border-color: $color-light;
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss
index 3e3744fdc3..097d5c5b73 100644
--- a/app/assets/stylesheets/themes/ui_basic.scss
+++ b/app/assets/stylesheets/themes/ui_basic.scss
@@ -9,17 +9,22 @@
.navbar-inner {
background: #F1F1F1;
border-bottom: 1px solid #DDD;
+
+ .title {
+ color: #555;
+
+ a {
+ color: #555;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
.nav > li > a {
color: $style_color;
}
- .separator {
- background: #F9F9F9;
- border-left: 1px solid #DDD;
- }
}
}
}
- .main-nav {
- background: #FFF;
- }
}
diff --git a/app/assets/stylesheets/themes/ui_blue.scss b/app/assets/stylesheets/themes/ui_blue.scss
new file mode 100644
index 0000000000..cb7980b5a0
--- /dev/null
+++ b/app/assets/stylesheets/themes/ui_blue.scss
@@ -0,0 +1,6 @@
+/**
+ * Modern GitLab UI theme
+ */
+.ui_blue {
+ @include dark-theme(#BECDE9, #2980b9, #1970a9, #096099);
+}
diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss
index a08f3ff3d4..7ac6903b2e 100644
--- a/app/assets/stylesheets/themes/ui_color.scss
+++ b/app/assets/stylesheets/themes/ui_color.scss
@@ -1,43 +1,6 @@
/**
- * This file represent some UI that can be changed
- * during web app restyle or theme select.
- *
- * Next items should be placed there
- * - link colors
- * - header restyles
- *
+ * Violet GitLab UI theme
*/
.ui_color {
- /*
- * Application Header
- *
- */
- header {
- @extend .header-dark;
- &.navbar-gitlab {
- .navbar-inner {
- background: #548;
- border-bottom: 1px solid #436;
- .app_logo, .navbar-toggle {
- &:hover {
- background-color: #436;
- }
- }
- .separator {
- background: #436;
- border-left: 1px solid #659;
- }
- .nav > li > a {
- color: #98C;
- }
- .search-input {
- border-color: #98C;
- }
- }
- }
- }
-
- .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
- background: #659;
- }
+ @include dark-theme(#98C, #548, #436, #325);
}
diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss
index 959febad6f..9257e5f4d4 100644
--- a/app/assets/stylesheets/themes/ui_gray.scss
+++ b/app/assets/stylesheets/themes/ui_gray.scss
@@ -1,33 +1,6 @@
/**
- * This file represent some UI that can be changed
- * during web app restyle or theme select.
- *
- * Next items should be placed there
- * - link colors
- * - header restyles
- *
+ * Gray GitLab UI theme
*/
.ui_gray {
- /*
- * Application Header
- *
- */
- header {
- @extend .header-dark;
- &.navbar-gitlab {
- .navbar-inner {
- background: #373737;
- border-bottom: 1px solid #272727;
- .app_logo, .navbar-toggle {
- &:hover {
- background-color: #272727;
- }
- }
- .separator {
- background: #272727;
- border-left: 1px solid #474747;
- }
- }
- }
- }
+ @include dark-theme(#979797, #373737, #272727, #222222);
}
diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss
index 9af5adbf10..4caf5843d9 100644
--- a/app/assets/stylesheets/themes/ui_mars.scss
+++ b/app/assets/stylesheets/themes/ui_mars.scss
@@ -1,39 +1,6 @@
/**
- * This file represent some UI that can be changed
- * during web app restyle or theme select.
- *
- * Next items should be placed there
- * - link colors
- * - header restyles
- *
+ * Classic GitLab UI theme
*/
.ui_mars {
- /*
- * Application Header
- *
- */
- header {
- @extend .header-dark;
- &.navbar-gitlab {
- .navbar-inner {
- background: #474D57;
- border-bottom: 1px solid #373D47;
- .app_logo, .navbar-toggle {
- &:hover {
- background-color: #373D47;
- }
- }
- .separator {
- background: #373D47;
- border-left: 1px solid #575D67;
- }
- .nav > li > a {
- color: #979DA7;
- }
- .search-input {
- border-color: #979DA7;
- }
- }
- }
- }
+ @include dark-theme(#979DA7, #474D57, #373D47, #24272D);
}
diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss
index 308a03477d..7044988231 100644
--- a/app/assets/stylesheets/themes/ui_modern.scss
+++ b/app/assets/stylesheets/themes/ui_modern.scss
@@ -1,43 +1,6 @@
/**
- * This file represent some UI that can be changed
- * during web app restyle or theme select.
- *
- * Next items should be placed there
- * - link colors
- * - header restyles
- *
+ * Modern GitLab UI theme
*/
.ui_modern {
- /*
- * Application Header
- *
- */
- header {
- @extend .header-dark;
- &.navbar-gitlab {
- .navbar-inner {
- background: #019875;
- border-bottom: 1px solid #019875;
- .app_logo, .navbar-toggle {
- &:hover {
- background-color: #018865;
- }
- }
- .separator {
- background: #018865;
- border-left: 1px solid #11A885;
- }
- .nav > li > a {
- color: #ADC;
- }
- .search-input {
- border-color: #8ba;
- }
- }
- }
- }
-
- .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
- background: #019875;
- }
+ @include dark-theme(#ADC, #019875, #018865, #017855);
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
new file mode 100644
index 0000000000..9a5685877f
--- /dev/null
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -0,0 +1,44 @@
+class Admin::ApplicationSettingsController < Admin::ApplicationController
+ before_filter :set_application_setting
+
+ def show
+ end
+
+ def update
+ if @application_setting.update_attributes(application_setting_params)
+ redirect_to admin_application_settings_path,
+ notice: 'Application settings saved successfully'
+ else
+ render :show
+ end
+ end
+
+ private
+
+ def set_application_setting
+ @application_setting = ApplicationSetting.current
+ end
+
+ def application_setting_params
+ restricted_levels = params[:application_setting][:restricted_visibility_levels]
+ if restricted_levels.nil?
+ params[:application_setting][:restricted_visibility_levels] = []
+ else
+ restricted_levels.map! do |level|
+ level.to_i
+ end
+ end
+
+ params.require(:application_setting).permit(
+ :default_projects_limit,
+ :default_branch_protection,
+ :signup_enabled,
+ :signin_enabled,
+ :gravatar_enabled,
+ :twitter_sharing_enabled,
+ :sign_in_text,
+ :home_page_url,
+ restricted_visibility_levels: []
+ )
+ end
+end
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
new file mode 100644
index 0000000000..471d24934a
--- /dev/null
+++ b/app/controllers/admin/applications_controller.rb
@@ -0,0 +1,52 @@
+class Admin::ApplicationsController < Admin::ApplicationController
+ before_action :set_application, only: [:show, :edit, :update, :destroy]
+
+ def index
+ @applications = Doorkeeper::Application.where("owner_id IS NULL")
+ end
+
+ def show
+ end
+
+ def new
+ @application = Doorkeeper::Application.new
+ end
+
+ def edit
+ end
+
+ def create
+ @application = Doorkeeper::Application.new(application_params)
+
+ if @application.save
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
+ redirect_to admin_application_url(@application)
+ else
+ render :new
+ end
+ end
+
+ def update
+ if @application.update(application_params)
+ redirect_to admin_application_path(@application), notice: 'Application was successfully updated.'
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ @application.destroy
+ redirect_to admin_applications_url, notice: 'Application was successfully destroyed.'
+ end
+
+ private
+
+ def set_application
+ @application = Doorkeeper::Application.where("owner_id IS NULL").find(params[:id])
+ end
+
+ # Only allow a trusted parameter "white list" through.
+ def application_params
+ params[:doorkeeper_application].permit(:name, :redirect_uri)
+ end
+end
diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb
index 4c1d0df411..338496013a 100644
--- a/app/controllers/admin/background_jobs_controller.rb
+++ b/app/controllers/admin/background_jobs_controller.rb
@@ -1,6 +1,6 @@
class Admin::BackgroundJobsController < Admin::ApplicationController
def show
- ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
+ ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
@sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
end
end
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index be19139c9b..c491e5c755 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -1,7 +1,7 @@
class Admin::DashboardController < Admin::ApplicationController
def index
- @projects = Project.order("created_at DESC").limit(10)
- @users = User.order("created_at DESC").limit(10)
- @groups = Group.order("created_at DESC").limit(10)
+ @projects = Project.limit(10)
+ @users = User.limit(10)
+ @groups = Group.limit(10)
end
end
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index e6d0c9323c..9d9adaa467 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -1,15 +1,16 @@
class Admin::GroupsController < Admin::ApplicationController
- before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update]
+ before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update]
def index
- @groups = Group.order('name ASC')
+ @groups = Group.all
+ @groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present?
- @groups = @groups.page(params[:page]).per(20)
+ @groups = @groups.page(params[:page]).per(PER_PAGE)
end
def show
- @members = @group.members.order("access_level DESC").page(params[:members_page]).per(30)
- @projects = @group.projects.page(params[:projects_page]).per(30)
+ @members = @group.members.order("access_level DESC").page(params[:members_page]).per(PER_PAGE)
+ @projects = @group.projects.page(params[:projects_page]).per(PER_PAGE)
end
def new
@@ -21,7 +22,7 @@ class Admin::GroupsController < Admin::ApplicationController
def create
@group = Group.new(group_params)
- @group.path = @group.name.dup.parameterize if @group.name
+ @group.name = @group.path.dup unless @group.name
if @group.save
@group.add_owner(current_user)
@@ -39,7 +40,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
end
- def project_teams_update
+ def members_update
@group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to [:admin, @group], notice: 'Users were successfully added.'
diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb
new file mode 100644
index 0000000000..21111bb44f
--- /dev/null
+++ b/app/controllers/admin/keys_controller.rb
@@ -0,0 +1,34 @@
+class Admin::KeysController < Admin::ApplicationController
+ before_filter :user, only: [:show, :destroy]
+
+ def show
+ @key = user.keys.find(params[:id])
+
+ respond_to do |format|
+ format.html
+ format.js { render nothing: true }
+ end
+ end
+
+ def destroy
+ key = user.keys.find(params[:id])
+
+ respond_to do |format|
+ if key.destroy
+ format.html { redirect_to [:admin, user], notice: 'User key was successfully removed.' }
+ else
+ format.html { redirect_to [:admin, user], alert: 'Failed to remove user key.' }
+ end
+ end
+ end
+
+ protected
+
+ def user
+ @user ||= User.find_by!(username: params[:user_id])
+ end
+
+ def key_params
+ params.require(:user_id, :id)
+ end
+end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 2f0d344802..5176a8399a 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -11,37 +11,34 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
+ @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE)
end
def show
if @group
- @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(30)
+ @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(PER_PAGE)
end
- @project_members = @project.project_members.page(params[:project_members_page]).per(30)
+ @project_members = @project.project_members.page(params[:project_members_page]).per(PER_PAGE)
end
def transfer
::Projects::TransferService.new(@project, current_user, params.dup).execute
- redirect_to [:admin, @project.reload]
+ @project.reload
+ redirect_to admin_namespace_project_path(@project.namespace, @project)
end
protected
def project
- id = params[:project_id] || params[:id]
-
- @project = Project.find_with_namespace(id)
+ @project = Project.find_with_namespace(
+ [params[:namespace_id], '/', params[:id]].join('')
+ )
@project || render_404
end
def group
- @group ||= project.group
- end
-
- def repository
- @repository ||= project.repository
+ @group ||= @project.group
end
end
diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
new file mode 100644
index 0000000000..44a3f1379d
--- /dev/null
+++ b/app/controllers/admin/services_controller.rb
@@ -0,0 +1,52 @@
+class Admin::ServicesController < Admin::ApplicationController
+ before_filter :service, only: [:edit, :update]
+
+ def index
+ @services = services_templates
+ end
+
+ def edit
+ unless service.present?
+ redirect_to admin_application_settings_services_path,
+ alert: "Service is unknown or it doesn't exist"
+ end
+ end
+
+ def update
+ if service.update_attributes(application_services_params[:service])
+ redirect_to admin_application_settings_services_path,
+ notice: 'Application settings saved successfully'
+ else
+ render :edit
+ end
+ end
+
+ private
+
+ def services_templates
+ templates = []
+
+ Service.available_services_names.each do |service_name|
+ service_template = service_name.concat("_service").camelize.constantize
+ templates << service_template.where(template: true).first_or_create
+ end
+
+ templates
+ end
+
+ def service
+ @service ||= Service.where(id: params[:id], template: true).first
+ end
+
+ def application_services_params
+ params.permit(:id,
+ service: [
+ :title, :token, :type, :active, :api_key, :subdomain,
+ :room, :recipients, :project_url, :webhook,
+ :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
+ :build_key, :server, :teamcity_url, :build_type,
+ :description, :issues_url, :new_issue_url, :restrict_to_branch,
+ :send_from_committer_email, :disable_diffs
+ ])
+ end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index f63df27eeb..693970e534 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -2,14 +2,16 @@ class Admin::UsersController < Admin::ApplicationController
before_filter :user, only: [:show, :edit, :update, :destroy]
def index
- @users = User.filter(params[:filter])
+ @users = User.order_name_asc.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present?
- @users = @users.alphabetically.page(params[:page])
+ @users = @users.sort(@sort = params[:sort])
+ @users = @users.page(params[:page])
end
def show
@personal_projects = user.personal_projects
@joined_projects = user.projects.joined(@user)
+ @keys = user.keys
end
def new
@@ -22,7 +24,7 @@ class Admin::UsersController < Admin::ApplicationController
def block
if user.block
- redirect_to :back, alert: "Successfully blocked"
+ redirect_to :back, notice: "Successfully blocked"
else
redirect_to :back, alert: "Error occurred. User was not blocked"
end
@@ -30,7 +32,7 @@ class Admin::UsersController < Admin::ApplicationController
def unblock
if user.activate
- redirect_to :back, alert: "Successfully unblocked"
+ redirect_to :back, notice: "Successfully unblocked"
else
redirect_to :back, alert: "Error occurred. User was not unblocked"
end
@@ -100,6 +102,9 @@ class Admin::UsersController < Admin::ApplicationController
email = user.emails.find(params[:email_id])
email.destroy
+ user.set_notification_email
+ user.save if user.notification_email_changed?
+
respond_to do |format|
format.html { redirect_to :back, notice: "Successfully removed email." }
format.js { render nothing: true }
@@ -116,8 +121,8 @@ class Admin::UsersController < Admin::ApplicationController
params.require(:user).permit(
:email, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password,
- :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key,
- :projects_limit, :can_create_group, :admin
+ :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, :hide_no_password,
+ :projects_limit, :can_create_group, :admin, :key_id
)
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 13d8d2a3e0..e284f31f7e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,13 +1,16 @@
require 'gon'
class ApplicationController < ActionController::Base
+ include Gitlab::CurrentSettings
+ include GitlabRoutingHelper
+
+ PER_PAGE = 20
+
before_filter :authenticate_user_from_token!
before_filter :authenticate_user!
before_filter :reject_blocked!
before_filter :check_password_expiration
- before_filter :add_abilities
before_filter :ldap_security_check
- before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers
before_filter :add_gon_variables
before_filter :configure_permitted_parameters, if: :devise_controller?
@@ -15,7 +18,8 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
- helper_method :abilities, :can?
+ helper_method :abilities, :can?, :current_application_settings
+ helper_method :github_import_enabled?, :gitlab_import_enabled?, :bitbucket_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -48,6 +52,17 @@ class ApplicationController < ActionController::Base
end
end
+ def authenticate_user!(*args)
+ # If user is not signed-in and tries to access root_path - redirect him to landing page
+ if current_application_settings.home_page_url.present?
+ if current_user.nil? && controller_name == 'dashboard' && action_name == 'show'
+ redirect_to current_application_settings.home_page_url and return
+ end
+ end
+
+ super(*args)
+ end
+
def log_exception(exception)
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" }
@@ -73,7 +88,7 @@ class ApplicationController < ActionController::Base
end
def abilities
- @abilities ||= Six.new
+ Ability.abilities
end
def can?(object, action, subject)
@@ -81,52 +96,45 @@ class ApplicationController < ActionController::Base
end
def project
- id = params[:project_id] || params[:id]
+ unless @project
+ namespace = params[:namespace_id]
+ id = params[:project_id] || params[:id]
- # Redirect from
- # localhost/group/project.git
- # to
- # localhost/group/project
- #
- if id =~ /\.git\Z/
- redirect_to request.original_url.gsub(/\.git\Z/, '') and return
- end
+ # Redirect from
+ # localhost/group/project.git
+ # to
+ # localhost/group/project
+ #
+ if id =~ /\.git\Z/
+ redirect_to request.original_url.gsub(/\.git\Z/, '') and return
+ end
- @project = Project.find_with_namespace(id)
+ @project = Project.find_with_namespace("#{namespace}/#{id}")
- if @project and can?(current_user, :read_project, @project)
- @project
- elsif current_user.nil?
- @project = nil
- authenticate_user!
- else
- @project = nil
- render_404 and return
+ if @project and can?(current_user, :read_project, @project)
+ @project
+ elsif current_user.nil?
+ @project = nil
+ authenticate_user!
+ else
+ @project = nil
+ render_404 and return
+ end
end
+ @project
end
def repository
@repository ||= project.repository
- rescue Grit::NoSuchPathError
+ rescue Grit::NoSuchPathError(e)
+ log_exception(e)
nil
end
- def add_abilities
- abilities << Ability
- end
-
def authorize_project!(action)
return access_denied! unless can?(current_user, action, project)
end
- def authorize_code_access!
- return access_denied! unless can?(current_user, :download_code, project)
- end
-
- def authorize_push!
- return access_denied! unless can?(current_user, :push_code, project)
- end
-
def authorize_labels!
# Labels should be accessible for issues and/or merge requests
authorize_read_issue! || authorize_read_merge_request!
@@ -170,9 +178,6 @@ class ApplicationController < ActionController::Base
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
- def dev_tools
- end
-
def default_headers
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
@@ -182,7 +187,7 @@ class ApplicationController < ActionController::Base
end
def add_gon_variables
- gon.default_issues_tracker = Project.issues_tracker.default_value
+ gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.api_version = API::API.version
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
@@ -253,4 +258,75 @@ class ApplicationController < ActionController::Base
redirect_to profile_path, notice: 'Please complete your profile with email address' and return
end
end
+
+ def set_filters_params
+ params[:sort] ||= 'created_desc'
+ params[:scope] = 'all' if params[:scope].blank?
+ params[:state] = 'opened' if params[:state].blank?
+
+ @filter_params = params.dup
+
+ if @project
+ @filter_params[:project_id] = @project.id
+ elsif @group
+ @filter_params[:group_id] = @group.id
+ else
+ # TODO: this filter ignore issues/mr created in public or
+ # internal repos where you are not a member. Enable this filter
+ # or improve current implementation to filter only issues you
+ # created or assigned or mentioned
+ #@filter_params[:authorized_only] = true
+ end
+
+ @filter_params
+ end
+
+ def set_filter_values(collection)
+ assignee_id = @filter_params[:assignee_id]
+ author_id = @filter_params[:author_id]
+ milestone_id = @filter_params[:milestone_id]
+
+ @sort = @filter_params[:sort]
+ @assignees = User.where(id: collection.pluck(:assignee_id))
+ @authors = User.where(id: collection.pluck(:author_id))
+ @milestones = Milestone.where(id: collection.pluck(:milestone_id))
+
+ if assignee_id.present? && !assignee_id.to_i.zero?
+ @assignee = @assignees.find_by(id: assignee_id)
+ end
+
+ if author_id.present? && !author_id.to_i.zero?
+ @author = @authors.find_by(id: author_id)
+ end
+
+ if milestone_id.present? && !milestone_id.to_i.zero?
+ @milestone = @milestones.find_by(id: milestone_id)
+ end
+ end
+
+ def get_issues_collection
+ set_filters_params
+ issues = IssuesFinder.new.execute(current_user, @filter_params)
+ set_filter_values(issues)
+ issues
+ end
+
+ def get_merge_requests_collection
+ set_filters_params
+ merge_requests = MergeRequestsFinder.new.execute(current_user, @filter_params)
+ set_filter_values(merge_requests)
+ merge_requests
+ end
+
+ def github_import_enabled?
+ OauthHelper.enabled_oauth_providers.include?(:github)
+ end
+
+ def gitlab_import_enabled?
+ OauthHelper.enabled_oauth_providers.include?(:gitlab)
+ end
+
+ def bitbucket_import_enabled?
+ OauthHelper.enabled_oauth_providers.include?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
+ end
end
diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb
new file mode 100644
index 0000000000..ed14f4e1f3
--- /dev/null
+++ b/app/controllers/dashboard/groups_controller.rb
@@ -0,0 +1,5 @@
+class Dashboard::GroupsController < ApplicationController
+ def index
+ @group_members = current_user.group_members.page(params[:page]).per(PER_PAGE)
+ end
+end
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
new file mode 100644
index 0000000000..cb51792df1
--- /dev/null
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -0,0 +1,34 @@
+class Dashboard::MilestonesController < ApplicationController
+ before_filter :load_projects
+
+ def index
+ project_milestones = case params[:state]
+ when 'all'; state
+ when 'closed'; state('closed')
+ else state('active')
+ end
+ @dashboard_milestones = Milestones::GroupService.new(project_milestones).execute
+ @dashboard_milestones = Kaminari.paginate_array(@dashboard_milestones).page(params[:page]).per(PER_PAGE)
+ end
+
+ def show
+ project_milestones = Milestone.where(project_id: @projects).order("due_date ASC")
+ @dashboard_milestone = Milestones::GroupService.new(project_milestones).milestone(title)
+ end
+
+ private
+
+ def load_projects
+ @projects = current_user.authorized_projects.sorted_by_activity.non_archived
+ end
+
+ def title
+ params[:title]
+ end
+
+ def state(state = nil)
+ conditions = { project_id: @projects }
+ conditions.reverse_merge!(state: state) if state
+ Milestone.where(conditions).order("title ASC")
+ end
+end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
new file mode 100644
index 0000000000..56e6fcc41c
--- /dev/null
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -0,0 +1,27 @@
+class Dashboard::ProjectsController < ApplicationController
+ before_filter :event_filter
+
+ def starred
+ @projects = current_user.starred_projects
+ @projects = @projects.includes(:namespace, :forked_from_project, :tags)
+ @projects = @projects.sort(@sort = params[:sort])
+ @groups = []
+
+ respond_to do |format|
+ format.html
+
+ format.json do
+ load_events
+ pager_json("events/_events", @events.count)
+ end
+ end
+ end
+
+ private
+
+ def load_events
+ @events = Event.in_projects(@projects.pluck(:id))
+ @events = @event_filter.apply_filter(@events).with_associations
+ @events = @events.limit(20).offset(params[:offset] || 0)
+ end
+end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 5aff526d1b..9bd853ed5c 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -3,66 +3,35 @@ class DashboardController < ApplicationController
before_filter :load_projects, except: [:projects]
before_filter :event_filter, only: :show
- before_filter :default_filter, only: [:issues, :merge_requests]
-
def show
- # Fetch only 30 projects.
- # If user needs more - point to Dashboard#projects page
- @projects_limit = 30
-
- @groups = current_user.authorized_groups.sort_by(&:human_name)
- @has_authorized_projects = @projects.count > 0
- @projects_count = @projects.count
- @projects = @projects.limit(@projects_limit)
-
- @events = Event.in_projects(current_user.authorized_projects.pluck(:id))
- @events = @event_filter.apply_filter(@events)
- @events = @events.limit(20).offset(params[:offset] || 0)
-
+ @projects = @projects.includes(:namespace)
@last_push = current_user.recent_push
- @publicish_project_count = Project.publicish(current_user).count
-
respond_to do |format|
format.html
- format.json { pager_json("events/_events", @events.count) }
- format.atom { render layout: false }
+
+ format.json do
+ load_events
+ pager_json("events/_events", @events.count)
+ end
+
+ format.atom do
+ load_events
+ render layout: false
+ end
end
end
- def projects
- @projects = case params[:scope]
- when 'personal' then
- current_user.namespace.projects
- when 'joined' then
- current_user.authorized_projects.joined(current_user)
- when 'owned' then
- current_user.owned_projects
- else
- current_user.authorized_projects
- end
-
- @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present?
- @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
- @projects = @projects.includes(:namespace)
- @projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
- @projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.page(params[:page]).per(30)
-
- @tags = current_user.authorized_projects.tags_on(:tags)
- @groups = current_user.authorized_groups
- end
-
def merge_requests
- @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
- @merge_requests = @merge_requests.page(params[:page]).per(20)
+ @merge_requests = get_merge_requests_collection
+ @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
@merge_requests = @merge_requests.preload(:author, :target_project)
end
def issues
- @issues = IssuesFinder.new.execute(current_user, params)
- @issues = @issues.page(params[:page]).per(20)
+ @issues = get_issues_collection
+ @issues = @issues.page(params[:page]).per(PER_PAGE)
@issues = @issues.preload(:author, :project)
respond_to do |format|
@@ -77,9 +46,9 @@ class DashboardController < ApplicationController
@projects = current_user.authorized_projects.sorted_by_activity.non_archived
end
- def default_filter
- params[:scope] = 'assigned-to-me' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
- params[:authorized_only] = true
+ def load_events
+ @events = Event.in_projects(current_user.authorized_projects.pluck(:id))
+ @events = @event_filter.apply_filter(@events).with_associations
+ @events = @events.limit(20).offset(params[:offset] || 0)
end
end
diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb
index f8e1a31e0b..c51a4a211a 100644
--- a/app/controllers/explore/groups_controller.rb
+++ b/app/controllers/explore/groups_controller.rb
@@ -1,7 +1,6 @@
class Explore::GroupsController < ApplicationController
skip_before_filter :authenticate_user!,
- :reject_blocked, :set_current_user_for_observers,
- :add_abilities
+ :reject_blocked, :set_current_user_for_observers
layout "explore"
@@ -9,6 +8,6 @@ class Explore::GroupsController < ApplicationController
@groups = GroupsFinder.new.execute(current_user)
@groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort])
- @groups = @groups.page(params[:page]).per(20)
+ @groups = @groups.page(params[:page]).per(PER_PAGE)
end
end
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index b6fa8b7e38..b295f295bb 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -1,25 +1,27 @@
class Explore::ProjectsController < ApplicationController
skip_before_filter :authenticate_user!,
- :reject_blocked,
- :add_abilities
+ :reject_blocked
layout 'explore'
def index
@projects = ProjectsFinder.new.execute(current_user)
+ @tags = @projects.tags_on(:tags)
+ @projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
+ @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.includes(:namespace).page(params[:page]).per(20)
+ @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
end
def trending
@trending_projects = TrendingProjectsFinder.new.execute(current_user)
- @trending_projects = @trending_projects.page(params[:page]).per(10)
+ @trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE)
end
def starred
@starred_projects = ProjectsFinder.new.execute(current_user)
- @starred_projects = @starred_projects.order('star_count DESC')
- @starred_projects = @starred_projects.page(params[:page]).per(10)
+ @starred_projects = @starred_projects.reorder('star_count DESC')
+ @starred_projects = @starred_projects.page(params[:page]).per(PER_PAGE)
end
end
diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb
deleted file mode 100644
index 7937454810..0000000000
--- a/app/controllers/files_controller.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-class FilesController < ApplicationController
- def download
- note = Note.find(params[:id])
- uploader = note.attachment
-
- if uploader.file_storage?
- if can?(current_user, :read_project, note.project)
- send_file uploader.file.path, disposition: 'attachment'
- else
- not_found!
- end
- else
- redirect_to uploader.url
- end
- end
-end
diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb
new file mode 100644
index 0000000000..a73b8fa212
--- /dev/null
+++ b/app/controllers/groups/application_controller.rb
@@ -0,0 +1,28 @@
+class Groups::ApplicationController < ApplicationController
+
+ private
+
+ def authorize_read_group!
+ unless @group and can?(current_user, :read_group, @group)
+ if current_user.nil?
+ return authenticate_user!
+ else
+ return render_404
+ end
+ end
+ end
+
+ def authorize_admin_group!
+ unless can?(current_user, :manage_group, group)
+ return render_404
+ end
+ end
+
+ def determine_layout
+ if current_user
+ 'group'
+ else
+ 'public_group'
+ end
+ end
+end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 63c05d4f33..2df51c97a2 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -1,15 +1,30 @@
-class Groups::GroupMembersController < ApplicationController
+class Groups::GroupMembersController < Groups::ApplicationController
+ skip_before_filter :authenticate_user!, only: [:index]
before_filter :group
# Authorize
- before_filter :authorize_admin_group!
+ before_filter :authorize_read_group!
+ before_filter :authorize_admin_group!, except: [:index, :leave]
- layout 'group'
+ layout :determine_layout
+
+ def index
+ @project = @group.projects.find(params[:project_id]) if params[:project_id]
+ @members = @group.group_members
+
+ if params[:search].present?
+ users = @group.users.search(params[:search]).to_a
+ @members = @members.where(user_id: users)
+ end
+
+ @members = @members.order('access_level DESC').page(params[:page]).per(50)
+ @group_member = GroupMember.new
+ end
def create
@group.add_users(params[:user_ids].split(','), params[:access_level])
- redirect_to members_group_path(@group), notice: 'Users were successfully added.'
+ redirect_to group_group_members_path(@group), notice: 'Users were successfully added.'
end
def update
@@ -18,11 +33,12 @@ class Groups::GroupMembersController < ApplicationController
end
def destroy
- @users_group = @group.group_members.find(params[:id])
- if can?(current_user, :destroy, @users_group) # May fail if last owner.
- @users_group.destroy
+ @group_member = @group.group_members.find(params[:id])
+
+ if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner.
+ @group_member.destroy
respond_to do |format|
- format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
+ format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true }
end
else
@@ -30,18 +46,23 @@ class Groups::GroupMembersController < ApplicationController
end
end
+ def leave
+ @group_member = @group.group_members.where(user_id: current_user.id).first
+
+ if can?(current_user, :destroy_group_member, @group_member)
+ @group_member.destroy
+ redirect_to(dashboard_groups_path, info: "You left #{group.name} group.")
+ else
+ return render_403
+ end
+ end
+
protected
def group
@group ||= Group.find_by(path: params[:group_id])
end
- def authorize_admin_group!
- unless can?(current_user, :manage_group, group)
- return render_404
- end
- end
-
def member_params
params.require(:group_member).permit(:access_level, :user_id)
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 860d8e0392..c46b8fff88 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -4,13 +4,13 @@ class Groups::MilestonesController < ApplicationController
before_filter :authorize_group_milestone!, only: :update
def index
- project_milestones = case params[:status]
- when 'all'; status
- when 'closed'; status('closed')
- else status('active')
+ project_milestones = case params[:state]
+ when 'all'; state
+ when 'closed'; state('closed')
+ else state('active')
end
@group_milestones = Milestones::GroupService.new(project_milestones).execute
- @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(30)
+ @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(PER_PAGE)
end
def show
@@ -44,7 +44,7 @@ class Groups::MilestonesController < ApplicationController
params[:title]
end
- def status(state = nil)
+ def state(state = nil)
conditions = { project_id: group.projects }
conditions.reverse_merge!(state: state) if state
Milestone.where(conditions).order("title ASC")
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 36222758eb..7af3c07718 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -1,5 +1,5 @@
-class GroupsController < ApplicationController
- skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests]
+class GroupsController < Groups::ApplicationController
+ skip_before_filter :authenticate_user!, only: [:show, :issues, :merge_requests]
respond_to :html
before_filter :group, except: [:new, :create]
@@ -10,20 +10,18 @@ class GroupsController < ApplicationController
# Load group projects
before_filter :load_projects, except: [:new, :create, :projects, :edit, :update]
-
- before_filter :default_filter, only: [:issues, :merge_requests]
+ before_filter :event_filter, only: :show
+ before_filter :set_title, only: [:new, :create]
layout :determine_layout
- before_filter :set_title, only: [:new, :create]
-
def new
@group = Group.new
end
def create
@group = Group.new(group_params)
- @group.path = @group.name.dup.parameterize if @group.name
+ @group.name = @group.path.dup unless @group.name
if @group.save
@group.add_owner(current_user)
@@ -34,27 +32,33 @@ class GroupsController < ApplicationController
end
def show
- @events = Event.in_projects(project_ids)
- @events = event_filter.apply_filter(@events)
- @events = @events.limit(20).offset(params[:offset] || 0)
@last_push = current_user.recent_push if current_user
+ @projects = @projects.includes(:namespace)
respond_to do |format|
format.html
- format.json { pager_json("events/_events", @events.count) }
- format.atom { render layout: false }
+
+ format.json do
+ load_events
+ pager_json("events/_events", @events.count)
+ end
+
+ format.atom do
+ load_events
+ render layout: false
+ end
end
end
def merge_requests
- @merge_requests = MergeRequestsFinder.new.execute(current_user, params)
- @merge_requests = @merge_requests.page(params[:page]).per(20)
+ @merge_requests = get_merge_requests_collection
+ @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
@merge_requests = @merge_requests.preload(:author, :target_project)
end
def issues
- @issues = IssuesFinder.new.execute(current_user, params)
- @issues = @issues.page(params[:page]).per(20)
+ @issues = get_issues_collection
+ @issues = @issues.page(params[:page]).per(PER_PAGE)
@issues = @issues.preload(:author, :project)
respond_to do |format|
@@ -63,19 +67,6 @@ class GroupsController < ApplicationController
end
end
- def members
- @project = group.projects.find(params[:project_id]) if params[:project_id]
- @members = group.group_members
-
- if params[:search].present?
- users = group.users.search(params[:search]).to_a
- @members = @members.where(user_id: users)
- end
-
- @members = @members.order('access_level DESC').page(params[:page]).per(50)
- @users_group = GroupMember.new
- end
-
def edit
end
@@ -128,12 +119,6 @@ class GroupsController < ApplicationController
end
end
- def authorize_admin_group!
- unless can?(current_user, :manage_group, group)
- return render_404
- end
- end
-
def set_title
@title = 'New Group'
end
@@ -148,19 +133,13 @@ class GroupsController < ApplicationController
end
end
- def default_filter
- if params[:scope].blank?
- if current_user
- params[:scope] = 'assigned-to-me'
- else
- params[:scope] = 'all'
- end
- end
- params[:state] = 'opened' if params[:state].blank?
- params[:group_id] = @group.id
- end
-
def group_params
params.require(:group).permit(:name, :description, :path, :avatar)
end
+
+ def load_events
+ @events = Event.in_projects(project_ids)
+ @events = event_filter.apply_filter(@events).with_associations
+ @events = @events.limit(20).offset(params[:offset] || 0)
+ end
end
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index fc498559d6..c4d620d87b 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -15,4 +15,7 @@ class HelpController < ApplicationController
def shortcuts
end
+
+ def ui
+ end
end
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
new file mode 100644
index 0000000000..edb8bd4160
--- /dev/null
+++ b/app/controllers/import/base_controller.rb
@@ -0,0 +1,19 @@
+class Import::BaseController < ApplicationController
+
+ private
+
+ def get_or_create_namespace
+ begin
+ namespace = Group.create!(name: @target_namespace, path: @target_namespace, owner: current_user)
+ namespace.add_owner(current_user)
+ rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
+ namespace = Namespace.find_by_path_or_name(@target_namespace)
+ unless namespace.owner == current_user
+ @already_been_taken = true
+ return false
+ end
+ end
+
+ namespace
+ end
+end
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
new file mode 100644
index 0000000000..83ebc5fddc
--- /dev/null
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -0,0 +1,79 @@
+class Import::BitbucketController < Import::BaseController
+ before_filter :verify_bitbucket_import_enabled
+ before_filter :bitbucket_auth, except: :callback
+
+ rescue_from OAuth::Error, with: :bitbucket_unauthorized
+
+ def callback
+ request_token = session.delete(:oauth_request_token)
+ raise "Session expired!" if request_token.nil?
+
+ request_token.symbolize_keys!
+
+ access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url)
+
+ current_user.bitbucket_access_token = access_token.token
+ current_user.bitbucket_access_token_secret = access_token.secret
+
+ current_user.save
+ redirect_to status_import_bitbucket_url
+ end
+
+ def status
+ @repos = client.projects
+
+ @already_added_projects = current_user.created_projects.where(import_type: "bitbucket")
+ already_added_projects_names = @already_added_projects.pluck(:import_source)
+
+ @repos.to_a.reject!{ |repo| already_added_projects_names.include? "#{repo["owner"]}/#{repo["slug"]}" }
+ end
+
+ def jobs
+ jobs = current_user.created_projects.where(import_type: "bitbucket").to_json(only: [:id, :import_status])
+ render json: jobs
+ end
+
+ def create
+ @repo_id = params[:repo_id] || ""
+ repo = client.project(@repo_id.gsub("___", "/"))
+ @target_namespace = params[:new_namespace].presence || repo["owner"]
+ @project_name = repo["slug"]
+
+ namespace = get_or_create_namespace || (render and return)
+
+ unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user).execute
+ @access_denied = true
+ render
+ return
+ end
+
+ @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user).execute
+ end
+
+ private
+
+ def client
+ @client ||= Gitlab::BitbucketImport::Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret)
+ end
+
+ def verify_bitbucket_import_enabled
+ not_found! unless bitbucket_import_enabled?
+ end
+
+ def bitbucket_auth
+ if current_user.bitbucket_access_token.blank?
+ go_to_bitbucket_for_permissions
+ end
+ end
+
+ def go_to_bitbucket_for_permissions
+ request_token = client.request_token(callback_import_bitbucket_url)
+ session[:oauth_request_token] = request_token
+
+ redirect_to client.authorize_url(request_token, callback_import_bitbucket_url)
+ end
+
+ def bitbucket_unauthorized
+ go_to_bitbucket_for_permissions
+ end
+end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
new file mode 100644
index 0000000000..8650b6464d
--- /dev/null
+++ b/app/controllers/import/github_controller.rb
@@ -0,0 +1,65 @@
+class Import::GithubController < Import::BaseController
+ before_filter :verify_github_import_enabled
+ before_filter :github_auth, except: :callback
+
+ rescue_from Octokit::Unauthorized, with: :github_unauthorized
+
+ def callback
+ token = client.get_token(params[:code])
+ current_user.github_access_token = token
+ current_user.save
+ redirect_to status_import_github_url
+ end
+
+ def status
+ @repos = client.repos
+ client.orgs.each do |org|
+ @repos += client.org_repos(org.login)
+ end
+
+ @already_added_projects = current_user.created_projects.where(import_type: "github")
+ already_added_projects_names = @already_added_projects.pluck(:import_source)
+
+ @repos.reject!{ |repo| already_added_projects_names.include? repo.full_name }
+ end
+
+ def jobs
+ jobs = current_user.created_projects.where(import_type: "github").to_json(only: [:id, :import_status])
+ render json: jobs
+ end
+
+ def create
+ @repo_id = params[:repo_id].to_i
+ repo = client.repo(@repo_id)
+ @target_namespace = params[:new_namespace].presence || repo.owner.login
+ @project_name = repo.name
+
+ namespace = get_or_create_namespace || (render and return)
+
+ @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user).execute
+ end
+
+ private
+
+ def client
+ @client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token)
+ end
+
+ def verify_github_import_enabled
+ not_found! unless github_import_enabled?
+ end
+
+ def github_auth
+ if current_user.github_access_token.blank?
+ go_to_github_for_permissions
+ end
+ end
+
+ def go_to_github_for_permissions
+ redirect_to client.authorize_url(callback_import_github_url)
+ end
+
+ def github_unauthorized
+ go_to_github_for_permissions
+ end
+end
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
new file mode 100644
index 0000000000..e979dad4b1
--- /dev/null
+++ b/app/controllers/import/gitlab_controller.rb
@@ -0,0 +1,62 @@
+class Import::GitlabController < Import::BaseController
+ before_filter :verify_gitlab_import_enabled
+ before_filter :gitlab_auth, except: :callback
+
+ rescue_from OAuth2::Error, with: :gitlab_unauthorized
+
+ def callback
+ token = client.get_token(params[:code], callback_import_gitlab_url)
+ current_user.gitlab_access_token = token
+ current_user.save
+ redirect_to status_import_gitlab_url
+ end
+
+ def status
+ @repos = client.projects
+
+ @already_added_projects = current_user.created_projects.where(import_type: "gitlab")
+ already_added_projects_names = @already_added_projects.pluck(:import_source)
+
+ @repos = @repos.to_a.reject{ |repo| already_added_projects_names.include? repo["path_with_namespace"] }
+ end
+
+ def jobs
+ jobs = current_user.created_projects.where(import_type: "gitlab").to_json(only: [:id, :import_status])
+ render json: jobs
+ end
+
+ def create
+ @repo_id = params[:repo_id].to_i
+ repo = client.project(@repo_id)
+ @target_namespace = params[:new_namespace].presence || repo["namespace"]["path"]
+ @project_name = repo["name"]
+
+ namespace = get_or_create_namespace || (render and return)
+
+ @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user).execute
+ end
+
+ private
+
+ def client
+ @client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token)
+ end
+
+ def verify_gitlab_import_enabled
+ not_found! unless gitlab_import_enabled?
+ end
+
+ def gitlab_auth
+ if current_user.gitlab_access_token.blank?
+ go_to_gitlab_for_permissions
+ end
+ end
+
+ def go_to_gitlab_for_permissions
+ redirect_to client.authorize_url(callback_import_gitlab_url)
+ end
+
+ def gitlab_unauthorized
+ go_to_gitlab_for_permissions
+ end
+end
diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb
new file mode 100644
index 0000000000..6067a87ee0
--- /dev/null
+++ b/app/controllers/import/gitorious_controller.rb
@@ -0,0 +1,43 @@
+class Import::GitoriousController < Import::BaseController
+
+ def new
+ redirect_to client.authorize_url(callback_import_gitorious_url)
+ end
+
+ def callback
+ session[:gitorious_repos] = params[:repos]
+ redirect_to status_import_gitorious_url
+ end
+
+ def status
+ @repos = client.repos
+
+ @already_added_projects = current_user.created_projects.where(import_type: "gitorious")
+ already_added_projects_names = @already_added_projects.pluck(:import_source)
+
+ @repos.reject! { |repo| already_added_projects_names.include? repo.full_name }
+ end
+
+ def jobs
+ jobs = current_user.created_projects.where(import_type: "gitorious").to_json(only: [:id, :import_status])
+ render json: jobs
+ end
+
+ def create
+ @repo_id = params[:repo_id]
+ repo = client.repo(@repo_id)
+ @target_namespace = params[:new_namespace].presence || repo.namespace
+ @project_name = repo.name
+
+ namespace = get_or_create_namespace || (render and return)
+
+ @project = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, current_user).execute
+ end
+
+ private
+
+ def client
+ @client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos])
+ end
+
+end
diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb
index c59a2401ce..b7a9d8c129 100644
--- a/app/controllers/namespaces_controller.rb
+++ b/app/controllers/namespaces_controller.rb
@@ -15,4 +15,3 @@ class NamespacesController < ApplicationController
end
end
end
-
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
new file mode 100644
index 0000000000..efa291d939
--- /dev/null
+++ b/app/controllers/oauth/applications_controller.rb
@@ -0,0 +1,39 @@
+class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
+ before_filter :authenticate_user!
+ layout "profile"
+
+ def index
+ head :forbidden and return
+ end
+
+ def create
+ @application = Doorkeeper::Application.new(application_params)
+
+ @application.owner = current_user
+
+ if @application.save
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
+ redirect_to oauth_application_url(@application)
+ else
+ render :new
+ end
+ end
+
+ def destroy
+ if @application.destroy
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :destroy])
+ end
+
+ redirect_to applications_profile_url
+ end
+
+ private
+
+ def set_application
+ @application = current_user.oauth_applications.find(params[:id])
+ end
+
+ rescue_from ActiveRecord::RecordNotFound do |exception|
+ render "errors/not_found", layout: "errors", status: 404
+ end
+end
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
new file mode 100644
index 0000000000..a57b4a60c2
--- /dev/null
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -0,0 +1,57 @@
+class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
+ before_filter :authenticate_resource_owner!
+ layout "profile"
+
+ def new
+ if pre_auth.authorizable?
+ if skip_authorization? || matching_token?
+ auth = authorization.authorize
+ redirect_to auth.redirect_uri
+ else
+ render "doorkeeper/authorizations/new"
+ end
+ else
+ render "doorkeeper/authorizations/error"
+ end
+ end
+
+ # TODO: Handle raise invalid authorization
+ def create
+ redirect_or_render authorization.authorize
+ end
+
+ def destroy
+ redirect_or_render authorization.deny
+ end
+
+ private
+
+ def matching_token?
+ Doorkeeper::AccessToken.matching_token_for(pre_auth.client,
+ current_resource_owner.id,
+ pre_auth.scopes)
+ end
+
+ def redirect_or_render(auth)
+ if auth.redirectable?
+ redirect_to auth.redirect_uri
+ else
+ render json: auth.body, status: auth.status
+ end
+ end
+
+ def pre_auth
+ @pre_auth ||=
+ Doorkeeper::OAuth::PreAuthorization.new(Doorkeeper.configuration,
+ server.client_via_uid,
+ params)
+ end
+
+ def authorization
+ @authorization ||= strategy.request
+ end
+
+ def strategy
+ @strategy ||= server.authorization_request(pre_auth.response_type)
+ end
+end
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
new file mode 100644
index 0000000000..0b27ce7da7
--- /dev/null
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -0,0 +1,8 @@
+class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
+ layout "profile"
+
+ def destroy
+ Doorkeeper::AccessToken.revoke_all_for(params[:id], current_resource_owner)
+ redirect_to applications_profile_url, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy])
+ end
+end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 3ed6a69c2d..bb9d65c9ed 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
error.to_s.humanize if error
end
+ # We only find ourselves here
+ # if the authentication to LDAP was successful.
def ldap
- # We only find ourselves here
- # if the authentication to LDAP was successful.
- @user = Gitlab::LDAP::User.find_or_create(oauth)
- @user.remember_me = true if @user.persisted?
+ @user = Gitlab::LDAP::User.new(oauth)
+ @user.save if @user.changed? # will also save new users
+ gl_user = @user.gl_user
+ gl_user.remember_me = true if @user.persisted?
# Do additional LDAP checks for the user filter and EE features
- if Gitlab::LDAP::Access.allowed?(@user)
- sign_in_and_redirect(@user)
+ if @user.allowed?
+ sign_in_and_redirect(gl_user)
else
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
@@ -40,32 +42,32 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_omniauth
if current_user
- # Change a logged-in user's authentication method:
- current_user.extern_uid = oauth['uid']
- current_user.provider = oauth['provider']
- current_user.save
- redirect_to profile_path
+ # Add new authentication method
+ current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
+ redirect_to profile_account_path, notice: 'Authentication method updated'
else
- @user = Gitlab::OAuth::User.find(oauth)
+ @user = Gitlab::OAuth::User.new(oauth)
+ @user.save
- # Create user if does not exist
- # and allow_single_sign_on is true
- if Gitlab.config.omniauth['allow_single_sign_on'] && !@user
- @user, errors = Gitlab::OAuth::User.create(oauth)
- end
-
- if @user && !errors
- sign_in_and_redirect(@user)
+ # Only allow properly saved users to login.
+ if @user.persisted? && @user.valid?
+ sign_in_and_redirect(@user.gl_user)
else
- if errors
- error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ")
- redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
- else
- flash[:notice] = "There's no such user!"
- end
- redirect_to new_user_session_path
+ error_message =
+ if @user.gl_user.errors.any?
+ @user.gl_user.errors.map do |attribute, message|
+ "#{attribute} #{message}"
+ end.join(", ")
+ else
+ ''
+ end
+
+ redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
+ rescue Gitlab::OAuth::ForbiddenAction => e
+ flash[:notice] = e.message
+ redirect_to new_user_session_path
end
def oauth
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 988ede3007..dcbbe5baa4 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -5,12 +5,12 @@ class PasswordsController < Devise::PasswordsController
resource_found = resource_class.find_by_email(email)
if resource_found && resource_found.ldap_user?
flash[:alert] = "Cannot reset password for LDAP user."
- respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) and return
+ respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) and return
end
self.resource = resource_class.send_reset_password_instructions(resource_params)
if successfully_sent?(resource)
- respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
+ respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
else
respond_with(resource)
end
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
index f3f0e69b83..4a65c978e5 100644
--- a/app/controllers/profiles/emails_controller.rb
+++ b/app/controllers/profiles/emails_controller.rb
@@ -18,6 +18,9 @@ class Profiles::EmailsController < ApplicationController
@email = current_user.emails.find(params[:id])
@email.destroy
+ current_user.set_notification_email
+ current_user.save if current_user.notification_email_changed?
+
respond_to do |format|
format.html { redirect_to profile_emails_url }
format.js { render nothing: true }
diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb
deleted file mode 100644
index ce9dd50df6..0000000000
--- a/app/controllers/profiles/groups_controller.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-class Profiles::GroupsController < ApplicationController
- layout "profile"
-
- def index
- @user_groups = current_user.group_members.page(params[:page]).per(20)
- end
-
- def leave
- @users_group = group.group_members.where(user_id: current_user.id).first
- if can?(current_user, :destroy, @users_group)
- @users_group.destroy
- redirect_to(profile_groups_path, info: "You left #{group.name} group.")
- else
- return render_403
- end
- end
-
- private
-
- def group
- @group ||= Group.find_by(path: params[:id])
- end
-end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 88414b1356..4e2bd0a9b4 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -3,7 +3,7 @@ class Profiles::KeysController < ApplicationController
skip_before_filter :authenticate_user!, only: [:get_keys]
def index
- @keys = current_user.keys.order('id DESC')
+ @keys = current_user.keys
end
def show
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index 638d1f9789..3fdcbbab61 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -2,6 +2,7 @@ class Profiles::NotificationsController < ApplicationController
layout 'profile'
def show
+ @user = current_user
@notification = current_user.notification
@project_members = current_user.project_members
@group_members = current_user.group_members
@@ -11,16 +12,33 @@ class Profiles::NotificationsController < ApplicationController
type = params[:notification_type]
@saved = if type == 'global'
- current_user.notification_level = params[:notification_level]
- current_user.save
+ current_user.update_attributes(user_params)
elsif type == 'group'
- users_group = current_user.group_members.find(params[:notification_id])
- users_group.notification_level = params[:notification_level]
- users_group.save
+ group_member = current_user.group_members.find(params[:notification_id])
+ group_member.notification_level = params[:notification_level]
+ group_member.save
else
project_member = current_user.project_members.find(params[:notification_id])
project_member.notification_level = params[:notification_level]
project_member.save
end
+
+ respond_to do |format|
+ format.html do
+ if @saved
+ flash[:notice] = "Notification settings saved"
+ else
+ flash[:alert] = "Failed to save new settings"
+ end
+
+ redirect_to :back
+ end
+
+ format.js
+ end
+ end
+
+ def user_params
+ params.require(:user).permit(:notification_email, :notification_level)
end
end
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index 1191ce47eb..0c614969a3 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -11,7 +11,7 @@ class Profiles::PasswordsController < ApplicationController
end
def create
- unless @user.valid_password?(user_params[:current_password])
+ unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
redirect_to new_profile_password_path, alert: 'You must provide a valid current password'
return
end
@@ -21,7 +21,8 @@ class Profiles::PasswordsController < ApplicationController
result = @user.update_attributes(
password: new_password,
- password_confirmation: new_password_confirmation
+ password_confirmation: new_password_confirmation,
+ password_automatically_set: false
)
if result
@@ -39,8 +40,9 @@ class Profiles::PasswordsController < ApplicationController
password_attributes = user_params.select do |key, value|
%w(password password_confirmation).include?(key.to_s)
end
+ password_attributes[:password_automatically_set] = false
- unless @user.valid_password?(user_params[:current_password])
+ unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password])
redirect_to edit_profile_password_path, alert: 'You must provide a valid current password'
return
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index e877f9b904..1b9a86ee42 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -13,6 +13,12 @@ class ProfilesController < ApplicationController
def design
end
+ def applications
+ @applications = current_user.oauth_applications
+ @authorized_tokens = current_user.oauth_authorized_tokens
+ @authorized_apps = @authorized_tokens.map(&:application).uniq
+ end
+
def update
user_params.except!(:email) if @user.ldap_user?
@@ -37,7 +43,7 @@ class ProfilesController < ApplicationController
end
def history
- @events = current_user.recent_events.page(params[:page]).per(20)
+ @events = current_user.recent_events.page(params[:page]).per(PER_PAGE)
end
def update_username
@@ -62,7 +68,7 @@ class ProfilesController < ApplicationController
params.require(:user).permit(
:email, :password, :password_confirmation, :bio, :name, :username,
:skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id,
- :avatar, :hide_no_ssh_key,
+ :avatar, :hide_no_ssh_key, :hide_no_password
)
end
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 7e4580017d..4719933394 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -8,7 +8,8 @@ class Projects::ApplicationController < ApplicationController
# for non-signed users
if !current_user
id = params[:project_id] || params[:id]
- @project = Project.find_with_namespace(id)
+ project_with_namespace = "#{params[:namespace_id]}/#{id}"
+ @project = Project.find_with_namespace(project_with_namespace)
return if @project && @project.public?
end
@@ -26,7 +27,10 @@ class Projects::ApplicationController < ApplicationController
def require_branch_head
unless @repository.branch_names.include?(@ref)
- redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch"
+ redirect_to(
+ namespace_project_tree_path(@project.namespace, @project, @ref),
+ notice: "This action is not allowed unless you are on top of a branch"
+ )
end
end
end
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
new file mode 100644
index 0000000000..a482b90880
--- /dev/null
+++ b/app/controllers/projects/avatars_controller.rb
@@ -0,0 +1,29 @@
+class Projects::AvatarsController < Projects::ApplicationController
+ layout 'project'
+
+ before_filter :project
+
+ def show
+ @blob = @project.repository.blob_at_branch('master', @project.avatar_in_git)
+ if @blob
+ headers['X-Content-Type-Options'] = 'nosniff'
+ send_data(
+ @blob.data,
+ type: @blob.mime_type,
+ disposition: 'inline',
+ filename: @blob.name
+ )
+ else
+ not_found!
+ end
+ end
+
+ def destroy
+ @project.remove_avatar!
+
+ @project.save
+ @project.reset_events_cache
+
+ redirect_to edit_project_path(@project)
+ end
+end
diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb
deleted file mode 100644
index 5e30593443..0000000000
--- a/app/controllers/projects/base_tree_controller.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class Projects::BaseTreeController < Projects::ApplicationController
- include ExtractsPath
-
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
-end
-
diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb
index a3c4130167..a87b8270a2 100644
--- a/app/controllers/projects/blame_controller.rb
+++ b/app/controllers/projects/blame_controller.rb
@@ -2,13 +2,12 @@
class Projects::BlameController < Projects::ApplicationController
include ExtractsPath
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :assign_ref_vars
+ before_filter :authorize_download_code!
def show
- @blob = @repository.blob_at(@commit.id, @path)
- @blame = Gitlab::Git::Blame.new(project.repository, @commit.id, @path)
+ @blame = Gitlab::Git::Blame.new(@repository, @commit.id, @path)
+ @blob = @blame.blob
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 7009e3b1bc..4b7eb4df29 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -1,26 +1,93 @@
# Controller for viewing a file's blame
class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
+ include ActionView::Helpers::SanitizeHelper
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
- before_filter :authorize_push!, only: [:destroy]
+ # Raised when given an invalid file path
+ class InvalidPathError < StandardError; end
- before_filter :blob
+ before_filter :require_non_empty_project, except: [:new, :create]
+ before_filter :authorize_download_code!
+ before_filter :authorize_push_code!, only: [:destroy]
+ before_filter :assign_blob_vars
+ before_filter :commit, except: [:new, :create]
+ before_filter :blob, except: [:new, :create]
+ before_filter :from_merge_request, only: [:edit, :update]
+ before_filter :after_edit_path, only: [:edit, :update]
+ before_filter :require_branch_head, only: [:edit, :update]
+
+ def new
+ commit unless @repository.empty?
+ end
+
+ def create
+ file_path = File.join(@path, File.basename(params[:file_name]))
+ result = Files::CreateService.new(
+ @project,
+ current_user,
+ params.merge(new_branch: sanitized_new_branch_name),
+ @ref,
+ file_path
+ ).execute
+
+ if result[:status] == :success
+ flash[:notice] = "Your changes have been successfully committed"
+ ref = sanitized_new_branch_name.presence || @ref
+ redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path))
+ else
+ flash[:alert] = result[:message]
+ render :new
+ end
+ end
def show
end
+ def edit
+ @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
+ end
+
+ def update
+ result = Files::UpdateService.
+ new(
+ @project,
+ current_user,
+ params.merge(new_branch: sanitized_new_branch_name),
+ @ref,
+ @path
+ ).execute
+
+ if result[:status] == :success
+ flash[:notice] = "Your changes have been successfully committed"
+
+ if from_merge_request
+ from_merge_request.reload_code
+ end
+
+ redirect_to after_edit_path
+ else
+ flash[:alert] = result[:message]
+ render :edit
+ end
+ end
+
+ def preview
+ @content = params[:content]
+ diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
+ @diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
+
+ render layout: false
+ end
+
def destroy
result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- redirect_to project_tree_path(@project, @ref)
+ redirect_to namespace_project_tree_path(@project.namespace, @project,
+ @ref)
else
- flash[:alert] = result[:error]
+ flash[:alert] = result[:message]
render :show
end
end
@@ -47,10 +114,50 @@ class Projects::BlobController < Projects::ApplicationController
if @blob
@blob
- elsif tree.entries.any?
- redirect_to project_tree_path(@project, File.join(@ref, @path)) and return
else
+ if tree = @repository.tree(@commit.id, @path)
+ if tree.entries.any?
+ redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) and return
+ end
+ end
+
return not_found!
end
end
+
+ def commit
+ @commit = @repository.commit(@ref)
+
+ return not_found! unless @commit
+ end
+
+ def assign_blob_vars
+ @id = params[:id]
+ @ref, @path = extract_ref(@id)
+
+
+ rescue InvalidPathError
+ not_found!
+ end
+
+ def after_edit_path
+ @after_edit_path ||=
+ if from_merge_request
+ diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
+ "#file-path-#{hexdigest(@path)}"
+ elsif sanitized_new_branch_name.present?
+ namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
+ else
+ namespace_project_blob_path(@project.namespace, @project, @id)
+ end
+ end
+
+ def from_merge_request
+ # If blob edit was initiated from merge request page
+ @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
+ end
+
+ def sanitized_new_branch_name
+ @new_branch ||= sanitize(strip_tags(params[:new_branch]))
+ end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index faa0ce67ca..f049e96e61 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -1,15 +1,14 @@
class Projects::BranchesController < Projects::ApplicationController
+ include ActionView::Helpers::SanitizeHelper
# Authorize
- before_filter :authorize_read_project!
before_filter :require_non_empty_project
-
- before_filter :authorize_code_access!
- before_filter :authorize_push!, only: [:create, :destroy]
+ before_filter :authorize_download_code!
+ before_filter :authorize_push_code!, only: [:create, :destroy]
def index
@sort = params[:sort] || 'name'
@branches = @repository.branches_sorted_by(@sort)
- @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(30)
+ @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE)
end
def recent
@@ -17,11 +16,15 @@ class Projects::BranchesController < Projects::ApplicationController
end
def create
+ branch_name = sanitize(strip_tags(params[:branch_name]))
+ ref = sanitize(strip_tags(params[:ref]))
result = CreateBranchService.new(project, current_user).
- execute(params[:branch_name], params[:ref])
+ execute(branch_name, ref)
+
if result[:status] == :success
@branch = result[:branch]
- redirect_to project_tree_path(@project, @branch.name)
+ redirect_to namespace_project_tree_path(@project.namespace, @project,
+ @branch.name)
else
@error = result[:message]
render action: 'new'
@@ -33,7 +36,10 @@ class Projects::BranchesController < Projects::ApplicationController
@branch_name = params[:id]
respond_to do |format|
- format.html { redirect_to project_branches_path(@project) }
+ format.html do
+ redirect_to namespace_project_branches_path(@project.namespace,
+ @project)
+ end
format.js
end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 66c67b661d..87e39f1363 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -3,20 +3,18 @@
# Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :authorize_download_code!
before_filter :commit
def show
return git_not_found! unless @commit
- @line_notes = project.notes.for_commit_id(commit.id).inline
- @branches = project.repository.branch_names_contains(commit.id)
+ @line_notes = @project.notes.for_commit_id(commit.id).inline
@diffs = @commit.diffs
- @note = project.build_commit_note(commit)
- @notes_count = project.notes.for_commit_id(commit.id).count
- @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
+ @note = @project.build_commit_note(commit)
+ @notes_count = @project.notes.for_commit_id(commit.id).count
+ @notes = @project.notes.for_commit_id(@commit.id).not_inline.fresh
@noteable = @commit
@comments_allowed = @reply_allowed = true
@comments_target = {
@@ -31,7 +29,13 @@ class Projects::CommitController < Projects::ApplicationController
end
end
+ def branches
+ @branches = @project.repository.branch_names_contains(commit.id)
+ @tags = @project.repository.tag_names_contains(commit.id)
+ render layout: false
+ end
+
def commit
- @commit ||= project.repository.commit(params[:id])
+ @commit ||= @project.repository.commit(params[:id])
end
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index b7f09eb271..4b6ab43747 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -3,10 +3,9 @@ require "base64"
class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :assign_ref_vars
+ before_filter :authorize_download_code!
def show
@repo = @project.repository
@@ -14,7 +13,7 @@ class Projects::CommitsController < Projects::ApplicationController
@commits = @repo.commits(@ref, @path, @limit, @offset)
@note_counts = Note.where(commit_id: @commits.map(&:id)).
- group(:commit_id).count
+ group(:commit_id).count
respond_to do |format|
format.html
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 7a671e8455..146808fa56 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -1,8 +1,7 @@
class Projects::CompareController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :authorize_download_code!
def index
end
@@ -26,6 +25,7 @@ class Projects::CompareController < Projects::ApplicationController
end
def create
- redirect_to project_compare_path(@project, params[:from], params[:to])
+ redirect_to namespace_project_compare_path(@project.namespace, @project,
+ params[:from], params[:to])
end
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index d20937ea8e..679a5d76ec 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -25,7 +25,8 @@ class Projects::DeployKeysController < Projects::ApplicationController
@key = DeployKey.new(deploy_key_params)
if @key.valid? && @project.deploy_keys << @key
- redirect_to project_deploy_keys_path(@project)
+ redirect_to namespace_project_deploy_keys_path(@project.namespace,
+ @project)
else
render "new"
end
@@ -36,21 +37,23 @@ class Projects::DeployKeysController < Projects::ApplicationController
@key.destroy
respond_to do |format|
- format.html { redirect_to project_deploy_keys_url }
+ format.html { redirect_to namespace_project_deploy_keys_path(@project.namespace, @project) }
format.js { render nothing: true }
end
end
def enable
- project.deploy_keys << available_keys.find(params[:id])
+ @project.deploy_keys << available_keys.find(params[:id])
- redirect_to project_deploy_keys_path(@project)
+ redirect_to namespace_project_deploy_keys_path(@project.namespace,
+ @project)
end
def disable
- @project.deploy_keys_projects.where(deploy_key_id: params[:id]).last.destroy
+ @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
- redirect_to project_deploy_keys_path(@project)
+ redirect_to namespace_project_deploy_keys_path(@project.namespace,
+ @project)
end
protected
diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb
deleted file mode 100644
index 8976d7c7be..0000000000
--- a/app/controllers/projects/edit_tree_controller.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-class Projects::EditTreeController < Projects::BaseTreeController
- before_filter :require_branch_head
- before_filter :blob
- before_filter :authorize_push!
- before_filter :from_merge_request
- before_filter :after_edit_path
-
- def show
- @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
- end
-
- def update
- result = Files::UpdateService.
- new(@project, current_user, params, @ref, @path).execute
-
- if result[:status] == :success
- flash[:notice] = "Your changes have been successfully committed"
-
- if from_merge_request
- from_merge_request.reload_code
- end
-
- redirect_to after_edit_path
- else
- flash[:alert] = result[:error]
- render :show
- end
- end
-
- def preview
- @content = params[:content]
-
- diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
- include_diff_info: true)
- @diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
-
- render layout: false
- end
-
- private
-
- def blob
- @blob ||= @repository.blob_at(@commit.id, @path)
- end
-
- def after_edit_path
- @after_edit_path ||=
- if from_merge_request
- diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
- "#file-path-#{hexdigest(@path)}"
- else
- project_blob_path(@project, @id)
- end
- end
-
- def from_merge_request
- # If blob edit was initiated from merge request page
- @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
- end
-end
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
new file mode 100644
index 0000000000..21a151a426
--- /dev/null
+++ b/app/controllers/projects/forks_controller.rb
@@ -0,0 +1,25 @@
+class Projects::ForksController < Projects::ApplicationController
+ # Authorize
+ before_filter :require_non_empty_project
+ before_filter :authorize_download_code!
+
+ def new
+ @namespaces = current_user.manageable_namespaces
+ @namespaces.delete(@project.namespace)
+ end
+
+ def create
+ namespace = Namespace.find(params[:namespace_key])
+ @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
+
+ if @forked_project.saved? && @forked_project.forked?
+ redirect_to(
+ namespace_project_path(@forked_project.namespace, @forked_project),
+ notice: 'Project was successfully forked.'
+ )
+ else
+ @title = 'Fork project'
+ render :error
+ end
+ end
+end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 610b4967fe..6e54af356e 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -1,8 +1,7 @@
class Projects::GraphsController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :authorize_download_code!
def show
respond_to do |format|
@@ -29,8 +28,8 @@ class Projects::GraphsController < Projects::ApplicationController
@commits.each do |commit|
@log << {
- author_name: commit.author_name.force_encoding('UTF-8'),
- author_email: commit.author_email.force_encoding('UTF-8'),
+ author_name: commit.author_name,
+ author_email: commit.author_email,
date: commit.committed_date.strftime("%Y-%m-%d")
}
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index cab8fd76e6..ba95bb13e1 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -16,7 +16,7 @@ class Projects::HooksController < Projects::ApplicationController
@hook.save
if @hook.valid?
- redirect_to project_hooks_path(@project)
+ redirect_to namespace_project_hooks_path(@project.namespace, @project)
else
@hooks = @project.hooks.select(&:persisted?)
render :index
@@ -26,6 +26,7 @@ class Projects::HooksController < Projects::ApplicationController
def test
if !@project.empty_repo?
status = TestHookService.new.execute(hook, current_user)
+
if status
flash[:notice] = 'Hook successfully executed.'
else
@@ -42,7 +43,7 @@ class Projects::HooksController < Projects::ApplicationController
def destroy
hook.destroy
- redirect_to project_hooks_path(@project)
+ redirect_to namespace_project_hooks_path(@project.namespace, @project)
end
private
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
new file mode 100644
index 0000000000..b64491b466
--- /dev/null
+++ b/app/controllers/projects/imports_controller.rb
@@ -0,0 +1,51 @@
+class Projects::ImportsController < Projects::ApplicationController
+ # Authorize
+ before_filter :authorize_admin_project!
+ before_filter :require_no_repo
+ before_filter :redirect_if_progress, except: :show
+
+ def new
+ end
+
+ def create
+ @project.import_url = params[:project][:import_url]
+
+ if @project.save
+ @project.reload
+
+ if @project.import_failed?
+ @project.import_retry
+ else
+ @project.import_start
+ end
+ end
+
+ redirect_to namespace_project_import_path(@project.namespace, @project)
+ end
+
+ def show
+ unless @project.import_in_progress?
+ if @project.import_finished?
+ redirect_to(project_path(@project)) and return
+ else
+ redirect_to new_namespace_project_import_path(@project.namespace,
+ @project) && return
+ end
+ end
+ end
+
+ private
+
+ def require_no_repo
+ if @project.repository_exists? && !@project.import_in_progress?
+ redirect_to(namespace_project_path(@project.namespace, @project)) and return
+ end
+ end
+
+ def redirect_if_progress
+ if @project.import_in_progress?
+ redirect_to namespace_project_import_path(@project.namespace, @project) &&
+ return
+ end
+ end
+end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c6d526f05c..88302276b5 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -1,6 +1,6 @@
class Projects::IssuesController < Projects::ApplicationController
before_filter :module_enabled
- before_filter :issue, only: [:edit, :update, :show]
+ before_filter :issue, only: [:edit, :update, :show, :toggle_subscription]
# Allow read any issue
before_filter :authorize_read_issue!
@@ -18,17 +18,9 @@ class Projects::IssuesController < Projects::ApplicationController
def index
terms = params['issue_search']
-
- @issues = issues_filtered
+ @issues = get_issues_collection
@issues = @issues.full_search(terms) if terms.present?
- @issues = @issues.page(params[:page]).per(20)
-
- assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
- @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
- @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
- sort_param = params[:sort] || 'newest'
- @sort = sort_param.humanize unless sort_param.empty?
- @assignees = User.where(id: @project.issues.pluck(:assignee_id)).active
+ @issues = @issues.page(params[:page]).per(PER_PAGE)
respond_to do |format|
format.html
@@ -68,7 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to do |format|
format.html do
if @issue.valid?
- redirect_to project_issue_path(@project, @issue)
+ redirect_to issue_path(@issue)
else
render :new
end
@@ -86,7 +78,7 @@ class Projects::IssuesController < Projects::ApplicationController
format.js
format.html do
if @issue.valid?
- redirect_to [@project, @issue]
+ redirect_to issue_path(@issue)
else
render :edit
end
@@ -101,10 +93,16 @@ class Projects::IssuesController < Projects::ApplicationController
end
def bulk_update
- result = Issues::BulkUpdateService.new(project, current_user, params).execute
+ result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_to :back, notice: "#{result[:count]} issues updated"
end
+ def toggle_subscription
+ @issue.toggle_subscription(current_user)
+
+ render nothing: true
+ end
+
protected
def issue
@@ -127,12 +125,6 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless @project.issues_enabled
end
- def issues_filtered
- params[:scope] = 'all' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
- @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
- end
-
# Since iids are implemented only in 6.1
# user may navigate to issue page using old global ids.
#
@@ -142,7 +134,7 @@ class Projects::IssuesController < Projects::ApplicationController
issue = @project.issues.find_by(id: params[:id])
if issue
- redirect_to project_issue_path(@project, issue)
+ redirect_to issue_path(issue)
return
else
raise ActiveRecord::RecordNotFound.new
@@ -155,4 +147,13 @@ class Projects::IssuesController < Projects::ApplicationController
:milestone_id, :state_event, :task_num, label_ids: []
)
end
+
+ def bulk_update_params
+ params.require(:update).permit(
+ :issues_ids,
+ :assignee_id,
+ :milestone_id,
+ :state_event
+ )
+ end
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 6c7bde9c5d..207a01ed3b 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -7,7 +7,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to :js, :html
def index
- @labels = @project.labels.order_by_name.page(params[:page]).per(20)
+ @labels = @project.labels.page(params[:page]).per(PER_PAGE)
end
def new
@@ -18,7 +18,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label = @project.labels.create(label_params)
if @label.valid?
- redirect_to project_labels_path(@project)
+ redirect_to namespace_project_labels_path(@project.namespace, @project)
else
render 'new'
end
@@ -29,7 +29,7 @@ class Projects::LabelsController < Projects::ApplicationController
def update
if @label.update_attributes(label_params)
- redirect_to project_labels_path(@project)
+ redirect_to namespace_project_labels_path(@project.namespace, @project)
else
render 'edit'
end
@@ -39,11 +39,12 @@ class Projects::LabelsController < Projects::ApplicationController
Gitlab::IssuesLabels.generate(@project)
if params[:redirect] == 'issues'
- redirect_to project_issues_path(@project)
+ redirect_to namespace_project_issues_path(@project.namespace, @project)
elsif params[:redirect] == 'merge_requests'
- redirect_to project_merge_requests_path(@project)
+ redirect_to namespace_project_merge_requests_path(@project.namespace,
+ @project)
else
- redirect_to project_labels_path(@project)
+ redirect_to namespace_project_labels_path(@project.namespace, @project)
end
end
@@ -51,7 +52,10 @@ class Projects::LabelsController < Projects::ApplicationController
@label.destroy
respond_to do |format|
- format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' }
+ format.html do
+ redirect_to(namespace_project_labels_path(@project.namespace, @project),
+ notice: 'Label was removed')
+ end
format.js
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 20a733b10e..c63a9b0cd4 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -2,7 +2,7 @@ require 'gitlab/satellite/satellite'
class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :module_enabled
- before_filter :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status]
+ before_filter :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status, :toggle_subscription]
before_filter :closes_issues, only: [:edit, :update, :show, :diffs]
before_filter :validates_merge_request, only: [:show, :diffs]
before_filter :define_show_vars, only: [:show, :diffs]
@@ -17,26 +17,28 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
def index
- params[:sort] ||= 'newest'
- params[:scope] = 'all' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
+ terms = params['issue_search']
+ @merge_requests = get_merge_requests_collection
+ @merge_requests = @merge_requests.full_search(terms) if terms.present?
+ @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
- @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id))
- @merge_requests = @merge_requests.page(params[:page]).per(20)
-
- @sort = params[:sort].humanize
- assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
- @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
- @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
- @assignees = User.where(id: @project.merge_requests.pluck(:assignee_id))
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: {
+ html: view_to_html_string("projects/merge_requests/_merge_requests")
+ }
+ end
+ end
end
def show
@note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)).
- group(:commit_id).count
+ group(:commit_id).count
respond_to do |format|
format.html
+ format.json { render json: @merge_request }
format.diff { render text: @merge_request.to_diff(current_user) }
format.patch { render text: @merge_request.to_patch(current_user) }
end
@@ -87,7 +89,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
if @merge_request.valid?
- redirect_to project_merge_request_path(@merge_request.target_project, @merge_request), notice: 'Merge request was successfully created.'
+ redirect_to(merge_request_path(@merge_request))
else
@source_project = @merge_request.source_project
@target_project = @merge_request.target_project
@@ -102,7 +104,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.js
format.html do
- redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.'
+ redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
+ @merge_request.target_project, @merge_request])
+ end
+ format.json do
+ render json: {
+ saved: @merge_request.valid?,
+ assignee_avatar_url: @merge_request.assignee.try(:avatar_url)
+ }
end
end
else
@@ -114,15 +123,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.unchecked?
@merge_request.check_if_can_be_merged
end
- render json: {merge_status: @merge_request.merge_status_name}
+
+ render json: { merge_status: @merge_request.merge_status_name }
end
def automerge
return access_denied! unless allowed_to_merge?
if @merge_request.open? && @merge_request.can_be_merged?
- @merge_request.should_remove_source_branch = params[:should_remove_source_branch]
- @merge_request.automerge!(current_user, params[:commit_message])
+ AutoMergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = true
else
@status = false
@@ -165,6 +174,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render json: response
end
+ def toggle_subscription
+ @merge_request.toggle_subscription(current_user)
+
+ render nothing: true
+ end
+
protected
def selected_target_project
@@ -225,6 +240,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@allowed_to_merge = allowed_to_merge?
@show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge
@source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name)
+
+ if @merge_request.locked_long_ago?
+ @merge_request.unlock_mr
+ @merge_request.close
+ end
end
def allowed_to_merge?
@@ -237,13 +257,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def allowed_to_push_code?(project, branch)
- action = if project.protected_branch?(branch)
- :push_code_to_protected_branches
- else
- :push_code
- end
-
- can?(current_user, action, project)
+ ::Gitlab::GitAccess.can_push_to_branch?(current_user, project, branch)
end
def merge_request_params
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index d338cdedfa..b49b549547 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -11,14 +11,14 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html
def index
- @milestones = case params[:f]
+ @milestones = case params[:state]
when 'all'; @project.milestones.order("state, due_date DESC")
when 'closed'; @project.milestones.closed.order("due_date DESC")
else @project.milestones.active.order("due_date ASC")
end
@milestones = @milestones.includes(:project)
- @milestones = @milestones.page(params[:page]).per(20)
+ @milestones = @milestones.page(params[:page]).per(PER_PAGE)
end
def new
@@ -40,7 +40,8 @@ class Projects::MilestonesController < Projects::ApplicationController
@milestone = Milestones::CreateService.new(project, current_user, milestone_params).execute
if @milestone.save
- redirect_to project_milestone_path(@project, @milestone)
+ redirect_to namespace_project_milestone_path(@project.namespace,
+ @project, @milestone)
else
render "new"
end
@@ -53,7 +54,8 @@ class Projects::MilestonesController < Projects::ApplicationController
format.js
format.html do
if @milestone.valid?
- redirect_to [@project, @milestone]
+ redirect_to namespace_project_milestone_path(@project.namespace,
+ @project, @milestone)
else
render :edit
end
@@ -67,7 +69,7 @@ class Projects::MilestonesController < Projects::ApplicationController
@milestone.destroy
respond_to do |format|
- format.html { redirect_to project_milestones_path }
+ format.html { redirect_to namespace_project_milestones_path }
format.js { render nothing: true }
end
end
@@ -103,7 +105,9 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def module_enabled
- return render_404 unless @project.issues_enabled
+ unless @project.issues_enabled || @project.merge_requests_enabled
+ return render_404
+ end
end
def milestone_params
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 9832495c64..83d1c1daca 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -2,10 +2,9 @@ class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath
include ApplicationHelper
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :assign_ref_vars
+ before_filter :authorize_download_code!
def show
respond_to do |format|
diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb
deleted file mode 100644
index 71a5c6499e..0000000000
--- a/app/controllers/projects/new_tree_controller.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-class Projects::NewTreeController < Projects::BaseTreeController
- before_filter :require_branch_head
- before_filter :authorize_push!
-
- def show
- end
-
- def update
- file_path = File.join(@path, File.basename(params[:file_name]))
- result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute
-
- if result[:status] == :success
- flash[:notice] = "Your changes have been successfully committed"
- redirect_to project_blob_path(@project, File.join(@ref, file_path))
- else
- flash[:alert] = result[:message]
- render :show
- end
- end
-end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 7b08b79d23..868629a0bc 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -3,10 +3,10 @@ class Projects::NotesController < Projects::ApplicationController
before_filter :authorize_read_note!
before_filter :authorize_write_note!, only: [:create]
before_filter :authorize_admin_note!, only: [:update, :destroy]
+ before_filter :find_current_user_notes, except: [:destroy, :delete_attachment]
def index
current_fetched_at = Time.now.to_i
- @notes = NotesFinder.new.execute(project, current_user, params)
notes_json = { notes: [], last_fetched_at: current_fetched_at }
@@ -61,10 +61,6 @@ class Projects::NotesController < Projects::ApplicationController
end
end
- def preview
- render text: view_context.markdown(params[:note])
- end
-
private
def note
@@ -120,4 +116,10 @@ class Projects::NotesController < Projects::ApplicationController
:attachment, :line_code, :commit_id
)
end
+
+ private
+
+ def find_current_user_notes
+ @notes = NotesFinder.new.execute(project, current_user, params)
+ end
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
new file mode 100644
index 0000000000..4ab15db01f
--- /dev/null
+++ b/app/controllers/projects/project_members_controller.rb
@@ -0,0 +1,88 @@
+class Projects::ProjectMembersController < Projects::ApplicationController
+ # Authorize
+ before_filter :authorize_admin_project!, except: :leave
+
+ layout "project_settings"
+
+ def index
+ @project_members = @project.project_members
+
+ if params[:search].present?
+ users = @project.users.search(params[:search]).to_a
+ @project_members = @project_members.where(user_id: users)
+ end
+
+ @project_members = @project_members.order('access_level DESC')
+
+ @group = @project.group
+ if @group
+ @group_members = @group.group_members
+
+ if params[:search].present?
+ users = @group.users.search(params[:search]).to_a
+ @group_members = @group_members.where(user_id: users)
+ end
+
+ @group_members = @group_members.order('access_level DESC').limit(20)
+ end
+
+ @project_member = @project.project_members.new
+ end
+
+ def new
+ @project_member = @project.project_members.new
+ end
+
+ def create
+ users = User.where(id: params[:user_ids].split(','))
+ @project.team << [users, params[:access_level]]
+
+ redirect_to namespace_project_project_members_path(@project.namespace, @project)
+ end
+
+ def update
+ @project_member = @project.project_members.find_by(user_id: member)
+ @project_member.update_attributes(member_params)
+ end
+
+ def destroy
+ @project_member = @project.project_members.find_by(user_id: member)
+ @project_member.destroy
+
+ respond_to do |format|
+ format.html do
+ redirect_to namespace_project_project_members_path(@project.namespace,
+ @project)
+ end
+ format.js { render nothing: true }
+ end
+ end
+
+ def leave
+ @project.project_members.find_by(user_id: current_user).destroy
+
+ respond_to do |format|
+ format.html { redirect_to :back }
+ format.js { render nothing: true }
+ end
+ end
+
+ def apply_import
+ giver = Project.find(params[:source_project_id])
+ status = @project.team.import(giver)
+ notice = status ? "Successfully imported" : "Import failed"
+
+ redirect_to(namespace_project_project_members_path(project.namespace, project),
+ notice: notice)
+ end
+
+ protected
+
+ def member
+ @member ||= User.find_by(username: params[:id])
+ end
+
+ def member_params
+ params.require(:project_member).permit(:user_id, :access_level)
+ end
+end
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index bd31b1d3c5..ac36ac6fcd 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -12,14 +12,33 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
def create
@project.protected_branches.create(protected_branch_params)
- redirect_to project_protected_branches_path(@project)
+ redirect_to namespace_project_protected_branches_path(@project.namespace,
+ @project)
+ end
+
+ def update
+ protected_branch = @project.protected_branches.find(params[:id])
+
+ if protected_branch &&
+ protected_branch.update_attributes(
+ developers_can_push: params[:developers_can_push]
+ )
+
+ respond_to do |format|
+ format.json { render json: protected_branch, status: :ok }
+ end
+ else
+ respond_to do |format|
+ format.json { render json: protected_branch.errors, status: :unprocessable_entity }
+ end
+ end
end
def destroy
@project.protected_branches.find(params[:id]).destroy
respond_to do |format|
- format.html { redirect_to project_protected_branches_path }
+ format.html { redirect_to namespace_project_protected_branches_path }
format.js { render nothing: true }
end
end
@@ -27,6 +46,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
private
def protected_branch_params
- params.require(:protected_branch).permit(:name)
+ params.require(:protected_branch).permit(:name, :developers_can_push)
end
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 5ec9c576a6..b1a029ce69 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -2,10 +2,9 @@
class Projects::RawController < Projects::ApplicationController
include ExtractsPath
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :assign_ref_vars
+ before_filter :authorize_download_code!
def show
@blob = @repository.blob_at(@commit.id, @path)
@@ -36,4 +35,3 @@ class Projects::RawController < Projects::ApplicationController
end
end
end
-
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 7997c726fb..67acf45ab7 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -1,22 +1,23 @@
class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
- # Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
before_filter :require_non_empty_project
+ before_filter :assign_ref_vars
+ before_filter :authorize_download_code!
def switch
respond_to do |format|
format.html do
new_path = if params[:destination] == "tree"
- project_tree_path(@project, (@id))
+ namespace_project_tree_path(@project.namespace, @project,
+ (@id))
elsif params[:destination] == "blob"
- project_blob_path(@project, (@id))
+ namespace_project_blob_path(@project.namespace, @project,
+ (@id))
elsif params[:destination] == "graph"
- project_network_path(@project, @id, @options)
+ namespace_project_network_path(@project.namespace, @project, @id, @options)
else
- project_commits_path(@project, @id)
+ namespace_project_commits_path(@project.namespace, @project, @id)
end
redirect_to new_path
@@ -32,19 +33,19 @@ class Projects::RefsController < Projects::ApplicationController
def logs_tree
@offset = if params[:offset].present?
- params[:offset].to_i
- else
- 0
- end
+ params[:offset].to_i
+ else
+ 0
+ end
@limit = 25
@path = params[:path]
contents = []
- contents += tree.trees
- contents += tree.blobs
- contents += tree.submodules
+ contents.push(*tree.trees)
+ contents.push(*tree.blobs)
+ contents.push(*tree.submodules)
@logs = contents[@offset, @limit].to_a.map do |content|
file = @path ? File.join(@path, content.name) : content.name
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 4e0f190ed1..cbb888b25e 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -1,8 +1,14 @@
class Projects::RepositoriesController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
+ before_filter :require_non_empty_project, except: :create
+ before_filter :authorize_download_code!
+ before_filter :authorize_admin_project!, only: :create
+
+ def create
+ @project.create_repository
+
+ redirect_to project_path(@project)
+ end
def archive
unless can?(current_user, :download_code, @project)
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index b50f628645..570447c746 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -9,7 +9,7 @@ class Projects::ServicesController < Projects::ApplicationController
def index
@project.build_missing_services
- @services = @project.services.reload
+ @services = @project.services.visible.reload
end
def edit
@@ -17,18 +17,25 @@ class Projects::ServicesController < Projects::ApplicationController
def update
if @service.update_attributes(service_params)
- redirect_to edit_project_service_path(@project, @service.to_param)
+ redirect_to(
+ edit_namespace_project_service_path(@project.namespace, @project,
+ @service.to_param, notice:
+ 'Successfully updated.')
+ )
else
render 'edit'
end
end
def test
- data = GitPushService.new.sample_data(project, current_user)
+ data = Gitlab::PushDataBuilder.build_sample(project, current_user)
+ if @service.execute(data)
+ message = { notice: 'We sent a request to the provided URL' }
+ else
+ message = { alert: 'We tried to send a request to the provided URL but an error occured' }
+ end
- @service.execute(data)
-
- redirect_to :back
+ redirect_to :back, message
end
private
@@ -41,7 +48,12 @@ class Projects::ServicesController < Projects::ApplicationController
params.require(:service).permit(
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
- :user_key, :device, :priority, :sound
+ :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
+ :build_key, :server, :teamcity_url, :build_type,
+ :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
+ :colorize_messages, :channels,
+ :push_events, :issues_events, :merge_requests_events, :tag_push_events,
+ :note_events, :send_from_committer_email, :disable_diffs
)
end
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 9d5dd8a95c..ed26840037 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -28,25 +28,22 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def create
- @snippet = @project.snippets.build(snippet_params)
- @snippet.author = current_user
-
- if @snippet.save
- redirect_to project_snippet_path(@project, @snippet)
- else
- respond_with(@snippet)
- end
+ @snippet = CreateSnippetService.new(@project, current_user,
+ snippet_params).execute
+ respond_with(@snippet,
+ location: namespace_project_snippet_path(@project.namespace,
+ @project, @snippet))
end
def edit
end
def update
- if @snippet.update_attributes(snippet_params)
- redirect_to project_snippet_path(@project, @snippet)
- else
- respond_with(@snippet)
- end
+ UpdateSnippetService.new(project, current_user, @snippet,
+ snippet_params).execute
+ respond_with(@snippet,
+ location: namespace_project_snippet_path(@project.namespace,
+ @project, @snippet))
end
def show
@@ -60,7 +57,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet.destroy
- redirect_to project_snippets_path(@project)
+ redirect_to namespace_project_snippets_path(@project.namespace, @project)
end
def raw
@@ -68,7 +65,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet.content,
type: 'text/plain; charset=utf-8',
disposition: 'inline',
- filename: @snippet.file_name
+ filename: @snippet.sanitized_file_name
)
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 537c94bda2..83f4937bce 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -1,23 +1,22 @@
class Projects::TagsController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
before_filter :require_non_empty_project
-
- before_filter :authorize_code_access!
- before_filter :authorize_push!, only: [:create]
+ before_filter :authorize_download_code!
+ before_filter :authorize_push_code!, only: [:create]
before_filter :authorize_admin_project!, only: [:destroy]
def index
sorted = VersionSorter.rsort(@repository.tag_names)
- @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(30)
+ @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE)
end
def create
result = CreateTagService.new(@project, current_user).
execute(params[:tag_name], params[:ref], params[:message])
+
if result[:status] == :success
@tag = result[:tag]
- redirect_to project_tags_path(@project)
+ redirect_to namespace_project_tags_path(@project.namespace, @project)
else
@error = result[:message]
render action: 'new'
@@ -25,14 +24,13 @@ class Projects::TagsController < Projects::ApplicationController
end
def destroy
- tag = @repository.find_tag(params[:id])
-
- if tag && @repository.rm_tag(tag.name)
- Event.create_ref_event(@project, current_user, tag, 'rm', 'refs/tags')
- end
+ DeleteTagService.new(project, current_user).execute(params[:id])
respond_to do |format|
- format.html { redirect_to project_tags_path }
+ format.html do
+ redirect_to namespace_project_tags_path(@project.namespace,
+ @project)
+ end
format.js
end
end
diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb
deleted file mode 100644
index 7bb799eba6..0000000000
--- a/app/controllers/projects/team_members_controller.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-class Projects::TeamMembersController < Projects::ApplicationController
- # Authorize
- before_filter :authorize_admin_project!, except: :leave
-
- layout "project_settings"
-
- def index
- @group = @project.group
- @project_members = @project.project_members.order('access_level DESC')
- end
-
- def new
- @user_project_relation = project.project_members.new
- end
-
- def create
- users = User.where(id: params[:user_ids].split(','))
-
- @project.team << [users, params[:access_level]]
-
- if params[:redirect_to]
- redirect_to params[:redirect_to]
- else
- redirect_to project_team_index_path(@project)
- end
- end
-
- def update
- @user_project_relation = project.project_members.find_by(user_id: member)
- @user_project_relation.update_attributes(member_params)
-
- unless @user_project_relation.valid?
- flash[:alert] = "User should have at least one role"
- end
- redirect_to project_team_index_path(@project)
- end
-
- def destroy
- @user_project_relation = project.project_members.find_by(user_id: member)
- @user_project_relation.destroy
-
- respond_to do |format|
- format.html { redirect_to project_team_index_path(@project) }
- format.js { render nothing: true }
- end
- end
-
- def leave
- project.project_members.find_by(user_id: current_user).destroy
-
- respond_to do |format|
- format.html { redirect_to :back }
- format.js { render nothing: true }
- end
- end
-
- def apply_import
- giver = Project.find(params[:source_project_id])
- status = @project.team.import(giver)
- notice = status ? "Successfully imported" : "Import failed"
-
- redirect_to project_team_index_path(project), notice: notice
- end
-
- protected
-
- def member
- @member ||= User.find_by(username: params[:id])
- end
-
- def member_params
- params.require(:project_member).permit(:user_id, :access_level)
- end
-end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 4d033b3684..b23010bf59 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -1,10 +1,18 @@
# Controller for viewing a repository's file structure
-class Projects::TreeController < Projects::BaseTreeController
- def show
+class Projects::TreeController < Projects::ApplicationController
+ include ExtractsPath
+ before_filter :require_non_empty_project, except: [:new, :create]
+ before_filter :assign_ref_vars
+ before_filter :authorize_download_code!
+
+ def show
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
- redirect_to project_blob_path(@project, File.join(@ref, @path)) and return
+ redirect_to(
+ namespace_project_blob_path(@project.namespace, @project,
+ File.join(@ref, @path))
+ ) and return
else
return not_found!
end
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
new file mode 100644
index 0000000000..9020e86c44
--- /dev/null
+++ b/app/controllers/projects/uploads_controller.rb
@@ -0,0 +1,35 @@
+class Projects::UploadsController < Projects::ApplicationController
+ layout 'project'
+
+ before_filter :project
+
+ def create
+ link_to_file = ::Projects::UploadService.new(project, params[:file]).
+ execute
+
+ respond_to do |format|
+ if link_to_file
+ format.json do
+ render json: { link: link_to_file }
+ end
+ else
+ format.json do
+ render json: 'Invalid file.', status: :unprocessable_entity
+ end
+ end
+ end
+ end
+
+ def show
+ uploader = FileUploader.new(project, params[:secret])
+
+ return redirect_to uploader.url unless uploader.file_storage?
+
+ uploader.retrieve_from_store!(params[:filename])
+
+ return not_found! unless uploader.file.exists?
+
+ disposition = uploader.image? ? 'inline' : 'attachment'
+ send_file uploader.file.path, disposition: disposition
+ end
+end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 0e03956e73..643167947b 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -7,7 +7,7 @@ class Projects::WikisController < Projects::ApplicationController
before_filter :load_project_wiki
def pages
- @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(30)
+ @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE)
end
def show
@@ -16,16 +16,16 @@ class Projects::WikisController < Projects::ApplicationController
if @page
render 'show'
elsif file = @project_wiki.find_file(params[:id], params[:version_id])
- if file.on_disk?
- send_file file.on_disk_path, disposition: 'inline'
- else
- send_data(
- file.raw_data,
- type: file.mime_type,
- disposition: 'inline',
- filename: file.name
- )
- end
+ if file.on_disk?
+ send_file file.on_disk_path, disposition: 'inline'
+ else
+ send_data(
+ file.raw_data,
+ type: file.mime_type,
+ disposition: 'inline',
+ filename: file.name
+ )
+ end
else
return render('empty') unless can?(current_user, :write_wiki, @project)
@page = WikiPage.new(@project_wiki)
@@ -45,7 +45,7 @@ class Projects::WikisController < Projects::ApplicationController
return render('empty') unless can?(current_user, :write_wiki, @project)
if @page.update(content, format, message)
- redirect_to [@project, @page], notice: 'Wiki was successfully updated.'
+ redirect_to [@project.namespace.becomes(Namespace), @project, @page], notice: 'Wiki was successfully updated.'
else
render 'edit'
end
@@ -55,7 +55,10 @@ class Projects::WikisController < Projects::ApplicationController
@page = WikiPage.new(@project_wiki)
if @page.create(wiki_params)
- redirect_to project_wiki_path(@project, @page), notice: 'Wiki was successfully updated.'
+ redirect_to(
+ namespace_project_wiki_path(@project.namespace, @project, @page),
+ notice: 'Wiki was successfully updated.'
+ )
else
render action: "edit"
end
@@ -65,7 +68,10 @@ class Projects::WikisController < Projects::ApplicationController
@page = @project_wiki.find_page(params[:id])
unless @page
- redirect_to(project_wiki_path(@project, :home), notice: "Page not found")
+ redirect_to(
+ namespace_project_wiki_path(@project.namespace, @project, :home),
+ notice: "Page not found"
+ )
end
end
@@ -73,7 +79,10 @@ class Projects::WikisController < Projects::ApplicationController
@page = @project_wiki.find_page(params[:id])
@page.delete if @page
- redirect_to project_wiki_path(@project, :home), notice: "Page was successfully deleted"
+ redirect_to(
+ namespace_project_wiki_path(@project.namespace, @project, :home),
+ notice: "Page was successfully deleted"
+ )
end
def git_access
@@ -88,7 +97,7 @@ class Projects::WikisController < Projects::ApplicationController
@project_wiki.wiki
rescue ProjectWiki::CouldNotCreateWikiError => ex
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
- redirect_to @project
+ redirect_to project_path(@project)
return false
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index b3380a6ff2..0f28794b73 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,30 +1,34 @@
class ProjectsController < ApplicationController
+ prepend_before_filter :render_go_import, only: [:show]
skip_before_filter :authenticate_user!, only: [:show]
before_filter :project, except: [:new, :create]
before_filter :repository, except: [:new, :create]
# Authorize
- before_filter :authorize_read_project!, except: [:index, :new, :create]
- before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
- before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
+ before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
+ before_filter :set_title, only: [:new, :create]
+ before_filter :event_filter, only: :show
layout 'navless', only: [:new, :create, :fork]
- before_filter :set_title, only: [:new, :create]
def new
@project = Project.new
end
def edit
- render 'edit', layout: "project_settings"
+ render 'edit', layout: 'project_settings'
end
def create
@project = ::Projects::CreateService.new(current_user, project_params).execute
- flash[:notice] = 'Project was successfully created.' if @project.saved?
- respond_to do |format|
- format.js
+ if @project.saved?
+ redirect_to(
+ project_path(@project),
+ notice: 'Project was successfully created.'
+ )
+ else
+ render 'new'
end
end
@@ -34,146 +38,112 @@ class ProjectsController < ApplicationController
respond_to do |format|
if status
flash[:notice] = 'Project was successfully updated.'
- format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' }
+ format.html do
+ redirect_to(
+ edit_project_path(@project),
+ notice: 'Project was successfully updated.'
+ )
+ end
format.js
else
- format.html { render "edit", layout: "project_settings" }
+ format.html { render 'edit', layout: 'project_settings' }
format.js
end
end
end
def transfer
- ::Projects::TransferService.new(project, current_user, project_params).execute
+ transfer_params = params.permit(:new_namespace_id)
+ ::Projects::TransferService.new(project, current_user, transfer_params).execute
+ if @project.errors[:namespace_id].present?
+ flash[:alert] = @project.errors[:namespace_id].first
+ end
end
def show
if @project.import_in_progress?
- redirect_to import_project_path(@project)
+ redirect_to namespace_project_import_path(@project.namespace, @project)
return
end
- return authenticate_user! unless @project.public? || current_user
-
limit = (params[:limit] || 20).to_i
- @events = @project.events.recent
- @events = event_filter.apply_filter(@events)
- @events = @events.limit(limit).offset(params[:offset] || 0)
@show_star = !(current_user && current_user.starred?(@project))
respond_to do |format|
format.html do
- if @project.empty_repo?
- render "projects/empty", layout: user_layout
+ if @project.repository_exists?
+ if @project.empty_repo?
+ render 'projects/empty', layout: user_layout
+ else
+ @last_push = current_user.recent_push(@project.id) if current_user
+ render :show, layout: user_layout
+ end
else
- @last_push = current_user.recent_push(@project.id) if current_user
- render :show, layout: user_layout
+ render 'projects/no_repo', layout: user_layout
end
end
- format.json { pager_json("events/_events", @events.count) }
+
+ format.json do
+ @events = @project.events.recent
+ @events = event_filter.apply_filter(@events).with_associations
+ @events = @events.limit(limit).offset(params[:offset] || 0)
+ pager_json('events/_events', @events.count)
+ end
end
end
- def import
- if project.import_finished?
- redirect_to @project
- return
- end
- end
-
- def retry_import
- unless @project.import_failed?
- redirect_to import_project_path(@project)
- end
-
- @project.import_url = project_params[:import_url]
-
- if @project.save
- @project.reload
- @project.import_retry
- end
-
- redirect_to import_project_path(@project)
- end
-
def destroy
- return access_denied! unless can?(current_user, :remove_project, project)
+ return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
respond_to do |format|
format.html do
- flash[:alert] = "Project deleted."
+ flash[:alert] = 'Project deleted.'
- if request.referer.include?("/admin")
- redirect_to admin_projects_path
+ if request.referer.include?('/admin')
+ redirect_to admin_namespaces_projects_path
else
- redirect_to projects_dashboard_path
+ redirect_to dashboard_path
end
end
end
end
- def fork
- @forked_project = ::Projects::ForkService.new(project, current_user).execute
-
- respond_to do |format|
- format.html do
- if @forked_project.saved? && @forked_project.forked?
- redirect_to(@forked_project, notice: 'Project was successfully forked.')
- else
- @title = 'Fork project'
- render "fork"
- end
- end
- format.js
- end
- end
-
def autocomplete_sources
note_type = params['type']
note_id = params['type_id']
- participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id)
+ autocomplete = ::Projects::AutocompleteService.new(@project)
+ participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
+
@suggestions = {
- emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } },
- issues: @project.issues.select([:iid, :title, :description]),
- mergerequests: @project.merge_requests.select([:iid, :title, :description]),
+ emojis: autocomplete_emojis,
+ issues: autocomplete.issues,
+ mergerequests: autocomplete.merge_requests,
members: participants
}
respond_to do |format|
- format.json { render :json => @suggestions }
+ format.json { render json: @suggestions }
end
end
def archive
- return access_denied! unless can?(current_user, :archive_project, project)
- project.archive!
+ return access_denied! unless can?(current_user, :archive_project, @project)
+ @project.archive!
respond_to do |format|
- format.html { redirect_to @project }
+ format.html { redirect_to project_path(@project) }
end
end
def unarchive
- return access_denied! unless can?(current_user, :archive_project, project)
- project.unarchive!
+ return access_denied! unless can?(current_user, :archive_project, @project)
+ @project.unarchive!
respond_to do |format|
- format.html { redirect_to @project }
- end
- end
-
- def upload_image
- link_to_image = ::Projects::ImageService.new(repository, params, root_url).execute
-
- respond_to do |format|
- if link_to_image
- format.json { render json: { link: link_to_image } }
- else
- format.json { render json: "Invalid file.", status: :unprocessable_entity }
- end
+ format.html { redirect_to project_path(@project) }
end
end
@@ -183,30 +153,46 @@ class ProjectsController < ApplicationController
render json: { star_count: @project.star_count }
end
+ def markdown_preview
+ render text: view_context.markdown(params[:md_text])
+ end
+
private
- def upload_path
- base_dir = FileUploader.generate_dir
- File.join(repository.path_with_namespace, base_dir)
- end
-
- def accepted_images
- %w(png jpg jpeg gif)
- end
-
def set_title
@title = 'New Project'
end
def user_layout
- current_user ? "projects" : "public_projects"
+ current_user ? 'projects' : 'public_projects'
end
def project_params
params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
- :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id
+ :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar
)
end
+
+ def autocomplete_emojis
+ Rails.cache.fetch("autocomplete-emoji-#{Gemojione::VERSION}") do
+ Emoji.emojis.map do |name, emoji|
+ {
+ name: name,
+ path: view_context.image_url("emoji/#{emoji["unicode"]}.png")
+ }
+ end
+ end
+ end
+
+ def render_go_import
+ return unless params["go-get"] == "1"
+
+ @namespace = params[:namespace_id]
+ @id = params[:project_id] || params[:id]
+ @id = @id.gsub(/\.git\Z/, "")
+
+ render "go_import", layout: false
+ end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 6d3214b70a..38d116a4ee 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -1,6 +1,10 @@
class RegistrationsController < Devise::RegistrationsController
before_filter :signup_enabled?
+ def new
+ redirect_to(new_user_session_path)
+ end
+
def destroy
current_user.destroy
@@ -15,18 +19,20 @@ class RegistrationsController < Devise::RegistrationsController
super
end
- def after_sign_up_path_for(resource)
+ def after_sign_up_path_for(_resource)
new_user_session_path
end
- def after_inactive_sign_up_path_for(resource)
+ def after_inactive_sign_up_path_for(_resource)
new_user_session_path
end
private
def signup_enabled?
- redirect_to new_user_session_path unless Gitlab.config.gitlab.signup_enabled
+ unless current_application_settings.signup_enabled?
+ redirect_to(new_user_session_path)
+ end
end
def sign_up_params
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 1bdba75c5e..7b6982c507 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,16 +1,16 @@
class SessionsController < Devise::SessionsController
-
def new
- redirect_path = if request.referer.present? && (params['redirect_to_referer'] == 'yes')
- referer_uri = URI(request.referer)
- if referer_uri.host == Gitlab.config.gitlab.host
- referer_uri.path
- else
- request.fullpath
- end
- else
- request.fullpath
- end
+ redirect_path =
+ if request.referer.present? && (params['redirect_to_referer'] == 'yes')
+ referer_uri = URI(request.referer)
+ if referer_uri.host == Gitlab.config.gitlab.host
+ referer_uri.path
+ else
+ request.fullpath
+ end
+ else
+ request.fullpath
+ end
# Prevent a 'you are already signed in' message directly after signing:
# we should never redirect to '/users/sign_in' after signing in successfully.
@@ -18,6 +18,10 @@ class SessionsController < Devise::SessionsController
store_location_for(:redirect, redirect_path)
end
+ if Gitlab.config.ldap.enabled
+ @ldap_servers = Gitlab::LDAP::Config.servers
+ end
+
super
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 30fb4c5552..cd52556b20 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -9,14 +9,14 @@ class SnippetsController < ApplicationController
before_filter :set_title
- skip_before_filter :authenticate_user!, only: [:index, :user_index]
+ skip_before_filter :authenticate_user!, only: [:index, :user_index, :show, :raw]
respond_to :html
layout :determine_layout
def index
- @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(20)
+ @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(PER_PAGE)
end
def user_index
@@ -27,8 +27,8 @@ class SnippetsController < ApplicationController
@snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
user: @user,
- scope: params[:scope]}).
- page(params[:page]).per(20)
+ scope: params[:scope] }).
+ page(params[:page]).per(PER_PAGE)
if @user == current_user
render 'current_user_index'
@@ -42,25 +42,19 @@ class SnippetsController < ApplicationController
end
def create
- @snippet = PersonalSnippet.new(snippet_params)
- @snippet.author = current_user
+ @snippet = CreateSnippetService.new(nil, current_user,
+ snippet_params).execute
- if @snippet.save
- redirect_to snippet_path(@snippet)
- else
- respond_with @snippet
- end
+ respond_with @snippet.becomes(Snippet)
end
def edit
end
def update
- if @snippet.update_attributes(snippet_params)
- redirect_to snippet_path(@snippet)
- else
- respond_with @snippet
- end
+ UpdateSnippetService.new(nil, current_user, @snippet,
+ snippet_params).execute
+ respond_with @snippet.becomes(Snippet)
end
def show
@@ -79,7 +73,7 @@ class SnippetsController < ApplicationController
@snippet.content,
type: 'text/plain; charset=utf-8',
disposition: 'inline',
- filename: @snippet.file_name
+ filename: @snippet.sanitized_file_name
)
end
@@ -106,6 +100,7 @@ class SnippetsController < ApplicationController
def set_title
@title = 'Snippets'
+ @title_url = snippets_path
end
def snippet_params
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
new file mode 100644
index 0000000000..c5f3da54ea
--- /dev/null
+++ b/app/controllers/uploads_controller.rb
@@ -0,0 +1,71 @@
+class UploadsController < ApplicationController
+ skip_before_filter :authenticate_user!
+ before_filter :find_model, :authorize_access!
+
+ def show
+ uploader = @model.send(upload_mount)
+
+ unless uploader.file_storage?
+ return redirect_to uploader.url
+ end
+
+ unless uploader.file && uploader.file.exists?
+ return not_found!
+ end
+
+ disposition = uploader.image? ? 'inline' : 'attachment'
+ send_file uploader.file.path, disposition: disposition
+ end
+
+ private
+
+ def find_model
+ unless upload_model && upload_mount
+ return not_found!
+ end
+
+ @model = upload_model.find(params[:id])
+ end
+
+ def authorize_access!
+ authorized =
+ case @model
+ when Project
+ can?(current_user, :read_project, @model)
+ when Group
+ can?(current_user, :read_group, @model)
+ when Note
+ can?(current_user, :read_project, @model.project)
+ else
+ # No authentication required for user avatars.
+ true
+ end
+
+ return if authorized
+
+ if current_user
+ not_found!
+ else
+ authenticate_user!
+ end
+ end
+
+ def upload_model
+ upload_models = {
+ user: User,
+ project: Project,
+ note: Note,
+ group: Group
+ }
+
+ upload_models[params[:model].to_sym]
+ end
+
+ def upload_mount
+ upload_mounts = %w(avatar attachment file)
+
+ if upload_mounts.include?(params[:mounted_as])
+ params[:mounted_as]
+ end
+ end
+end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 0b442f5383..8a13394dba 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1,28 +1,43 @@
class UsersController < ApplicationController
- skip_before_filter :authenticate_user!, only: [:show]
+ skip_before_filter :authenticate_user!
+ before_filter :set_user
layout :determine_layout
def show
- @user = User.find_by_username!(params[:username])
-
- unless current_user || @user.public_profile?
- return authenticate_user!
- end
-
- # Projects user can view
- authorized_projects_ids = ProjectsFinder.new.execute(current_user).pluck(:id)
+ @contributed_projects = Project.
+ where(id: authorized_projects_ids & @user.contributed_projects_ids).
+ in_group_namespace.
+ includes(:namespace).
+ reject(&:forked?)
@projects = @user.personal_projects.
- where(id: authorized_projects_ids)
+ where(id: authorized_projects_ids).includes(:namespace)
# Collect only groups common for both users
@groups = @user.groups & GroupsFinder.new.execute(current_user)
# Get user activity feed for projects common for both users
@events = @user.recent_events.
- where(project_id: authorized_projects_ids).limit(20)
+ where(project_id: authorized_projects_ids).
+ with_associations.limit(30)
@title = @user.name
+ @title_url = user_path(@user)
+
+ respond_to do |format|
+ format.html
+ format.atom { render layout: false }
+ end
+ end
+
+ def calendar
+ projects = Project.where(id: authorized_projects_ids & @user.contributed_projects_ids)
+ calendar = Gitlab::CommitsCalendar.new(projects, @user)
+ @timestamps = calendar.timestamps
+ @starting_year = calendar.starting_year
+ @starting_month = calendar.starting_month
+
+ render 'calendar', layout: false
end
def determine_layout
@@ -32,4 +47,20 @@ class UsersController < ApplicationController
'public_users'
end
end
+
+ private
+
+ def set_user
+ @user = User.find_by_username!(params[:username])
+
+ unless current_user || @user.public_profile?
+ return authenticate_user!
+ end
+ end
+
+ def authorized_projects_ids
+ # Projects user can view
+ @authorized_projects_ids ||=
+ ProjectsFinder.new.execute(current_user).pluck(:id)
+ end
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 56c4f22120..088a766ed3 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -33,6 +33,7 @@ class IssuableFinder
items = by_search(items)
items = by_milestone(items)
items = by_assignee(items)
+ items = by_author(items)
items = by_label(items)
items = sort(items)
end
@@ -43,12 +44,12 @@ class IssuableFinder
table_name = klass.table_name
if project
- if project.public? || (current_user && current_user.can?(:read_project, project))
+ if Ability.abilities.allowed?(current_user, :read_project, project)
project.send(table_name)
else
[]
end
- elsif current_user && params[:authorized_only].presence
+ elsif current_user && params[:authorized_only].presence && !current_user_related?
klass.of_projects(current_user.authorized_projects).references(:project)
else
klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
@@ -125,6 +126,14 @@ class IssuableFinder
items
end
+ def by_author(items)
+ if params[:author_id].present?
+ items = items.where(author_id: (params[:author_id] == '0' ? nil : params[:author_id]))
+ end
+
+ items
+ end
+
def by_label(items)
if params[:label_name].present?
label_names = params[:label_name].split(",")
@@ -142,4 +151,8 @@ class IssuableFinder
def project
Project.where(id: params[:project_id]).first if params[:project_id].present?
end
+
+ def current_user_related?
+ params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
+ end
end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index bef82d7f0f..ab252821b5 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -7,20 +7,21 @@ class NotesFinder
# Default to 0 to remain compatible with old clients
last_fetched_at = Time.at(params.fetch(:last_fetched_at, 0).to_i)
- notes = case target_type
- when "commit"
- project.notes.for_commit_id(target_id).not_inline.fresh
- when "issue"
- project.issues.find(target_id).notes.inc_author.fresh
- when "merge_request"
- project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
- when "snippet", "project_snippet"
- project.snippets.find(target_id).notes.fresh
- else
- raise 'invalid target_type'
- end
+ notes =
+ case target_type
+ when "commit"
+ project.notes.for_commit_id(target_id).not_inline
+ when "issue"
+ project.issues.find(target_id).notes.inc_author
+ when "merge_request"
+ project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
+ when "snippet", "project_snippet"
+ project.snippets.find(target_id).notes
+ else
+ raise 'invalid target_type'
+ end
# Use overlapping intervals to avoid worrying about race conditions
- notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP)
+ notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP).fresh
end
end
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index fda375aca2..07b5759443 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -29,19 +29,21 @@ class SnippetsFinder
def by_user(current_user, user, scope)
snippets = user.snippets.fresh.non_expired
+ return snippets.are_public unless current_user
+
if user == current_user
- snippets = case scope
- when 'are_internal' then
- snippets.are_internal
- when 'are_private' then
- snippets.are_private
- when 'are_public' then
- snippets.are_public
- else
- snippets
- end
+ case scope
+ when 'are_internal' then
+ snippets.are_internal
+ when 'are_private' then
+ snippets.are_private
+ when 'are_public' then
+ snippets.are_public
+ else
+ snippets
+ end
else
- snippets = snippets.public_and_internal
+ snippets.public_and_internal
end
end
diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb
index 32d7968924..a79bd47d98 100644
--- a/app/finders/trending_projects_finder.rb
+++ b/app/finders/trending_projects_finder.rb
@@ -8,7 +8,7 @@ class TrendingProjectsFinder
# for period of time - ex. month
projects.joins(:notes).where('notes.created_at > ?', start_date).
select("projects.*, count(notes.id) as ncount").
- group("projects.id").order("ncount DESC")
+ group("projects.id").reorder("ncount DESC")
end
private
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index 96e5d43a36..bb8d568380 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -14,4 +14,8 @@ module AppearancesHelper
def brand_text
nil
end
+
+ def brand_header_logo
+ image_tag 'logo-white.png'
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 021bd0a494..8ed6d59c20 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -5,8 +5,9 @@ module ApplicationHelper
COLOR_SCHEMES = {
1 => 'white',
2 => 'dark',
- 3 => 'solarized-dark',
- 4 => 'monokai',
+ 3 => 'solarized-light',
+ 4 => 'solarized-dark',
+ 5 => 'monokai',
}
COLOR_SCHEMES.default = 'white'
@@ -49,12 +50,39 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name }
end
- def group_icon(group_path)
- group = Group.find_by(path: group_path)
- if group && group.avatar.present?
- group.avatar.url
- else
- image_path('no_group_avatar.png')
+ def project_icon(project_id, options = {})
+ project =
+ if project_id.is_a?(Project)
+ project = project_id
+ else
+ Project.find_with_namespace(project_id)
+ end
+
+ if project.avatar_url
+ image_tag project.avatar_url, options
+ else # generated icon
+ project_identicon(project, options)
+ end
+ end
+
+ def project_identicon(project, options = {})
+ allowed_colors = {
+ red: 'FFEBEE',
+ purple: 'F3E5F5',
+ indigo: 'E8EAF6',
+ blue: 'E3F2FD',
+ teal: 'E0F2F1',
+ orange: 'FBE9E7',
+ gray: 'EEEEEE'
+ }
+
+ options[:class] ||= ''
+ options[:class] << ' identicon'
+ bg_key = project.id % 7
+ style = "background-color: ##{ allowed_colors.values[bg_key] }; color: #555"
+
+ content_tag(:div, class: options[:class], style: style) do
+ project.name[0, 1].upcase
end
end
@@ -81,24 +109,24 @@ module ApplicationHelper
if project.repo_exists?
time_ago_with_tooltip(project.repository.commit.committed_date)
else
- "Never"
+ 'Never'
end
rescue
- "Never"
+ 'Never'
end
def grouped_options_refs
repository = @project.repository
options = [
- ["Branches", repository.branch_names],
- ["Tags", VersionSorter.rsort(repository.tag_names)]
+ ['Branches', repository.branch_names],
+ ['Tags', VersionSorter.rsort(repository.tag_names)]
]
# If reference is commit id - we should add it to branch/tag selectbox
if(@ref && !options.flatten.include?(@ref) &&
@ref =~ /^[0-9a-zA-Z]{6,52}$/)
- options << ["Commit", [@ref]]
+ options << ['Commit', [@ref]]
end
grouped_options_for_select(options, @ref || @project.default_branch)
@@ -114,6 +142,10 @@ module ApplicationHelper
Gitlab::Theme.css_class_by_id(current_user.try(:theme_id))
end
+ def theme_type
+ Gitlab::Theme.type_css_class_by_id(current_user.try(:theme_id))
+ end
+
def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
@@ -156,7 +188,7 @@ module ApplicationHelper
path = controller.controller_path.split('/')
namespace = path.first if path.second
- [namespace, controller.controller_name, controller.action_name].compact.join(":")
+ [namespace, controller.controller_name, controller.action_name].compact.join(':')
end
# shortcut for gitlab config
@@ -171,13 +203,13 @@ module ApplicationHelper
def search_placeholder
if @project && @project.persisted?
- "Search in this project"
+ 'Search in this project'
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
- "Search in this group"
+ 'Search in this group'
else
- "Search"
+ 'Search'
end
end
@@ -185,24 +217,10 @@ module ApplicationHelper
BroadcastMessage.current
end
- def highlight_js(&block)
- string = capture(&block)
-
- content_tag :div, class: "highlighted-data #{user_color_scheme_class}" do
- content_tag :div, class: 'highlight' do
- content_tag :pre do
- content_tag :code do
- string.html_safe
- end
- end
- end
- end
- end
-
def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago')
capture_haml do
haml_tag :time, date.to_s,
- class: html_class, datetime: date.getutc.iso8601, title: date.stamp("Aug 21, 2011 9:23pm"),
+ class: html_class, datetime: date.getutc.iso8601, title: date.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement }
haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
@@ -224,15 +242,6 @@ module ApplicationHelper
Gitlab::MarkdownHelper.gitlab_markdown?(filename)
end
- def spinner(text = nil, visible = false)
- css_class = "loading"
- css_class << " hide" unless visible
-
- content_tag :div, class: css_class do
- content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text
- end
- end
-
def link_to(name = nil, options = nil, html_options = nil, &block)
begin
uri = URI(options)
@@ -243,17 +252,17 @@ module ApplicationHelper
absolute_uri = nil
end
- # Add "nofollow" only to external links
+ # Add 'nofollow' only to external links
if host && host != Gitlab.config.gitlab.host && absolute_uri
if html_options
if html_options[:rel]
- html_options[:rel] << " nofollow"
+ html_options[:rel] << ' nofollow'
else
- html_options.merge!(rel: "nofollow")
+ html_options.merge!(rel: 'nofollow')
end
else
html_options = Hash.new
- html_options[:rel] = "nofollow"
+ html_options[:rel] = 'nofollow'
end
end
@@ -271,4 +280,42 @@ module ApplicationHelper
def promo_url
'https://' + promo_host
end
+
+ def page_filter_path(options={})
+ exist_opts = {
+ state: params[:state],
+ scope: params[:scope],
+ label_name: params[:label_name],
+ milestone_id: params[:milestone_id],
+ assignee_id: params[:assignee_id],
+ author_id: params[:author_id],
+ sort: params[:sort],
+ }
+
+ options = exist_opts.merge(options)
+
+ path = request.path
+ path << "?#{options.to_param}"
+ path
+ end
+
+ def outdated_browser?
+ browser.ie? && browser.version.to_i < 10
+ end
+
+ def path_to_key(key, admin = false)
+ if admin
+ admin_user_key_path(@user, key)
+ else
+ profile_key_path(key)
+ end
+ end
+
+ def nav_sidebar_class
+ if nav_menu_collapsed?
+ "page-sidebar-collapsed"
+ else
+ "page-sidebar-expanded"
+ end
+ end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
new file mode 100644
index 0000000000..241d6075c9
--- /dev/null
+++ b/app/helpers/application_settings_helper.rb
@@ -0,0 +1,38 @@
+module ApplicationSettingsHelper
+ def gravatar_enabled?
+ current_application_settings.gravatar_enabled?
+ end
+
+ def twitter_sharing_enabled?
+ current_application_settings.twitter_sharing_enabled?
+ end
+
+ def signup_enabled?
+ current_application_settings.signup_enabled?
+ end
+
+ def signin_enabled?
+ current_application_settings.signin_enabled?
+ end
+
+ def extra_sign_in_text
+ current_application_settings.sign_in_text
+ end
+
+ # Return a group of checkboxes that use Bootstrap's button plugin for a
+ # toggle button effect.
+ def restricted_level_checkboxes(help_block_id)
+ Gitlab::VisibilityLevel.options.map do |name, level|
+ checked = restricted_visibility_levels(true).include?(level)
+ css_class = 'btn btn-primary'
+ css_class += ' active' if checked
+ checkbox_name = 'application_setting[restricted_visibility_levels][]'
+
+ label_tag(checkbox_name, class: css_class) do
+ check_box_tag(checkbox_name, level, checked,
+ autocomplete: 'off',
+ 'aria-describedby' => help_block_id) + name
+ end
+ end
+ end
+end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 11fbf1baae..798d62b3a0 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -1,18 +1,64 @@
module BlobHelper
- def highlightjs_class(blob_name)
- if blob_name.include?('.')
- ext = blob_name.split('.').last
- return 'language-' + ext
- else
- if no_highlight_files.include?(blob_name.downcase)
- 'no-highlight'
- else
- blob_name.downcase
- end
+ def highlight(blob_name, blob_content, nowrap = false)
+ formatter = Rugments::Formatters::HTML.new(
+ nowrap: nowrap,
+ cssclass: 'code highlight',
+ lineanchors: true,
+ lineanchorsid: 'LC'
+ )
+
+ begin
+ lexer = Rugments::Lexer.guess(filename: blob_name, source: blob_content)
+ rescue Rugments::Lexer::AmbiguousGuess
+ lexer = Rugments::Lexers::PlainText
end
+
+ formatter.format(lexer.lex(blob_content)).html_safe
end
def no_highlight_files
- %w(credits changelog copying copyright license authors)
+ %w(credits changelog news copying copyright license authors)
+ end
+
+ def edit_blob_link(project, ref, path, options = {})
+ blob =
+ begin
+ project.repository.blob_at(ref, path)
+ rescue
+ nil
+ end
+
+ if blob && blob.text?
+ text = 'Edit'
+ after = options[:after] || ''
+ from_mr = options[:from_merge_request_id]
+ link_opts = {}
+ link_opts[:from_merge_request_id] = from_mr if from_mr
+ cls = 'btn btn-small'
+ if allowed_tree_edit?(project, ref)
+ link_to(text,
+ namespace_project_edit_blob_path(project.namespace, project,
+ tree_join(ref, path),
+ link_opts),
+ class: cls
+ )
+ else
+ content_tag :span, text, class: cls + ' disabled'
+ end + after.html_safe
+ else
+ ''
+ end
+ end
+
+ def leave_edit_message
+ "Leave edit mode?\nAll unsaved changes will be lost."
+ end
+
+ def editing_preview_title(filename)
+ if Gitlab::MarkdownHelper.previewable?(filename)
+ 'Preview'
+ else
+ 'Preview changes'
+ end
end
end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index 2ec2cc9615..4a5edf6d10 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -11,12 +11,7 @@ module BranchesHelper
def can_push_branch?(project, branch_name)
return false unless project.repository.branch_names.include?(branch_name)
- action = if project.protected_branch?(branch_name)
- :push_code_to_protected_branches
- else
- :push_code
- end
-
- current_user.can?(action, project)
+
+ ::Gitlab::GitAccess.can_push_to_branch?(current_user, project, branch_name)
end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index cab2984a4c..5aae697e2f 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -37,16 +37,26 @@ module CommitsHelper
# Add the root project link and the arrow icon
crumbs = content_tag(:li) do
- link_to(@project.path, project_commits_path(@project, @ref))
+ link_to(
+ @project.path,
+ namespace_project_commits_path(@project.namespace, @project, @ref)
+ )
end
if @path
parts = @path.split('/')
parts.each_with_index do |part, i|
- crumbs += content_tag(:li) do
+ crumbs << content_tag(:li) do
# The text is just the individual part, but the link needs all the parts before it
- link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/')))
+ link_to(
+ part,
+ namespace_project_commits_path(
+ @project.namespace,
+ @project,
+ tree_join(@ref, parts[0..i].join('/'))
+ )
+ )
end
end
end
@@ -62,18 +72,55 @@ module CommitsHelper
# Returns the sorted alphabetically links to branches, separated by a comma
def commit_branches_links(project, branches)
- branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe
+ branches.sort.map do |branch|
+ link_to(
+ namespace_project_tree_path(project.namespace, project, branch)
+ ) do
+ content_tag :span, class: 'label label-gray' do
+ icon('code-fork') + ' ' + branch
+ end
+ end
+ end.join(" ").html_safe
+ end
+
+ # Returns the sorted links to tags, separated by a comma
+ def commit_tags_links(project, tags)
+ sorted = VersionSorter.rsort(tags)
+ sorted.map do |tag|
+ link_to(
+ namespace_project_commits_path(project.namespace, project,
+ project.repository.find_tag(tag).name)
+ ) do
+ content_tag :span, class: 'label label-gray' do
+ icon('tag') + ' ' + tag
+ end
+ end
+ end.join(" ").html_safe
end
def link_to_browse_code(project, commit)
if current_controller?(:projects, :commits)
if @repo.blob_at(commit.id, @path)
- return link_to "Browse File »", project_blob_path(project, tree_join(commit.id, @path)), class: "pull-right"
+ return link_to(
+ "Browse File »",
+ namespace_project_blob_path(project.namespace, project,
+ tree_join(commit.id, @path)),
+ class: "pull-right"
+ )
elsif @path.present?
- return link_to "Browse Dir »", project_tree_path(project, tree_join(commit.id, @path)), class: "pull-right"
+ return link_to(
+ "Browse Dir »",
+ namespace_project_tree_path(project.namespace, project,
+ tree_join(commit.id, @path)),
+ class: "pull-right"
+ )
end
end
- link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right"
+ link_to(
+ "Browse Code »",
+ namespace_project_tree_path(project.namespace, project, commit),
+ class: "pull-right"
+ )
end
protected
@@ -87,19 +134,20 @@ module CommitsHelper
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
- source_name = commit.send "#{options[:source]}_name".to_sym
- source_email = commit.send "#{options[:source]}_email".to_sym
+ source_name = clean(commit.send "#{options[:source]}_name".to_sym)
+ source_email = clean(commit.send "#{options[:source]}_email".to_sym)
user = User.find_for_commit(source_email, source_name)
person_name = user.nil? ? source_name : user.name
person_email = user.nil? ? source_email : user.email
- text = if options[:avatar]
- avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
- %Q{#{avatar} #{person_name} }
- else
- person_name
- end
+ text =
+ if options[:avatar]
+ avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
+ %Q{#{avatar} #{person_name} }
+ else
+ person_name
+ end
options = {
class: "commit-#{options[:source]}-link has_tooltip",
@@ -114,10 +162,21 @@ module CommitsHelper
end
def view_file_btn(commit_sha, diff, project)
- link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)),
- class: 'btn btn-small view-file js-view-file' do
+ link_to(
+ namespace_project_blob_path(project.namespace, project,
+ tree_join(commit_sha, diff.new_path)),
+ class: 'btn btn-small view-file js-view-file'
+ ) do
raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id')
end
end
+
+ def truncate_sha(sha)
+ Commit.truncate_sha(sha)
+ end
+
+ def clean(string)
+ Sanitize.clean(string, remove_contents: true)
+ end
end
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 5ff19b8829..01847c6b80 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -1,7 +1,7 @@
module CompareHelper
def compare_to_mr_button?
@project.merge_requests_enabled &&
- params[:from].present? &&
+ params[:from].present? &&
params[:to].present? &&
@repository.branch_names.include?(params[:from]) &&
@repository.branch_names.include?(params[:to]) &&
@@ -10,6 +10,13 @@ module CompareHelper
end
def compare_mr_path
- new_project_merge_request_path(@project, merge_request: {source_branch: params[:to], target_branch: params[:from]})
+ new_namespace_project_merge_request_path(
+ @project.namespace,
+ @project,
+ merge_request: {
+ source_branch: params[:to],
+ target_branch: params[:from]
+ }
+ )
end
end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index acc0eeb76b..c25b54eadc 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -1,67 +1,9 @@
module DashboardHelper
- def filter_path(entity, options={})
- exist_opts = {
- state: params[:state],
- scope: params[:scope],
- project_id: params[:project_id],
- }
-
- options = exist_opts.merge(options)
-
- path = request.path
- path << "?#{options.to_param}"
- path
+ def assigned_issues_dashboard_path
+ issues_dashboard_path(assignee_id: current_user.id)
end
- def entities_per_project(project, entity)
- case entity.to_sym
- when :issue then @issues.where(project_id: project.id)
- when :merge_request then @merge_requests.where(target_project_id: project.id)
- else
- []
- end.count
- end
-
- def projects_dashboard_filter_path(options={})
- exist_opts = {
- sort: params[:sort],
- scope: params[:scope],
- group: params[:group],
- }
-
- options = exist_opts.merge(options)
-
- path = request.path
- path << "?#{options.to_param}"
- path
- end
-
- def assigned_entities_count(current_user, entity, scope = nil)
- items = current_user.send('assigned_' + entity.pluralize)
- get_count(items, scope)
- end
-
- def authored_entities_count(current_user, entity, scope = nil)
- items = current_user.send(entity.pluralize)
- get_count(items, scope)
- end
-
- def authorized_entities_count(current_user, entity, scope = nil)
- items = entity.classify.constantize
- get_count(items, scope, true, current_user)
- end
-
- protected
-
- def get_count(items, scope, get_authorized = false, current_user = nil)
- items = items.opened
- if scope.kind_of?(Group)
- items = items.of_group(scope)
- elsif scope.kind_of?(Project)
- items = items.of_projects(scope)
- elsif get_authorized
- items = items.of_projects(current_user.authorized_projects)
- end
- items.count
+ def assigned_mrs_dashboard_path
+ merge_requests_dashboard_path(assignee_id: current_user.id)
end
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index cb50d89cba..f81504991d 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -117,4 +117,37 @@ module DiffHelper
[comments_left, comments_right]
end
+
+ def inline_diff_btn
+ params_copy = params.dup
+ params_copy[:view] = 'inline'
+
+ link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do
+ 'Inline'
+ end
+ end
+
+ def parallel_diff_btn
+ params_copy = params.dup
+ params_copy[:view] = 'parallel'
+
+ link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do
+ 'Side-by-side'
+ end
+ end
+
+ def submodule_link(blob, ref)
+ tree, commit = submodule_links(blob, ref)
+ commit_id = if commit.nil?
+ blob.id[0..10]
+ else
+ link_to "#{blob.id[0..10]}", commit
+ end
+
+ [
+ content_tag(:span, link_to(truncate(blob.name, length: 40), tree)),
+ '@',
+ content_tag(:span, commit_id, class: 'monospace'),
+ ].join(' ').html_safe
+ end
end
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
new file mode 100644
index 0000000000..08476f8516
--- /dev/null
+++ b/app/helpers/emails_helper.rb
@@ -0,0 +1,67 @@
+require 'html/pipeline'
+require 'html/pipeline/gitlab'
+
+module EmailsHelper
+
+ # Google Actions
+ # https://developers.google.com/gmail/markup/reference/go-to-action
+ def email_action(url)
+ name = action_title(url)
+ if name
+ data = {
+ "@context" => "http://schema.org",
+ "@type" => "EmailMessage",
+ "action" => {
+ "@type" => "ViewAction",
+ "name" => name,
+ "url" => url,
+ }
+ }
+
+ content_tag :script, type: 'application/ld+json' do
+ data.to_json.html_safe
+ end
+ end
+ end
+
+ def action_title(url)
+ return unless url
+ ["merge_requests", "issues", "commit"].each do |action|
+ if url.split("/").include?(action)
+ return "View #{action.humanize.singularize}"
+ end
+ end
+ end
+
+ def add_email_highlight_css
+ Rugments::Themes::Github.render(scope: '.highlight')
+ end
+
+ def color_email_diff(diffcontent)
+ formatter = Rugments::Formatters::HTML.new(cssclass: 'highlight')
+ lexer = Rugments::Lexers::Diff.new
+ raw formatter.format(lexer.lex(diffcontent))
+ end
+
+ def replace_image_links_with_base64(text, project)
+ # Used pipelines in GitLab:
+ # GitlabEmailImageFilter - replaces images that have been uploaded as attachments with inline images in emails.
+ #
+ # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
+ filters = [
+ HTML::Pipeline::Gitlab::GitlabEmailImageFilter
+ ]
+
+ context = {
+ base_url: File.join(Gitlab.config.gitlab.url, project.path_with_namespace, 'uploads'),
+ upload_path: File.join(Rails.root, 'public', 'uploads', project.path_with_namespace),
+ }
+
+ pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
+
+ result = pipeline.call(text, context)
+ text = result[:output].to_html(save_with: 0)
+
+ text.html_safe
+ end
+end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 6aeab7bb8c..779cebc013 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -10,70 +10,87 @@ module EventsHelper
end
def event_action_name(event)
- target = if event.target_type
- event.target_type.titleize.downcase
- else
- 'project'
- end
+ target = if event.target_type
+ if event.note?
+ event.note_target_type
+ else
+ event.target_type.titleize.downcase
+ end
+ else
+ 'project'
+ end
[event.action_name, target].join(" ")
end
def event_filter_link(key, tooltip)
key = key.to_s
- inactive = if @event_filter.active? key
- nil
- else
- 'inactive'
- end
+ active = if @event_filter.active? key
+ 'active'
+ end
- content_tag :div, class: "filter_icon #{inactive}" do
- link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
- content_tag :i, nil, class: icon_for_event[key]
+ content_tag :li, class: "filter_icon #{active}" do
+ link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => 'Filter by ' + tooltip.downcase do
+ icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip)
end
end
end
def icon_for_event
{
- EventFilter.push => 'fa fa-upload',
- EventFilter.merged => 'fa fa-check-square-o',
- EventFilter.comments => 'fa fa-comments',
- EventFilter.team => 'fa fa-user',
+ EventFilter.push => 'upload',
+ EventFilter.merged => 'check-square-o',
+ EventFilter.comments => 'comments',
+ EventFilter.team => 'user',
}
end
def event_feed_title(event)
- if event.issue?
- "#{event.author_name} #{event.action_name} issue ##{event.target_iid}: #{event.issue_title} at #{event.project_name}"
- elsif event.merge_request?
- "#{event.author_name} #{event.action_name} MR ##{event.target_iid}: #{event.merge_request_title} at #{event.project_name}"
- elsif event.push?
- "#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}"
- elsif event.membership_changed?
- "#{event.author_name} #{event.action_name} #{event.project_name}"
- elsif event.note? && event.note_commit?
- "#{event.author_name} commented on #{event.note_target_type} #{event.note_short_commit_id} at #{event.project_name}"
- elsif event.note?
- "#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}"
- else
- ""
+ words = []
+ words << event.author_name
+ words << event_action_name(event)
+
+ if event.push?
+ words << event.ref_type
+ words << event.ref_name
+ words << "at"
+ elsif event.commented?
+ if event.note_commit?
+ words << event.note_short_commit_id
+ else
+ words << "##{truncate event.note_target_iid}"
+ end
+ words << "at"
+ elsif event.target
+ words << "##{event.target_iid}:"
+ words << event.target.title if event.target.respond_to?(:title)
+ words << "at"
end
+
+ words << event.project_name
+
+ words.join(" ")
end
def event_feed_url(event)
if event.issue?
- project_issue_url(event.project, event.issue)
+ namespace_project_issue_url(event.project.namespace, event.project,
+ event.issue)
elsif event.merge_request?
- project_merge_request_url(event.project, event.merge_request)
+ namespace_project_merge_request_url(event.project.namespace,
+ event.project, event.merge_request)
elsif event.note? && event.note_commit?
- project_commit_url(event.project, event.note_target)
+ namespace_project_commit_url(event.project.namespace, event.project,
+ event.note_target)
elsif event.note?
if event.note_target
if event.note_commit?
- project_commit_path(event.project, event.note_commit_id, anchor: dom_id(event.target))
+ namespace_project_commit_path(event.project.namespace, event.project,
+ event.note_commit_id,
+ anchor: dom_id(event.target))
elsif event.note_project_snippet?
- project_snippet_path(event.project, event.note_target)
+ namespace_project_snippet_path(event.project.namespace,
+ event.project, event.note_target)
else
event_note_target_path(event)
end
@@ -81,12 +98,16 @@ module EventsHelper
elsif event.push?
if event.push_with_commits?
if event.commits_count > 1
- project_compare_url(event.project, from: event.commit_from, to: event.commit_to)
+ namespace_project_compare_url(event.project.namespace, event.project,
+ from: event.commit_from, to:
+ event.commit_to)
else
- project_commit_url(event.project, id: event.commit_to)
+ namespace_project_commit_url(event.project.namespace, event.project,
+ id: event.commit_to)
end
else
- project_commits_url(event.project, event.ref_name)
+ namespace_project_commits_url(event.project.namespace, event.project,
+ event.ref_name)
end
end
end
@@ -98,8 +119,6 @@ module EventsHelper
render "events/event_push", event: event
elsif event.merge_request?
render "events/event_merge_request", merge_request: event.merge_request
- elsif event.push?
- render "events/event_push", event: event
elsif event.note?
render "events/event_note", note: event.note
end
@@ -107,20 +126,30 @@ module EventsHelper
def event_note_target_path(event)
if event.note? && event.note_commit?
- project_commit_path(event.project, event.note_target)
+ namespace_project_commit_path(event.project.namespace, event.project,
+ event.note_target)
else
- polymorphic_path([event.project, event.note_target], anchor: dom_id(event.target))
+ polymorphic_path([event.project.namespace.becomes(Namespace),
+ event.project, event.note_target],
+ anchor: dom_id(event.target))
end
end
def event_note_title_html(event)
if event.note_target
if event.note_commit?
- link_to project_commit_path(event.project, event.note_commit_id, anchor: dom_id(event.target)), class: "commit_short_id" do
+ link_to(
+ namespace_project_commit_path(event.project.namespace, event.project,
+ event.note_commit_id,
+ anchor: dom_id(event.target)),
+ class: "commit_short_id"
+ ) do
"#{event.note_target_type} #{event.note_short_commit_id}"
end
elsif event.note_project_snippet?
- link_to(project_snippet_path(event.project, event.note_target)) do
+ link_to(namespace_project_snippet_path(event.project.namespace,
+ event.project,
+ event.note_target)) do
"#{event.note_target_type} ##{truncate event.note_target_id}"
end
else
@@ -136,9 +165,8 @@ module EventsHelper
end
def event_note(text)
- text = first_line_in_markdown(text)
- text = truncate(text, length: 150)
- sanitize(markdown(text), tags: %w(a img b pre p))
+ text = first_line_in_markdown(text, 150)
+ sanitize(text, tags: %w(a img b pre code p span))
end
def event_commit_title(message)
@@ -146,4 +174,26 @@ module EventsHelper
rescue
"--broken encoding"
end
+
+ def event_to_atom(xml, event)
+ if event.proper?
+ xml.entry do
+ event_link = event_feed_url(event)
+ event_title = event_feed_title(event)
+ event_summary = event_feed_summary(event)
+
+ xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
+ xml.link href: event_link
+ xml.title truncate(event_title, length: 80)
+ xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+ xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email)
+ xml.author do |author|
+ xml.name event.author_name
+ xml.email event.author_email
+ end
+
+ xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? }
+ end
+ end
+ end
end
diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb
new file mode 100644
index 0000000000..7616fe6bad
--- /dev/null
+++ b/app/helpers/explore_helper.rb
@@ -0,0 +1,17 @@
+module ExploreHelper
+ def explore_projects_filter_path(options={})
+ exist_opts = {
+ sort: params[:sort],
+ scope: params[:scope],
+ group: params[:group],
+ tag: params[:tag],
+ visibility_level: params[:visibility_level],
+ }
+
+ options = exist_opts.merge(options)
+
+ path = request.path
+ path << "?#{options.to_param}"
+ path
+ end
+end
diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb
new file mode 100644
index 0000000000..0968495523
--- /dev/null
+++ b/app/helpers/git_helper.rb
@@ -0,0 +1,5 @@
+module GitHelper
+ def strip_gpg_signature(text)
+ text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
+ end
+end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 3a0e8bcda4..7bafbbd5f3 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -31,7 +31,9 @@ module GitlabMarkdownHelper
def markdown(text, options={})
unless (@markdown and options == @options)
@options = options
- gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self, {
+ gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self,
+ user_color_scheme_class,
+ {
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
with_toc_data: true,
safe_links_only: true
@@ -50,12 +52,14 @@ module GitlabMarkdownHelper
@markdown.render(sanitize_html(text)).html_safe
end
- def first_line_in_markdown(text)
- line = text.split("\n").detect do |i|
- i.present? && markdown(i).present?
- end
- line += '...' unless line.nil?
- line
+ # Return the first line of +text+, up to +max_chars+, after parsing the line
+ # as Markdown. HTML tags in the parsed output are not counted toward the
+ # +max_chars+ limit. If the length limit falls within a tag's contents, then
+ # the tag contents are truncated without removing the closing tag.
+ def first_line_in_markdown(text, max_chars = nil)
+ md = markdown(text).strip
+
+ truncate_visible(md, max_chars || md.length) if md.present?
end
def render_wiki_content(wiki_page)
@@ -107,7 +111,7 @@ module GitlabMarkdownHelper
end
def link_to_ignore?(link)
- if link =~ /\#\w+/
+ if link =~ /\A\#\w+/
# ignore anchors like
true
else
@@ -116,13 +120,14 @@ module GitlabMarkdownHelper
end
def ignored_protocols
- ["http://","https://", "ftp://", "mailto:"]
+ ["http://","https://", "ftp://", "mailto:", "smb://"]
end
- def rebuild_path(path)
- path.gsub!(/(#.*)/, "")
+ def rebuild_path(file_path)
+ file_path = file_path.dup
+ file_path.gsub!(/(#.*)/, "")
id = $1 || ""
- file_path = relative_file_path(path)
+ file_path = relative_file_path(file_path)
file_path = sanitize_slashes(file_path)
[
@@ -203,4 +208,64 @@ module GitlabMarkdownHelper
def correct_ref
@ref ? @ref : "master"
end
+
+ private
+
+ # Return +text+, truncated to +max_chars+ characters, excluding any HTML
+ # tags.
+ def truncate_visible(text, max_chars)
+ doc = Nokogiri::HTML.fragment(text)
+ content_length = 0
+ truncated = false
+
+ doc.traverse do |node|
+ if node.text? || node.content.empty?
+ if truncated
+ node.remove
+ next
+ end
+
+ # Handle line breaks within a node
+ if node.content.strip.lines.length > 1
+ node.content = "#{node.content.lines.first.chomp}..."
+ truncated = true
+ end
+
+ num_remaining = max_chars - content_length
+ if node.content.length > num_remaining
+ node.content = node.content.truncate(num_remaining)
+ truncated = true
+ end
+ content_length += node.content.length
+ end
+
+ truncated = truncate_if_block(node, truncated)
+ end
+
+ doc.to_html
+ end
+
+ # Used by #truncate_visible. If +node+ is the first block element, and the
+ # text hasn't already been truncated, then append "..." to the node contents
+ # and return true. Otherwise return false.
+ def truncate_if_block(node, truncated)
+ if node.element? && node.description.block? && !truncated
+ node.content = "#{node.content}..." if node.next_sibling
+ true
+ else
+ truncated
+ end
+ end
+
+ def cross_project_reference(project, entity)
+ path = project.path_with_namespace
+
+ if entity.kind_of?(Issue)
+ [path, entity.iid].join('#')
+ elsif entity.kind_of?(MergeRequest)
+ [path, entity.iid].join('!')
+ else
+ raise 'Not supported type'
+ end
+ end
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
new file mode 100644
index 0000000000..b005cb8e41
--- /dev/null
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -0,0 +1,52 @@
+# Shorter routing method for project and project items
+# Since update to rails 4.1.9 we are now allowed to use `/` in project routing
+# so we use nested routing for project resources which include project and
+# project namespace. To avoid writing long methods every time we define shortcuts for
+# some of routing.
+#
+# For example instead of this:
+#
+# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request)
+#
+# We can simply use shortcut:
+#
+# merge_request_path(merge_request)
+#
+module GitlabRoutingHelper
+ def project_path(project, *args)
+ namespace_project_path(project.namespace, project, *args)
+ end
+
+ def edit_project_path(project, *args)
+ edit_namespace_project_path(project.namespace, project, *args)
+ end
+
+ def issue_path(entity, *args)
+ namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
+ end
+
+ def merge_request_path(entity, *args)
+ namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
+ end
+
+ def project_url(project, *args)
+ namespace_project_url(project.namespace, project, *args)
+ end
+
+ def edit_project_url(project, *args)
+ edit_namespace_project_url(project.namespace, project, *args)
+ end
+
+ def issue_url(entity, *args)
+ namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args)
+ end
+
+ def merge_request_url(entity, *args)
+ namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args)
+ end
+
+ def project_snippet_url(entity, *args)
+ namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args)
+
+ end
+end
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index 7cb1b6f8d1..e1dda20de8 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -1,10 +1,10 @@
module GraphHelper
def get_refs(repo, commit)
refs = ""
- refs += commit.ref_names(repo).join(" ")
+ refs << commit.ref_names(repo).join(' ')
# append note count
- refs += "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
+ refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
refs
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 0dc53dedeb..2d0d0b494f 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -6,7 +6,7 @@ module GroupsHelper
def leave_group_message(group)
"Are you sure you want to leave \"#{group}\" group?"
end
-
+
def should_user_see_group_roles?(user, group)
if user
user.is_admin? || group.members.exists?(user_id: user.id)
@@ -33,15 +33,23 @@ module GroupsHelper
title
end
- def group_filter_path(entity, options={})
- exist_opts = {
- status: params[:status]
- }
+ def group_settings_page?
+ if current_controller?('groups')
+ current_action?('edit') || current_action?('projects')
+ else
+ false
+ end
+ end
- options = exist_opts.merge(options)
+ def group_icon(group)
+ if group.is_a?(String)
+ group = Group.find_by(path: group)
+ end
- path = request.path
- path << "?#{options.to_param}"
- path
+ if group && group.avatar.present?
+ group.avatar.url
+ else
+ image_path('no_group_avatar.png')
+ end
end
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index aaa8f8d007..18260f0ed4 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -1,21 +1,39 @@
module IconsHelper
+ # Creates an icon tag given icon name(s) and possible icon modifiers.
+ #
+ # Right now this method simply delegates directly to `fa_icon` from the
+ # font-awesome-rails gem, but should we ever use a different icon pack in the
+ # future we won't have to change hundreds of method calls.
+ def icon(names, options = {})
+ fa_icon(names, options)
+ end
+
+ def spinner(text = nil, visible = false)
+ css_class = 'loading'
+ css_class << ' hide' unless visible
+
+ content_tag :div, class: css_class do
+ icon('spinner spin') + text
+ end
+ end
+
def boolean_to_icon(value)
if value.to_s == "true"
- content_tag :i, nil, class: 'fa fa-circle cgreen'
+ icon('circle', class: 'cgreen')
else
- content_tag :i, nil, class: 'fa fa-power-off clgray'
+ icon('power-off', class: 'clgray')
end
end
def public_icon
- content_tag :i, nil, class: 'fa fa-globe'
+ icon('globe')
end
def internal_icon
- content_tag :i, nil, class: 'fa fa-shield'
+ icon('shield')
end
def private_icon
- content_tag :i, nil, class: 'fa fa-lock'
+ icon('lock')
end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 7671033b53..15c5dcb6a2 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -16,45 +16,25 @@ module IssuesHelper
def url_for_project_issues(project = @project)
return '' if project.nil?
- if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
- project_issues_path(project)
- else
- url = Gitlab.config.issues_tracker[project.issues_tracker]['project_url']
- url.gsub(':project_id', project.id.to_s).
- gsub(':issues_tracker_id', project.issues_tracker_id.to_s)
- end
+ project.issues_tracker.project_url
end
def url_for_new_issue(project = @project)
return '' if project.nil?
- if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
- url = new_project_issue_path project_id: project
- else
- issues_tracker = Gitlab.config.issues_tracker[project.issues_tracker]
- url = issues_tracker['new_issue_url']
- url.gsub(':project_id', project.id.to_s).
- gsub(':issues_tracker_id', project.issues_tracker_id.to_s)
- end
+ project.issues_tracker.new_issue_url
end
def url_for_issue(issue_iid, project = @project)
return '' if project.nil?
- if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
- url = project_issue_url project_id: project, id: issue_iid
- else
- url = Gitlab.config.issues_tracker[project.issues_tracker]['issues_url']
- url.gsub(':id', issue_iid.to_s).
- gsub(':project_id', project.id.to_s).
- gsub(':issues_tracker_id', project.issues_tracker_id.to_s)
- end
+ project.issues_tracker.issue_url(issue_iid)
end
def title_for_issue(issue_iid, project = @project)
return '' if project.nil?
- if project.used_default_issues_tracker?
+ if project.default_issues_tracker?
issue = project.issues.where(iid: issue_iid).first
return issue.title if issue
end
@@ -62,9 +42,19 @@ module IssuesHelper
''
end
- # Checks if issues_tracker setting exists in gitlab.yml
- def external_issues_tracker_enabled?
- Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any?
+ def issue_timestamp(issue)
+ # Shows the created at time and the updated at time if different
+ ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}"
+ if issue.updated_at != issue.created_at
+ ts << capture_haml do
+ haml_tag :span do
+ haml_concat '·'
+ haml_concat icon('edit', title: 'edited')
+ haml_concat time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')
+ end
+ end
+ end
+ ts.html_safe
end
def bulk_update_milestone_options
@@ -100,4 +90,21 @@ module IssuesHelper
'issue-box-open'
end
end
+
+ def issue_to_atom(xml, issue)
+ xml.entry do
+ xml.id namespace_project_issue_url(issue.project.namespace,
+ issue.project, issue)
+ xml.link href: namespace_project_issue_url(issue.project.namespace,
+ issue.project, issue)
+ xml.title truncate(issue.title, length: 80)
+ xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+ xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email)
+ xml.author do |author|
+ xml.name issue.author_name
+ xml.email issue.author_email
+ end
+ xml.summary issue.title
+ end
+ end
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 19d688c4bb..49063491ab 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -7,21 +7,34 @@ module LabelsHelper
label_color = label.color || Label::DEFAULT_COLOR
text_color = text_color_for_bg(label_color)
- content_tag :span, class: 'label color-label', style: "background:#{label_color};color:#{text_color}" do
+ content_tag :span, class: 'label color-label', style: "background-color:#{label_color};color:#{text_color}" do
label.name
end
end
def suggested_colors
[
- '#D9534F',
- '#F0AD4E',
+ '#0033CC',
'#428BCA',
+ '#44AD8E',
+ '#A8D695',
'#5CB85C',
+ '#69D100',
+ '#004E00',
'#34495E',
'#7F8C8D',
+ '#A295D6',
+ '#5843AD',
'#8E44AD',
- '#FFECDB'
+ '#FFECDB',
+ '#AD4363',
+ '#D10069',
+ '#CC0033',
+ '#FF0000',
+ '#D9534F',
+ '#D1D100',
+ '#F0AD4E',
+ '#AD8D43'
]
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index fe6fd5832f..3b1589da57 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -1,25 +1,29 @@
module MergeRequestsHelper
def new_mr_path_from_push_event(event)
target_project = event.project.forked_from_project || event.project
- new_project_merge_request_path(
+ new_namespace_project_merge_request_path(
+ event.project.namespace,
event.project,
new_mr_from_push_event(event, target_project)
)
end
def new_mr_path_for_fork_from_push_event(event)
- new_project_merge_request_path(
+ new_namespace_project_merge_request_path(
+ event.project.namespace,
event.project,
new_mr_from_push_event(event, event.project.forked_from_project)
)
end
def new_mr_from_push_event(event, target_project)
- return :merge_request => {
- source_project_id: event.project.id,
- target_project_id: target_project.id,
- source_branch: event.branch_name,
- target_branch: target_project.repository.root_ref
+ return {
+ merge_request: {
+ source_project_id: event.project.id,
+ target_project_id: target_project.id,
+ source_branch: event.branch_name,
+ target_branch: target_project.repository.root_ref
+ }
}
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
new file mode 100644
index 0000000000..59fdc0d49c
--- /dev/null
+++ b/app/helpers/milestones_helper.rb
@@ -0,0 +1,22 @@
+module MilestonesHelper
+ def milestones_filter_path(opts = {})
+ if @project
+ namespace_project_milestones_path(@project.namespace, @project, opts)
+ elsif @group
+ group_milestones_path(@group, opts)
+ else
+ dashboard_milestones_path(opts)
+ end
+ end
+
+ def milestone_progress_bar(milestone)
+ options = {
+ class: 'progress-bar progress-bar-success',
+ style: "width: #{milestone.percent_complete}%;"
+ }
+
+ content_tag :div, class: 'progress' do
+ content_tag :div, nil, options
+ end
+ end
+end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index bf25dce230..b3132a1f3b 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -25,4 +25,12 @@ module NamespacesHelper
hidden_field_tag(id, value, class: css_class)
end
+
+ def namespace_icon(namespace, size = 40)
+ if namespace.kind_of?(Group)
+ group_icon(namespace)
+ else
+ avatar_icon(namespace.owner.email, size)
+ end
+ end
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
new file mode 100644
index 0000000000..2b03269800
--- /dev/null
+++ b/app/helpers/nav_helper.rb
@@ -0,0 +1,5 @@
+module NavHelper
+ def nav_menu_collapsed?
+ cookies[:collapsed_nav] == 'true'
+ end
+end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 901052edec..ab44fa6ee4 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,17 +1,21 @@
module NotesHelper
- # Helps to distinguish e.g. commit notes in mr notes list
+ # Helps to distinguish e.g. commit notes in mr notes list
def note_for_main_target?(note)
(@noteable.class.name == note.noteable_type && !note.for_diff_line?)
end
- def note_target_fields
- hidden_field_tag(:target_type, @target_type) +
- hidden_field_tag(:target_id, @target_id)
+ def note_target_fields(note)
+ hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
+ hidden_field_tag(:target_id, note.noteable.id)
end
def link_to_commit_diff_line_note(note)
if note.for_commit_diff_line?
- link_to "#{note.diff_file_name}:L#{note.diff_new_line}", project_commit_path(@project, note.noteable, anchor: note.line_code)
+ link_to(
+ "#{note.diff_file_name}:L#{note.diff_new_line}",
+ namespace_project_commit_path(@project.namespace, @project,
+ note.noteable, anchor: note.line_code)
+ )
end
end
@@ -20,8 +24,10 @@ module NotesHelper
ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}"
if note.updated_at != note.created_at
ts << capture_haml do
- haml_tag :small do
- haml_concat " (Edited #{time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')})"
+ haml_tag :span do
+ haml_concat '·'
+ haml_concat icon('edit', title: 'edited')
+ haml_concat time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')
end
end
end
@@ -52,8 +58,11 @@ module NotesHelper
discussion_id: discussion_id
}
- button_tag '', class: 'btn add-diff-note js-add-diff-note-button',
- data: data, title: 'Add a comment to this line'
+ button_tag(class: 'btn add-diff-note js-add-diff-note-button',
+ data: data,
+ title: 'Add a comment to this line') do
+ icon('comment-o')
+ end
end
def link_to_reply_diff(note)
@@ -69,7 +78,7 @@ module NotesHelper
button_tag class: 'btn reply-btn js-discussion-reply-button',
data: data, title: 'Add a reply' do
- link_text = content_tag(:i, nil, class: 'fa fa-comment')
+ link_text = icon('comment')
link_text << ' Reply'
end
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index bad380e98a..f771fe761e 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -1,13 +1,13 @@
module NotificationsHelper
def notification_icon(notification)
if notification.disabled?
- content_tag :i, nil, class: 'fa fa-volume-off ns-mute'
+ icon('volume-off', class: 'ns-mute')
elsif notification.participating?
- content_tag :i, nil, class: 'fa fa-volume-down ns-part'
+ icon('volume-down', class: 'ns-part')
elsif notification.watch?
- content_tag :i, nil, class: 'fa fa-volume-up ns-watch'
+ icon('volume-up', class: 'ns-watch')
else
- content_tag :i, nil, class: 'fa fa-circle-o ns-default'
+ icon('circle-o', class: 'ns-default')
end
end
end
diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb
index c0177dacbf..1a0ad17b60 100644
--- a/app/helpers/oauth_helper.rb
+++ b/app/helpers/oauth_helper.rb
@@ -1,10 +1,10 @@
module OauthHelper
def ldap_enabled?
- Devise.omniauth_providers.include?(:ldap)
+ Gitlab.config.ldap.enabled
end
def default_providers
- [:twitter, :github, :google_oauth2, :ldap]
+ [:twitter, :github, :gitlab, :bitbucket, :google_oauth2, :ldap]
end
def enabled_oauth_providers
@@ -13,7 +13,13 @@ module OauthHelper
def enabled_social_providers
enabled_oauth_providers.select do |name|
- [:twitter, :github, :google_oauth2].include?(name.to_sym)
+ [:twitter, :gitlab, :github, :bitbucket, :google_oauth2].include?(name.to_sym)
end
end
+
+ def additional_providers
+ enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')}
+ end
+
+ extend self
end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
index 0b37555830..9e37e44732 100644
--- a/app/helpers/profile_helper.rb
+++ b/app/helpers/profile_helper.rb
@@ -1,6 +1,6 @@
module ProfileHelper
def oauth_active_class(provider)
- if current_user.provider == provider.to_s
+ if current_user.identities.exists?(provider: provider.to_s)
'active'
end
end
@@ -10,10 +10,10 @@ module ProfileHelper
end
def show_profile_social_tab?
- enabled_social_providers.any? && !current_user.ldap_user?
+ enabled_social_providers.any?
end
def show_profile_remove_tab?
- gitlab_config.signup_enabled && !current_user.ldap_user?
+ signup_enabled?
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 883c1f63af..2225b11065 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -4,7 +4,7 @@ module ProjectsHelper
end
def link_to_project(project)
- link_to project do
+ link_to [project.namespace.becomes(Namespace), project] do
title = content_tag(:span, project.name, class: 'project-name')
if project.namespace
@@ -42,12 +42,20 @@ module ProjectsHelper
def project_title(project)
if project.group
content_tag :span do
- link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
+ link_to(
+ simple_sanitize(project.group.name), group_path(project.group)
+ ) + ' / ' +
+ link_to(simple_sanitize(project.name),
+ project_path(project))
end
else
owner = project.namespace.owner
content_tag :span do
- link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name
+ link_to(
+ simple_sanitize(owner.name), user_path(owner)
+ ) + ' / ' +
+ link_to(simple_sanitize(project.name),
+ project_path(project))
end
end
end
@@ -68,67 +76,13 @@ module ProjectsHelper
project_nav_tabs.include? name
end
- def selected_label?(label_name)
- params[:label_name].to_s.split(',').include?(label_name)
- end
-
- def labels_filter_path(label_name)
- label_name =
- if selected_label?(label_name)
- params[:label_name].split(',').reject { |l| l == label_name }.join(',')
- elsif params[:label_name].present?
- "#{params[:label_name]},#{label_name}"
- else
- label_name
- end
-
- project_filter_path(label_name: label_name)
- end
-
- def label_filter_class(label_name)
- if selected_label?(label_name)
- 'label-filter-item active'
- else
- 'label-filter-item light'
- end
- end
-
- def project_filter_path(options={})
- exist_opts = {
- state: params[:state],
- scope: params[:scope],
- label_name: params[:label_name],
- milestone_id: params[:milestone_id],
- assignee_id: params[:assignee_id],
- sort: params[:sort],
- }
-
- options = exist_opts.merge(options)
-
- path = request.path
- path << "?#{options.to_param}"
- path
- end
-
def project_active_milestones
@project.milestones.active.order("due_date, title ASC")
end
- def project_issues_trackers(current_tracker = nil)
- values = Project.issues_tracker.values.map do |tracker_key|
- if tracker_key.to_sym == :gitlab
- ['GitLab', tracker_key]
- else
- [Gitlab.config.issues_tracker[tracker_key]['title'] || tracker_key, tracker_key]
- end
- end
-
- options_for_select(values, current_tracker)
- end
-
def link_to_toggle_star(title, starred, signed_in)
cls = 'star-btn'
- cls += ' disabled' unless signed_in
+ cls << ' disabled' unless signed_in
toggle_html = content_tag('span', class: 'toggle') do
toggle_text = if starred
@@ -137,7 +91,7 @@ module ProjectsHelper
' Star'
end
- content_tag('i', ' ', class: 'fa fa-star') + toggle_text
+ icon('star') + toggle_text
end
count_html = content_tag('span', class: 'count') do
@@ -149,19 +103,22 @@ module ProjectsHelper
class: cls,
method: :post,
remote: true,
- data: {type: 'json'}
+ data: { type: 'json' }
}
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
- link_to toggle_star_project_path(@project), link_opts do
+ link_to(
+ toggle_star_namespace_project_path(@project.namespace, @project),
+ link_opts
+ ) do
toggle_html + ' ' + count_html
end
end
end
def link_to_toggle_fork
- out = content_tag(:i, '', class: 'fa fa-code-fork')
+ out = icon('code-fork')
out << ' Fork'
out << content_tag(:span, class: 'count') do
@project.forks_count.to_s
@@ -229,7 +186,13 @@ module ProjectsHelper
"Issues - " + title
end
elsif current_controller?(:blob)
- "#{@project.path}\/#{@blob.path} at #{@ref} - " + title
+ if current_action?(:new) || current_action?(:create)
+ "New file at #{@ref}"
+ elsif current_action?(:show)
+ "#{@blob.path} at #{@ref}"
+ elsif @blob
+ "Edit file #{@blob.path} at #{@ref}"
+ end
elsif current_controller?(:commits)
"Commits at #{@ref} - " + title
elsif current_controller?(:merge_requests)
@@ -270,13 +233,46 @@ module ProjectsHelper
def contribution_guide_url(project)
if project && project.repository.contribution_guide
- project_blob_path(project, tree_join(project.default_branch, project.repository.contribution_guide.name))
+ namespace_project_blob_path(
+ project.namespace,
+ project,
+ tree_join(project.default_branch,
+ project.repository.contribution_guide.name)
+ )
end
end
def hidden_pass_url(original_url)
result = URI(original_url)
- result.password = '*****' if result.password.present?
+ result.password = '*****' unless result.password.nil?
result
+ rescue
+ original_url
+ end
+
+ def project_wiki_path_with_version(proj, page, version, is_newest)
+ url_params = is_newest ? {} : { version_id: version }
+ namespace_project_wiki_path(proj.namespace, proj, page, url_params)
+ end
+
+ def project_status_css_class(status)
+ case status
+ when "started"
+ "active"
+ when "failed"
+ "danger"
+ when "finished"
+ "success"
+ end
+ end
+
+ def service_field_value(type, value)
+ return value unless type == 'password'
+
+ if value.present?
+ "***********"
+ else
+ nil
+ end
end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 65b9408cfa..7d3fcfa703 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -52,16 +52,16 @@ module SearchHelper
ref = @ref || @project.repository.root_ref
[
- { label: "#{prefix} - Files", url: project_tree_path(@project, ref) },
- { label: "#{prefix} - Commits", url: project_commits_path(@project, ref) },
- { label: "#{prefix} - Network", url: project_network_path(@project, ref) },
- { label: "#{prefix} - Graph", url: project_graph_path(@project, ref) },
- { label: "#{prefix} - Issues", url: project_issues_path(@project) },
- { label: "#{prefix} - Merge Requests", url: project_merge_requests_path(@project) },
- { label: "#{prefix} - Milestones", url: project_milestones_path(@project) },
- { label: "#{prefix} - Snippets", url: project_snippets_path(@project) },
- { label: "#{prefix} - Team", url: project_team_index_path(@project) },
- { label: "#{prefix} - Wiki", url: project_wikis_path(@project) },
+ { label: "#{prefix} - Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
+ { label: "#{prefix} - Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
+ { label: "#{prefix} - Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
+ { label: "#{prefix} - Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
+ { label: "#{prefix} - Issues", url: namespace_project_issues_path(@project.namespace, @project) },
+ { label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
+ { label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
+ { label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
+ { label: "#{prefix} - Members", url: namespace_project_project_members_path(@project.namespace, @project) },
+ { label: "#{prefix} - Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
]
else
[]
@@ -84,7 +84,7 @@ module SearchHelper
sorted_by_stars.non_archived.limit(limit).map do |p|
{
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
- url: project_path(p)
+ url: namespace_project_path(p.namespace, p)
}
end
end
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index ab24367c45..796d805f21 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -17,4 +17,13 @@ module SelectsHelper
project_id = opts[:project_id] || @project.id
hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id)
end
+
+ def groups_select_tag(id, opts = {})
+ css_class = "ajax-groups-select "
+ css_class << "multiselect " if opts[:multiple]
+ css_class << (opts[:class] || '')
+ value = opts[:selected] || ''
+
+ hidden_field_tag(id, value, class: css_class)
+ end
end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index b0abc2cae3..906cb12cd4 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -11,7 +11,8 @@ module SnippetsHelper
def reliable_snippet_path(snippet)
if snippet.project_id?
- project_snippet_path(snippet.project, snippet)
+ namespace_project_snippet_path(snippet.project.namespace,
+ snippet.project, snippet)
else
snippet_path(snippet)
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
new file mode 100644
index 0000000000..bb12d43f39
--- /dev/null
+++ b/app/helpers/sorting_helper.rb
@@ -0,0 +1,96 @@
+module SortingHelper
+ def sort_options_hash
+ {
+ sort_value_name => sort_title_name,
+ sort_value_recently_updated => sort_title_recently_updated,
+ sort_value_oldest_updated => sort_title_oldest_updated,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_milestone_soon => sort_title_milestone_soon,
+ sort_value_milestone_later => sort_title_milestone_later,
+ sort_value_largest_repo => sort_title_largest_repo,
+ sort_value_recently_signin => sort_title_recently_signin,
+ sort_value_oldest_signin => sort_title_oldest_signin,
+ }
+ end
+
+ def sort_title_oldest_updated
+ 'Oldest updated'
+ end
+
+ def sort_title_recently_updated
+ 'Recently updated'
+ end
+
+ def sort_title_oldest_created
+ 'Oldest created'
+ end
+
+ def sort_title_recently_created
+ 'Recently created'
+ end
+
+ def sort_title_milestone_soon
+ 'Milestone due soon'
+ end
+
+ def sort_title_milestone_later
+ 'Milestone due later'
+ end
+
+ def sort_title_name
+ 'Name'
+ end
+
+ def sort_title_largest_repo
+ 'Largest repository'
+ end
+
+ def sort_title_recently_signin
+ 'Recent sign in'
+ end
+
+ def sort_title_oldest_signin
+ 'Oldest sign in'
+ end
+
+ def sort_value_oldest_updated
+ 'updated_asc'
+ end
+
+ def sort_value_recently_updated
+ 'updated_desc'
+ end
+
+ def sort_value_oldest_created
+ 'created_asc'
+ end
+
+ def sort_value_recently_created
+ 'created_desc'
+ end
+
+ def sort_value_milestone_soon
+ 'milestone_due_asc'
+ end
+
+ def sort_value_milestone_later
+ 'milestone_due_desc'
+ end
+
+ def sort_value_name
+ 'name_asc'
+ end
+
+ def sort_value_largest_repo
+ 'repository_size_desc'
+ end
+
+ def sort_value_recently_signin
+ 'recent_sign_in'
+ end
+
+ def sort_value_oldest_signin
+ 'oldest_sign_in'
+ end
+end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 09e5c08e62..525266fb3b 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -2,22 +2,25 @@ module SubmoduleHelper
include Gitlab::ShellAdapter
# links to files listing for submodule if submodule is a project on this server
- def submodule_links(submodule_item)
- url = @repository.submodule_url_for(@ref, submodule_item.path)
+ def submodule_links(submodule_item, ref = nil)
+ url = @repository.submodule_url_for(ref, submodule_item.path)
- return url, nil unless url =~ /([^\/:]+\/[^\/]+\.git)\Z/
+ return url, nil unless url =~ /([^\/:]+)\/([^\/]+\.git)\Z/
- project = $1
+ namespace = $1
+ project = $2
project.chomp!('.git')
- if self_url?(url, project)
- return project_path(project), project_tree_path(project, submodule_item.id)
+ if self_url?(url, namespace, project)
+ return namespace_project_path(namespace, project),
+ namespace_project_tree_path(namespace, project,
+ submodule_item.id)
elsif relative_self_url?(url)
relative_self_links(url, submodule_item.id)
elsif github_dot_com_url?(url)
- standard_links('github.com', project, submodule_item.id)
+ standard_links('github.com', namespace, project, submodule_item.id)
elsif gitlab_dot_com_url?(url)
- standard_links('gitlab.com', project, submodule_item.id)
+ standard_links('gitlab.com', namespace, project, submodule_item.id)
else
return url, nil
end
@@ -33,9 +36,10 @@ module SubmoduleHelper
url =~ /gitlab\.com[\/:][^\/]+\/[^\/]+\Z/
end
- def self_url?(url, project)
- return true if url == [ Gitlab.config.gitlab.url, '/', project, '.git' ].join('')
- url == gitlab_shell.url_to_repo(project)
+ def self_url?(url, namespace, project)
+ return true if url == [ Gitlab.config.gitlab.url, '/', namespace, '/',
+ project, '.git' ].join('')
+ url == gitlab_shell.url_to_repo([namespace, '/', project].join(''))
end
def relative_self_url?(url)
@@ -43,8 +47,8 @@ module SubmoduleHelper
url =~ /^((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*\.git\Z/ || url =~ /^((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*\.git\Z/
end
- def standard_links(host, project, commit)
- base = [ 'https://', host, '/', project ].join('')
+ def standard_links(host, namespace, project, commit)
+ base = [ 'https://', host, '/', namespace, '/', project ].join('')
return base, [ base, '/tree/', commit ].join('')
end
@@ -54,6 +58,7 @@ module SubmoduleHelper
else
base = [ @project.group.path, '/', url[/([^\/]*)\.git/, 1] ].join('')
end
- return project_path(base), project_tree_path(base, commit)
+ return namespace_project_path(base.namespace, base),
+ namespace_project_tree_path(base.namespace, base, commit)
end
end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index bc43e07856..a1d263d9d3 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -28,6 +28,10 @@ module TabHelper
# nav_link(controller: [:tree, :refs]) { "Hello" }
# # => 'Hello '
#
+ # # Several paths
+ # nav_link(path: ['tree#show', 'profile#show']) { "Hello" }
+ # # => 'Hello '
+ #
# # Shorthand path
# nav_link(path: 'tree#show') { "Hello" }
# # => 'Hello '
@@ -38,25 +42,7 @@ module TabHelper
#
# Returns a list item element String
def nav_link(options = {}, &block)
- if path = options.delete(:path)
- if path.respond_to?(:each)
- c = path.map { |p| p.split('#').first }
- a = path.map { |p| p.split('#').last }
- else
- c, a, _ = path.split('#')
- end
- else
- c = options.delete(:controller)
- a = options.delete(:action)
- end
-
- if c && a
- # When given both options, make sure BOTH are active
- klass = current_controller?(*c) && current_action?(*a) ? 'active' : ''
- else
- # Otherwise check EITHER option
- klass = current_controller?(*c) || current_action?(*a) ? 'active' : ''
- end
+ klass = active_nav_link?(options) ? 'active' : ''
# Add our custom class into the html_options, which may or may not exist
# and which may or may not already have a :class key
@@ -72,18 +58,47 @@ module TabHelper
end
end
+ def active_nav_link?(options)
+ if path = options.delete(:path)
+ unless path.respond_to?(:each)
+ path = [path]
+ end
+
+ path.any? do |single_path|
+ current_path?(single_path)
+ end
+ else
+ c = options.delete(:controller)
+ a = options.delete(:action)
+
+ if c && a
+ # When given both options, make sure BOTH are true
+ current_controller?(*c) && current_action?(*a)
+ else
+ # Otherwise check EITHER option
+ current_controller?(*c) || current_action?(*a)
+ end
+ end
+ end
+
+ def current_path?(path)
+ c, a, _ = path.split('#')
+ current_controller?(c) && current_action?(a)
+ end
+
def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
- if ['services', 'hooks', 'deploy_keys', 'team_members', 'protected_branches'].include? controller.controller_name
- "active"
+ if ['services', 'hooks', 'deploy_keys', 'project_members', 'protected_branches'].include? controller.controller_name
+ "active"
end
end
def branches_tab_class
if current_controller?(:protected_branches) ||
current_controller?(:branches) ||
- current_page?(project_repository_path(@project))
+ current_page?(namespace_project_repository_path(@project.namespace,
+ @project))
'active'
end
end
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index ef89bb32c6..fb85544df2 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -6,7 +6,7 @@ module TagsHelper
def tag_list(project)
html = ''
project.tag_list.each do |tag|
- html += link_to tag, tag_path(tag)
+ html << link_to(tag, tag_path(tag))
end
html.html_safe
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 9c611a1c14..b6fb7a8aa5 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -10,13 +10,16 @@ module TreeHelper
tree = ""
# Render folders if we have any
- tree += render partial: 'projects/tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present?
+ tree << render(partial: 'projects/tree/tree_item', collection: folders,
+ locals: { type: 'folder' }) if folders.present?
# Render files if we have any
- tree += render partial: 'projects/tree/blob_item', collection: files, locals: {type: 'file'} if files.present?
+ tree << render(partial: 'projects/tree/blob_item', collection: files,
+ locals: { type: 'file' }) if files.present?
# Render submodules if we have any
- tree += render partial: 'projects/tree/submodule_item', collection: submodules if submodules.present?
+ tree << render(partial: 'projects/tree/submodule_item',
+ collection: submodules) if submodules.present?
tree.html_safe
end
@@ -35,13 +38,8 @@ module TreeHelper
#
# type - String type of the tree item; either 'folder' or 'file'
def tree_icon(type)
- icon_class = if type == 'folder'
- 'fa fa-folder'
- else
- 'fa fa-file-o'
- end
-
- content_tag :i, nil, class: icon_class
+ icon_class = type == 'folder' ? 'folder' : 'file-o'
+ icon(icon_class)
end
def tree_hex_class(content)
@@ -53,20 +51,18 @@ module TreeHelper
File.join(*args)
end
- def allowed_tree_edit?
- return false unless @repository.branch_names.include?(@ref)
+ def allowed_tree_edit?(project = nil, ref = nil)
+ project ||= @project
+ ref ||= @ref
+ return false unless project.repository.branch_names.include?(ref)
- if @project.protected_branch? @ref
- can?(current_user, :push_code_to_protected_branches, @project)
- else
- can?(current_user, :push_code, @project)
- end
+ ::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref)
end
def tree_breadcrumbs(tree, max_links = 2)
if @path.present?
part_path = ""
- parts = @path.split("\/")
+ parts = @path.split('/')
yield('..', nil) if parts.count > max_links
@@ -80,20 +76,18 @@ module TreeHelper
end
end
- def up_dir_path(tree)
+ def up_dir_path
file = File.join(@path, "..")
tree_join(@ref, file)
end
- def leave_edit_message
- "Leave edit mode?\nAll unsaved changes will be lost."
- end
-
- def editing_preview_title(filename)
- if Gitlab::MarkdownHelper.previewable?(filename)
- 'Preview'
+ # returns the relative path of the first subdir that doesn't have only one directory descendant
+ def flatten_tree(tree)
+ subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
+ if subtree.count == 1 && subtree.first.dir?
+ return tree_join(tree.name, flatten_tree(subtree.first))
else
- 'Diff'
+ return tree.name
end
end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index deb9c8b4d4..0d573e72a8 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -60,7 +60,8 @@ module VisibilityLevelHelper
Project.visibility_levels.key(level)
end
- def restricted_visibility_levels
- current_user.is_admin? ? [] : gitlab_config.restricted_visibility_levels
+ def restricted_visibility_levels(show_all = false)
+ return [] if current_user.is_admin? && !show_all
+ current_application_settings.restricted_visibility_levels || []
end
end
diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb
index 8c09389985..26f43bf955 100644
--- a/app/mailers/emails/groups.rb
+++ b/app/mailers/emails/groups.rb
@@ -1,10 +1,10 @@
module Emails
module Groups
- def group_access_granted_email(user_group_id)
- @membership = GroupMember.find(user_group_id)
- @group = @membership.group
+ def group_access_granted_email(group_member_id)
+ @group_member = GroupMember.find(group_member_id)
+ @group = @group_member.group
@target_url = group_url(@group)
- mail(to: @membership.user.email,
+ mail(to: @group_member.user.email,
subject: subject("Access to group was granted"))
end
end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index e534623596..687bac3aa3 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -3,7 +3,7 @@ module Emails
def new_issue_email(recipient_id, issue_id)
@issue = Issue.find(issue_id)
@project = @issue.project
- @target_url = project_issue_url(@project, @issue)
+ @target_url = namespace_project_issue_url(@project.namespace, @project, @issue)
mail_new_thread(@issue,
from: sender(@issue.author_id),
to: recipient(recipient_id),
@@ -14,7 +14,7 @@ module Emails
@issue = Issue.find(issue_id)
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
@project = @issue.project
- @target_url = project_issue_url(@project, @issue)
+ @target_url = namespace_project_issue_url(@project.namespace, @project, @issue)
mail_answer_thread(@issue,
from: sender(updated_by_user_id),
to: recipient(recipient_id),
@@ -25,7 +25,7 @@ module Emails
@issue = Issue.find issue_id
@project = @issue.project
@updated_by = User.find updated_by_user_id
- @target_url = project_issue_url(@project, @issue)
+ @target_url = namespace_project_issue_url(@project.namespace, @project, @issue)
mail_answer_thread(@issue,
from: sender(updated_by_user_id),
to: recipient(recipient_id),
@@ -37,7 +37,7 @@ module Emails
@issue_status = status
@project = @issue.project
@updated_by = User.find updated_by_user_id
- @target_url = project_issue_url(@project, @issue)
+ @target_url = namespace_project_issue_url(@project.namespace, @project, @issue)
mail_answer_thread(@issue,
from: sender(updated_by_user_id),
to: recipient(recipient_id),
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 9ecdac87d7..512a8f7ea6 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -3,7 +3,9 @@ module Emails
def new_merge_request_email(recipient_id, merge_request_id)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
- @target_url = project_merge_request_url(@project, @merge_request)
+ @target_url = namespace_project_merge_request_url(@project.namespace,
+ @project,
+ @merge_request)
mail_new_thread(@merge_request,
from: sender(@merge_request.author_id),
to: recipient(recipient_id),
@@ -14,7 +16,9 @@ module Emails
@merge_request = MergeRequest.find(merge_request_id)
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
@project = @merge_request.project
- @target_url = project_merge_request_url(@project, @merge_request)
+ @target_url = namespace_project_merge_request_url(@project.namespace,
+ @project,
+ @merge_request)
mail_answer_thread(@merge_request,
from: sender(updated_by_user_id),
to: recipient(recipient_id),
@@ -25,7 +29,9 @@ module Emails
@merge_request = MergeRequest.find(merge_request_id)
@updated_by = User.find updated_by_user_id
@project = @merge_request.project
- @target_url = project_merge_request_url(@project, @merge_request)
+ @target_url = namespace_project_merge_request_url(@project.namespace,
+ @project,
+ @merge_request)
mail_answer_thread(@merge_request,
from: sender(updated_by_user_id),
to: recipient(recipient_id),
@@ -35,7 +41,9 @@ module Emails
def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
- @target_url = project_merge_request_url(@project, @merge_request)
+ @target_url = namespace_project_merge_request_url(@project.namespace,
+ @project,
+ @merge_request)
mail_answer_thread(@merge_request,
from: sender(updated_by_user_id),
to: recipient(recipient_id),
@@ -47,7 +55,9 @@ module Emails
@mr_status = status
@project = @merge_request.project
@updated_by = User.find updated_by_user_id
- @target_url = project_merge_request_url(@project, @merge_request)
+ @target_url = namespace_project_merge_request_url(@project.namespace,
+ @project,
+ @merge_request)
set_reference("merge_request_#{merge_request_id}")
mail_answer_thread(@merge_request,
from: sender(updated_by_user_id),
@@ -56,7 +66,7 @@ module Emails
end
end
- # Over rides default behavour to show source/target
+ # Over rides default behaviour to show source/target
# Formats arguments into a String suitable for use as an email subject
#
# extra - Extra Strings to be inserted into the subject
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index ef9af726a6..ff251209e0 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -4,7 +4,9 @@ module Emails
@note = Note.find(note_id)
@commit = @note.noteable
@project = @note.project
- @target_url = project_commit_url(@project, @commit, anchor: "note_#{@note.id}")
+ @target_url = namespace_project_commit_url(@project.namespace, @project,
+ @commit, anchor:
+ "note_#{@note.id}")
mail_answer_thread(@commit,
from: sender(@note.author_id),
to: recipient(recipient_id),
@@ -15,7 +17,9 @@ module Emails
@note = Note.find(note_id)
@issue = @note.noteable
@project = @note.project
- @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}")
+ @target_url = namespace_project_issue_url(@project.namespace, @project,
+ @issue, anchor:
+ "note_#{@note.id}")
mail_answer_thread(@issue,
from: sender(@note.author_id),
to: recipient(recipient_id),
@@ -26,7 +30,10 @@ module Emails
@note = Note.find(note_id)
@merge_request = @note.noteable
@project = @note.project
- @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}")
+ @target_url = namespace_project_merge_request_url(@project.namespace,
+ @project,
+ @merge_request, anchor:
+ "note_#{@note.id}")
mail_answer_thread(@merge_request,
from: sender(@note.author_id),
to: recipient(recipient_id),
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index f8a7d133d1..ab5b076535 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -1,24 +1,23 @@
module Emails
module Profile
- def new_user_email(user_id, password, token = nil)
+ def new_user_email(user_id, token = nil)
@user = User.find(user_id)
- @password = password
@target_url = user_url(@user)
@token = token
- mail(to: @user.email, subject: subject("Account was created for you"))
+ mail(to: @user.notification_email, subject: subject("Account was created for you"))
end
def new_email_email(email_id)
@email = Email.find(email_id)
@user = @email.user
- mail(to: @user.email, subject: subject("Email was added to your account"))
+ mail(to: @user.notification_email, subject: subject("Email was added to your account"))
end
def new_ssh_key_email(key_id)
@key = Key.find(key_id)
@user = @key.user
@target_url = user_url(@user)
- mail(to: @user.email, subject: subject("SSH key was added to your account"))
+ mail(to: @user.notification_email, subject: subject("SSH key was added to your account"))
end
end
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index d6edfd7059..b55129de29 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -3,7 +3,7 @@ module Emails
def project_access_granted_email(user_project_id)
@project_member = ProjectMember.find user_project_id
@project = @project_member.project
- @target_url = project_url(@project)
+ @target_url = namespace_project_url(@project.namespace, @project)
mail(to: @project_member.user.email,
subject: subject("Access to project was granted"))
end
@@ -11,29 +11,43 @@ module Emails
def project_was_moved_email(project_id, user_id)
@user = User.find user_id
@project = Project.find project_id
- @target_url = project_url(@project)
- mail(to: @user.email,
+ @target_url = namespace_project_url(@project.namespace, @project)
+ mail(to: @user.notification_email,
subject: subject("Project was moved"))
end
- def repository_push_email(project_id, recipient, author_id, branch, compare)
+ def repository_push_email(project_id, recipient, author_id, branch, compare, reverse_compare = false, send_from_committer_email = false, disable_diffs = false)
@project = Project.find(project_id)
@author = User.find(author_id)
+ @reverse_compare = reverse_compare
@compare = compare
@commits = Commit.decorate(compare.commits)
@diffs = compare.diffs
- @branch = branch
+ @branch = Gitlab::Git.ref_name(branch)
+ @disable_diffs = disable_diffs
+
+ @subject = "[#{@project.path_with_namespace}][#{@branch}] "
+
if @commits.length > 1
- @target_url = project_compare_url(@project, from: @commits.first, to: @commits.last)
- @subject = "#{@commits.length} new commits pushed to repository"
+ @target_url = namespace_project_compare_url(@project.namespace,
+ @project,
+ from: Commit.new(@compare.base),
+ to: Commit.new(@compare.head))
+ @subject << "Deleted " if @reverse_compare
+ @subject << "#{@commits.length} commits: #{@commits.first.title}"
else
- @target_url = project_commit_url(@project, @commits.first)
- @subject = @commits.first.title
+ @target_url = namespace_project_commit_url(@project.namespace,
+ @project, @commits.first)
+
+ @subject << "Deleted 1 commit: " if @reverse_compare
+ @subject << @commits.first.title
end
- mail(from: sender(author_id),
+ @disable_footer = true
+
+ mail(from: sender(author_id, send_from_committer_email),
to: recipient,
- subject: subject(@subject))
+ subject: @subject)
end
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index bd438bab89..ee27879cf4 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -11,6 +11,7 @@ class Notify < ActionMailer::Base
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
add_template_helper MergeRequestsHelper
+ add_template_helper EmailsHelper
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
@@ -25,21 +26,49 @@ class Notify < ActionMailer::Base
delay_for(2.seconds)
end
+ def test_email(recipient_email, subject, body)
+ mail(to: recipient_email,
+ subject: subject,
+ body: body.html_safe,
+ content_type: 'text/html'
+ )
+ end
+
+ # Splits "gitlab.corp.company.com" up into "gitlab.corp.company.com",
+ # "corp.company.com" and "company.com".
+ # Respects set tld length so "company.co.uk" won't match "somethingelse.uk"
+ def self.allowed_email_domains
+ domain_parts = Gitlab.config.gitlab.host.split(".")
+ allowed_domains = []
+ begin
+ allowed_domains << domain_parts.join(".")
+ domain_parts.shift
+ end while domain_parts.length > ActionDispatch::Http::URL.tld_length
+
+ allowed_domains
+ end
+
private
# The default email address to send emails from
def default_sender_address
address = Mail::Address.new(Gitlab.config.gitlab.email_from)
- address.display_name = "GitLab"
+ address.display_name = Gitlab.config.gitlab.email_display_name
address
end
# Return an email address that displays the name of the sender.
# Only the displayed name changes; the actual email address is always the same.
- def sender(sender_id)
+ def sender(sender_id, send_from_user_email = false)
if sender = User.find(sender_id)
address = default_sender_address
address.display_name = sender.name
+
+ sender_domain = sender.email.split("@").last
+ if send_from_user_email && self.class.allowed_email_domains.include?(sender_domain)
+ address.address = sender.email
+ end
+
address.format
end
end
@@ -51,7 +80,7 @@ class Notify < ActionMailer::Base
# Returns a String containing the User's email address.
def recipient(recipient_id)
if recipient = User.find(recipient_id)
- recipient.email
+ recipient.notification_email
end
end
@@ -102,6 +131,7 @@ class Notify < ActionMailer::Base
# See: mail_answer_thread
def mail_new_thread(model, headers = {}, &block)
headers['Message-ID'] = message_id(model)
+ headers['X-GitLab-Project'] = "#{@project.name} | " if @project
mail(headers, &block)
end
@@ -116,6 +146,7 @@ class Notify < ActionMailer::Base
def mail_answer_thread(model, headers = {}, &block)
headers['In-Reply-To'] = message_id(model)
headers['References'] = message_id(model)
+ headers['X-GitLab-Project'] = "#{@project.name} | " if @project
if (headers[:subject])
headers[:subject].prepend('Re: ')
diff --git a/app/models/ability.rb b/app/models/ability.rb
index e155abc144..d2b39f667f 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -14,7 +14,7 @@ class Ability
when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject)
- when "GroupMember" then users_group_abilities(user, subject)
+ when "GroupMember" then group_member_abilities(user, subject)
else []
end.concat(global_abilities(user))
end
@@ -37,7 +37,7 @@ class Ability
:read_issue,
:read_milestone,
:read_project_snippet,
- :read_team_member,
+ :read_project_member,
:read_merge_request,
:read_note,
:download_code
@@ -73,28 +73,28 @@ class Ability
# Rules based on role in project
if team.master?(user)
- rules += project_master_rules
+ rules.push(*project_master_rules)
elsif team.developer?(user)
- rules += project_dev_rules
+ rules.push(*project_dev_rules)
elsif team.reporter?(user)
- rules += project_report_rules
+ rules.push(*project_report_rules)
elsif team.guest?(user)
- rules += project_guest_rules
+ rules.push(*project_guest_rules)
end
if project.public? || project.internal?
- rules += public_project_rules
+ rules.push(*public_project_rules)
end
if project.owner == user || user.admin?
- rules += project_admin_rules
+ rules.push(*project_admin_rules)
end
if project.group && project.group.has_owner?(user)
- rules += project_admin_rules
+ rules.push(*project_admin_rules)
end
if project.archived?
@@ -119,7 +119,7 @@ class Ability
:read_issue,
:read_milestone,
:read_project_snippet,
- :read_team_member,
+ :read_project_member,
:read_merge_request,
:read_note,
:write_project,
@@ -166,7 +166,7 @@ class Ability
:admin_issue,
:admin_milestone,
:admin_project_snippet,
- :admin_team_member,
+ :admin_project_member,
:admin_merge_request,
:admin_note,
:admin_wiki,
@@ -193,17 +193,17 @@ class Ability
# Only group masters and group owners can create new projects in group
if group.has_master?(user) || group.has_owner?(user) || user.admin?
- rules += [
+ rules.push(*[
:create_projects,
- ]
+ ])
end
# Only group owner and administrators can manage group
if group.has_owner?(user) || user.admin?
- rules += [
+ rules.push(*[
:manage_group,
:manage_namespace
- ]
+ ])
end
rules.flatten
@@ -214,10 +214,10 @@ class Ability
# Only namespace owner and administrators can manage it
if namespace.owner == user || user.admin?
- rules += [
+ rules.push(*[
:create_projects,
:manage_namespace
- ]
+ ])
end
rules.flatten
@@ -225,13 +225,15 @@ class Ability
[:issue, :note, :project_snippet, :personal_snippet, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject|
- if subject.author == user
- [
+ if subject.author == user || user.is_admin?
+ rules = [
:"read_#{name}",
:"write_#{name}",
:"modify_#{name}",
:"admin_#{name}"
]
+ rules.push(:change_visibility_level) if subject.is_a?(Snippet)
+ rules
elsif subject.respond_to?(:assignee) && subject.assignee == user
[
:"read_#{name}",
@@ -248,19 +250,27 @@ class Ability
end
end
- def users_group_abilities(user, subject)
+ def group_member_abilities(user, subject)
rules = []
target_user = subject.user
group = subject.group
can_manage = group_abilities(user, group).include?(:manage_group)
if can_manage && (user != target_user)
- rules << :modify
- rules << :destroy
+ rules << :modify_group_member
+ rules << :destroy_group_member
end
if !group.last_owner?(user) && (can_manage || (user == target_user))
- rules << :destroy
+ rules << :destroy_group_member
end
rules
end
+
+ def abilities
+ @abilities ||= begin
+ abilities = Six.new
+ abilities << self
+ abilities
+ end
+ end
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
new file mode 100644
index 0000000000..1c87db613a
--- /dev/null
+++ b/app/models/application_setting.rb
@@ -0,0 +1,59 @@
+# == Schema Information
+#
+# Table name: application_settings
+#
+# id :integer not null, primary key
+# default_projects_limit :integer
+# default_branch_protection :integer
+# signup_enabled :boolean
+# signin_enabled :boolean
+# gravatar_enabled :boolean
+# twitter_sharing_enabled :boolean
+# sign_in_text :text
+# created_at :datetime
+# updated_at :datetime
+# home_page_url :string(255)
+# default_branch_protection :integer default(2)
+# twitter_sharing_enabled :boolean default(TRUE)
+# restricted_visibility_levels :text
+#
+
+class ApplicationSetting < ActiveRecord::Base
+ serialize :restricted_visibility_levels
+
+ validates :home_page_url,
+ allow_blank: true,
+ format: { with: URI::regexp(%w(http https)), message: "should be a valid url" },
+ if: :home_page_url_column_exist
+
+ validates_each :restricted_visibility_levels do |record, attr, value|
+ unless value.nil?
+ value.each do |level|
+ unless Gitlab::VisibilityLevel.options.has_value?(level)
+ record.errors.add(attr, "'#{level}' is not a valid visibility level")
+ end
+ end
+ end
+ end
+
+ def self.current
+ ApplicationSetting.last
+ end
+
+ def self.create_from_defaults
+ create(
+ default_projects_limit: Settings.gitlab['default_projects_limit'],
+ default_branch_protection: Settings.gitlab['default_branch_protection'],
+ signup_enabled: Settings.gitlab['signup_enabled'],
+ signin_enabled: Settings.gitlab['signin_enabled'],
+ twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'],
+ gravatar_enabled: Settings.gravatar['enabled'],
+ sign_in_text: Settings.extra['sign_in_text'],
+ restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels']
+ )
+ end
+
+ def home_page_url_column_exist
+ ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
+ end
+end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 4d0c04bcc3..05f5e97969 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -14,6 +14,8 @@
#
class BroadcastMessage < ActiveRecord::Base
+ include Sortable
+
validates :message, presence: true
validates :starts_at, presence: true
validates :ends_at, presence: true
diff --git a/app/models/commit.rb b/app/models/commit.rb
index a1343b65c7..e0461809e1 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -10,22 +10,33 @@ class Commit
# Used to prevent 500 error on huge commits by suppressing diff
#
# User can force display of diff above this size
- DIFF_SAFE_FILES = 100
- DIFF_SAFE_LINES = 5000
+ DIFF_SAFE_FILES = 100 unless defined?(DIFF_SAFE_FILES)
+ DIFF_SAFE_LINES = 5000 unless defined?(DIFF_SAFE_LINES)
# Commits above this size will not be rendered in HTML
- DIFF_HARD_LIMIT_FILES = 1000
- DIFF_HARD_LIMIT_LINES = 50000
+ DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
+ DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES)
class << self
def decorate(commits)
- commits.map { |c| self.new(c) }
+ commits.map do |commit|
+ if commit.kind_of?(Commit)
+ commit
+ else
+ self.new(commit)
+ end
+ end
end
# Calculate number of lines to render for diffs
def diff_line_count(diffs)
diffs.reduce(0) { |sum, d| sum + d.diff.lines.count }
end
+
+ # Truncate sha to 8 characters
+ def truncate_sha(sha)
+ sha[0..7]
+ end
end
attr_accessor :raw
@@ -64,11 +75,11 @@ class Commit
return no_commit_message if title.blank?
- title_end = title.index(/\n/)
+ title_end = title.index("\n")
if (!title_end && title.length > 100) || (title_end && title_end > 100)
title[0..79] << "…".html_safe
else
- title.split(/\n/, 2).first
+ title.split("\n", 2).first
end
end
@@ -76,12 +87,13 @@ class Commit
#
# cut off, ellipses (`&hellp;`) are prepended to the commit message.
def description
- title_end = safe_message.index(/\n/)
- @description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
- "…".html_safe << safe_message[80..-1]
- else
- safe_message.split(/\n/, 2)[1].try(:chomp)
- end
+ title_end = safe_message.index("\n")
+ @description ||=
+ if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
+ "…".html_safe << safe_message[80..-1]
+ else
+ safe_message.split("\n", 2)[1].try(:chomp)
+ end
end
def description?
@@ -111,7 +123,7 @@ class Commit
# Mentionable override.
def gfm_reference
- "commit #{sha[0..5]}"
+ "commit #{id}"
end
def method_missing(m, *args, &block)
@@ -124,6 +136,11 @@ class Commit
super
end
+ # Truncate sha to 8 characters
+ def short_id
+ @raw.short_id(7)
+ end
+
def parents
@parents ||= Commit.decorate(super)
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 553087946d..88ac83744d 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -15,6 +15,7 @@ module Issuable
has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
+ has_many :subscriptions, dependent: :destroy, as: :subscribable
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
@@ -29,6 +30,8 @@ module Issuable
scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) }
+ scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
+ scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
delegate :name,
:email,
@@ -55,13 +58,10 @@ module Issuable
def sort(method)
case method.to_s
- when 'newest' then reorder("#{table_name}.created_at DESC")
- when 'oldest' then reorder("#{table_name}.created_at ASC")
- when 'recently_updated' then reorder("#{table_name}.updated_at DESC")
- when 'last_updated' then reorder("#{table_name}.updated_at ASC")
- when 'milestone_due_soon' then joins(:milestone).reorder("milestones.due_date ASC")
- when 'milestone_due_later' then joins(:milestone).reorder("milestones.due_date DESC")
- else reorder("#{table_name}.created_at DESC")
+ when 'milestone_due_asc' then order_milestone_due_asc
+ when 'milestone_due_desc' then order_milestone_due_desc
+ else
+ order_by(method)
end
end
end
@@ -88,7 +88,7 @@ module Issuable
# Return the number of -1 comments (downvotes)
def downvotes
- notes.select(&:downvote?).size
+ filter_superceded_votes(notes.select(&:downvote?), notes).size
end
def downvotes_in_percent
@@ -101,7 +101,7 @@ module Issuable
# Return the number of +1 comments (upvotes)
def upvotes
- notes.select(&:upvote?).size
+ filter_superceded_votes(notes.select(&:upvote?), notes).size
end
def upvotes_in_percent
@@ -124,16 +124,35 @@ module Issuable
users << assignee if is_assigned?
mentions = []
mentions << self.mentioned_users
+
notes.each do |note|
users << note.author
mentions << note.mentioned_users
end
+
users.concat(mentions.reduce([], :|)).uniq
end
- def to_hook_data
+ def subscribed?(user)
+ subscription = subscriptions.find_by_user_id(user.id)
+
+ if subscription
+ return subscription.subscribed
+ end
+
+ participants.include?(user)
+ end
+
+ def toggle_subscription(user)
+ subscriptions.
+ find_or_initialize_by(user_id: user.id).
+ update(subscribed: !subscribed?(user))
+ end
+
+ def to_hook_data(user)
{
object_kind: self.class.name.underscore,
+ user: user.hook_attrs,
object_attributes: hook_attrs
}
end
@@ -148,9 +167,23 @@ module Issuable
def add_labels_by_names(label_names)
label_names.each do |label_name|
- label = project.labels.create_with(
- color: Label::DEFAULT_COLOR).find_or_create_by(title: label_name.strip)
+ label = project.labels.create_with(color: Label::DEFAULT_COLOR).
+ find_or_create_by(title: label_name.strip)
self.labels << label
end
end
+
+ private
+
+ def filter_superceded_votes(votes, notes)
+ filteredvotes = [] + votes
+
+ votes.each do |vote|
+ if vote.superceded?(notes)
+ filteredvotes.delete(vote)
+ end
+ end
+
+ filteredvotes
+ end
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 5938d9cb28..74900d4675 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -50,14 +50,13 @@ module Mentionable
matches.each do |match|
identifier = match.delete "@"
if identifier == "all"
- users += project.team.members.flatten
- else
- if has_project
- id = project.team.members.find_by(username: identifier).try(:id)
+ users.push(*project.team.members.flatten)
+ elsif namespace = Namespace.find_by(path: identifier)
+ if namespace.type == "Group"
+ users.push(*namespace.users)
else
- id = User.find_by(username: identifier).try(:id)
+ users << namespace.owner
end
- users << User.find(id) unless id.blank?
end
end
users.uniq
@@ -68,9 +67,10 @@ module Mentionable
return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new
ext.analyze(text, p)
- (ext.issues_for +
- ext.merge_requests_for +
- ext.commits_for).uniq - [local_reference]
+
+ (ext.issues_for(p) +
+ ext.merge_requests_for(p) +
+ ext.commits_for(p)).uniq - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
@@ -99,5 +99,4 @@ module Mentionable
preexisting = references(p, original)
create_cross_references!(p, a, preexisting)
end
-
end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
new file mode 100644
index 0000000000..0ad2654867
--- /dev/null
+++ b/app/models/concerns/sortable.rb
@@ -0,0 +1,35 @@
+# == Sortable concern
+#
+# Set default scope for ordering objects
+#
+module Sortable
+ extend ActiveSupport::Concern
+
+ included do
+ # By default all models should be ordered
+ # by created_at field starting from newest
+ default_scope { order(created_at: :desc, id: :desc) }
+
+ scope :order_created_desc, -> { reorder(created_at: :desc, id: :desc) }
+ scope :order_created_asc, -> { reorder(created_at: :asc, id: :asc) }
+ scope :order_updated_desc, -> { reorder(updated_at: :desc, id: :desc) }
+ scope :order_updated_asc, -> { reorder(updated_at: :asc, id: :asc) }
+ scope :order_name_asc, -> { reorder(name: :asc) }
+ scope :order_name_desc, -> { reorder(name: :desc) }
+ end
+
+ module ClassMethods
+ def order_by(method)
+ case method.to_s
+ when 'name_asc' then order_name_asc
+ when 'name_desc' then order_name_desc
+ when 'updated_asc' then order_updated_asc
+ when 'updated_desc' then order_updated_desc
+ when 'created_asc' then order_created_asc
+ when 'created_desc' then order_created_desc
+ else
+ all
+ end
+ end
+ end
+end
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index f23d8205dd..7e88903b9a 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -16,4 +16,12 @@ class DeployKeysProject < ActiveRecord::Base
validates :deploy_key_id, presence: true
validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_id, presence: true
+
+ after_destroy :destroy_orphaned_deploy_key
+
+ private
+
+ def destroy_orphaned_deploy_key
+ self.deploy_key.destroy if self.deploy_key.deploy_keys_projects.length == 0
+ end
end
diff --git a/app/models/email.rb b/app/models/email.rb
index 57f476bd51..556b0e9586 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -10,6 +10,8 @@
#
class Email < ActiveRecord::Base
+ include Sortable
+
belongs_to :user
validates :user_id, presence: true
diff --git a/app/models/event.rb b/app/models/event.rb
index 9e296c0028..8d20d7ef25 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -15,6 +15,7 @@
#
class Event < ActiveRecord::Base
+ include Sortable
default_scope { where.not(author_id: nil) }
CREATED = 1
@@ -46,31 +47,9 @@ class Event < ActiveRecord::Base
scope :recent, -> { order("created_at DESC") }
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent }
+ scope :with_associations, -> { includes(project: :namespace) }
class << self
- def create_ref_event(project, user, ref, action = 'add', prefix = 'refs/heads')
- commit = project.repository.commit(ref.target)
-
- if action.to_s == 'add'
- before = '00000000'
- after = commit.id
- else
- before = commit.id
- after = '00000000'
- end
-
- Event.create(
- project: project,
- action: Event::PUSHED,
- data: {
- ref: "#{prefix}/#{ref.name}",
- before: before,
- after: after
- },
- author_id: user.id
- )
- end
-
def reset_event_cache_for(target)
Event.where(target_id: target.id, target_type: target.class.to_s).
order('id DESC').limit(100).
@@ -83,6 +62,8 @@ class Event < ActiveRecord::Base
true
elsif membership_changed?
true
+ elsif created_project?
+ true
else
(issue? || merge_request? || note? || milestone?) && target
end
@@ -97,25 +78,51 @@ class Event < ActiveRecord::Base
end
def target_title
- if target && target.respond_to?(:title)
- target.title
- end
+ target.title if target && target.respond_to?(:title)
+ end
+
+ def created?
+ action == CREATED
end
def push?
- action == self.class::PUSHED && valid_push?
+ action == PUSHED && valid_push?
end
def merged?
- action == self.class::MERGED
+ action == MERGED
end
def closed?
- action == self.class::CLOSED
+ action == CLOSED
end
def reopened?
- action == self.class::REOPENED
+ action == REOPENED
+ end
+
+ def joined?
+ action == JOINED
+ end
+
+ def left?
+ action == LEFT
+ end
+
+ def commented?
+ action == COMMENTED
+ end
+
+ def membership_changed?
+ joined? || left?
+ end
+
+ def created_project?
+ created? && !target
+ end
+
+ def created_target?
+ created? && target
end
def milestone?
@@ -134,32 +141,32 @@ class Event < ActiveRecord::Base
target_type == "MergeRequest"
end
- def joined?
- action == JOINED
- end
-
- def left?
- action == LEFT
- end
-
- def membership_changed?
- joined? || left?
+ def milestone
+ target if milestone?
end
def issue
- target if target_type == "Issue"
+ target if issue?
end
def merge_request
- target if target_type == "MergeRequest"
+ target if merge_request?
end
def note
- target if target_type == "Note"
+ target if note?
end
def action_name
- if closed?
+ if push?
+ if new_ref?
+ "pushed new"
+ elsif rm_ref?
+ "deleted"
+ else
+ "pushed to"
+ end
+ elsif closed?
"closed"
elsif merged?
"accepted"
@@ -167,6 +174,10 @@ class Event < ActiveRecord::Base
'joined'
elsif left?
'left'
+ elsif commented?
+ "commented on"
+ elsif created_project?
+ "created"
else
"opened"
end
@@ -174,28 +185,24 @@ class Event < ActiveRecord::Base
def valid_push?
data[:ref] && ref_name.present?
- rescue => ex
+ rescue
false
end
def tag?
- data[:ref]["refs/tags"]
+ Gitlab::Git.tag_ref?(data[:ref])
end
def branch?
- data[:ref]["refs/heads"]
- end
-
- def new_branch?
- commit_from =~ /^00000/
+ Gitlab::Git.branch_ref?(data[:ref])
end
def new_ref?
- commit_from =~ /^00000/
+ Gitlab::Git.blank_ref?(commit_from)
end
def rm_ref?
- commit_to =~ /^00000/
+ Gitlab::Git.blank_ref?(commit_to)
end
def md_ref?
@@ -219,11 +226,11 @@ class Event < ActiveRecord::Base
end
def branch_name
- @branch_name ||= data[:ref].gsub("refs/heads/", "")
+ @branch_name ||= Gitlab::Git.ref_name(data[:ref])
end
def tag_name
- @tag_name ||= data[:ref].gsub("refs/tags/", "")
+ @tag_name ||= Gitlab::Git.ref_name(data[:ref])
end
# Max 20 commits from push DESC
@@ -239,16 +246,6 @@ class Event < ActiveRecord::Base
tag? ? "tag" : "branch"
end
- def push_action_name
- if new_ref?
- "pushed new"
- elsif rm_ref?
- "deleted"
- else
- "pushed to"
- end
- end
-
def push_with_commits?
md_ref? && commits.any? && commit_from && commit_to
end
@@ -266,7 +263,7 @@ class Event < ActiveRecord::Base
end
def note_short_commit_id
- note_commit_id[0..8]
+ Commit.truncate_sha(note_commit_id)
end
def note_commit?
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
new file mode 100644
index 0000000000..50efcb32f1
--- /dev/null
+++ b/app/models/external_issue.rb
@@ -0,0 +1,25 @@
+class ExternalIssue
+ def initialize(issue_identifier, project)
+ @issue_identifier, @project = issue_identifier, project
+ end
+
+ def to_s
+ @issue_identifier.to_s
+ end
+
+ def id
+ @issue_identifier.to_s
+ end
+
+ def iid
+ @issue_identifier.to_s
+ end
+
+ def ==(other)
+ other.is_a?(self.class) && (to_s == other.to_s)
+ end
+
+ def project
+ @project
+ end
+end
diff --git a/app/models/group.rb b/app/models/group.rb
index b8ed3b8ac7..da9621a2a1 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -21,9 +21,22 @@ class Group < Namespace
has_many :users, through: :group_members
validate :avatar_type, if: ->(user) { user.avatar_changed? }
- validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
+ validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
- mount_uploader :avatar, AttachmentUploader
+ mount_uploader :avatar, AvatarUploader
+
+ after_create :post_create_hook
+ after_destroy :post_destroy_hook
+
+ class << self
+ def search(query)
+ where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%")
+ end
+
+ def sort(method)
+ order_by(method)
+ end
+ end
def human_name
name
@@ -74,19 +87,15 @@ class Group < Namespace
projects.public_only.any?
end
- class << self
- def search(query)
- where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
- end
+ def post_create_hook
+ system_hook_service.execute_hooks_for(self, :create)
+ end
- def sort(method)
- case method.to_s
- when "newest" then reorder("namespaces.created_at DESC")
- when "oldest" then reorder("namespaces.created_at ASC")
- when "recently_updated" then reorder("namespaces.updated_at DESC")
- when "last_updated" then reorder("namespaces.updated_at ASC")
- else reorder("namespaces.path, namespaces.name ASC")
- end
- end
+ def post_destroy_hook
+ system_hook_service.execute_hooks_for(self, :destroy)
+ end
+
+ def system_hook_service
+ SystemHooksService.new
end
end
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index 3391531378..7e4f16ebf1 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -66,15 +66,15 @@ class GroupMilestone
end
def issues
- @group_issues ||= milestones.map { |milestone| milestone.issues }.flatten.group_by(&:state)
+ @group_issues ||= milestones.map(&:issues).flatten.group_by(&:state)
end
def merge_requests
- @group_merge_requests ||= milestones.map { |milestone| milestone.merge_requests }.flatten.group_by(&:state)
+ @group_merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state)
end
def participants
- milestones.map { |milestone| milestone.participants.uniq }.reject(&:empty?).flatten
+ @group_participants ||= milestones.map(&:participants).flatten.compact.uniq
end
def opened_issues
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 23fa01e0b7..defef7216f 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -16,6 +16,7 @@
#
class WebHook < ActiveRecord::Base
+ include Sortable
include HTTParty
default_value_for :push_events, true
@@ -32,7 +33,10 @@ class WebHook < ActiveRecord::Base
def execute(data)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
- WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
+ WebHook.post(url,
+ body: data.to_json,
+ headers: { "Content-Type" => "application/json" },
+ verify: false)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
@@ -41,10 +45,13 @@ class WebHook < ActiveRecord::Base
}
WebHook.post(post_url,
body: data.to_json,
- headers: {"Content-Type" => "application/json"},
+ headers: { "Content-Type" => "application/json" },
verify: false,
basic_auth: auth)
end
+ rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
+ logger.error("WebHook Error => #{e}")
+ false
end
def async_execute(data)
diff --git a/app/models/identity.rb b/app/models/identity.rb
new file mode 100644
index 0000000000..440fcd0d05
--- /dev/null
+++ b/app/models/identity.rb
@@ -0,0 +1,18 @@
+# == Schema Information
+#
+# Table name: identities
+#
+# id :integer not null, primary key
+# extern_uid :string(255)
+# provider :string(255)
+# user_id :integer
+# created_at :datetime
+# updated_at :datetime
+#
+
+class Identity < ActiveRecord::Base
+ include Sortable
+ belongs_to :user
+
+ validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider }
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 8a9e969248..6e10205138 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -24,6 +24,7 @@ class Issue < ActiveRecord::Base
include Issuable
include InternalId
include Taskable
+ include Sortable
ActsAsTaggableOn.strict_case_match = true
@@ -31,7 +32,6 @@ class Issue < ActiveRecord::Base
validates :project, presence: true
scope :of_group, ->(group) { where(project_id: group.project_ids) }
- scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) }
diff --git a/app/models/key.rb b/app/models/key.rb
index 095c73d8ba..e2e59296ee 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -15,11 +15,12 @@
require 'digest/md5'
class Key < ActiveRecord::Base
+ include Sortable
include Gitlab::Popen
belongs_to :user
- before_validation :strip_white_space, :generate_fingerpint
+ before_validation :strip_white_space, :generate_fingerprint
validates :title, presence: true, length: { within: 0..255 }
validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
@@ -76,7 +77,7 @@ class Key < ActiveRecord::Base
private
- def generate_fingerpint
+ def generate_fingerprint
self.fingerprint = nil
return unless key.present?
@@ -89,7 +90,7 @@ class Key < ActiveRecord::Base
end
if cmd_status.zero?
- cmd_output.gsub /([\d\h]{2}:)+[\d\h]{2}/ do |match|
+ cmd_output.gsub /(\h{2}:)+\h{2}/ do |match|
self.fingerprint = match
end
end
diff --git a/app/models/label.rb b/app/models/label.rb
index 2b2b02e064..9d7099c565 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -28,7 +28,7 @@ class Label < ActiveRecord::Base
format: { with: /\A[^&\?,&]+\z/ },
uniqueness: { scope: :project_id }
- scope :order_by_name, -> { reorder("labels.title ASC") }
+ default_scope { order(title: :asc) }
alias_attribute :name, :title
diff --git a/app/models/member.rb b/app/models/member.rb
index 671ef466ba..fe3d2f40e8 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -14,6 +14,7 @@
#
class Member < ActiveRecord::Base
+ include Sortable
include Notifiable
include Gitlab::Access
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index b7f296b13f..28d0b4483b 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -27,8 +27,9 @@ class GroupMember < Member
scope :with_group, ->(group) { where(source_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.id) }
- after_create :notify_create
+ after_create :post_create_hook
after_update :notify_update
+ after_destroy :post_destroy_hook
def self.access_level_roles
Gitlab::Access.options_with_owner
@@ -42,8 +43,9 @@ class GroupMember < Member
access_level
end
- def notify_create
+ def post_create_hook
notification_service.new_group_member(self)
+ system_hook_service.execute_hooks_for(self, :create)
end
def notify_update
@@ -52,6 +54,14 @@ class GroupMember < Member
end
end
+ def post_destroy_hook
+ system_hook_service.execute_hooks_for(self, :destroy)
+ end
+
+ def system_hook_service
+ SystemHooksService.new
+ end
+
def notification_service
NotificationService.new
end
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 30c09f768d..6b13e0ff30 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -114,30 +114,27 @@ class ProjectMember < Member
end
def post_create_hook
- Event.create(
- project_id: self.project.id,
- action: Event::JOINED,
- author_id: self.user.id
- )
-
- notification_service.new_team_member(self) unless owner?
+ unless owner?
+ event_service.join_project(self.project, self.user)
+ notification_service.new_project_member(self)
+ end
+
system_hook_service.execute_hooks_for(self, :create)
end
def post_update_hook
- notification_service.update_team_member(self) if self.access_level_changed?
+ notification_service.update_project_member(self) if self.access_level_changed?
end
def post_destroy_hook
- Event.create(
- project_id: self.project.id,
- action: Event::LEFT,
- author_id: self.user.id
- )
-
+ event_service.leave_project(self.project, self.user)
system_hook_service.execute_hooks_for(self, :destroy)
end
+ def event_service
+ EventCreateService.new
+ end
+
def notification_service
NotificationService.new
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 7c525b02f4..4cbdc61229 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -18,6 +18,7 @@
# iid :integer
# description :text
# position :integer default(0)
+# locked_at :datetime
#
require Rails.root.join("app/models/commit")
@@ -27,6 +28,7 @@ class MergeRequest < ActiveRecord::Base
include Issuable
include Taskable
include InternalId
+ include Sortable
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
@@ -70,6 +72,16 @@ class MergeRequest < ActiveRecord::Base
transition locked: :reopened
end
+ after_transition any => :locked do |merge_request, transition|
+ merge_request.locked_at = Time.now
+ merge_request.save
+ end
+
+ after_transition locked: (any - :locked) do |merge_request, transition|
+ merge_request.locked_at = nil
+ merge_request.save
+ end
+
state :opened
state :reopened
state :closed
@@ -103,7 +115,6 @@ class MergeRequest < ActiveRecord::Base
validate :validate_fork
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
- scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
scope :merged, -> { with_state(:merged) }
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
@@ -179,7 +190,9 @@ class MergeRequest < ActiveRecord::Base
end
def automerge!(current_user, commit_message = nil)
- MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message)
+ MergeRequests::AutoMergeService.
+ new(target_project, current_user).
+ execute(self, commit_message)
end
def open?
@@ -238,7 +251,8 @@ class MergeRequest < ActiveRecord::Base
def closes_issues
if target_branch == project.default_branch
issues = commits.flat_map { |c| c.closes_issues(project) }
- issues += Gitlab::ClosingIssueExtractor.closed_by_message_in_project(description, project)
+ issues.push(*Gitlab::ClosingIssueExtractor.
+ closed_by_message_in_project(description, project))
issues.uniq.sort_by(&:id)
else
[]
@@ -318,7 +332,7 @@ class MergeRequest < ActiveRecord::Base
end
# Return array of possible target branches
- # dependes on target project of MR
+ # depends on target project of MR
def target_branches
if target_project.nil?
[]
@@ -328,7 +342,7 @@ class MergeRequest < ActiveRecord::Base
end
# Return array of possible source branches
- # dependes on source project of MR
+ # depends on source project of MR
def source_branches
if source_project.nil?
[]
@@ -336,4 +350,8 @@ class MergeRequest < ActiveRecord::Base
source_project.repository.branch_names
end
end
+
+ def locked_long_ago?
+ locked_at && locked_at < (Time.now - 1.day)
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 409e82ed1e..acac1ca4cf 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -14,6 +14,8 @@
require Rails.root.join("app/models/commit")
class MergeRequestDiff < ActiveRecord::Base
+ include Sortable
+
# Prevent store of diff
# if commits amount more then 200
COMMITS_SAFE_SIZE = 200
@@ -55,7 +57,7 @@ class MergeRequestDiff < ActiveRecord::Base
end
def last_commit_short_sha
- @last_commit_short_sha ||= last_commit.sha[0..10]
+ @last_commit_short_sha ||= last_commit.short_id
end
private
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 8fd3e56d2e..9bbb2bafb9 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -15,6 +15,7 @@
class Milestone < ActiveRecord::Base
include InternalId
+ include Sortable
belongs_to :project
has_many :issues
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index c0c6de0ee7..35280889a8 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -14,21 +14,27 @@
#
class Namespace < ActiveRecord::Base
+ include Sortable
include Gitlab::ShellAdapter
has_many :projects, dependent: :destroy
belongs_to :owner, class_name: "User"
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
- validates :name, presence: true, uniqueness: true,
- length: { within: 0..255 },
- format: { with: Gitlab::Regex.name_regex,
- message: Gitlab::Regex.name_regex_message }
+ validates :name,
+ presence: true, uniqueness: true,
+ length: { within: 0..255 },
+ format: { with: Gitlab::Regex.name_regex,
+ message: Gitlab::Regex.name_regex_message }
+
validates :description, length: { within: 0..255 }
- validates :path, uniqueness: { case_sensitive: false }, presence: true, length: { within: 1..255 },
- exclusion: { in: Gitlab::Blacklist.path },
- format: { with: Gitlab::Regex.path_regex,
- message: Gitlab::Regex.path_regex_message }
+ validates :path,
+ uniqueness: { case_sensitive: false },
+ presence: true,
+ length: { within: 1..255 },
+ exclusion: { in: Gitlab::Blacklist.path },
+ format: { with: Gitlab::Regex.path_regex,
+ message: Gitlab::Regex.path_regex_message }
delegate :name, to: :owner, allow_nil: true, prefix: true
@@ -38,6 +44,15 @@ class Namespace < ActiveRecord::Base
scope :root, -> { where('type IS NULL') }
+ def self.by_path(path)
+ where('lower(path) = :value', value: path.downcase).first
+ end
+
+ # Case insensetive search for namespace by path or name
+ def self.find_by_path_or_name(path)
+ find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase)
+ end
+
def self.search(query)
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end
@@ -90,4 +105,8 @@ class Namespace < ActiveRecord::Base
def kind
type == 'Group' ? 'group' : 'user'
end
+
+ def find_fork_of(project)
+ projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first
+ end
end
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index 43979b5e80..f4e9012537 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -84,7 +84,7 @@ module Network
skip += self.class.max_count
end
else
- # Cant't find the target commit in the repo.
+ # Can't find the target commit in the repo.
offset = 0
end
end
@@ -226,7 +226,7 @@ module Network
reserved = []
for day in time_range
- reserved += @reserved[day]
+ reserved.push(*@reserved[day])
end
reserved.uniq!
diff --git a/app/models/note.rb b/app/models/note.rb
index 6f1b1a4da9..9ca3e4d7e9 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -49,7 +49,7 @@ class Note < ActiveRecord::Base
scope :not_inline, ->{ where(line_code: [nil, '']) }
scope :system, ->{ where(system: true) }
scope :common, ->{ where(noteable_type: ["", nil]) }
- scope :fresh, ->{ order("created_at ASC, id ASC") }
+ scope :fresh, ->{ order(created_at: :asc, id: :asc) }
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
@@ -80,7 +80,7 @@ class Note < ActiveRecord::Base
note_options = {
project: project,
author: author,
- note: "_mentioned in #{gfm_reference}_",
+ note: cross_reference_note_content(gfm_reference),
system: true
}
@@ -90,7 +90,7 @@ class Note < ActiveRecord::Base
note_options.merge!(noteable: noteable)
end
- create(note_options)
+ create(note_options) unless cross_reference_disallowed?(noteable, mentioner)
end
def create_milestone_change_note(noteable, project, author, milestone)
@@ -121,18 +121,71 @@ class Note < ActiveRecord::Base
})
end
- def create_new_commits_note(noteable, project, author, commits)
- commits_text = ActionController::Base.helpers.pluralize(commits.size, 'new commit')
+ def create_labels_change_note(noteable, project, author, added_labels, removed_labels)
+ labels_count = added_labels.count + removed_labels.count
+ added_labels = added_labels.map{ |label| "~#{label.id}" }.join(' ')
+ removed_labels = removed_labels.map{ |label| "~#{label.id}" }.join(' ')
+ message = ''
+
+ if added_labels.present?
+ message << "added #{added_labels}"
+ end
+
+ if added_labels.present? && removed_labels.present?
+ message << ' and '
+ end
+
+ if removed_labels.present?
+ message << "removed #{removed_labels}"
+ end
+
+ message << ' ' << 'label'.pluralize(labels_count)
+ body = "_#{message.capitalize}_"
+
+ create(
+ noteable: noteable,
+ project: project,
+ author: author,
+ note: body,
+ system: true
+ )
+ end
+
+ def create_new_commits_note(merge_request, project, author, new_commits, existing_commits = [])
+ total_count = new_commits.length + existing_commits.length
+ commits_text = ActionController::Base.helpers.pluralize(total_count, 'commit')
body = "Added #{commits_text}:\n\n"
- commits.each do |commit|
+ if existing_commits.length > 0
+ commit_ids =
+ if existing_commits.length == 1
+ existing_commits.first.short_id
+ else
+ "#{existing_commits.first.short_id}..#{existing_commits.last.short_id}"
+ end
+
+ commits_text = ActionController::Base.helpers.pluralize(existing_commits.length, 'commit')
+
+ branch =
+ if merge_request.for_fork?
+ "#{merge_request.target_project_namespace}:#{merge_request.target_branch}"
+ else
+ merge_request.target_branch
+ end
+
+ message = "* #{commit_ids} - _#{commits_text} from branch `#{branch}`_"
+ body << message
+ body << "\n"
+ end
+
+ new_commits.each do |commit|
message = "* #{commit.short_id} - #{commit.title}"
body << message
body << "\n"
end
create(
- noteable: noteable,
+ noteable: merge_request,
project: project,
author: author,
note: body,
@@ -165,6 +218,15 @@ class Note < ActiveRecord::Base
[:discussion, type.try(:underscore), id, line_code].join("-").to_sym
end
+ # Determine if cross reference note should be created.
+ # eg. mentioning a commit in MR comments which exists inside a MR
+ # should not create "mentioned in" note.
+ def cross_reference_disallowed?(noteable, mentioner)
+ if mentioner.kind_of?(MergeRequest)
+ mentioner.commits.map(&:id).include? noteable.id
+ end
+ end
+
# Determine whether or not a cross-reference note already exists.
def cross_reference_exists?(noteable, mentioner)
gfm_reference = mentioner_gfm_ref(noteable, mentioner)
@@ -174,7 +236,7 @@ class Note < ActiveRecord::Base
where(noteable_id: noteable.id)
end
- notes.where('note like ?', "_mentioned in #{gfm_reference}_").
+ notes.where('note like ?', cross_reference_note_content(gfm_reference)).
system.any?
end
@@ -182,8 +244,16 @@ class Note < ActiveRecord::Base
where("note like :query", query: "%#{query}%")
end
+ def cross_reference_note_prefix
+ '_mentioned in '
+ end
+
private
+ def cross_reference_note_content(gfm_reference)
+ cross_reference_note_prefix + "#{gfm_reference}_"
+ end
+
# Prepend the mentioner's namespaced project path to the GFM reference for
# cross-project references. For same-project references, return the
# unmodified GFM reference.
@@ -243,12 +313,16 @@ class Note < ActiveRecord::Base
def commit_author
@commit_author ||=
- project.users.find_by(email: noteable.author_email) ||
- project.users.find_by(name: noteable.author_name)
+ project.team.users.find_by(email: noteable.author_email) ||
+ project.team.users.find_by(name: noteable.author_name)
rescue
nil
end
+ def cross_reference?
+ note.start_with?(self.class.cross_reference_note_prefix)
+ end
+
def find_diff
return nil unless noteable && noteable.diffs.present?
@@ -257,6 +331,10 @@ class Note < ActiveRecord::Base
end
end
+ def hook_attrs
+ attributes
+ end
+
def set_diff
# First lets find notes with same diff
# before iterating over all mr diffs
@@ -275,6 +353,7 @@ class Note < ActiveRecord::Base
# If not - its outdated diff
def active?
return true unless self.diff
+ return false unless noteable
noteable.diffs.each do |mr_diff|
next unless mr_diff.new_path == self.diff.new_path
@@ -296,7 +375,7 @@ class Note < ActiveRecord::Base
end
def diff_file_index
- line_code.split('_')[0]
+ line_code.split('_')[0] if line_code
end
def diff_file_name
@@ -312,11 +391,11 @@ class Note < ActiveRecord::Base
end
def diff_old_line
- line_code.split('_')[1].to_i
+ line_code.split('_')[1].to_i if line_code
end
def diff_new_line
- line_code.split('_')[2].to_i
+ line_code.split('_')[2].to_i if line_code
end
def generate_line_code(line)
@@ -337,25 +416,39 @@ class Note < ActiveRecord::Base
@diff_line
end
+ def diff_line_type
+ return @diff_line_type if @diff_line_type
+
+ if diff
+ diff_lines.each do |line|
+ if generate_line_code(line) == self.line_code
+ @diff_line_type = line.type
+ end
+ end
+ end
+
+ @diff_line_type
+ end
+
def truncated_diff_lines
max_number_of_lines = 16
prev_match_line = nil
prev_lines = []
diff_lines.each do |line|
- if generate_line_code(line) != self.line_code
- if line.type == "match"
- prev_lines.clear
- prev_match_line = line
- else
- prev_lines.push(line)
- prev_lines.shift if prev_lines.length >= max_number_of_lines
- end
+ if line.type == "match"
+ prev_lines.clear
+ prev_match_line = line
else
prev_lines << line
- return prev_lines
+
+ break if generate_line_code(line) == self.line_code
+
+ prev_lines.shift if prev_lines.length >= max_number_of_lines
end
end
+
+ prev_lines
end
def diff_lines
@@ -400,6 +493,10 @@ class Note < ActiveRecord::Base
for_merge_request? && for_diff_line?
end
+ def for_project_snippet?
+ noteable_type == "Snippet"
+ end
+
# override to return commits, which are not active record
def noteable
if for_commit?
@@ -423,6 +520,26 @@ class Note < ActiveRecord::Base
)
end
+ def superceded?(notes)
+ return false unless vote?
+
+ notes.each do |note|
+ next if note == self
+
+ if note.vote? &&
+ self[:author_id] == note[:author_id] &&
+ self[:created_at] <= note[:created_at]
+ return true
+ end
+ end
+
+ false
+ end
+
+ def vote?
+ upvote? || downvote?
+ end
+
def votable?
for_issue? || (for_merge_request? && !for_diff_line?)
end
@@ -444,7 +561,7 @@ class Note < ActiveRecord::Base
end
# FIXME: Hack for polymorphic associations with STI
- # For more information wisit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
+ # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
def noteable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
@@ -467,6 +584,6 @@ class Note < ActiveRecord::Base
end
def editable?
- !system
+ !read_attribute(:system)
end
end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index b0f8ed6a4e..1395274173 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -6,12 +6,13 @@ class Notification
N_PARTICIPATING = 1
N_WATCH = 2
N_GLOBAL = 3
+ N_MENTION = 4
attr_accessor :target
class << self
def notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH]
+ [N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION]
end
def options_with_labels
@@ -19,12 +20,13 @@ class Notification
disabled: N_DISABLED,
participating: N_PARTICIPATING,
watch: N_WATCH,
+ mention: N_MENTION,
global: N_GLOBAL
}
end
def project_notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL]
+ [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION]
end
end
@@ -48,6 +50,10 @@ class Notification
target.notification_level == N_GLOBAL
end
+ def mention?
+ target.notification_level == N_MENTION
+ end
+
def level
target.notification_level
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 90d2649ba2..b19606e963 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -24,12 +24,21 @@
# import_status :string(255)
# repository_size :float default(0.0)
# star_count :integer default(0), not null
+# import_type :string(255)
+# import_source :string(255)
+# avatar :string(255)
#
+require 'carrierwave/orm/activerecord'
+require 'file_size_validator'
+
class Project < ActiveRecord::Base
+ include Sortable
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
include Gitlab::ConfigHelper
+ include Rails.application.routes.url_helpers
+
extend Gitlab::ConfigHelper
extend Enumerize
@@ -41,14 +50,20 @@ class Project < ActiveRecord::Base
default_value_for :wall_enabled, false
default_value_for :snippets_enabled, gitlab_config_features.snippets
+ # set last_activity_at to the same as created_at
+ after_create :set_last_activity_at
+ def set_last_activity_at
+ update_column(:last_activity_at, self.created_at)
+ end
+
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :tags
attr_accessor :new_default_branch
# Relations
- belongs_to :creator, foreign_key: "creator_id", class_name: "User"
- belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id"
+ belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
+ belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
belongs_to :namespace
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
@@ -58,28 +73,38 @@ class Project < ActiveRecord::Base
has_one :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy
has_one :emails_on_push_service, dependent: :destroy
+ has_one :irker_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy
has_one :hipchat_service, dependent: :destroy
has_one :flowdock_service, dependent: :destroy
has_one :assembla_service, dependent: :destroy
+ has_one :asana_service, dependent: :destroy
has_one :gemnasium_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
has_one :buildbox_service, dependent: :destroy
+ has_one :bamboo_service, dependent: :destroy
+ has_one :teamcity_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy
+ has_one :jira_service, dependent: :destroy
+ has_one :redmine_service, dependent: :destroy
+ has_one :custom_issue_tracker_service, dependent: :destroy
+ has_one :gitlab_issue_tracker_service, dependent: :destroy
+
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
+
has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it
- has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
+ has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
# Merge requests from source project should be kept when source project was removed
- has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
- has_many :issues, -> { order 'issues.state DESC, issues.created_at DESC' }, dependent: :destroy
+ has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest
+ has_many :issues, dependent: :destroy
has_many :labels, dependent: :destroy
has_many :services, dependent: :destroy
has_many :events, dependent: :destroy
has_many :milestones, dependent: :destroy
has_many :notes, dependent: :destroy
- has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
- has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
+ has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet'
+ has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
has_many :protected_branches, dependent: :destroy
has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember'
has_many :users, through: :project_members
@@ -94,67 +119,71 @@ class Project < ActiveRecord::Base
# Validations
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
- validates :name, presence: true, length: { within: 0..255 },
- format: { with: Gitlab::Regex.project_name_regex,
- message: Gitlab::Regex.project_regex_message }
- validates :path, presence: true, length: { within: 0..255 },
- exclusion: { in: Gitlab::Blacklist.path },
- format: { with: Gitlab::Regex.path_regex,
- message: Gitlab::Regex.path_regex_message }
+ validates :name,
+ presence: true,
+ length: { within: 0..255 },
+ format: { with: Gitlab::Regex.project_name_regex,
+ message: Gitlab::Regex.project_regex_message }
+ validates :path,
+ presence: true,
+ length: { within: 0..255 },
+ format: { with: Gitlab::Regex.path_regex,
+ message: Gitlab::Regex.path_regex_message }
validates :issues_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] }
- validates :visibility_level,
- exclusion: { in: gitlab_config.restricted_visibility_levels },
- if: -> { gitlab_config.restricted_visibility_levels.any? }
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
validates :import_url,
- format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" },
+ format: { with: URI::regexp(%w(ssh git http https)), message: 'should be a valid url' },
if: :import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
+ validate :avatar_type,
+ if: ->(project) { project.avatar && project.avatar_changed? }
+ validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
+
+ mount_uploader :avatar, AvatarUploader
# Scopes
- scope :without_user, ->(user) { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
- scope :without_team, ->(team) { team.projects.present? ? where("projects.id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped }
- scope :not_in_group, ->(group) { where("projects.id NOT IN (:ids)", ids: group.project_ids ) }
- scope :in_team, ->(team) { where("projects.id IN (:ids)", ids: team.projects.map(&:id)) }
+ scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
+ scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }
+ scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') }
+
+ scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) }
+ scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped }
+ scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) }
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
scope :in_group_namespace, -> { joins(:group) }
- scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
- scope :sorted_by_stars, -> { reorder("projects.star_count DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
- scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
+ scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) }
- enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
-
state_machine :import_status, initial: :none do
event :import_start do
- transition :none => :started
+ transition [:none, :finished] => :started
end
event :import_finish do
- transition :started => :finished
+ transition started: :finished
end
event :import_fail do
- transition :started => :failed
+ transition started: :failed
end
event :import_retry do
- transition :failed => :started
+ transition failed: :started
end
state :started
state :finished
state :failed
- after_transition any => :started, :do => :add_import_job
+ after_transition any => :started, do: :add_import_job
end
class << self
@@ -168,30 +197,35 @@ class Project < ActiveRecord::Base
def publicish(user)
visibility_levels = [Project::PUBLIC]
- visibility_levels += [Project::INTERNAL] if user
+ visibility_levels << Project::INTERNAL if user
where(visibility_level: visibility_levels)
end
def with_push
- includes(:events).where('events.action = ?', Event::PUSHED)
+ joins(:events).where('events.action = ?', Event::PUSHED)
end
def active
- joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
+ joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC')
end
def search(query)
- joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%")
+ joins(:namespace).where('projects.archived = ?', false).
+ where('LOWER(projects.name) LIKE :query OR
+ LOWER(projects.path) LIKE :query OR
+ LOWER(namespaces.name) LIKE :query OR
+ LOWER(projects.description) LIKE :query',
+ query: "%#{query.try(:downcase)}%")
end
def search_by_title(query)
- where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%")
+ where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
end
def find_with_namespace(id)
- return nil unless id.include?("/")
+ return nil unless id.include?('/')
- id = id.split("/")
+ id = id.split('/')
namespace = Namespace.find_by(path: id.first)
return nil unless namespace
@@ -203,13 +237,10 @@ class Project < ActiveRecord::Base
end
def sort(method)
- case method.to_s
- when 'newest' then reorder('projects.created_at DESC')
- when 'oldest' then reorder('projects.created_at ASC')
- when 'recently_updated' then reorder('projects.updated_at DESC')
- when 'last_updated' then reorder('projects.updated_at ASC')
- when 'largest_repository' then reorder('projects.repository_size DESC')
- else reorder("namespaces.path, projects.name ASC")
+ if method == 'repository_size_desc'
+ reorder(repository_size: :desc, id: :desc)
+ else
+ order_by(method)
end
end
end
@@ -259,19 +290,19 @@ class Project < ActiveRecord::Base
end
def to_param
- namespace.path + "/" + path
+ path
end
def web_url
- [gitlab_config.url, path_with_namespace].join("/")
+ [gitlab_config.url, path_with_namespace].join('/')
end
def web_url_without_protocol
- web_url.split("://")[1]
+ web_url.split('://')[1]
end
def build_commit_note(commit)
- notes.new(commit_id: commit.id, noteable_type: "Commit")
+ notes.new(commit_id: commit.id, noteable_type: 'Commit')
end
def last_activity
@@ -287,33 +318,68 @@ class Project < ActiveRecord::Base
end
def issue_exists?(issue_id)
- if used_default_issues_tracker?
+ if default_issues_tracker?
self.issues.where(iid: issue_id).first.present?
else
true
end
end
- def used_default_issues_tracker?
- self.issues_tracker == Project.issues_tracker.default_value
+ def default_issue_tracker
+ gitlab_issue_tracker_service || create_gitlab_issue_tracker_service
end
- def can_have_issues_tracker_id?
- self.issues_enabled && !self.used_default_issues_tracker?
- end
-
- def build_missing_services
- available_services_names.each do |service_name|
- service = services.find { |service| service.to_param == service_name }
-
- # If service is available but missing in db
- # we should create an instance. Ex `create_gitlab_ci_service`
- service = self.send :"create_#{service_name}_service" if service.nil?
+ def issues_tracker
+ if external_issue_tracker
+ external_issue_tracker
+ else
+ default_issue_tracker
end
end
- def available_services_names
- %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox)
+ def default_issues_tracker?
+ if external_issue_tracker
+ false
+ else
+ true
+ end
+ end
+
+ def external_issues_trackers
+ services.select(&:issue_tracker?).reject(&:default?)
+ end
+
+ def external_issue_tracker
+ @external_issues_tracker ||= external_issues_trackers.select(&:activated?).first
+ end
+
+ def can_have_issues_tracker_id?
+ self.issues_enabled && !self.default_issues_tracker?
+ end
+
+ def build_missing_services
+ services_templates = Service.where(template: true)
+
+ Service.available_services_names.each do |service_name|
+ service = find_service(services, service_name)
+
+ # If service is available but missing in db
+ if service.nil?
+ # We should check if template for the service exists
+ template = find_service(services_templates, service_name)
+
+ if template.nil?
+ # If no template, we should create an instance. Ex `create_gitlab_ci_service`
+ service = self.send :"create_#{service_name}_service"
+ else
+ Service.create_from_template(self.id, template)
+ end
+ end
+ end
+ end
+
+ def find_service(list, name)
+ list.find { |service| service.to_param == name }
end
def gitlab_ci?
@@ -328,6 +394,27 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.select(&:activated?).first
end
+ def avatar_type
+ unless self.avatar.image?
+ self.errors.add :avatar, 'only images allowed'
+ end
+ end
+
+ def avatar_in_git
+ @avatar_file ||= 'logo.png' if repository.blob_at_branch('master', 'logo.png')
+ @avatar_file ||= 'logo.jpg' if repository.blob_at_branch('master', 'logo.jpg')
+ @avatar_file ||= 'logo.gif' if repository.blob_at_branch('master', 'logo.gif')
+ @avatar_file
+ end
+
+ def avatar_url
+ if avatar.present?
+ [gitlab_config.url, avatar.url].join
+ elsif avatar_in_git
+ [gitlab_config.url, namespace_project_avatar_path(namespace, self)].join
+ end
+ end
+
# For compatibility with old code
def code
path
@@ -354,20 +441,20 @@ class Project < ActiveRecord::Base
end
end
- def team_member_by_name_or_email(name = nil, email = nil)
- user = users.where("name like ? or email like ?", name, email).first
+ def project_member_by_name_or_email(name = nil, email = nil)
+ user = users.where('name like ? or email like ?', name, email).first
project_members.where(user: user) if user
end
# Get Team Member record by user id
- def team_member_by_id(user_id)
+ def project_member_by_id(user_id)
project_members.find_by(user_id: user_id)
end
def name_with_namespace
@name_with_namespace ||= begin
if namespace
- namespace.human_name + " / " + name
+ namespace.human_name + ' / ' + name
else
name
end
@@ -388,62 +475,22 @@ class Project < ActiveRecord::Base
end
end
- def execute_services(data)
- services.each do |service|
-
- # Call service hook only if it is active
- begin
- service.execute(data) if service.active
- rescue => e
- logger.error(e)
- end
+ def execute_services(data, hooks_scope = :push_hooks)
+ # Call only service hooks that are active for this scope
+ services.send(hooks_scope).each do |service|
+ service.async_execute(data)
end
end
def update_merge_requests(oldrev, newrev, ref, user)
- return true unless ref =~ /heads/
- branch_name = ref.gsub("refs/heads/", "")
- commits = self.repository.commits_between(oldrev, newrev)
- c_ids = commits.map(&:id)
-
- # Close merge requests
- mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
- mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
-
- mrs.uniq.each do |merge_request|
- MergeRequests::MergeService.new.execute(merge_request, user, nil)
- end
-
- # Update code for merge requests into project between project branches
- mrs = self.merge_requests.opened.by_branch(branch_name).to_a
- # Update code for merge requests between project and project fork
- mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
-
- mrs.uniq.each do |merge_request|
- merge_request.reload_code
- merge_request.mark_as_unchecked
- end
-
- # Add comment about pushing new commits to merge requests
- comment_mr_with_commits(branch_name, commits, user)
-
- true
- end
-
- def comment_mr_with_commits(branch_name, commits, user)
- mrs = self.origin_merge_requests.opened.where(source_branch: branch_name).to_a
- mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a
-
- mrs.uniq.each do |merge_request|
- Note.create_new_commits_note(merge_request, merge_request.project,
- user, commits)
- end
+ MergeRequests::RefreshService.new(self, user).
+ execute(oldrev, newrev, ref)
end
def valid_repo?
repository.exists?
rescue
- errors.add(:path, "Invalid repository path")
+ errors.add(:path, 'Invalid repository path')
false
end
@@ -502,7 +549,7 @@ class Project < ActiveRecord::Base
end
def http_url_to_repo
- [gitlab_config.url, "/", path_with_namespace, ".git"].join('')
+ [gitlab_config.url, '/', path_with_namespace, '.git'].join('')
end
# Check if current branch name is marked as protected in the system
@@ -510,6 +557,10 @@ class Project < ActiveRecord::Base
protected_branches_names.include?(branch_name)
end
+ def developers_can_push_to_protected_branch?(branch_name)
+ protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push }
+ end
+
def forked?
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end
@@ -561,6 +612,7 @@ class Project < ActiveRecord::Base
# Since we do cache @event we need to reset cache in special cases:
# * when project was moved
# * when project was renamed
+ # * when the project avatar changes
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
@@ -620,4 +672,25 @@ class Project < ActiveRecord::Base
def origin_merge_requests
merge_requests.where(source_project_id: self.id)
end
+
+ def create_repository
+ if gitlab_shell.add_repository(path_with_namespace)
+ true
+ else
+ errors.add(:base, 'Failed to create repository')
+ false
+ end
+ end
+
+ def repository_exists?
+ !!repository.exists?
+ end
+
+ def create_wiki
+ ProjectWiki.new(self, self.owner).wiki
+ true
+ rescue ProjectWiki::CouldNotCreateWikiError => ex
+ errors.add(:base, 'Failed create wiki')
+ false
+ end
end
diff --git a/app/models/project_contributions.rb b/app/models/project_contributions.rb
new file mode 100644
index 0000000000..8ab2d814a9
--- /dev/null
+++ b/app/models/project_contributions.rb
@@ -0,0 +1,23 @@
+class ProjectContributions
+ attr_reader :project, :user
+
+ def initialize(project, user)
+ @project, @user = project, user
+ end
+
+ def commits_log
+ repository = project.repository
+
+ if !repository.exists? || repository.empty?
+ return {}
+ end
+
+ Rails.cache.fetch(cache_key) do
+ repository.commits_per_day_for_user(user)
+ end
+ end
+
+ def cache_key
+ "#{Date.today.to_s}-commits-log-#{project.id}-#{user.email}"
+ end
+end
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
new file mode 100644
index 0000000000..d52214cdd6
--- /dev/null
+++ b/app/models/project_services/asana_service.rb
@@ -0,0 +1,127 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+require 'asana'
+
+class AsanaService < Service
+ prop_accessor :api_key, :restrict_to_branch
+ validates :api_key, presence: true, if: :activated?
+
+ def title
+ 'Asana'
+ end
+
+ def description
+ 'Asana - Teamwork without email'
+ end
+
+ def help
+ 'This service adds commit messages as comments to Asana tasks.
+Once enabled, commit messages are checked for Asana task URLs
+(for example, `https://app.asana.com/0/123456/987654`) or task IDs
+starting with # (for example, `#987654`). Every task ID found will
+get the commit comment added to it.
+
+You can also close a task with a message containing: `fix #123456`.
+
+You can find your Api Keys here:
+http://developer.asana.com/documentation/#api_keys'
+ end
+
+ def to_param
+ 'asana'
+ end
+
+ def fields
+ [
+ {
+ type: 'text',
+ name: 'api_key',
+ placeholder: 'User API token. User must have access to task,
+all comments will be attributed to this user.'
+ },
+ {
+ type: 'text',
+ name: 'restrict_to_branch',
+ placeholder: 'Comma-separated list of branches which will be
+automatically inspected. Leave blank to include all branches.'
+ }
+ ]
+ end
+
+ def supported_events
+ %w(push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ Asana.configure do |client|
+ client.api_key = api_key
+ end
+
+ user = data[:user_name]
+ branch = Gitlab::Git.ref_name(data[:ref])
+
+ branch_restriction = restrict_to_branch.to_s
+
+ # check the branch restriction is poplulated and branch is not included
+ if branch_restriction.length > 0 && branch_restriction.index(branch) == nil
+ return
+ end
+
+ project_name = project.name_with_namespace
+ push_msg = user + ' pushed to branch ' + branch + ' of ' + project_name
+
+ data[:commits].each do |commit|
+ check_commit(' ( ' + commit[:url] + ' ): ' + commit[:message], push_msg)
+ end
+ end
+
+ def check_commit(message, push_msg)
+ task_list = []
+ close_list = []
+
+ message.split("\n").each do |line|
+ # look for a task ID or a full Asana url
+ task_list.concat(line.scan(/#(\d+)/))
+ task_list.concat(line.scan(/https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)/))
+ # look for a word starting with 'fix' followed by a task ID
+ close_list.concat(line.scan(/(fix\w*)\W*#(\d+)/i))
+ end
+
+ # post commit to every taskid found
+ task_list.each do |taskid|
+ task = Asana::Task.find(taskid[0])
+
+ if task
+ task.create_story(text: push_msg + ' ' + message)
+ end
+ end
+
+ # close all tasks that had 'fix(ed/es/ing) #:id' in them
+ close_list.each do |taskid|
+ task = Asana::Task.find(taskid.last)
+
+ if task
+ task.modify(completed: true)
+ end
+ end
+ end
+end
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index 0b90a14f39..fb7e0c0fb0 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -2,14 +2,20 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
#
class AssemblaService < Service
@@ -37,8 +43,14 @@ class AssemblaService < Service
]
end
- def execute(push)
+ def supported_events
+ %w(push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
url = "https://atlas.assembla.com/spaces/#{subdomain}/github_tool?secret_key=#{token}"
- AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' })
+ AssemblaService.post(url, body: { payload: data }.to_json, headers: { 'Content-Type' => 'application/json' })
end
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
new file mode 100644
index 0000000000..0100f1e4a1
--- /dev/null
+++ b/app/models/project_services/bamboo_service.rb
@@ -0,0 +1,137 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+class BambooService < CiService
+ include HTTParty
+
+ prop_accessor :bamboo_url, :build_key, :username, :password
+
+ validates :bamboo_url,
+ presence: true,
+ format: { with: URI::regexp },
+ if: :activated?
+ validates :build_key, presence: true, if: :activated?
+ validates :username,
+ presence: true,
+ if: ->(service) { service.password? },
+ if: :activated?
+ validates :password,
+ presence: true,
+ if: ->(service) { service.username? },
+ if: :activated?
+
+ attr_accessor :response
+
+ after_save :compose_service_hook, if: :activated?
+
+ def compose_service_hook
+ hook = service_hook || build_service_hook
+ hook.save
+ end
+
+ def title
+ 'Atlassian Bamboo CI'
+ end
+
+ def description
+ 'A continuous integration and build server'
+ end
+
+ def help
+ 'You must set up automatic revision labeling and a repository trigger in Bamboo.'
+ end
+
+ def to_param
+ 'bamboo'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'bamboo_url',
+ placeholder: 'Bamboo root URL like https://bamboo.example.com' },
+ { type: 'text', name: 'build_key',
+ placeholder: 'Bamboo build plan key like KEY' },
+ { type: 'text', name: 'username',
+ placeholder: 'A user with API access, if applicable' },
+ { type: 'password', name: 'password' },
+ ]
+ end
+
+ def supported_events
+ %w(push)
+ end
+
+ def build_info(sha)
+ url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
+
+ if username.blank? && password.blank?
+ @response = HTTParty.get(parsed_url.to_s, verify: false)
+ else
+ get_url = "#{url}&os_authType=basic"
+ auth = {
+ username: username,
+ password: password,
+ }
+ @response = HTTParty.get(get_url, verify: false, basic_auth: auth)
+ end
+ end
+
+ def build_page(sha)
+ build_info(sha) if @response.nil? || !@response.code
+
+ if @response.code != 200 || @response['results']['results']['size'] == '0'
+ # If actual build link can't be determined, send user to build summary page.
+ "#{bamboo_url}/browse/#{build_key}"
+ else
+ # If actual build link is available, go to build result page.
+ result_key = @response['results']['results']['result']['planResultKey']['key']
+ "#{bamboo_url}/browse/#{result_key}"
+ end
+ end
+
+ def commit_status(sha)
+ build_info(sha) if @response.nil? || !@response.code
+ return :error unless @response.code == 200 || @response.code == 404
+
+ status = if @response.code == 404 || @response['results']['results']['size'] == '0'
+ 'Pending'
+ else
+ @response['results']['results']['result']['buildState']
+ end
+
+ if status.include?('Success')
+ 'success'
+ elsif status.include?('Failed')
+ 'failed'
+ elsif status.include?('Pending')
+ 'pending'
+ else
+ :error
+ end
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ # Bamboo requires a GET and does not take any data.
+ self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
+ verify: false)
+ end
+end
diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb
index b0f8e28c97..270863c157 100644
--- a/app/models/project_services/buildbox_service.rb
+++ b/app/models/project_services/buildbox_service.rb
@@ -2,16 +2,24 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
#
+require "addressable/uri"
+
class BuildboxService < CiService
prop_accessor :project_url, :token
@@ -30,7 +38,13 @@ class BuildboxService < CiService
hook.save
end
+ def supported_events
+ %w(push)
+ end
+
def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
service_hook.execute(data)
end
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index 0736ddab99..e591afdda6 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -2,14 +2,20 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
#
class CampfireService < Service
@@ -36,11 +42,17 @@ class CampfireService < Service
]
end
- def execute(push_data)
+ def supported_events
+ %w(push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
room = gate.find_room_by_name(self.room)
return true unless room
- message = build_message(push_data)
+ message = build_message(data)
room.speak(message)
end
@@ -52,7 +64,7 @@ class CampfireService < Service
end
def build_message(push)
- ref = push[:ref].gsub("refs/heads/", "")
+ ref = Gitlab::Git.ref_name(push[:ref])
before = push[:before]
after = push[:after]
@@ -60,9 +72,9 @@ class CampfireService < Service
message << "[#{project.name_with_namespace}] "
message << "#{push[:user_name]} "
- if before =~ /000000/
+ if Gitlab::Git.blank_ref?(before)
message << "pushed new branch #{ref} \n"
- elsif after =~ /000000/
+ elsif Gitlab::Git.blank_ref?(after)
message << "removed branch #{ref} \n"
else
message << "pushed #{push[:total_commits_count]} commits to #{ref}. "
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index b1d5e49ede..c6f6b4952c 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -2,14 +2,19 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
#
# Base class for CI services
@@ -20,6 +25,10 @@ class CiService < Service
:ci
end
+ def supported_events
+ %w(push)
+ end
+
# Return complete url to build page
#
# Ex.
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
new file mode 100644
index 0000000000..8d25f62787
--- /dev/null
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -0,0 +1,57 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+class CustomIssueTrackerService < IssueTrackerService
+
+ prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
+
+ def title
+ if self.properties && self.properties['title'].present?
+ self.properties['title']
+ else
+ 'Custom Issue Tracker'
+ end
+ end
+
+ def description
+ if self.properties && self.properties['description'].present?
+ self.properties['description']
+ else
+ 'Custom issue tracker'
+ end
+ end
+
+ def to_param
+ 'custom_issue_tracker'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'title', placeholder: title },
+ { type: 'text', name: 'description', placeholder: description },
+ { type: 'text', name: 'project_url', placeholder: 'Project url' },
+ { type: 'text', name: 'issues_url', placeholder: 'Issue url' },
+ { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' }
+ ]
+ end
+
+ def initialize_properties
+ self.properties = {} if properties.nil?
+ end
+end
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index b9071b9829..acb5e7f1af 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -2,17 +2,24 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
#
class EmailsOnPushService < Service
+ prop_accessor :send_from_committer_email
+ prop_accessor :disable_diffs
prop_accessor :recipients
validates :recipients, presence: true, if: :activated?
@@ -28,12 +35,31 @@ class EmailsOnPushService < Service
'emails_on_push'
end
+ def supported_events
+ %w(push)
+ end
+
def execute(push_data)
- EmailsOnPushWorker.perform_async(project_id, recipients, push_data)
+ return unless supported_events.include?(push_data[:object_kind])
+
+ EmailsOnPushWorker.perform_async(project_id, recipients, push_data, send_from_committer_email?, disable_diffs?)
+ end
+
+ def send_from_committer_email?
+ self.send_from_committer_email == "1"
+ end
+
+ def disable_diffs?
+ self.disable_diffs == "1"
end
def fields
+ domains = Notify.allowed_email_domains.map { |domain| "user@#{domain}" }.join(", ")
[
+ { type: 'checkbox', name: 'send_from_committer_email', title: "Send from committer",
+ help: "Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. #{domains})." },
+ { type: 'checkbox', name: 'disable_diffs', title: "Disable code diffs",
+ help: "Don't include possibly sensitive code diffs in notification body." },
{ type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' },
]
end
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 0020b4482e..99e361dd6e 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -2,14 +2,19 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
#
require "flowdock-git-hook"
@@ -36,14 +41,19 @@ class FlowdockService < Service
]
end
- def execute(push_data)
- repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
+ def supported_events
+ %w(push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
Flowdock::Git.post(
- push_data[:ref],
- push_data[:before],
- push_data[:after],
+ data[:ref],
+ data[:before],
+ data[:after],
token: token,
- repo: repo_path,
+ repo: project.repository.path_to_repo,
repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 6d2fc06a5d..4e75bdfc95 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -2,14 +2,19 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
#
require "gemnasium/gitlab_service"
@@ -37,15 +42,20 @@ class GemnasiumService < Service
]
end
- def execute(push_data)
- repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
+ def supported_events
+ %w(push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
Gemnasium::GitlabService.execute(
- ref: push_data[:ref],
- before: push_data[:before],
- after: push_data[:after],
+ ref: data[:ref],
+ before: data[:before],
+ after: data[:after],
token: token,
api_key: api_key,
- repo: repo_path
+ repo: project.repository.path_to_repo
)
end
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index a897c4ab76..d81623625c 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -2,14 +2,19 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
#
class GitlabCiService < CiService
@@ -17,8 +22,6 @@ class GitlabCiService < CiService
validates :project_url, presence: true, if: :activated?
validates :token, presence: true, if: :activated?
- delegate :execute, to: :service_hook, prefix: nil
-
after_save :compose_service_hook, if: :activated?
def compose_service_hook
@@ -27,8 +30,18 @@ class GitlabCiService < CiService
hook.save
end
+ def supported_events
+ %w(push tag_push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ service_hook.execute(data)
+ end
+
def commit_status_path(sha)
- project_url + "/builds/#{sha}/status.json?token=#{token}"
+ project_url + "/commits/#{sha}/status.json?token=#{token}"
end
def get_ci_build(sha)
@@ -55,7 +68,7 @@ class GitlabCiService < CiService
end
def build_page(sha)
- project_url + "/builds/#{sha}"
+ project_url + "/commits/#{sha}"
end
def builds_path
@@ -81,7 +94,7 @@ class GitlabCiService < CiService
def fields
[
{ type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },
- { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3'}
+ { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' }
]
end
end
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
new file mode 100644
index 0000000000..84346350a6
--- /dev/null
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -0,0 +1,51 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+class GitlabIssueTrackerService < IssueTrackerService
+ include Rails.application.routes.url_helpers
+ prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
+
+
+ def default?
+ true
+ end
+
+ def to_param
+ 'gitlab'
+ end
+
+ def project_url
+ "#{gitlab_url}#{namespace_project_issues_path(project.namespace, project)}"
+ end
+
+ def new_issue_url
+ "#{gitlab_url}#{new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project)}"
+ end
+
+ def issue_url(iid)
+ "#{gitlab_url}#{namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid)}"
+ end
+
+ private
+
+ def gitlab_url
+ Gitlab.config.gitlab.relative_url_root.chomp("/") if Gitlab.config.gitlab.relative_url_root
+ end
+end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 4078938cdb..d264a56ebd 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -2,24 +2,29 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
#
class HipchatService < Service
MAX_COMMITS = 3
- prop_accessor :token, :room
+ prop_accessor :token, :room, :server
validates :token, presence: true, if: :activated?
def title
- 'Hipchat'
+ 'HipChat'
end
def description
@@ -32,37 +37,64 @@ class HipchatService < Service
def fields
[
- { type: 'text', name: 'token', placeholder: '' },
- { type: 'text', name: 'room', placeholder: '' }
+ { type: 'text', name: 'token', placeholder: 'Room token' },
+ { type: 'text', name: 'room', placeholder: 'Room name or ID' },
+ { type: 'text', name: 'server',
+ placeholder: 'Leave blank for default. https://hipchat.example.com' }
]
end
- def execute(push_data)
- gate[room].send('GitLab', create_message(push_data))
+ def supported_events
+ %w(push issue merge_request note tag_push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ gate[room].send('GitLab', create_message(data))
end
private
def gate
- @gate ||= HipChat::Client.new(token)
+ options = { api_version: 'v2' }
+ options[:server_url] = server unless server.blank?
+ @gate ||= HipChat::Client.new(token, options)
end
- def create_message(push)
- ref = push[:ref].gsub("refs/heads/", "")
+ def create_message(data)
+ object_kind = data[:object_kind]
+
+ message = \
+ case object_kind
+ when "push", "tag_push"
+ create_push_message(data)
+ when "issue"
+ create_issue_message(data) unless is_update?(data)
+ when "merge_request"
+ create_merge_request_message(data) unless is_update?(data)
+ when "note"
+ create_note_message(data)
+ end
+ end
+
+ def create_push_message(push)
+ ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch'
+ ref = Gitlab::Git.ref_name(push[:ref])
+
before = push[:before]
after = push[:after]
message = ""
message << "#{push[:user_name]} "
- if before =~ /000000/
- message << "pushed new branch #{ref} "\
- " to "\
- "#{project.name_with_namespace.gsub!(/\s/, "")} \n"
- elsif after =~ /000000/
- message << "removed branch #{ref} from #{project.name_with_namespace.gsub!(/\s/,'')} \n"
+ if Gitlab::Git.blank_ref?(before)
+ message << "pushed new #{ref_type} #{ref} "\
+ " to #{project_link}\n"
+ elsif Gitlab::Git.blank_ref?(after)
+ message << "removed #{ref_type} #{ref} from #{project_name} \n"
else
- message << "pushed to branch #{ref} "
message << "of #{project.name_with_namespace.gsub!(/\s/,'')} "
message << "(Compare changes )"
@@ -78,4 +110,129 @@ class HipchatService < Service
message
end
+
+ def format_body(body)
+ if body
+ body = body.truncate(200, separator: ' ', omission: '...')
+ end
+
+ "#{body} "
+ end
+
+ def create_issue_message(data)
+ user_name = data[:user][:name]
+
+ obj_attr = data[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ title = obj_attr[:title]
+ state = obj_attr[:state]
+ issue_iid = obj_attr[:iid]
+ issue_url = obj_attr[:url]
+ description = obj_attr[:description]
+
+ issue_link = "issue ##{issue_iid} "
+ message = "#{user_name} #{state} #{issue_link} in #{project_link}: #{title} "
+
+ if description
+ description = format_body(description)
+ message << description
+ end
+
+ message
+ end
+
+ def create_merge_request_message(data)
+ user_name = data[:user][:name]
+
+ obj_attr = data[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ merge_request_id = obj_attr[:iid]
+ source_branch = obj_attr[:source_branch]
+ target_branch = obj_attr[:target_branch]
+ state = obj_attr[:state]
+ description = obj_attr[:description]
+ title = obj_attr[:title]
+
+ merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
+ merge_request_link = "merge request ##{merge_request_id} "
+ message = "#{user_name} #{state} #{merge_request_link} in " \
+ "#{project_link}: #{title} "
+
+ if description
+ description = format_body(description)
+ message << description
+ end
+
+ message
+ end
+
+ def format_title(title)
+ "" + title.lines.first.chomp + " "
+ end
+
+ def create_note_message(data)
+ data = HashWithIndifferentAccess.new(data)
+ user_name = data[:user][:name]
+
+ repo_attr = HashWithIndifferentAccess.new(data[:repository])
+
+ obj_attr = HashWithIndifferentAccess.new(data[:object_attributes])
+ note = obj_attr[:note]
+ note_url = obj_attr[:url]
+ noteable_type = obj_attr[:noteable_type]
+
+ case noteable_type
+ when "Commit"
+ commit_attr = HashWithIndifferentAccess.new(data[:commit])
+ subject_desc = commit_attr[:id]
+ subject_desc = Commit.truncate_sha(subject_desc)
+ subject_type = "commit"
+ title = format_title(commit_attr[:message])
+ when "Issue"
+ subj_attr = HashWithIndifferentAccess.new(data[:issue])
+ subject_id = subj_attr[:iid]
+ subject_desc = "##{subject_id}"
+ subject_type = "issue"
+ title = format_title(subj_attr[:title])
+ when "MergeRequest"
+ subj_attr = HashWithIndifferentAccess.new(data[:merge_request])
+ subject_id = subj_attr[:iid]
+ subject_desc = "##{subject_id}"
+ subject_type = "merge request"
+ title = format_title(subj_attr[:title])
+ when "Snippet"
+ subj_attr = HashWithIndifferentAccess.new(data[:snippet])
+ subject_id = subj_attr[:id]
+ subject_desc = "##{subject_id}"
+ subject_type = "snippet"
+ title = format_title(subj_attr[:title])
+ end
+
+ subject_html = "#{subject_type} #{subject_desc} "
+ message = "#{user_name} commented on #{subject_html} in #{project_link}: "
+ message << title
+
+ if note
+ note = format_body(note)
+ message << note
+ end
+
+ message
+ end
+
+ def project_name
+ project.name_with_namespace.gsub(/\s/, '')
+ end
+
+ def project_url
+ project.web_url
+ end
+
+ def project_link
+ "#{project_name} "
+ end
+
+ def is_update?(data)
+ data[:object_attributes][:action] == 'update'
+ end
end
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
new file mode 100644
index 0000000000..2bddb7b881
--- /dev/null
+++ b/app/models/project_services/irker_service.rb
@@ -0,0 +1,163 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'uri'
+
+class IrkerService < Service
+ prop_accessor :colorize_messages, :recipients, :channels
+ validates :recipients, presence: true, if: :activated?
+ validate :check_recipients_count, if: :activated?
+
+ before_validation :get_channels
+ after_initialize :initialize_settings
+
+ # Writer for RSpec tests
+ attr_writer :settings
+
+ def initialize_settings
+ # See the documentation (doc/project_services/irker.md) for possible values
+ # here
+ @settings ||= {
+ server_ip: 'localhost',
+ server_port: 6659,
+ max_channels: 3,
+ default_irc_uri: nil
+ }
+ end
+
+ def title
+ 'Irker (IRC gateway)'
+ end
+
+ def description
+ 'Send IRC messages, on update, to a list of recipients through an Irker '\
+ 'gateway.'
+ end
+
+ def help
+ msg = 'Recipients have to be specified with a full URI: '\
+ 'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\
+ 'the channel to be a nickname instead, append ",isnick" to the channel '\
+ 'name; if the channel is protected by a secret password, append '\
+ '"?key=secretpassword" to the URI.'
+
+ unless @settings[:default_irc].nil?
+ msg += ' Note that a default IRC URI is provided by this service\'s '\
+ "administrator: #{default_irc}. You can thus just give a channel name."
+ end
+ msg
+ end
+
+ def to_param
+ 'irker'
+ end
+
+ def supported_events
+ %w(push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ IrkerWorker.perform_async(project_id, channels,
+ colorize_messages, data, @settings)
+ end
+
+ def fields
+ [
+ { type: 'textarea', name: 'recipients',
+ placeholder: 'Recipients/channels separated by whitespaces' },
+ { type: 'checkbox', name: 'colorize_messages' },
+ ]
+ end
+
+ private
+
+ def check_recipients_count
+ return true if recipients.nil? || recipients.empty?
+
+ if recipients.split(/\s+/).count > max_chans
+ errors.add(:recipients, "are limited to #{max_chans}")
+ end
+ end
+
+ def max_chans
+ @settings[:max_channels]
+ end
+
+ def get_channels
+ return true unless :activated?
+ return true if recipients.nil? || recipients.empty?
+
+ map_recipients
+
+ errors.add(:recipients, 'are all invalid') if channels.empty?
+ true
+ end
+
+ def map_recipients
+ self.channels = recipients.split(/\s+/).map do |recipient|
+ format_channel default_irc_uri, recipient
+ end
+ channels.reject! &:nil?
+ end
+
+ def default_irc_uri
+ default_irc = @settings[:default_irc_uri]
+ if !(default_irc.nil? || default_irc[-1] == '/')
+ default_irc += '/'
+ end
+ default_irc
+ end
+
+ def format_channel(default_irc, recipient)
+ cnt = 0
+ url = nil
+
+ # Try to parse the chan as a full URI
+ begin
+ uri = URI.parse(recipient)
+ raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0
+ rescue URI::InvalidURIError
+ unless default_irc.nil?
+ cnt += 1
+ recipient = "#{default_irc}#{recipient}"
+ retry if cnt == 1
+ end
+ else
+ url = consider_uri uri
+ end
+ url
+ end
+
+ def consider_uri(uri)
+ # Authorize both irc://domain.com/#chan and irc://domain.com/chan
+ if uri.is_a?(URI) && uri.scheme[/^ircs?$/] && !uri.path.nil?
+ # Do not authorize irc://domain.com/
+ if uri.fragment.nil? && uri.path.length > 1
+ uri.to_s
+ else
+ # Authorize irc://domain.com/smthg#chan
+ # The irker daemon will deal with it by concatenating smthg and
+ # chan, thus sending messages on #smthgchan
+ uri.to_s
+ end
+ end
+ end
+end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
new file mode 100644
index 0000000000..8e90c44d10
--- /dev/null
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -0,0 +1,113 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+class IssueTrackerService < Service
+
+ validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
+
+ def category
+ :issue_tracker
+ end
+
+ def default?
+ false
+ end
+
+ def issue_url(iid)
+ self.issues_url.gsub(':id', iid.to_s)
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'description', placeholder: description },
+ { type: 'text', name: 'project_url', placeholder: 'Project url' },
+ { type: 'text', name: 'issues_url', placeholder: 'Issue url' },
+ { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' }
+ ]
+ end
+
+ def initialize_properties
+ if properties.nil?
+ if enabled_in_gitlab_config
+ self.properties = {
+ title: issues_tracker['title'],
+ project_url: add_issues_tracker_id(issues_tracker['project_url']),
+ issues_url: add_issues_tracker_id(issues_tracker['issues_url']),
+ new_issue_url: add_issues_tracker_id(issues_tracker['new_issue_url'])
+ }
+ else
+ self.properties = {}
+ end
+ end
+ end
+
+ def supported_events
+ %w(push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ message = "#{self.type} was unable to reach #{self.project_url}. Check the url and try again."
+ result = false
+
+ begin
+ url = URI.parse(self.project_url)
+
+ if url.host && url.port
+ http = Net::HTTP.start(url.host, url.port, { open_timeout: 5, read_timeout: 5 })
+ response = http.head("/")
+
+ if response
+ message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
+ result = true
+ end
+ end
+ rescue Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => error
+ message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}"
+ end
+ Rails.logger.info(message)
+ result
+ end
+
+ private
+
+ def enabled_in_gitlab_config
+ Gitlab.config.issues_tracker &&
+ Gitlab.config.issues_tracker.values.any? &&
+ issues_tracker
+ end
+
+ def issues_tracker
+ Gitlab.config.issues_tracker[to_param]
+ end
+
+ def add_issues_tracker_id(url)
+ if self.project
+ id = self.project.issues_tracker_id
+
+ if id
+ url = url.gsub(":issues_tracker_id", id)
+ end
+ end
+
+ url
+ end
+end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
new file mode 100644
index 0000000000..fcd9dc2f33
--- /dev/null
+++ b/app/models/project_services/jira_service.rb
@@ -0,0 +1,58 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+class JiraService < IssueTrackerService
+ include Rails.application.routes.url_helpers
+
+ prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
+
+ def help
+ issue_tracker_link = help_page_path("integration", "external-issue-tracker")
+
+ line1 = "Setting `project_url`, `issues_url` and `new_issue_url` will "\
+ "allow a user to easily navigate to the Jira issue tracker. "\
+ "See the [integration doc](#{issue_tracker_link}) for details."
+
+ line2 = 'Support for referencing commits and automatic closing of Jira issues directly ' \
+ 'from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html)'
+
+ [line1, line2].join("\n\n")
+ end
+
+ def title
+ if self.properties && self.properties['title'].present?
+ self.properties['title']
+ else
+ 'JIRA'
+ end
+ end
+
+ def description
+ if self.properties && self.properties['description'].present?
+ self.properties['description']
+ else
+ 'Jira issue tracker'
+ end
+ end
+
+ def to_param
+ 'jira'
+ end
+end
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 09e114f9cc..ade9ee9787 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -2,14 +2,20 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
#
class PivotaltrackerService < Service
@@ -36,9 +42,15 @@ class PivotaltrackerService < Service
]
end
- def execute(push)
+ def supported_events
+ %w(push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
url = 'https://www.pivotaltracker.com/services/v5/source_commits'
- push[:commits].each do |commit|
+ data[:commits].each do |commit|
message = {
'source_commit' => {
'commit_id' => commit[:id],
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index f247fde776..53edf522e9 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -2,14 +2,20 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
#
class PushoverService < Service
@@ -75,21 +81,27 @@ class PushoverService < Service
]
end
- def execute(push_data)
- ref = push_data[:ref].gsub('refs/heads/', '')
- before = push_data[:before]
- after = push_data[:after]
+ def supported_events
+ %w(push)
+ end
- if before =~ /000000/
- message = "#{push_data[:user_name]} pushed new branch \"#{ref}\"."
- elsif after =~ /000000/
- message = "#{push_data[:user_name]} deleted branch \"#{ref}\"."
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ ref = Gitlab::Git.ref_name(data[:ref])
+ before = data[:before]
+ after = data[:after]
+
+ if Gitlab::Git.blank_ref?(before)
+ message = "#{data[:user_name]} pushed new branch \"#{ref}\"."
+ elsif Gitlab::Git.blank_ref?(after)
+ message = "#{data[:user_name]} deleted branch \"#{ref}\"."
else
- message = "#{push_data[:user_name]} push to branch \"#{ref}\"."
+ message = "#{data[:user_name]} push to branch \"#{ref}\"."
end
- if push_data[:total_commits_count] > 0
- message << "\nTotal commits count: #{push_data[:total_commits_count]}"
+ if data[:total_commits_count] > 0
+ message << "\nTotal commits count: #{data[:total_commits_count]}"
end
pushover_data = {
@@ -99,7 +111,7 @@ class PushoverService < Service
priority: priority,
title: "#{project.name_with_namespace}",
message: message,
- url: push_data[:repository][:homepage],
+ url: data[:repository][:homepage],
url_title: "See project #{project.name_with_namespace}"
}
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
new file mode 100644
index 0000000000..dd9ba97ee1
--- /dev/null
+++ b/app/models/project_services/redmine_service.rb
@@ -0,0 +1,44 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+class RedmineService < IssueTrackerService
+
+ prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
+
+ def title
+ if self.properties && self.properties['title'].present?
+ self.properties['title']
+ else
+ 'Redmine'
+ end
+ end
+
+ def description
+ if self.properties && self.properties['description'].present?
+ self.properties['description']
+ else
+ 'Redmine issue tracker'
+ end
+ end
+
+ def to_param
+ 'redmine'
+ end
+end
diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb
deleted file mode 100644
index 28204e5ea6..0000000000
--- a/app/models/project_services/slack_message.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-require 'slack-notifier'
-
-class SlackMessage
- def initialize(params)
- @after = params.fetch(:after)
- @before = params.fetch(:before)
- @commits = params.fetch(:commits, [])
- @project_name = params.fetch(:project_name)
- @project_url = params.fetch(:project_url)
- @ref = params.fetch(:ref).gsub('refs/heads/', '')
- @username = params.fetch(:user_name)
- end
-
- def pretext
- format(message)
- end
-
- def attachments
- return [] if new_branch? || removed_branch?
-
- commit_message_attachments
- end
-
- private
-
- attr_reader :after
- attr_reader :before
- attr_reader :commits
- attr_reader :project_name
- attr_reader :project_url
- attr_reader :ref
- attr_reader :username
-
- def message
- if new_branch?
- new_branch_message
- elsif removed_branch?
- removed_branch_message
- else
- push_message
- end
- end
-
- def format(string)
- Slack::Notifier::LinkFormatter.format(string)
- end
-
- def new_branch_message
- "#{username} pushed new branch #{branch_link} to #{project_link}"
- end
-
- def removed_branch_message
- "#{username} removed branch #{ref} from #{project_link}"
- end
-
- def push_message
- "#{username} pushed to branch #{branch_link} of #{project_link} (#{compare_link})"
- end
-
- def commit_messages
- commits.each_with_object('') do |commit, str|
- str << compose_commit_message(commit)
- end.chomp
- end
-
- def commit_message_attachments
- [{ text: format(commit_messages), color: attachment_color }]
- end
-
- def compose_commit_message(commit)
- author = commit.fetch(:author).fetch(:name)
- id = commit.fetch(:id)[0..8]
- message = commit.fetch(:message)
- url = commit.fetch(:url)
-
- "[#{id}](#{url}): #{message} - #{author}\n"
- end
-
- def new_branch?
- before =~ /000000/
- end
-
- def removed_branch?
- after =~ /000000/
- end
-
- def branch_url
- "#{project_url}/commits/#{ref}"
- end
-
- def compare_url
- "#{project_url}/compare/#{before}...#{after}"
- end
-
- def branch_link
- "[#{ref}](#{branch_url})"
- end
-
- def project_link
- "[#{project_name}](#{project_url})"
- end
-
- def compare_link
- "[Compare changes](#{compare_url})"
- end
-
- def attachment_color
- '#345'
- end
-end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 95f3ddcef4..36d9874edd 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -2,18 +2,24 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
#
class SlackService < Service
- prop_accessor :webhook
+ prop_accessor :webhook, :username, :channel
validates :webhook, presence: true, if: :activated?
def title
@@ -30,21 +36,50 @@ class SlackService < Service
def fields
[
- { type: 'text', name: 'webhook', placeholder: '' }
+ { type: 'text', name: 'webhook',
+ placeholder: 'https://hooks.slack.com/services/...' },
+ { type: 'text', name: 'username', placeholder: 'username' },
+ { type: 'text', name: 'channel', placeholder: '#channel' }
]
end
- def execute(push_data)
- message = SlackMessage.new(push_data.merge(
+ def supported_events
+ %w(push issue merge_request note tag_push)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+ return unless webhook.present?
+
+ object_kind = data[:object_kind]
+
+ data = data.merge(
project_url: project_url,
project_name: project_name
- ))
+ )
- credentials = webhook.match(/(\w*).slack.com.*services\/(.*)/)
- if credentials.present?
- subdomain = credentials[1]
- token = credentials[2].split("token=").last
- notifier = Slack::Notifier.new(subdomain, token)
+ # WebHook events often have an 'update' event that follows a 'open' or
+ # 'close' action. Ignore update events for now to prevent duplicate
+ # messages from arriving.
+
+ message = \
+ case object_kind
+ when "push", "tag_push"
+ PushMessage.new(data)
+ when "issue"
+ IssueMessage.new(data) unless is_update?(data)
+ when "merge_request"
+ MergeMessage.new(data) unless is_update?(data)
+ when "note"
+ NoteMessage.new(data)
+ end
+
+ opt = {}
+ opt[:channel] = channel if channel
+ opt[:username] = username if username
+
+ if message
+ notifier = Slack::Notifier.new(webhook, opt)
notifier.ping(message.pretext, attachments: message.attachments)
end
end
@@ -58,4 +93,13 @@ class SlackService < Service
def project_url
project.web_url
end
+
+ def is_update?(data)
+ data[:object_attributes][:action] == 'update'
+ end
end
+
+require "slack_service/issue_message"
+require "slack_service/push_message"
+require "slack_service/merge_message"
+require "slack_service/note_message"
diff --git a/app/models/project_services/slack_service/base_message.rb b/app/models/project_services/slack_service/base_message.rb
new file mode 100644
index 0000000000..aa00d6061a
--- /dev/null
+++ b/app/models/project_services/slack_service/base_message.rb
@@ -0,0 +1,31 @@
+require 'slack-notifier'
+
+class SlackService
+ class BaseMessage
+ def initialize(params)
+ raise NotImplementedError
+ end
+
+ def pretext
+ format(message)
+ end
+
+ def attachments
+ raise NotImplementedError
+ end
+
+ private
+
+ def message
+ raise NotImplementedError
+ end
+
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def attachment_color
+ '#345'
+ end
+ end
+end
diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb
new file mode 100644
index 0000000000..5af24a8060
--- /dev/null
+++ b/app/models/project_services/slack_service/issue_message.rb
@@ -0,0 +1,56 @@
+class SlackService
+ class IssueMessage < BaseMessage
+ attr_reader :user_name
+ attr_reader :title
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :issue_iid
+ attr_reader :issue_url
+ attr_reader :action
+ attr_reader :state
+ attr_reader :description
+
+ def initialize(params)
+ @user_name = params[:user][:name]
+ @project_name = params[:project_name]
+ @project_url = params[:project_url]
+
+ obj_attr = params[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ @title = obj_attr[:title]
+ @issue_iid = obj_attr[:iid]
+ @issue_url = obj_attr[:url]
+ @action = obj_attr[:action]
+ @state = obj_attr[:state]
+ @description = obj_attr[:description]
+ end
+
+ def attachments
+ return [] unless opened_issue?
+
+ description_message
+ end
+
+ private
+
+ def message
+ "#{user_name} #{state} #{issue_link} in #{project_link}: *#{title}*"
+ end
+
+ def opened_issue?
+ action == "open"
+ end
+
+ def description_message
+ [{ text: format(description), color: attachment_color }]
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def issue_link
+ "[issue ##{issue_iid}](#{issue_url})"
+ end
+ end
+end
diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb
new file mode 100644
index 0000000000..e792c258f7
--- /dev/null
+++ b/app/models/project_services/slack_service/merge_message.rb
@@ -0,0 +1,60 @@
+class SlackService
+ class MergeMessage < BaseMessage
+ attr_reader :user_name
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :merge_request_id
+ attr_reader :source_branch
+ attr_reader :target_branch
+ attr_reader :state
+ attr_reader :title
+
+ def initialize(params)
+ @user_name = params[:user][:name]
+ @project_name = params[:project_name]
+ @project_url = params[:project_url]
+
+ obj_attr = params[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ @merge_request_id = obj_attr[:iid]
+ @source_branch = obj_attr[:source_branch]
+ @target_branch = obj_attr[:target_branch]
+ @state = obj_attr[:state]
+ @title = format_title(obj_attr[:title])
+ end
+
+ def pretext
+ format(message)
+ end
+
+ def attachments
+ []
+ end
+
+ private
+
+ def format_title(title)
+ '*' + title.lines.first.chomp + '*'
+ end
+
+ def message
+ merge_request_message
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def merge_request_message
+ "#{user_name} #{state} #{merge_request_link} in #{project_link}: #{title}"
+ end
+
+ def merge_request_link
+ "[merge request ##{merge_request_id}](#{merge_request_url})"
+ end
+
+ def merge_request_url
+ "#{project_url}/merge_requests/#{merge_request_id}"
+ end
+ end
+end
diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb
new file mode 100644
index 0000000000..074478b292
--- /dev/null
+++ b/app/models/project_services/slack_service/note_message.rb
@@ -0,0 +1,82 @@
+class SlackService
+ class NoteMessage < BaseMessage
+ attr_reader :message
+ attr_reader :user_name
+ attr_reader :project_name
+ attr_reader :project_link
+ attr_reader :note
+ attr_reader :note_url
+ attr_reader :title
+
+ def initialize(params)
+ params = HashWithIndifferentAccess.new(params)
+ @user_name = params[:user][:name]
+ @project_name = params[:project_name]
+ @project_url = params[:project_url]
+
+ obj_attr = params[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ @note = obj_attr[:note]
+ @note_url = obj_attr[:url]
+ noteable_type = obj_attr[:noteable_type]
+
+ case noteable_type
+ when "Commit"
+ create_commit_note(HashWithIndifferentAccess.new(params[:commit]))
+ when "Issue"
+ create_issue_note(HashWithIndifferentAccess.new(params[:issue]))
+ when "MergeRequest"
+ create_merge_note(HashWithIndifferentAccess.new(params[:merge_request]))
+ when "Snippet"
+ create_snippet_note(HashWithIndifferentAccess.new(params[:snippet]))
+ end
+ end
+
+ def attachments
+ description_message
+ end
+
+ private
+
+ def format_title(title)
+ title.lines.first.chomp
+ end
+
+ def create_commit_note(commit)
+ commit_sha = commit[:id]
+ commit_sha = Commit.truncate_sha(commit_sha)
+ commit_link = "[commit #{commit_sha}](#{@note_url})"
+ title = format_title(commit[:message])
+ @message = "#{@user_name} commented on #{commit_link} in #{project_link}: *#{title}*"
+ end
+
+ def create_issue_note(issue)
+ issue_iid = issue[:iid]
+ note_link = "[issue ##{issue_iid}](#{@note_url})"
+ title = format_title(issue[:title])
+ @message = "#{@user_name} commented on #{note_link} in #{project_link}: *#{title}*"
+ end
+
+ def create_merge_note(merge_request)
+ merge_request_id = merge_request[:iid]
+ merge_request_link = "[merge request ##{merge_request_id}](#{@note_url})"
+ title = format_title(merge_request[:title])
+ @message = "#{@user_name} commented on #{merge_request_link} in #{project_link}: *#{title}*"
+ end
+
+ def create_snippet_note(snippet)
+ snippet_id = snippet[:id]
+ snippet_link = "[snippet ##{snippet_id}](#{@note_url})"
+ title = format_title(snippet[:title])
+ @message = "#{@user_name} commented on #{snippet_link} in #{project_link}: *#{title}*"
+ end
+
+ def description_message
+ [{ text: format(@note), color: attachment_color }]
+ end
+
+ def project_link
+ "[#{@project_name}](#{@project_url})"
+ end
+ end
+end
diff --git a/app/models/project_services/slack_service/push_message.rb b/app/models/project_services/slack_service/push_message.rb
new file mode 100644
index 0000000000..b26f3e9ddc
--- /dev/null
+++ b/app/models/project_services/slack_service/push_message.rb
@@ -0,0 +1,110 @@
+class SlackService
+ class PushMessage < BaseMessage
+ attr_reader :after
+ attr_reader :before
+ attr_reader :commits
+ attr_reader :project_name
+ attr_reader :project_url
+ attr_reader :ref
+ attr_reader :ref_type
+ attr_reader :user_name
+
+ def initialize(params)
+ @after = params[:after]
+ @before = params[:before]
+ @commits = params.fetch(:commits, [])
+ @project_name = params[:project_name]
+ @project_url = params[:project_url]
+ @ref_type = Gitlab::Git.tag_ref?(params[:ref]) ? 'tag' : 'branch'
+ @ref = Gitlab::Git.ref_name(params[:ref])
+ @user_name = params[:user_name]
+ end
+
+ def pretext
+ format(message)
+ end
+
+ def attachments
+ return [] if new_branch? || removed_branch?
+
+ commit_message_attachments
+ end
+
+ private
+
+ def message
+ if new_branch?
+ new_branch_message
+ elsif removed_branch?
+ removed_branch_message
+ else
+ push_message
+ end
+ end
+
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def new_branch_message
+ "#{user_name} pushed new #{ref_type} #{branch_link} to #{project_link}"
+ end
+
+ def removed_branch_message
+ "#{user_name} removed #{ref_type} #{ref} from #{project_link}"
+ end
+
+ def push_message
+ "#{user_name} pushed to #{ref_type} #{branch_link} of #{project_link} (#{compare_link})"
+ end
+
+ def commit_messages
+ commits.map { |commit| compose_commit_message(commit) }.join("\n")
+ end
+
+ def commit_message_attachments
+ [{ text: format(commit_messages), color: attachment_color }]
+ end
+
+ def compose_commit_message(commit)
+ author = commit[:author][:name]
+ id = Commit.truncate_sha(commit[:id])
+ message = commit[:message]
+ url = commit[:url]
+
+ "[#{id}](#{url}): #{message} - #{author}"
+ end
+
+ def new_branch?
+ Gitlab::Git.blank_ref?(before)
+ end
+
+ def removed_branch?
+ Gitlab::Git.blank_ref?(after)
+ end
+
+ def branch_url
+ "#{project_url}/commits/#{ref}"
+ end
+
+ def compare_url
+ "#{project_url}/compare/#{before}...#{after}"
+ end
+
+ def branch_link
+ "[#{ref}](#{branch_url})"
+ end
+
+ def project_link
+ "[#{project_name}](#{project_url})"
+ end
+
+ def compare_link
+ "[Compare changes](#{compare_url})"
+ end
+
+ def attachment_color
+ '#345'
+ end
+ end
+end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
new file mode 100644
index 0000000000..7403e19da9
--- /dev/null
+++ b/app/models/project_services/teamcity_service.rb
@@ -0,0 +1,145 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+class TeamcityService < CiService
+ include HTTParty
+
+ prop_accessor :teamcity_url, :build_type, :username, :password
+
+ validates :teamcity_url,
+ presence: true,
+ format: { with: URI::regexp }, if: :activated?
+ validates :build_type, presence: true, if: :activated?
+ validates :username,
+ presence: true,
+ if: ->(service) { service.password? }, if: :activated?
+ validates :password,
+ presence: true,
+ if: ->(service) { service.username? }, if: :activated?
+
+ attr_accessor :response
+
+ after_save :compose_service_hook, if: :activated?
+
+ def compose_service_hook
+ hook = service_hook || build_service_hook
+ hook.save
+ end
+
+ def title
+ 'JetBrains TeamCity CI'
+ end
+
+ def description
+ 'A continuous integration and build server'
+ end
+
+ def help
+ 'The build configuration in Teamcity must use the build format '\
+ 'number %build.vcs.number% '\
+ 'you will also want to configure monitoring of all branches so merge '\
+ 'requests build, that setting is in the vsc root advanced settings.'
+ end
+
+ def to_param
+ 'teamcity'
+ end
+
+ def supported_events
+ %w(push)
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'teamcity_url',
+ placeholder: 'TeamCity root URL like https://teamcity.example.com' },
+ { type: 'text', name: 'build_type',
+ placeholder: 'Build configuration ID' },
+ { type: 'text', name: 'username',
+ placeholder: 'A user with permissions to trigger a manual build' },
+ { type: 'password', name: 'password' },
+ ]
+ end
+
+ def build_info(sha)
+ url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\
+ "branch:unspecified:any,number:#{sha}")
+ auth = {
+ username: username,
+ password: password,
+ }
+ @response = HTTParty.get("#{url}", verify: false, basic_auth: auth)
+ end
+
+ def build_page(sha)
+ build_info(sha) if @response.nil? || !@response.code
+
+ if @response.code != 200
+ # If actual build link can't be determined,
+ # send user to build summary page.
+ "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}"
+ else
+ # If actual build link is available, go to build result page.
+ built_id = @response['build']['id']
+ "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\
+ "&buildTypeId=#{build_type}"
+ end
+ end
+
+ def commit_status(sha)
+ build_info(sha) if @response.nil? || !@response.code
+ return :error unless @response.code == 200 || @response.code == 404
+
+ status = if @response.code == 404
+ 'Pending'
+ else
+ @response['build']['status']
+ end
+
+ if status.include?('SUCCESS')
+ 'success'
+ elsif status.include?('FAILURE')
+ 'failed'
+ elsif status.include?('Pending')
+ 'pending'
+ else
+ :error
+ end
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ auth = {
+ username: username,
+ password: password,
+ }
+
+ branch = Gitlab::Git.ref_name(data[:ref])
+
+ self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
+ body: ""\
+ ""\
+ ' ',
+ headers: { 'Content-type' => 'application/xml' },
+ basic_auth: auth
+ )
+ end
+end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 657ee23ae2..d4a07caf9e 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -31,16 +31,16 @@ class ProjectTeam
user
end
- def find_tm(user_id)
- tm = project.project_members.find_by(user_id: user_id)
+ def find_member(user_id)
+ member = project.project_members.find_by(user_id: user_id)
# If user is not in project members
# we should check for group membership
- if group && !tm
- tm = group.group_members.find_by(user_id: user_id)
+ if group && !member
+ member = group.group_members.find_by(user_id: user_id)
end
- tm
+ member
end
def add_user(user, access)
@@ -91,24 +91,24 @@ class ProjectTeam
def import(source_project)
target_project = project
- source_team = source_project.project_members.to_a
+ source_members = source_project.project_members.to_a
target_user_ids = target_project.project_members.pluck(:user_id)
- source_team.reject! do |tm|
+ source_members.reject! do |member|
# Skip if user already present in team
- target_user_ids.include?(tm.user_id)
+ target_user_ids.include?(member.user_id)
end
- source_team.map! do |tm|
- new_tm = tm.dup
- new_tm.id = nil
- new_tm.source = target_project
- new_tm
+ source_members.map! do |member|
+ new_member = member.dup
+ new_member.id = nil
+ new_member.source = target_project
+ new_member
end
ProjectMember.transaction do
- source_team.each do |tm|
- tm.save
+ source_members.each do |member|
+ member.save
end
end
@@ -118,26 +118,26 @@ class ProjectTeam
end
def guest?(user)
- max_tm_access(user.id) == Gitlab::Access::GUEST
+ max_member_access(user.id) == Gitlab::Access::GUEST
end
def reporter?(user)
- max_tm_access(user.id) == Gitlab::Access::REPORTER
+ max_member_access(user.id) == Gitlab::Access::REPORTER
end
def developer?(user)
- max_tm_access(user.id) == Gitlab::Access::DEVELOPER
+ max_member_access(user.id) == Gitlab::Access::DEVELOPER
end
def master?(user)
- max_tm_access(user.id) == Gitlab::Access::MASTER
+ max_member_access(user.id) == Gitlab::Access::MASTER
end
def member?(user_id)
- !!find_tm(user_id)
+ !!find_member(user_id)
end
- def max_tm_access(user_id)
+ def max_member_access(user_id)
access = []
access << project.project_members.find_by(user_id: user_id).try(:access_field)
@@ -160,7 +160,7 @@ class ProjectTeam
end
user_ids = project_members.pluck(:user_id)
- user_ids += group_members.pluck(:user_id) if group
+ user_ids.push(*group_members.pluck(:user_id)) if group
User.where(id: user_ids)
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 770a26ed89..55438bee24 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -5,7 +5,7 @@ class ProjectWiki
'Markdown' => :markdown,
'RDoc' => :rdoc,
'AsciiDoc' => :asciidoc
- }
+ } unless defined?(MARKUPS)
class CouldNotCreateWikiError < StandardError; end
@@ -136,7 +136,7 @@ class ProjectWiki
def commit_details(action, message = nil, title = nil)
commit_message = message || default_message(action, title)
- {email: @user.email, name: @user.name, message: commit_message}
+ { email: @user.email, name: @user.name, message: commit_message }
end
def default_message(action, title)
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 1b06dd7752..97207ba127 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -2,11 +2,12 @@
#
# Table name: protected_branches
#
-# id :integer not null, primary key
-# project_id :integer not null
-# name :string(255) not null
-# created_at :datetime
-# updated_at :datetime
+# id :integer not null, primary key
+# project_id :integer not null
+# name :string(255) not null
+# created_at :datetime
+# updated_at :datetime
+# developers_can_push :boolean default(FALSE), not null
#
class ProtectedBranch < ActiveRecord::Base
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 339e485e6d..47758b8ad6 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -30,6 +30,8 @@ class Repository
commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit) if commit
commit
+ rescue Rugged::OdbError
+ nil
end
def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false)
@@ -59,25 +61,25 @@ class Repository
end
def add_branch(branch_name, ref)
- Rails.cache.delete(cache_key(:branch_names))
+ cache.expire(:branch_names)
gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
end
def add_tag(tag_name, ref, message = nil)
- Rails.cache.delete(cache_key(:tag_names))
+ cache.expire(:tag_names)
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
def rm_branch(branch_name)
- Rails.cache.delete(cache_key(:branch_names))
+ cache.expire(:branch_names)
gitlab_shell.rm_branch(path_with_namespace, branch_name)
end
def rm_tag(tag_name)
- Rails.cache.delete(cache_key(:tag_names))
+ cache.expire(:tag_names)
gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
@@ -95,19 +97,15 @@ class Repository
end
def branch_names
- Rails.cache.fetch(cache_key(:branch_names)) do
- raw_repository.branch_names
- end
+ cache.fetch(:branch_names) { raw_repository.branch_names }
end
def tag_names
- Rails.cache.fetch(cache_key(:tag_names)) do
- raw_repository.tag_names
- end
+ cache.fetch(:tag_names) { raw_repository.tag_names }
end
def commit_count
- Rails.cache.fetch(cache_key(:commit_count)) do
+ cache.fetch(:commit_count) do
begin
raw_repository.commit_count(self.root_ref)
rescue
@@ -119,41 +117,53 @@ class Repository
# Return repo size in megabytes
# Cached in redis
def size
- Rails.cache.fetch(cache_key(:size)) do
- raw_repository.size
- end
+ cache.fetch(:size) { raw_repository.size }
end
def expire_cache
- Rails.cache.delete(cache_key(:size))
- Rails.cache.delete(cache_key(:branch_names))
- Rails.cache.delete(cache_key(:tag_names))
- Rails.cache.delete(cache_key(:commit_count))
- Rails.cache.delete(cache_key(:graph_log))
- Rails.cache.delete(cache_key(:readme))
- Rails.cache.delete(cache_key(:version))
- Rails.cache.delete(cache_key(:contribution_guide))
+ %i(size branch_names tag_names commit_count graph_log
+ readme version contribution_guide).each do |key|
+ cache.expire(key)
+ end
end
def graph_log
- Rails.cache.fetch(cache_key(:graph_log)) do
+ cache.fetch(:graph_log) do
commits = raw_repository.log(limit: 6000, skip_merges: true,
ref: root_ref)
+
commits.map do |rugged_commit|
commit = Gitlab::Git::Commit.new(rugged_commit)
{
- author_name: commit.author_name.force_encoding('UTF-8'),
- author_email: commit.author_email.force_encoding('UTF-8'),
+ author_name: commit.author_name,
+ author_email: commit.author_email,
additions: commit.stats.additions,
- deletions: commit.stats.deletions
+ deletions: commit.stats.deletions,
}
end
end
end
- def cache_key(type)
- "#{type}:#{path_with_namespace}"
+ def timestamps_by_user_log(user)
+ author_emails = '(' + user.all_emails.map{ |e| Regexp.escape(e) }.join('|') + ')'
+ args = %W(git log -E --author=#{author_emails} --since=#{(Date.today - 1.year).to_s} --branches --pretty=format:%cd --date=short)
+ dates = Gitlab::Popen.popen(args, path_to_repo).first.split("\n")
+
+ if dates.present?
+ dates
+ else
+ []
+ end
+ end
+
+ def commits_per_day_for_user(user)
+ timestamps_by_user_log(user).
+ group_by { |commit_date| commit_date }.
+ inject({}) do |hash, (timestamp_date, commits)|
+ hash[timestamp_date] = commits.count
+ hash
+ end
end
def method_missing(m, *args, &block)
@@ -175,13 +185,11 @@ class Repository
end
def readme
- Rails.cache.fetch(cache_key(:readme)) do
- tree(:head).readme
- end
+ cache.fetch(:readme) { tree(:head).readme }
end
def version
- Rails.cache.fetch(cache_key(:version)) do
+ cache.fetch(:version) do
tree(:head).blobs.find do |file|
file.name.downcase == 'version'
end
@@ -189,9 +197,7 @@ class Repository
end
def contribution_guide
- Rails.cache.fetch(cache_key(:contribution_guide)) do
- tree(:head).contribution_guide
- end
+ cache.fetch(:contribution_guide) { tree(:head).contribution_guide }
end
def head_commit
@@ -233,7 +239,7 @@ class Repository
end
def last_commit_for_path(sha, path)
- args = %W(git rev-list --max-count 1 #{sha} -- #{path})
+ args = %W(git rev-list --max-count=1 #{sha} -- #{path})
sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
commit(sha)
end
@@ -310,4 +316,27 @@ class Repository
[]
end
end
+
+ def tag_names_contains(sha)
+ args = %W(git tag --contains #{sha})
+ names = Gitlab::Popen.popen(args, path_to_repo).first
+
+ if names.respond_to?(:split)
+ names = names.split("\n").map(&:strip)
+
+ names.each do |name|
+ name.slice! '* '
+ end
+
+ names
+ else
+ []
+ end
+ end
+
+ private
+
+ def cache
+ @cache ||= RepositoryCache.new(path_with_namespace)
+ end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index c489c1e96e..33734e97c5 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -2,34 +2,57 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
#
# To add new service you should build a class inherited from Service
# and implement a set of methods
class Service < ActiveRecord::Base
+ include Sortable
serialize :properties, JSON
default_value_for :active, false
+ default_value_for :push_events, true
+ default_value_for :issues_events, true
+ default_value_for :merge_requests_events, true
+ default_value_for :tag_push_events, true
+ default_value_for :note_events, true
after_initialize :initialize_properties
belongs_to :project
has_one :service_hook
- validates :project_id, presence: true
+ validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
+
+ scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
+
+ scope :push_hooks, -> { where(push_events: true, active: true) }
+ scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
+ scope :issue_hooks, -> { where(issues_events: true, active: true) }
+ scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
+ scope :note_hooks, -> { where(note_events: true, active: true) }
def activated?
active
end
+ def template?
+ template
+ end
+
def category
:common
end
@@ -59,6 +82,10 @@ class Service < ActiveRecord::Base
[]
end
+ def supported_events
+ %w(push tag_push issue merge_request)
+ end
+
def execute
# implement inside child
end
@@ -82,4 +109,27 @@ class Service < ActiveRecord::Base
}
end
end
+
+ def async_execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ Sidekiq::Client.enqueue(ProjectServiceWorker, id, data)
+ end
+
+ def issue_tracker?
+ self.category == :issue_tracker
+ end
+
+ def self.available_services_names
+ %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla asana
+ emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira
+ redmine custom_issue_tracker irker)
+ end
+
+ def self.create_from_template(project_id, template)
+ service = template.dup
+ service.template = false
+ service.project_id = project_id
+ service if service.save
+ end
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index a47fbca326..3fb2ec1d66 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -16,6 +16,7 @@
#
class Snippet < ActiveRecord::Base
+ include Sortable
include Linguist::BlobHelper
include Gitlab::VisibilityLevel
@@ -29,7 +30,11 @@ class Snippet < ActiveRecord::Base
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
- validates :file_name, presence: true, length: { within: 0..255 }
+ validates :file_name,
+ presence: true,
+ length: { within: 0..255 },
+ format: { with: Gitlab::Regex.path_regex,
+ message: Gitlab::Regex.path_regex_message }
validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
@@ -54,6 +59,10 @@ class Snippet < ActiveRecord::Base
content
end
+ def hook_attrs
+ attributes
+ end
+
def size
0
end
@@ -62,6 +71,10 @@ class Snippet < ActiveRecord::Base
file_name
end
+ def sanitized_file_name
+ file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '')
+ end
+
def mode
nil
end
@@ -72,7 +85,7 @@ class Snippet < ActiveRecord::Base
def visibility_level_field
visibility_level
- end
+ end
class << self
def search(query)
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
new file mode 100644
index 0000000000..dd75d3ab8b
--- /dev/null
+++ b/app/models/subscription.rb
@@ -0,0 +1,21 @@
+# == Schema Information
+#
+# Table name: subscriptions
+#
+# id :integer not null, primary key
+# user_id :integer
+# subscribable_id :integer
+# subscribable_type :string(255)
+# subscribed :boolean
+# created_at :datetime
+# updated_at :datetime
+#
+
+class Subscription < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :subscribable, polymorphic: true
+
+ validates :user_id,
+ uniqueness: { scope: [:subscribable_id, :subscribable_type] },
+ presence: true
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index c90f246242..ba325132df 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2,63 +2,70 @@
#
# Table name: users
#
-# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime
-# updated_at :datetime
-# name :string(255)
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# extern_uid :string(255)
-# provider :string(255)
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# color_scheme_id :integer default(1), not null
-# notification_level :integer default(1), not null
-# password_expires_at :datetime
-# created_by_id :integer
-# last_credential_check_at :datetime
-# avatar :string(255)
-# confirmation_token :string(255)
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
+# id :integer not null, primary key
+# email :string(255) default(""), not null
+# encrypted_password :string(255) default(""), not null
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# created_at :datetime
+# updated_at :datetime
+# name :string(255)
+# admin :boolean default(FALSE), not null
+# projects_limit :integer default(10)
+# skype :string(255) default(""), not null
+# linkedin :string(255) default(""), not null
+# twitter :string(255) default(""), not null
+# authentication_token :string(255)
+# theme_id :integer default(1), not null
+# bio :string(255)
+# failed_attempts :integer default(0)
+# locked_at :datetime
+# username :string(255)
+# can_create_group :boolean default(TRUE), not null
+# can_create_team :boolean default(TRUE), not null
+# state :string(255)
+# color_scheme_id :integer default(1), not null
+# notification_level :integer default(1), not null
+# password_expires_at :datetime
+# created_by_id :integer
+# last_credential_check_at :datetime
+# avatar :string(255)
+# confirmation_token :string(255)
+# confirmed_at :datetime
+# confirmation_sent_at :datetime
+# unconfirmed_email :string(255)
+# hide_no_ssh_key :boolean default(FALSE)
+# website_url :string(255) default(""), not null
+# github_access_token :string(255)
+# gitlab_access_token :string(255)
+# notification_email :string(255)
+# hide_no_password :boolean default(FALSE)
+# password_automatically_set :boolean default(FALSE)
+# bitbucket_access_token :string(255)
+# bitbucket_access_token_secret :string(255)
#
require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class User < ActiveRecord::Base
+ include Sortable
include Gitlab::ConfigHelper
- extend Gitlab::ConfigHelper
include TokenAuthenticatable
+ extend Gitlab::ConfigHelper
+ include Gitlab::CurrentSettings
default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group
default_value_for :can_create_team, false
default_value_for :hide_no_ssh_key, false
- default_value_for :projects_limit, gitlab_config.default_projects_limit
+ default_value_for :hide_no_password, false
default_value_for :theme_id, gitlab_config.default_theme
devise :database_authenticatable, :lockable, :async,
@@ -79,6 +86,7 @@ class User < ActiveRecord::Base
# Profile
has_many :keys, dependent: :destroy
has_many :emails, dependent: :destroy
+ has_many :identities, dependent: :destroy
# Groups
has_many :members, dependent: :destroy
@@ -105,32 +113,38 @@ class User < ActiveRecord::Base
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_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
+ has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
#
# Validations
#
validates :name, presence: true
- validates :email, presence: true, email: {strict_mode: true}, uniqueness: true
+ validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
+ validates :notification_email, presence: true, email: { strict_mode: true }
validates :bio, length: { maximum: 255 }, allow_blank: true
- validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
- validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
- validates :username, presence: true, uniqueness: { case_sensitive: false },
- exclusion: { in: Gitlab::Blacklist.path },
- format: { with: Gitlab::Regex.username_regex,
- message: Gitlab::Regex.username_regex_message }
+ validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
+ validates :username,
+ presence: true,
+ uniqueness: { case_sensitive: false },
+ exclusion: { in: Gitlab::Blacklist.path },
+ format: { with: Gitlab::Regex.username_regex,
+ message: Gitlab::Regex.username_regex_message }
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? }
- validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
+ validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
+ validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create
before_validation :sanitize_attrs
+ before_validation :set_notification_email, if: ->(user) { user.email_changed? }
before_save :ensure_authentication_token
after_save :ensure_namespace_correct
+ after_initialize :set_projects_limit
after_create :post_create_hook
after_destroy :post_destroy_hook
@@ -140,24 +154,6 @@ class User < ActiveRecord::Base
delegate :path, to: :namespace, allow_nil: true, prefix: true
state_machine :state, initial: :active do
- after_transition any => :blocked do |user, transition|
- # Remove user from all projects and
- user.project_members.find_each do |membership|
- # skip owned resources
- next if membership.project.owner == user
-
- return false unless membership.destroy
- end
-
- # Remove user from all groups
- user.group_members.find_each do |membership|
- # skip owned resources
- next if membership.group.last_owner?(user)
-
- return false unless membership.destroy
- end
- end
-
event :block do
transition active: :blocked
end
@@ -167,20 +163,14 @@ class User < ActiveRecord::Base
end
end
- mount_uploader :avatar, AttachmentUploader
+ mount_uploader :avatar, AvatarUploader
# Scopes
scope :admins, -> { where(admin: true) }
scope :blocked, -> { with_state(:blocked) }
scope :active, -> { with_state(:active) }
- scope :alphabetically, -> { order('name ASC') }
- scope :in_team, ->(team){ where(id: team.member_ids) }
- scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
- scope :ldap, -> { where(provider: 'ldap') }
-
- scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
#
# Class methods
@@ -196,6 +186,15 @@ class User < ActiveRecord::Base
end
end
+ def sort(method)
+ case method.to_s
+ when 'recent_sign_in' then reorder(last_sign_in_at: :desc)
+ when 'oldest_sign_in' then reorder(last_sign_in_at: :asc)
+ else
+ order_by(method)
+ end
+ end
+
def find_for_commit(email, name)
# Prefer email match over name match
User.where(email: email).first ||
@@ -217,6 +216,11 @@ class User < ActiveRecord::Base
where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
end
+ def by_login(login)
+ where('lower(username) = :value OR lower(email) = :value',
+ value: login.to_s.downcase).first
+ end
+
def by_username_or_id(name_or_id)
where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
end
@@ -224,6 +228,22 @@ class User < ActiveRecord::Base
def build_user(attrs = {})
User.new(attrs)
end
+
+ def clean_username(username)
+ username.gsub!(/@.*\z/, "")
+ username.gsub!(/\.git\z/, "")
+ username.gsub!(/\A-/, "")
+ username.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
+
+ counter = 0
+ base = username
+ while User.by_login(username).present? || Namespace.by_path(username).present?
+ counter += 1
+ username = "#{base}#{counter}"
+ end
+
+ username
+ end
end
#
@@ -255,7 +275,8 @@ class User < ActiveRecord::Base
def namespace_uniq
namespace_name = self.username
- if Namespace.find_by(path: namespace_name)
+ existing_namespace = Namespace.by_path(namespace_name)
+ if existing_namespace && existing_namespace != self.namespace
self.errors.add :username, "already exists"
end
end
@@ -270,11 +291,15 @@ class User < ActiveRecord::Base
self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email)
end
+ def owns_notification_email
+ self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
+ end
+
# Groups user has access to
def authorized_groups
@authorized_groups ||= begin
group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id))
- Group.where(id: group_ids).order('namespaces.name ASC')
+ Group.where(id: group_ids)
end
end
@@ -283,9 +308,9 @@ class User < ActiveRecord::Base
def authorized_projects
@authorized_projects ||= begin
project_ids = personal_projects.pluck(:id)
- project_ids += groups_projects.pluck(:id)
- project_ids += projects.pluck(:id).uniq
- Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC')
+ project_ids.push(*groups_projects.pluck(:id))
+ project_ids.push(*projects.pluck(:id).uniq)
+ Project.where(id: project_ids)
end
end
@@ -308,6 +333,10 @@ class User < ActiveRecord::Base
keys.count == 0
end
+ def require_password?
+ password_automatically_set? && !ldap_user?
+ end
+
def can_change_username?
gitlab_config.username_changing_enabled
end
@@ -321,11 +350,7 @@ class User < ActiveRecord::Base
end
def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
+ Ability.abilities
end
def can_select_namespace?
@@ -379,7 +404,7 @@ class User < ActiveRecord::Base
end
def tm_of(project)
- project.team_member_by_id(self.id)
+ project.project_member_by_id(self.id)
end
def already_forked?(project)
@@ -397,7 +422,11 @@ class User < ActiveRecord::Base
end
def ldap_user?
- extern_uid && provider == 'ldap'
+ identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
+ end
+
+ def ldap_identity
+ @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
end
def accessible_deploy_keys
@@ -415,6 +444,19 @@ class User < ActiveRecord::Base
end
end
+ def set_notification_email
+ if self.notification_email.blank? || !self.all_emails.include?(self.notification_email)
+ self.notification_email = self.email
+ end
+ end
+
+ def set_projects_limit
+ connection_default_value_defined = new_record? && !projects_limit_changed?
+ return unless self.projects_limit.nil? || connection_default_value_defined
+
+ self.projects_limit = current_application_settings.default_projects_limit
+ end
+
def requires_ldap_check?
if !Gitlab.config.ldap.enabled
false
@@ -473,7 +515,7 @@ class User < ActiveRecord::Base
end
def temp_oauth_email?
- email =~ /\Atemp-email-for-oauth/
+ email.start_with?('temp-email-for-oauth')
end
def public_profile?
@@ -482,12 +524,24 @@ class User < ActiveRecord::Base
def avatar_url(size = nil)
if avatar.present?
- [gitlab_config.url, avatar.url].join("/")
+ [gitlab_config.url, avatar.url].join
else
GravatarService.new.execute(email, size)
end
end
+ def all_emails
+ [self.email, *self.emails.map(&:email)]
+ end
+
+ def hook_attrs
+ {
+ name: name,
+ username: username,
+ avatar_url: avatar_url
+ }
+ end
+
def ensure_namespace_correct
# Ensure user has namespace
self.create_namespace!(path: self.username, name: self.username) unless self.namespace
@@ -499,7 +553,7 @@ class User < ActiveRecord::Base
def post_create_hook
log_info("User \"#{self.name}\" (#{self.email}) was created")
- notification_service.new_user(self, @reset_token)
+ notification_service.new_user(self, @reset_token) if self.created_by_id
system_hook_service.execute_hooks_for(self, :create)
end
@@ -533,4 +587,29 @@ class User < ActiveRecord::Base
UsersStarProject.create!(project: project, user: self)
end
end
+
+ def manageable_namespaces
+ @manageable_namespaces ||=
+ begin
+ namespaces = []
+ namespaces << namespace
+ namespaces += owned_groups
+ namespaces += masters_groups
+ end
+ end
+
+ def oauth_authorized_tokens
+ Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
+ end
+
+ def contributed_projects_ids
+ Event.where(author_id: self).
+ where("created_at > ?", Time.now - 1.year).
+ where("action = :pushed OR (target_type = 'MergeRequest' AND action = :created)",
+ pushed: Event::PUSHED, created: Event::CREATED).
+ reorder(project_id: :desc).
+ select(:project_id).
+ uniq
+ .map(&:project_id)
+ end
end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index b9ab6702c5..32981a0e66 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -43,7 +43,7 @@ class WikiPage
@attributes[:slug]
end
- alias :to_param :slug
+ alias_method :to_param, :slug
# The formatted title of this page.
def title
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index ed286c0409..6d9ed34591 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -1,4 +1,6 @@
class BaseService
+ include Gitlab::CurrentSettings
+
attr_accessor :project, :current_user, :params
def initialize(project, user, params = {})
@@ -6,11 +8,7 @@ class BaseService
end
def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
+ Ability.abilities
end
def can?(object, action, subject)
@@ -33,13 +31,31 @@ class BaseService
SystemHooksService.new
end
+ # Add an error to the specified model for restricted visibility levels
+ def deny_visibility_level(model, denied_visibility_level = nil)
+ denied_visibility_level ||= model.visibility_level
+
+ level_name = 'Unknown'
+ Gitlab::VisibilityLevel.options.each do |name, level|
+ level_name = name if level == denied_visibility_level
+ end
+
+ model.errors.add(
+ :visibility_level,
+ "#{level_name} visibility has been restricted by your GitLab administrator"
+ )
+ end
+
private
- def error(message)
- {
+ def error(message, http_status = nil)
+ result = {
message: message,
status: :error
}
+
+ result[:http_status] = http_status if http_status
+ result
end
def success
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index 901f67bafb..cf7ae4345f 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -17,10 +17,15 @@ class CreateBranchService < BaseService
new_branch = repository.find_branch(branch_name)
if new_branch
- Event.create_ref_event(project, current_user, new_branch, 'add')
- return success(new_branch)
+ push_data = build_push_data(project, current_user, new_branch)
+
+ EventCreateService.new.push(project, current_user, push_data)
+ project.execute_hooks(push_data.dup, :push_hooks)
+ project.execute_services(push_data.dup, :push_hooks)
+
+ success(new_branch)
else
- return error('Invalid reference name')
+ error('Invalid reference name')
end
end
@@ -29,4 +34,9 @@ class CreateBranchService < BaseService
out[:branch] = branch
out
end
+
+ def build_push_data(project, user, branch)
+ Gitlab::PushDataBuilder.
+ build(project, user, Gitlab::Git::BLANK_SHA, branch.target, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
+ end
end
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
new file mode 100644
index 0000000000..101a3df5ee
--- /dev/null
+++ b/app/services/create_snippet_service.rb
@@ -0,0 +1,20 @@
+class CreateSnippetService < BaseService
+ def execute
+ if project.nil?
+ snippet = PersonalSnippet.new(params)
+ else
+ snippet = project.snippets.build(params)
+ end
+
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user,
+ params[:visibility_level])
+ deny_visibility_level(snippet)
+ return snippet
+ end
+
+ snippet.author = current_user
+
+ snippet.save
+ snippet
+ end
+end
diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb
index 9b2a227023..af4b537cb9 100644
--- a/app/services/create_tag_service.rb
+++ b/app/services/create_tag_service.rb
@@ -21,10 +21,15 @@ class CreateTagService < BaseService
new_tag = repository.find_tag(tag_name)
if new_tag
- Event.create_ref_event(project, current_user, new_tag, 'add', 'refs/tags')
- return success(new_tag)
+ push_data = create_push_data(project, current_user, new_tag)
+
+ EventCreateService.new.push(project, current_user, push_data)
+ project.execute_hooks(push_data.dup, :tag_push_hooks)
+ project.execute_services(push_data.dup, :tag_push_hooks)
+
+ success(new_tag)
else
- return error('Invalid reference name')
+ error('Invalid reference name')
end
end
@@ -33,4 +38,9 @@ class CreateTagService < BaseService
out[:tag] = branch
out
end
+
+ def create_push_data(project, user, tag)
+ Gitlab::PushDataBuilder.
+ build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
+ end
end
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index cae6327fe7..b19b112a0c 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -25,10 +25,15 @@ class DeleteBranchService < BaseService
end
if repository.rm_branch(branch_name)
- Event.create_ref_event(project, current_user, branch, 'rm')
+ push_data = build_push_data(branch)
+
+ EventCreateService.new.push(project, current_user, push_data)
+ project.execute_hooks(push_data.dup, :push_hooks)
+ project.execute_services(push_data.dup, :push_hooks)
+
success('Branch was removed')
else
- return error('Failed to remove branch')
+ error('Failed to remove branch')
end
end
@@ -43,4 +48,9 @@ class DeleteBranchService < BaseService
out[:message] = message
out
end
+
+ def build_push_data(branch)
+ Gitlab::PushDataBuilder
+ .build(project, current_user, branch.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
+ end
end
diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb
new file mode 100644
index 0000000000..0c83640113
--- /dev/null
+++ b/app/services/delete_tag_service.rb
@@ -0,0 +1,42 @@
+require_relative 'base_service'
+
+class DeleteTagService < BaseService
+ def execute(tag_name)
+ repository = project.repository
+ tag = repository.find_tag(tag_name)
+
+ # No such tag
+ unless tag
+ return error('No such tag', 404)
+ end
+
+ if repository.rm_tag(tag_name)
+ push_data = build_push_data(tag)
+
+ EventCreateService.new.push(project, current_user, push_data)
+ project.execute_hooks(push_data.dup, :tag_push_hooks)
+ project.execute_services(push_data.dup, :tag_push_hooks)
+
+ success('Tag was removed')
+ else
+ error('Failed to remove tag')
+ end
+ end
+
+ def error(message, return_code = 400)
+ out = super(message)
+ out[:return_code] = return_code
+ out
+ end
+
+ def success(message)
+ out = super()
+ out[:message] = message
+ out
+ end
+
+ def build_push_data(tag)
+ Gitlab::PushDataBuilder
+ .build(project, current_user, tag.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
+ end
+end
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index 8d8a5873e6..103d6b0a08 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -7,58 +7,78 @@
#
class EventCreateService
def open_issue(issue, current_user)
- create_event(issue, current_user, Event::CREATED)
+ create_record_event(issue, current_user, Event::CREATED)
end
def close_issue(issue, current_user)
- create_event(issue, current_user, Event::CLOSED)
+ create_record_event(issue, current_user, Event::CLOSED)
end
def reopen_issue(issue, current_user)
- create_event(issue, current_user, Event::REOPENED)
+ create_record_event(issue, current_user, Event::REOPENED)
end
def open_mr(merge_request, current_user)
- create_event(merge_request, current_user, Event::CREATED)
+ create_record_event(merge_request, current_user, Event::CREATED)
end
def close_mr(merge_request, current_user)
- create_event(merge_request, current_user, Event::CLOSED)
+ create_record_event(merge_request, current_user, Event::CLOSED)
end
def reopen_mr(merge_request, current_user)
- create_event(merge_request, current_user, Event::REOPENED)
+ create_record_event(merge_request, current_user, Event::REOPENED)
end
def merge_mr(merge_request, current_user)
- create_event(merge_request, current_user, Event::MERGED)
+ create_record_event(merge_request, current_user, Event::MERGED)
end
def open_milestone(milestone, current_user)
- create_event(milestone, current_user, Event::CREATED)
+ create_record_event(milestone, current_user, Event::CREATED)
end
def close_milestone(milestone, current_user)
- create_event(milestone, current_user, Event::CLOSED)
+ create_record_event(milestone, current_user, Event::CLOSED)
end
def reopen_milestone(milestone, current_user)
- create_event(milestone, current_user, Event::REOPENED)
+ create_record_event(milestone, current_user, Event::REOPENED)
end
def leave_note(note, current_user)
- create_event(note, current_user, Event::COMMENTED)
+ create_record_event(note, current_user, Event::COMMENTED)
+ end
+
+ def join_project(project, current_user)
+ create_event(project, current_user, Event::JOINED)
+ end
+
+ def leave_project(project, current_user)
+ create_event(project, current_user, Event::LEFT)
+ end
+
+ def create_project(project, current_user)
+ create_event(project, current_user, Event::CREATED)
+ end
+
+ def push(project, current_user, push_data)
+ create_event(project, current_user, Event::PUSHED, data: push_data)
end
private
- def create_event(record, current_user, status)
- Event.create(
- project: record.project,
- target_id: record.id,
- target_type: record.class.name,
+ def create_record_event(record, current_user, status)
+ create_event(record.project, current_user, status, target_id: record.id, target_type: record.class.name)
+ end
+
+ def create_event(project, current_user, status, attributes = {})
+ attributes.reverse_merge!(
+ project: project,
action: status,
author_id: current_user.id
)
+
+ Event.create(attributes)
end
end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index db6f0831f8..bd24510095 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -10,12 +10,6 @@ module Files
private
- def success
- out = super()
- out[:error] = ''
- out
- end
-
def repository
project.repository
end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 82e4d7b684..de5322e990 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -3,20 +3,12 @@ require_relative "base_service"
module Files
class CreateService < BaseService
def execute
- allowed = if project.protected_branch?(ref)
- can?(current_user, :push_code_to_protected_branches, project)
- else
- can?(current_user, :push_code, project)
- end
+ allowed = Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref)
unless allowed
return error("You are not allowed to create file in this branch")
end
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
file_name = File.basename(path)
file_path = path
@@ -27,17 +19,27 @@ module Files
)
end
- blob = repository.blob_at_branch(ref, file_path)
+ if project.empty_repo?
+ # everything is ok because repo does not have a commits yet
+ else
+ unless repository.branch_names.include?(ref)
+ return error("You can only create files if you are on top of a branch")
+ end
- if blob
- return error("Your changes could not be committed, because file with such name exists")
+ blob = repository.blob_at_branch(ref, file_path)
+
+ if blob
+ return error("Your changes could not be committed, because file with such name exists")
+ end
end
+
new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
created_successfully = new_file_action.commit!(
params[:content],
params[:commit_message],
- params[:encoding]
+ params[:encoding],
+ params[:new_branch]
)
if created_successfully
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index ff5dc6ef34..8e73c2e272 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -3,11 +3,7 @@ require_relative "base_service"
module Files
class DeleteService < BaseService
def execute
- allowed = if project.protected_branch?(ref)
- can?(current_user, :push_code_to_protected_branches, project)
- else
- can?(current_user, :push_code, project)
- end
+ allowed = ::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref)
unless allowed
return error("You are not allowed to push into this branch")
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index a0f40154db..328cf3a4b0 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -3,11 +3,7 @@ require_relative "base_service"
module Files
class UpdateService < BaseService
def execute
- allowed = if project.protected_branch?(ref)
- can?(current_user, :push_code_to_protected_branches, project)
- else
- can?(current_user, :push_code, project)
- end
+ allowed = ::Gitlab::GitAccess.can_push_to_branch?(current_user, project, ref)
unless allowed
return error("You are not allowed to push into this branch")
@@ -24,17 +20,20 @@ module Files
end
edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path)
- created_successfully = edit_file_action.commit!(
+ edit_file_action.commit!(
params[:content],
params[:commit_message],
- params[:encoding]
+ params[:encoding],
+ params[:new_branch]
)
- if created_successfully
- success
- else
- error("Your changes could not be committed. Maybe the file was changed by another process or there was nothing to commit?")
- end
+ success
+ rescue Gitlab::Satellite::CheckoutFailed => ex
+ error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400)
+ rescue Gitlab::Satellite::CommitFailed => ex
+ error("Your changes could not be committed. Maybe there was nothing to commit?", 409)
+ rescue Gitlab::Satellite::PushFailed => ex
+ error("Your changes could not be committed. Maybe the file was changed by another process?", 409)
end
end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 8f2b0e347f..4885e1b2fc 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -1,5 +1,7 @@
class GitPushService
attr_accessor :project, :user, :push_data, :push_commits
+ include Gitlab::CurrentSettings
+ include Gitlab::Access
# This method will be called after each git update
# and only if the provided user and project is present in GitLab.
@@ -29,15 +31,21 @@ class GitPushService
if is_default_branch?(ref)
# Initial push to the default branch. Take the full history of that branch as "newly pushed".
@push_commits = project.repository.commits(newrev)
- # Default branch is protected by default
- project.protected_branches.create({ name: project.default_branch })
+
+ # Set protection on the default branch if configured
+ if (current_application_settings.default_branch_protection != PROTECTION_NONE)
+ developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
+ project.protected_branches.create({ name: project.default_branch, developers_can_push: developers_can_push })
+ end
else
# Use the pushed commits that aren't reachable by the default branch
# as a heuristic. This may include more commits than are actually pushed, but
# that shouldn't matter because we check for existing cross-references later.
@push_commits = project.repository.commits_between(project.default_branch, newrev)
+
+ # don't process commits for the initial push to the default branch
+ process_commit_messages(ref)
end
- process_commit_messages(ref)
elsif push_to_existing_branch?(ref, oldrev)
# Collect data for this git push
@push_commits = project.repository.commits_between(oldrev, newrev)
@@ -45,34 +53,16 @@ class GitPushService
process_commit_messages(ref)
end
- @push_data = post_receive_data(oldrev, newrev, ref)
- create_push_event(@push_data)
+ @push_data = build_push_data(oldrev, newrev, ref)
+
+ EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
- project.execute_services(@push_data.dup)
+ project.execute_services(@push_data.dup, :push_hooks)
end
end
- # This method provide a sample data
- # generated with post_receive_data method
- # for given project
- #
- def sample_data(project, user)
- @project, @user = project, user
- @push_commits = project.repository.commits(project.default_branch, nil, 3)
- post_receive_data(@push_commits.last.id, @push_commits.first.id, "refs/heads/#{project.default_branch}")
- end
-
protected
- def create_push_event(push_data)
- Event.create!(
- project: project,
- action: Event::PUSHED,
- data: push_data,
- author_id: push_data[:user_id]
- )
- end
-
# Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched,
# close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables.
def process_commit_messages(ref)
@@ -83,9 +73,14 @@ class GitPushService
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch.
issues_to_close = commit.closes_issues(project)
- author = commit_user(commit)
- if !issues_to_close.empty? && is_default_branch
+ # Load commit author only if needed.
+ # For push with 1k commits it prevents 900+ requests in database
+ author = nil
+
+ if issues_to_close.present? && is_default_branch
+ author ||= commit_user(commit)
+
issues_to_close.each do |issue|
Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
@@ -96,91 +91,41 @@ class GitPushService
# being pushed to a different branch).
refs = commit.references(project) - issues_to_close
refs.reject! { |r| commit.has_mentioned?(r) }
- refs.each do |r|
- Note.create_cross_reference_note(r, commit, author, project)
+
+ if refs.present?
+ author ||= commit_user(commit)
+
+ refs.each do |r|
+ Note.create_cross_reference_note(r, commit, author, project)
+ end
end
end
end
- # Produce a hash of post-receive data
- #
- # data = {
- # before: String,
- # after: String,
- # ref: String,
- # user_id: String,
- # user_name: String,
- # project_id: String,
- # repository: {
- # name: String,
- # url: String,
- # description: String,
- # homepage: String,
- # },
- # commits: Array,
- # total_commits_count: Fixnum
- # }
- #
- def post_receive_data(oldrev, newrev, ref)
- # Total commits count
- push_commits_count = push_commits.size
-
- # Get latest 20 commits ASC
- push_commits_limited = push_commits.last(20)
-
- # Hash to be passed as post_receive_data
- data = {
- before: oldrev,
- after: newrev,
- ref: ref,
- user_id: user.id,
- user_name: user.name,
- project_id: project.id,
- repository: {
- name: project.name,
- url: project.url_to_repo,
- description: project.description,
- homepage: project.web_url,
- },
- commits: [],
- total_commits_count: push_commits_count
- }
-
- # For performance purposes maximum 20 latest commits
- # will be passed as post receive hook data.
- #
- push_commits_limited.each do |commit|
- data[:commits] << commit.hook_attrs(project)
- end
-
- data
+ def build_push_data(oldrev, newrev, ref)
+ Gitlab::PushDataBuilder.
+ build(project, user, oldrev, newrev, ref, push_commits)
end
def push_to_existing_branch?(ref, oldrev)
- ref_parts = ref.split('/')
-
# Return if this is not a push to a branch (e.g. new commits)
- ref_parts[1] =~ /heads/ && oldrev != "0000000000000000000000000000000000000000"
+ Gitlab::Git.branch_ref?(ref) && oldrev != Gitlab::Git::BLANK_SHA
end
def push_to_new_branch?(ref, oldrev)
- ref_parts = ref.split('/')
-
- ref_parts[1] =~ /heads/ && oldrev == "0000000000000000000000000000000000000000"
+ Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(oldrev)
end
def push_remove_branch?(ref, newrev)
- ref_parts = ref.split('/')
-
- ref_parts[1] =~ /heads/ && newrev == "0000000000000000000000000000000000000000"
+ Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(newrev)
end
def push_to_branch?(ref)
- ref =~ /refs\/heads/
+ Gitlab::Git.branch_ref?(ref)
end
def is_default_branch?(ref)
- ref == "refs/heads/#{project.default_branch}"
+ Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch
end
def commit_user(commit)
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 62eaf9b4f5..0d8e6e85e4 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -3,38 +3,21 @@ class GitTagPushService
def execute(project, user, oldrev, newrev, ref)
@project, @user = project, user
- @push_data = create_push_data(oldrev, newrev, ref)
+
+ @push_data = build_push_data(oldrev, newrev, ref)
- create_push_event
- project.repository.expire_cache
+ EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
+ project.execute_services(@push_data.dup, :tag_push_hooks)
+
+ project.repository.expire_cache
+
+ true
end
private
- def create_push_data(oldrev, newrev, ref)
- data = {
- ref: ref,
- before: oldrev,
- after: newrev,
- user_id: user.id,
- user_name: user.name,
- project_id: project.id,
- repository: {
- name: project.name,
- url: project.url_to_repo,
- description: project.description,
- homepage: project.web_url
- }
- }
- end
-
- def create_push_event
- Event.create!(
- project: project,
- action: Event::PUSHED,
- data: push_data,
- author_id: push_data[:user_id]
- )
+ def build_push_data(oldrev, newrev, ref)
+ Gitlab::PushDataBuilder.build(project, user, oldrev, newrev, ref, [])
end
end
diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb
index a69c7c7837..4bee0c26a6 100644
--- a/app/services/gravatar_service.rb
+++ b/app/services/gravatar_service.rb
@@ -1,6 +1,8 @@
class GravatarService
+ include Gitlab::CurrentSettings
+
def execute(email, size = nil)
- if gravatar_config.enabled && email.present?
+ if current_application_settings.gravatar_enabled? && email.present?
size = 40 if size.nil? || size <= 0
sprintf gravatar_url,
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index e3371ec3c1..5e1906ad2a 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -10,4 +10,9 @@ class IssuableBaseService < BaseService
Note.create_milestone_change_note(
issuable, issuable.project, current_user, issuable.milestone)
end
+
+ def create_labels_note(issuable, added_labels, removed_labels)
+ Note.create_labels_change_note(
+ issuable, issuable.project, current_user, added_labels, removed_labels)
+ end
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 41948f226a..c3ca04a434 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -1,13 +1,19 @@
module Issues
class BaseService < ::IssuableBaseService
+ def hook_data(issue, action)
+ issue_data = issue.to_hook_data(current_user)
+ issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
+ issue_data[:object_attributes].merge!(url: issue_url, action: action)
+ issue_data
+ end
+
private
def execute_hooks(issue, action = 'open')
- issue_data = issue.to_hook_data
- issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
- issue_data[:object_attributes].merge!(url: issue_url, action: action)
+ issue_data = hook_data(issue, action)
issue.project.execute_hooks(issue_data, :issue_hooks)
+ issue.project.execute_services(issue_data, :issue_hooks)
end
end
end
diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb
index f72a346af6..c7cd20b6b6 100644
--- a/app/services/issues/bulk_update_service.rb
+++ b/app/services/issues/bulk_update_service.rb
@@ -1,38 +1,23 @@
module Issues
class BulkUpdateService < BaseService
def execute
- update_data = params[:update]
+ issues_ids = params.delete(:issues_ids).split(",")
+ issue_params = params
- issues_ids = update_data[:issues_ids].split(",")
- milestone_id = update_data[:milestone_id]
- assignee_id = update_data[:assignee_id]
- status = update_data[:status]
-
- new_state = nil
-
- if status.present?
- if status == 'closed'
- new_state = :close
- else
- new_state = :reopen
- end
- end
-
- opts = {}
- opts[:milestone_id] = milestone_id if milestone_id.present?
- opts[:assignee_id] = assignee_id if assignee_id.present?
+ issue_params.delete(:state_event) unless issue_params[:state_event].present?
+ issue_params.delete(:milestone_id) unless issue_params[:milestone_id].present?
+ issue_params.delete(:assignee_id) unless issue_params[:assignee_id].present?
issues = Issue.where(id: issues_ids)
- issues = issues.select { |issue| can?(current_user, :modify_issue, issue) }
-
issues.each do |issue|
- issue.update_attributes(opts)
- issue.send new_state if new_state
+ next unless can?(current_user, :modify_issue, issue)
+
+ Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue)
end
{
- count: issues.count,
- success: !issues.count.zero?
+ count: issues.count,
+ success: !issues.count.zero?
}
end
end
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index ffed13a12e..f670019cc6 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -2,9 +2,9 @@ module Issues
class CloseService < Issues::BaseService
def execute(issue, commit = nil)
if issue.close
- notification_service.close_issue(issue, current_user)
event_service.close_issue(issue, current_user)
create_note(issue, commit)
+ notification_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 0ee9635ed9..c61d67a789 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -14,17 +14,24 @@ module Issues
issue.update_nth_task(params[:task_num].to_i, false)
end
+ old_labels = issue.labels.to_a
+
if params.present? && issue.update_attributes(params.except(:state_event,
:task_num))
issue.reset_events_cache
+ if issue.labels != old_labels
+ create_labels_note(
+ issue, issue.labels - old_labels, old_labels - issue.labels)
+ end
+
if issue.previous_changes.include?('milestone_id')
create_milestone_note(issue)
end
if issue.previous_changes.include?('assignee_id')
- notification_service.reassigned_issue(issue, current_user)
create_assignee_note(issue)
+ notification_service.reassigned_issue(issue, current_user)
end
issue.notice_added_references(issue.project, current_user)
diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb
index 20b88d1510..378b39bb9d 100644
--- a/app/services/merge_requests/auto_merge_service.rb
+++ b/app/services/merge_requests/auto_merge_service.rb
@@ -5,15 +5,16 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications
# Called when you do merge via GitLab UI
class AutoMergeService < BaseMergeService
- def execute(merge_request, current_user, commit_message)
+ def execute(merge_request, commit_message)
merge_request.lock_mr
if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
merge_request.merge
- notification.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user)
- execute_project_hooks(merge_request)
+ create_note(merge_request)
+ notification_service.merge_mr(merge_request, current_user)
+ execute_hooks(merge_request)
true
else
diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb
index 9bc50d3d16..9579573adf 100644
--- a/app/services/merge_requests/base_merge_service.rb
+++ b/app/services/merge_requests/base_merge_service.rb
@@ -1,20 +1,10 @@
module MergeRequests
- class BaseMergeService
+ class BaseMergeService < MergeRequests::BaseService
private
- def notification
- NotificationService.new
- end
-
def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user)
end
-
- def execute_project_hooks(merge_request)
- if merge_request.project
- merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
- end
- end
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 694994001b..f6e1ae6f28 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -5,9 +5,19 @@ module MergeRequests
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end
- def execute_hooks(merge_request)
+ def hook_data(merge_request, action)
+ hook_data = merge_request.to_hook_data(current_user)
+ merge_request_url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id)
+ hook_data[:object_attributes][:url] = merge_request_url
+ hook_data[:object_attributes][:action] = action
+ hook_data
+ end
+
+ def execute_hooks(merge_request, action = 'open')
if merge_request.project
- merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
+ merge_data = hook_data(merge_request, action)
+ merge_request.project.execute_hooks(merge_data, :merge_request_hooks)
+ merge_request.project.execute_services(merge_data, :merge_request_hooks)
end
end
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 1475973e54..a44b91166e 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -13,12 +13,9 @@ module MergeRequests
merge_request.target_branch ||= merge_request.target_project.default_branch
unless merge_request.target_branch && merge_request.source_branch
- return build_failed(merge_request, "You must select source and target branches")
+ return build_failed(merge_request, nil)
end
- # Generate suggested MR title based on source branch name
- merge_request.title = merge_request.source_branch.titleize.humanize
-
compare_result = CompareService.new.execute(
current_user,
merge_request.source_project,
@@ -52,6 +49,15 @@ module MergeRequests
merge_request.compare_failed = false
end
+ commits = merge_request.compare_commits
+ if commits && commits.count == 1
+ commit = commits.first
+ merge_request.title = commit.title
+ merge_request.description = commit.description.try(:strip)
+ else
+ merge_request.title = merge_request.source_branch.titleize.humanize
+ end
+
merge_request
rescue Gitlab::Satellite::BranchesWithoutParent
@@ -59,7 +65,7 @@ module MergeRequests
end
def build_failed(merge_request, message)
- merge_request.errors.add(:base, message)
+ merge_request.errors.add(:base, message) unless message.nil?
merge_request.compare_commits = []
merge_request.can_be_created = false
merge_request
diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb
index 64e37a23e6..47454f9f0c 100644
--- a/app/services/merge_requests/close_service.rb
+++ b/app/services/merge_requests/close_service.rb
@@ -7,9 +7,9 @@ module MergeRequests
if merge_request.close
event_service.close_mr(merge_request, current_user)
- notification_service.close_mr(merge_request, current_user)
create_note(merge_request)
- execute_hooks(merge_request)
+ notification_service.close_mr(merge_request, current_user)
+ execute_hooks(merge_request, 'close')
end
merge_request
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 680766140b..327ead4ff3 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -6,12 +6,13 @@ module MergeRequests
# Called when you do merge via command line and push code
# to target branch
class MergeService < BaseMergeService
- def execute(merge_request, current_user, commit_message)
+ def execute(merge_request, commit_message)
merge_request.merge
- notification.merge_mr(merge_request, current_user)
create_merge_event(merge_request, current_user)
- execute_project_hooks(merge_request)
+ create_note(merge_request)
+ notification_service.merge_mr(merge_request, current_user)
+ execute_hooks(merge_request, 'merge')
true
rescue
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
new file mode 100644
index 0000000000..cab8a1e880
--- /dev/null
+++ b/app/services/merge_requests/refresh_service.rb
@@ -0,0 +1,100 @@
+module MergeRequests
+ class RefreshService < MergeRequests::BaseService
+ def execute(oldrev, newrev, ref)
+ return true unless Gitlab::Git.branch_ref?(ref)
+
+ @oldrev, @newrev = oldrev, newrev
+ @branch_name = Gitlab::Git.ref_name(ref)
+ @fork_merge_requests = @project.fork_merge_requests.opened
+ @commits = @project.repository.commits_between(oldrev, newrev)
+
+ close_merge_requests
+ reload_merge_requests
+ comment_mr_with_commits
+
+ true
+ end
+
+ private
+
+ # Collect open merge requests that target same branch we push into
+ # and close if push to master include last commit from merge request
+ # We need this to close(as merged) merge requests that were merged into
+ # target branch manually
+ def close_merge_requests
+ commit_ids = @commits.map(&:id)
+ merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a
+ merge_requests = merge_requests.select(&:last_commit)
+
+ merge_requests = merge_requests.select do |merge_request|
+ commit_ids.include?(merge_request.last_commit.id)
+ end
+
+
+ merge_requests.uniq.select(&:source_project).each do |merge_request|
+ MergeRequests::MergeService.
+ new(merge_request.target_project, @current_user).
+ execute(merge_request, nil)
+ end
+ end
+
+ def force_push?
+ Gitlab::ForcePushCheck.force_push?(@project, @oldrev, @newrev)
+ end
+
+ # Refresh merge request diff if we push to source or target branch of merge request
+ # Note: we should update merge requests from forks too
+ def reload_merge_requests
+ merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a
+ merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a
+ merge_requests = filter_merge_requests(merge_requests)
+
+ merge_requests.each do |merge_request|
+
+ if merge_request.source_branch == @branch_name || force_push?
+ merge_request.reload_code
+ update_merge_request(merge_request)
+ else
+ mr_commit_ids = merge_request.commits.map(&:id)
+ push_commit_ids = @commits.map(&:id)
+ matches = mr_commit_ids & push_commit_ids
+
+ if matches.any?
+ merge_request.reload_code
+ update_merge_request(merge_request)
+ else
+ update_merge_request(merge_request)
+ end
+ end
+ end
+ end
+
+ def update_merge_request(merge_request)
+ MergeRequests::UpdateService.new(
+ merge_request.target_project,
+ @current_user, merge_status: 'unchecked').execute(merge_request)
+ end
+
+ # Add comment about pushing new commits to merge requests
+ def comment_mr_with_commits
+ merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
+ merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
+ merge_requests = filter_merge_requests(merge_requests)
+
+ merge_requests.each do |merge_request|
+ mr_commit_ids = Set.new(merge_request.commits.map(&:id))
+
+ new_commits, existing_commits = @commits.partition do |commit|
+ mr_commit_ids.include?(commit.id)
+ end
+
+ Note.create_new_commits_note(merge_request, merge_request.project,
+ @current_user, new_commits, existing_commits)
+ end
+ end
+
+ def filter_merge_requests(merge_requests)
+ merge_requests.uniq.select(&:source_project)
+ end
+ end
+end
diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb
index bd68919a55..8279ad2001 100644
--- a/app/services/merge_requests/reopen_service.rb
+++ b/app/services/merge_requests/reopen_service.rb
@@ -3,9 +3,9 @@ module MergeRequests
def execute(merge_request)
if merge_request.reopen
event_service.reopen_mr(merge_request, current_user)
- notification_service.reopen_mr(merge_request, current_user)
create_note(merge_request)
- execute_hooks(merge_request)
+ notification_service.reopen_mr(merge_request, current_user)
+ execute_hooks(merge_request, 'reopen')
merge_request.reload_code
merge_request.mark_as_unchecked
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index fc26619cd1..870b50bb60 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -23,22 +23,32 @@ module MergeRequests
merge_request.update_nth_task(params[:task_num].to_i, false)
end
+ old_labels = merge_request.labels.to_a
+
if params.present? && merge_request.update_attributes(
params.except(:state_event, :task_num)
)
merge_request.reset_events_cache
+ if merge_request.labels != old_labels
+ create_labels_note(
+ merge_request,
+ merge_request.labels - old_labels,
+ old_labels - merge_request.labels
+ )
+ end
+
if merge_request.previous_changes.include?('milestone_id')
create_milestone_note(merge_request)
end
if merge_request.previous_changes.include?('assignee_id')
- notification_service.reassigned_merge_request(merge_request, current_user)
create_assignee_note(merge_request)
+ notification_service.reassigned_merge_request(merge_request, current_user)
end
merge_request.notice_added_references(merge_request.project, current_user)
- execute_hooks(merge_request)
+ execute_hooks(merge_request, 'update')
end
merge_request
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index f64006a4ed..e969061f22 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -17,10 +17,22 @@ module Notes
note.references.each do |mentioned|
Note.create_cross_reference_note(mentioned, note.noteable, note.author, note.project)
end
+
+ execute_hooks(note)
end
end
note
end
+
+ def hook_data(note)
+ Gitlab::NoteDataBuilder.build(note, current_user)
+ end
+
+ def execute_hooks(note)
+ note_data = hook_data(note)
+ # TODO: Support Webhooks
+ note.project.execute_services(note_data, :note_hooks)
+ end
end
end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
new file mode 100644
index 0000000000..63431b8247
--- /dev/null
+++ b/app/services/notes/update_service.rb
@@ -0,0 +1,25 @@
+module Notes
+ class UpdateService < BaseService
+ def execute
+ note = project.notes.find(params[:note_id])
+ note.note = params[:note]
+ if note.save
+ notification_service.new_note(note)
+
+ # Skip system notes, like status changes and cross-references.
+ unless note.system
+ event_service.leave_note(note, note.author)
+
+ # Create a cross-reference note if this Note contains GFM that
+ # names an issue, merge request, or commit.
+ note.references.each do |mentioned|
+ Note.create_cross_reference_note(mentioned, note.noteable,
+ note.author, note.project)
+ end
+ end
+ end
+
+ note
+ end
+ end
+end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index fe39f83b40..848ed77ebf 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -92,6 +92,8 @@ class NotificationService
#
def merge_mr(merge_request, current_user)
recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project)
+ recipients = add_subscribed_users(recipients, merge_request)
+ recipients = reject_unsubscribed_users(recipients, merge_request)
recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq
recipients.delete(current_user)
@@ -107,7 +109,7 @@ class NotificationService
# Notify new user with email after creation
def new_user(user, token = nil)
# Don't email omniauth created users
- mailer.new_user_email(user.id, user.password, token) unless user.extern_uid?
+ mailer.new_user_email(user.id, token) unless user.identities.any?
end
# Notify users on new note in system
@@ -118,12 +120,13 @@ class NotificationService
return true unless note.noteable_type.present?
# ignore gitlab service messages
- return true if note.note =~ /\A_Status changed to closed_/
- return true if note.note =~ /\A_mentioned in / && note.system == true
+ return true if note.note.start_with?('_Status changed to closed_')
+ return true if note.cross_reference? && note.system == true
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
target = note.noteable
+
if target.respond_to?(:participants)
recipients = target.participants
else
@@ -143,9 +146,17 @@ class NotificationService
# Merge project watchers
recipients = recipients.concat(project_watchers(note.project)).compact.uniq
+ # Reject mention users unless mentioned in comment
+ recipients = reject_mention_users(recipients - note.mentioned_users, note.project)
+ recipients = recipients + note.mentioned_users
+
# Reject mutes users
recipients = reject_muted_users(recipients, note.project)
+ recipients = add_subscribed_users(recipients, note.noteable)
+
+ recipients = reject_unsubscribed_users(recipients, note.noteable)
+
# Reject author
recipients.delete(note.author)
@@ -157,20 +168,20 @@ class NotificationService
end
end
- def new_team_member(project_member)
+ def new_project_member(project_member)
mailer.project_access_granted_email(project_member.id)
end
- def update_team_member(project_member)
+ def update_project_member(project_member)
mailer.project_access_granted_email(project_member.id)
end
- def new_group_member(users_group)
- mailer.group_access_granted_email(users_group.id)
+ def new_group_member(group_member)
+ mailer.group_access_granted_email(group_member.id)
end
- def update_group_member(users_group)
- mailer.group_access_granted_email(users_group.id)
+ def update_group_member(group_member)
+ mailer.group_access_granted_email(group_member.id)
end
def project_was_moved(project)
@@ -189,11 +200,11 @@ class NotificationService
project_members = project_member_notification(project)
users_with_project_level_global = project_member_notification(project, Notification::N_GLOBAL)
- users_with_group_level_global = users_group_notification(project, Notification::N_GLOBAL)
+ users_with_group_level_global = group_member_notification(project, Notification::N_GLOBAL)
users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users)
- users_with_group_setting = select_users_group_setting(project, project_members, users_with_group_level_global, users)
+ users_with_group_setting = select_group_member_setting(project, project_members, users_with_group_level_global, users)
User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
end
@@ -208,7 +219,7 @@ class NotificationService
end
end
- def users_group_notification(project, notification_level)
+ def group_member_notification(project, notification_level)
if project.group
project.group.group_members.where(notification_level: notification_level).pluck(:user_id)
else
@@ -237,9 +248,9 @@ class NotificationService
users
end
- # Build a list of users based on group notifcation settings
- def select_users_group_setting(project, project_members, global_setting, users_global_level_watch)
- uids = users_group_notification(project, Notification::N_WATCH)
+ # Build a list of users based on group notification settings
+ def select_group_member_setting(project, project_members, global_setting, users_global_level_watch)
+ uids = group_member_notification(project, Notification::N_WATCH)
# Group setting is watch, add to users list if user is not project member
users = []
@@ -263,35 +274,75 @@ class NotificationService
# Also remove duplications and nil recipients
def reject_muted_users(users, project = nil)
users = users.to_a.compact.uniq
+ users = users.reject(&:blocked?)
users.reject do |user|
next user.notification.disabled? unless project
- tm = project.project_members.find_by(user_id: user.id)
+ member = project.project_members.find_by(user_id: user.id)
- if !tm && project.group
- tm = project.group.group_members.find_by(user_id: user.id)
+ if !member && project.group
+ member = project.group.group_members.find_by(user_id: user.id)
end
# reject users who globally disabled notification and has no membership
- next user.notification.disabled? unless tm
+ next user.notification.disabled? unless member
# reject users who disabled notification in project
- next true if tm.notification.disabled?
+ next true if member.notification.disabled?
# reject users who have N_GLOBAL in project and disabled in global settings
- tm.notification.global? && user.notification.disabled?
+ member.notification.global? && user.notification.disabled?
end
end
- def new_resource_email(target, project, method)
- if target.respond_to?(:participants)
- recipients = target.participants
- else
- recipients = []
+ # Remove users with notification level 'Mentioned'
+ def reject_mention_users(users, project = nil)
+ users = users.to_a.compact.uniq
+
+ users.reject do |user|
+ next user.notification.mention? unless project
+
+ member = project.project_members.find_by(user_id: user.id)
+
+ if !member && project.group
+ member = project.group.group_members.find_by(user_id: user.id)
+ end
+
+ # reject users who globally set mention notification and has no membership
+ next user.notification.mention? unless member
+
+ # reject users who set mention notification in project
+ next true if member.notification.mention?
+
+ # reject users who have N_MENTION in project and disabled in global settings
+ member.notification.global? && user.notification.mention?
end
- recipients = reject_muted_users(recipients, project)
- recipients = recipients.concat(project_watchers(project)).uniq
+ end
+
+ def reject_unsubscribed_users(recipients, target)
+ return recipients unless target.respond_to? :subscriptions
+
+ recipients.reject do |user|
+ subscription = target.subscriptions.find_by_user_id(user.id)
+ subscription && !subscription.subscribed
+ end
+ end
+
+ def add_subscribed_users(recipients, target)
+ return recipients unless target.respond_to? :subscriptions
+
+ subscriptions = target.subscriptions
+
+ if subscriptions.any?
+ recipients + subscriptions.where(subscribed: true).map(&:user)
+ else
+ recipients
+ end
+ end
+
+ def new_resource_email(target, project, method)
+ recipients = build_recipients(target, project)
recipients.delete(target.author)
recipients.each do |recipient|
@@ -300,8 +351,7 @@ class NotificationService
end
def close_resource_email(target, project, current_user, method)
- recipients = reject_muted_users([target.author, target.assignee], project)
- recipients = recipients.concat(project_watchers(project)).uniq
+ recipients = build_recipients(target, project)
recipients.delete(current_user)
recipients.each do |recipient|
@@ -311,16 +361,7 @@ class NotificationService
def reassign_resource_email(target, project, current_user, method)
assignee_id_was = previous_record(target, "assignee_id")
-
- recipients = User.where(id: [target.assignee_id, assignee_id_was])
-
- # Add watchers to email list
- recipients = recipients.concat(project_watchers(project))
-
- # reject users with disabled notifications
- recipients = reject_muted_users(recipients, project)
-
- # Reject me from recipients if I reassign an item
+ recipients = build_recipients(target, project)
recipients.delete(current_user)
recipients.each do |recipient|
@@ -329,8 +370,7 @@ class NotificationService
end
def reopen_resource_email(target, project, current_user, method, status)
- recipients = reject_muted_users([target.author, target.assignee], project)
- recipients = recipients.concat(project_watchers(project)).uniq
+ recipients = build_recipients(target, project)
recipients.delete(current_user)
recipients.each do |recipient|
@@ -338,6 +378,22 @@ class NotificationService
end
end
+ def build_recipients(target, project)
+ recipients =
+ if target.respond_to?(:participants)
+ target.participants
+ else
+ [target.author, target.assignee]
+ end
+
+ recipients = reject_muted_users(recipients, project)
+ recipients = reject_mention_users(recipients, project)
+ recipients = add_subscribed_users(recipients, target)
+ recipients = recipients.concat(project_watchers(project)).uniq
+ recipients = reject_unsubscribed_users(recipients, target)
+ recipients
+ end
+
def mailer
Notify.delay
end
diff --git a/app/services/oauth2/access_token_validation_service.rb b/app/services/oauth2/access_token_validation_service.rb
new file mode 100644
index 0000000000..6194f6ce91
--- /dev/null
+++ b/app/services/oauth2/access_token_validation_service.rb
@@ -0,0 +1,41 @@
+module Oauth2::AccessTokenValidationService
+ # Results:
+ VALID = :valid
+ EXPIRED = :expired
+ REVOKED = :revoked
+ INSUFFICIENT_SCOPE = :insufficient_scope
+
+ class << self
+ def validate(token, scopes: [])
+ if token.expired?
+ return EXPIRED
+
+ elsif token.revoked?
+ return REVOKED
+
+ elsif !self.sufficient_scope?(token, scopes)
+ return INSUFFICIENT_SCOPE
+
+ else
+ return VALID
+ end
+ end
+
+ protected
+ # True if the token's scope is a superset of required scopes,
+ # or the required scopes is empty.
+ def sufficient_scope?(token, scopes)
+ if scopes.blank?
+ # if no any scopes required, the scopes of token is sufficient.
+ return true
+ else
+ # If there are scopes required, then check whether
+ # the set of authorized scopes is a superset of the set of required scopes
+ required_scopes = Set.new(scopes)
+ authorized_scopes = Set.new(token.scopes)
+
+ return authorized_scopes >= required_scopes
+ end
+ end
+ end
+end
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
new file mode 100644
index 0000000000..7408e09ed1
--- /dev/null
+++ b/app/services/projects/autocomplete_service.rb
@@ -0,0 +1,15 @@
+module Projects
+ class AutocompleteService < BaseService
+ def initialize(project)
+ @project = project
+ end
+
+ def issues
+ @project.issues.opened.select([:iid, :title])
+ end
+
+ def merge_requests
+ @project.merge_requests.opened.select([:iid, :title])
+ end
+ end
+end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 12386792aa..7ffd0b3882 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -7,17 +7,25 @@ module Projects
def execute
@project = Project.new(params)
- # Reset visibility levet if is not allowed to set it
- unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
- @project.visibility_level = default_features.visibility_level
+ # Make sure that the user is allowed to use the specified visibility
+ # level
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user,
+ params[:visibility_level])
+ deny_visibility_level(@project)
+ return @project
end
- # Parametrize path for project
- #
- # Ex.
- # 'GitLab HQ'.parameterize => "gitlab-hq"
- #
- @project.path = @project.name.dup.parameterize unless @project.path.present?
+ # Set project name from path
+ if @project.name.present? && @project.path.present?
+ # if both name and path set - everything is ok
+ elsif @project.path.present?
+ # Set project name from path
+ @project.name = @project.path.dup
+ elsif @project.name.present?
+ # For compatibility - set path from name
+ # TODO: remove this in 8.0
+ @project.path = @project.name.dup.parameterize
+ end
# get namespace id
namespace_id = params[:namespace_id]
@@ -37,37 +45,18 @@ module Projects
@project.creator = current_user
- if @project.save
- log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
- system_hook_service.execute_hooks_for(@project, :create)
+ Project.transaction do
+ @project.save
- unless @project.group
- @project.team << [current_user, :master]
- end
-
- @project.update_column(:last_activity_at, @project.created_at)
-
- if @project.import?
- @project.import_start
- else
- GitlabShellWorker.perform_async(
- :add_repository,
- @project.path_with_namespace
- )
- end
-
- if @project.wiki_enabled?
- begin
- # force the creation of a wiki,
- ProjectWiki.new(@project, @project.owner).wiki
- rescue ProjectWiki::CouldNotCreateWikiError => ex
- # Prevent project observer crash
- # if failed to create wiki
- nil
+ unless @project.import?
+ unless @project.create_repository
+ raise 'Failed to create repository'
end
end
end
+ after_create_actions if @project.persisted?
+
@project
rescue => ex
@project.errors.add(:base, "Can't save project. Please try again later")
@@ -84,5 +73,24 @@ module Projects
namespace = Namespace.find_by(id: namespace_id)
current_user.can?(:create_projects, namespace)
end
+
+ def after_create_actions
+ log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
+
+ @project.create_wiki if @project.wiki_enabled?
+
+ event_service.create_project(@project, current_user)
+ system_hook_service.execute_hooks_for(@project, :create)
+
+ unless @project.group
+ @project.team << [current_user, :master]
+ end
+
+ @project.update_column(:last_activity_at, @project.created_at)
+
+ if @project.import?
+ @project.import_start
+ end
+ end
end
end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index a59311bf94..6b0d4aca3e 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -2,11 +2,9 @@ module Projects
class ForkService < BaseService
include Gitlab::ShellAdapter
- def initialize(project, user)
- @from_project, @current_user = project, user
- end
-
def execute
+ @from_project = @project
+
project_params = {
visibility_level: @from_project.visibility_level,
description: @from_project.description,
@@ -15,8 +13,21 @@ module Projects
project = Project.new(project_params)
project.name = @from_project.name
project.path = @from_project.path
- project.namespace = current_user.namespace
- project.creator = current_user
+ project.creator = @current_user
+ if @from_project.avatar.present? && @from_project.avatar.image?
+ project.avatar = @from_project.avatar
+ end
+
+ if namespace = @params[:namespace]
+ project.namespace = namespace
+ else
+ project.namespace = @current_user.namespace
+ end
+
+ unless @current_user.can?(:create_projects, project.namespace)
+ project.errors.add(:namespace, 'insufficient access rights')
+ return project
+ end
# If the project cannot save, we do not want to trigger the project destroy
# as this can have the side effect of deleting a repo attached to an existing
@@ -27,23 +38,23 @@ module Projects
#First save the DB entries as they can be rolled back if the repo fork fails
project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id)
if project.save
- project.team << [current_user, :master]
+ project.team << [@current_user, :master]
end
#Now fork the repo
unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path)
- raise "forking failed in gitlab-shell"
+ raise 'forking failed in gitlab-shell'
end
project.ensure_satellite_exists
end
rescue => ex
- project.errors.add(:base, "Fork transaction failed.")
+ project.errors.add(:base, 'Fork transaction failed.')
project.destroy
end
else
- project.errors.add(:base, "Invalid fork destination")
+ project.errors.add(:base, 'Invalid fork destination')
end
- project
+ project
end
end
end
diff --git a/app/services/projects/image_service.rb b/app/services/projects/image_service.rb
deleted file mode 100644
index c79ddddd97..0000000000
--- a/app/services/projects/image_service.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module Projects
- class ImageService < BaseService
- include Rails.application.routes.url_helpers
- def initialize(repository, params, root_url)
- @repository, @params, @root_url = repository, params.dup, root_url
- end
-
- def execute
- uploader = FileUploader.new('uploads', upload_path, accepted_images)
- image = @params['markdown_img']
-
- if image && correct_mime_type?(image)
- alt = image.original_filename
- uploader.store!(image)
- link = {
- 'alt' => File.basename(alt, '.*'),
- 'url' => File.join(@root_url, uploader.url)
- }
- else
- link = nil
- end
- end
-
- protected
-
- def upload_path
- base_dir = FileUploader.generate_dir
- File.join(@repository.path_with_namespace, base_dir)
- end
-
- def accepted_images
- %w(png jpg jpeg gif)
- end
-
- def correct_mime_type?(image)
- accepted_images.map{ |format| image.content_type.include? format }.any?
- end
- end
-end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index c4d2c0963b..bcbacbff56 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -1,17 +1,19 @@
module Projects
class ParticipantsService < BaseService
- def initialize(project)
- @project = project
+ def initialize(project, user)
+ @project = project
+ @user = user
end
def execute(note_type, note_id)
- participating = if note_type && note_id
- participants_in(note_type, note_id)
- else
- []
- end
- team_members = sorted(@project.team.members)
- participants = all_members + team_members + participating
+ participating =
+ if note_type && note_id
+ participants_in(note_type, note_id)
+ else
+ []
+ end
+ project_members = sorted(@project.team.members)
+ participants = all_members + groups + project_members + participating
participants.uniq
end
@@ -33,11 +35,21 @@ module Projects
end
def sorted(users)
- users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
+ users.uniq.to_a.compact.sort_by(&:username).map do |user|
+ { username: user.username, name: user.name }
+ end
+ end
+
+ def groups
+ @user.authorized_groups.sort_by(&:path).map do |group|
+ count = group.users.count
+ { username: group.path, name: "#{group.name} (#{count})" }
+ end
end
def all_members
- [{ username: "all", name: "Project and Group Members" }]
+ count = @project.team.members.flatten.count
+ [{ username: "all", name: "All Project and Group Members (#{count})" }]
end
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index e39fe882cb..3372cfc11d 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -12,7 +12,7 @@ module Projects
class TransferError < StandardError; end
def execute
- namespace_id = params[:namespace_id]
+ namespace_id = params[:new_namespace_id]
namespace = Namespace.find_by(id: namespace_id)
if allowed_transfer?(current_user, project, namespace)
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 36877a6167..69bdd045dd 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -2,8 +2,13 @@ module Projects
class UpdateService < BaseService
def execute
# check that user is allowed to set specified visibility_level
- unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
- params[:visibility_level] = project.visibility_level
+ new_visibility = params[:visibility_level]
+ if new_visibility && new_visibility.to_i != project.visibility_level
+ unless can?(current_user, :change_visibility_level, project) &&
+ Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
+ deny_visibility_level(project, new_visibility)
+ return project
+ end
end
new_branch = params[:default_branch]
diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb
new file mode 100644
index 0000000000..a186c97628
--- /dev/null
+++ b/app/services/projects/upload_service.rb
@@ -0,0 +1,22 @@
+module Projects
+ class UploadService < BaseService
+ def initialize(project, file)
+ @project, @file = project, file
+ end
+
+ def execute
+ return nil unless @file
+
+ uploader = FileUploader.new(@project)
+ uploader.store!(@file)
+
+ filename = uploader.image? ? uploader.file.basename : uploader.file.filename
+
+ {
+ 'alt' => filename,
+ 'url' => uploader.secure_url,
+ 'is_image' => uploader.image?
+ }
+ end
+ end
+end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index a6b68749a7..c5d0b08845 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -18,7 +18,7 @@ class SystemHooksService
def build_event_data(model, event)
data = {
event_name: build_event_name(model, event),
- created_at: model.created_at
+ created_at: model.created_at.xmlschema
}
case model
@@ -41,7 +41,7 @@ class SystemHooksService
path_with_namespace: model.path_with_namespace,
project_id: model.id,
owner_name: owner.name,
- owner_email: owner.respond_to?(:email) ? owner.email : nil,
+ owner_email: owner.respond_to?(:email) ? owner.email : "",
project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase
})
when User
@@ -60,6 +60,26 @@ class SystemHooksService
access_level: model.human_access,
project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase
})
+ when Group
+ owner = model.owner
+
+ data.merge!(
+ name: model.name,
+ path: model.path,
+ group_id: model.id,
+ owner_name: owner.respond_to?(:name) ? owner.name : nil,
+ owner_email: owner.respond_to?(:email) ? owner.email : nil,
+ )
+ when GroupMember
+ data.merge!(
+ group_name: model.group.name,
+ group_path: model.group.path,
+ group_id: model.group.id,
+ user_name: model.user.name,
+ user_email: model.user.email,
+ user_id: model.user.id,
+ group_access: model.human_access,
+ )
end
end
@@ -68,6 +88,9 @@ class SystemHooksService
when ProjectMember
return "user_add_to_team" if event == :create
return "user_remove_from_team" if event == :destroy
+ when GroupMember
+ return 'user_add_to_group' if event == :create
+ return 'user_remove_from_group' if event == :destroy
else
"#{model.class.name.downcase}_#{event.to_s}"
end
diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb
index b6b1ef29b5..21ec2c01cb 100644
--- a/app/services/test_hook_service.rb
+++ b/app/services/test_hook_service.rb
@@ -1,9 +1,6 @@
class TestHookService
def execute(hook, current_user)
- data = GitPushService.new.sample_data(hook.project, current_user)
+ data = Gitlab::PushDataBuilder.build_sample(hook.project, current_user)
hook.execute(data)
- true
- rescue SocketError
- false
end
end
diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb
new file mode 100644
index 0000000000..9d181c2d2a
--- /dev/null
+++ b/app/services/update_snippet_service.rb
@@ -0,0 +1,22 @@
+class UpdateSnippetService < BaseService
+ attr_accessor :snippet
+
+ def initialize(project, user, snippet, params)
+ super(project, user, params)
+ @snippet = snippet
+ end
+
+ def execute
+ # check that user is allowed to set specified visibility_level
+ new_visibility = params[:visibility_level]
+ if new_visibility && new_visibility.to_i != snippet.visibility_level
+ unless can?(current_user, :change_visibility_level, snippet) &&
+ Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
+ deny_visibility_level(snippet, new_visibility)
+ return snippet
+ end
+ end
+
+ snippet.update_attributes(params)
+ end
+end
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index b122b6c865..a9691bee46 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -3,8 +3,6 @@
class AttachmentUploader < CarrierWave::Uploader::Base
storage :file
- after :store, :reset_events_cache
-
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
@@ -22,15 +20,7 @@ class AttachmentUploader < CarrierWave::Uploader::Base
false
end
- def secure_url
- Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}"
- end
-
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
-
- def reset_events_cache(file)
- model.reset_events_cache if model.is_a?(User)
- end
end
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
new file mode 100644
index 0000000000..7cad044555
--- /dev/null
+++ b/app/uploaders/avatar_uploader.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+
+class AvatarUploader < CarrierWave::Uploader::Base
+ storage :file
+
+ after :store, :reset_events_cache
+
+ def store_dir
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
+ end
+
+ def image?
+ img_ext = %w(png jpg jpeg gif bmp tiff)
+ if file.respond_to?(:extension)
+ img_ext.include?(file.extension.downcase)
+ else
+ # Not all CarrierWave storages respond to :extension
+ ext = file.path.split('.').last.downcase
+ img_ext.include?(ext)
+ end
+ rescue
+ false
+ end
+
+ def file_storage?
+ self.class.storage == CarrierWave::Storage::File
+ end
+
+ def reset_events_cache(file)
+ model.reset_events_cache if model.is_a?(User)
+ end
+end
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index 0fa987c93f..f9673abbfe 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -2,40 +2,47 @@
class FileUploader < CarrierWave::Uploader::Base
storage :file
- def initialize(base_dir, path = '', allowed_extensions = nil)
- @base_dir = base_dir
- @path = path
- @allowed_extensions = allowed_extensions
+ attr_accessor :project, :secret
+
+ def initialize(project, secret = self.class.generate_secret)
+ @project = project
+ @secret = secret
end
def base_dir
- @base_dir
+ "uploads"
end
def store_dir
- File.join(@base_dir, @path)
+ File.join(base_dir, @project.path_with_namespace, @secret)
end
def cache_dir
- File.join(@base_dir, 'tmp', @path)
+ File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end
- def extension_white_list
- @allowed_extensions
+ def self.generate_secret
+ SecureRandom.hex
end
- def store!(file)
- @filename = self.class.generate_filename(file)
- super
+ def secure_url
+ File.join(Gitlab.config.gitlab.url, @project.path_with_namespace, "uploads", @secret, file.filename)
end
- def self.generate_filename(file)
- original_filename = File.basename(file.original_filename, '.*')
- extension = File.extname(file.original_filename)
- new_filename = Digest::MD5.hexdigest(original_filename) + extension
+ def file_storage?
+ self.class.storage == CarrierWave::Storage::File
end
- def self.generate_dir
- SecureRandom.hex(5)
+ def image?
+ img_ext = %w(png jpg jpeg gif bmp tiff)
+ if file.respond_to?(:extension)
+ img_ext.include?(file.extension.downcase)
+ else
+ # Not all CarrierWave storages respond to :extension
+ ext = file.path.split('.').last.downcase
+ img_ext.include?(ext)
+ end
+ rescue
+ false
end
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
new file mode 100644
index 0000000000..781600a376
--- /dev/null
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -0,0 +1,64 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ - if @application_setting.errors.any?
+ #error_explanation
+ .alert.alert-danger
+ - @application_setting.errors.full_messages.each do |msg|
+ %p= msg
+
+ %fieldset
+ %legend Features
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :signup_enabled do
+ = f.check_box :signup_enabled
+ Signin enabled
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :signin_enabled do
+ = f.check_box :signin_enabled
+ Signup enabled
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :gravatar_enabled do
+ = f.check_box :gravatar_enabled
+ Gravatar enabled
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :twitter_sharing_enabled do
+ = f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block'
+ %strong Twitter enabled
+ %span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter
+ %fieldset
+ %legend Misc
+ .form-group
+ = f.label :default_projects_limit, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :default_projects_limit, class: 'form-control'
+ .form-group
+ = f.label :default_branch_protection, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control'
+ .form-group
+ = f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
+ .col-sm-10
+ - data_attrs = { toggle: 'buttons' }
+ .btn-group{ data: data_attrs }
+ - restricted_level_checkboxes('restricted-visibility-help').each do |level|
+ = level
+ %span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets
+ .form-group
+ = f.label :home_page_url, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block'
+ %span.help-block#home_help_block We will redirect non-logged in users to this page
+ .form-group
+ = f.label :sign_in_text, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_area :sign_in_text, class: 'form-control', rows: 4
+ .help-block Markdown enabled
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
new file mode 100644
index 0000000000..39b66647a5
--- /dev/null
+++ b/app/views/admin/application_settings/show.html.haml
@@ -0,0 +1,3 @@
+%h3.page-title Application settings
+%hr
+= render 'form'
diff --git a/app/views/admin/applications/_delete_form.html.haml b/app/views/admin/applications/_delete_form.html.haml
new file mode 100644
index 0000000000..3147cbd659
--- /dev/null
+++ b/app/views/admin/applications/_delete_form.html.haml
@@ -0,0 +1,4 @@
+- submit_btn_css ||= 'btn btn-link btn-remove btn-sm'
+= form_tag admin_application_path(application) do
+ %input{:name => "_method", :type => "hidden", :value => "delete"}/
+ = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css
\ No newline at end of file
diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml
new file mode 100644
index 0000000000..b77d188a38
--- /dev/null
+++ b/app/views/admin/applications/_form.html.haml
@@ -0,0 +1,24 @@
+= form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f|
+ - if application.errors.any?
+ .alert.alert-danger{"data-alert" => ""}
+ %p Whoops! Check your form for possible errors
+ = content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do
+ = f.label :name, class: 'col-sm-2 control-label'
+ .col-sm-10
+ = f.text_field :name, class: 'form-control'
+ = doorkeeper_errors_for application, :name
+ = content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do
+ = f.label :redirect_uri, class: 'col-sm-2 control-label'
+ .col-sm-10
+ = f.text_area :redirect_uri, class: 'form-control'
+ = doorkeeper_errors_for application, :redirect_uri
+ %span.help-block
+ Use one line per URI
+ - if Doorkeeper.configuration.native_redirect_uri
+ %span.help-block
+ Use
+ %code= Doorkeeper.configuration.native_redirect_uri
+ for local tests
+ .form-actions
+ = f.submit 'Submit', class: "btn btn-primary wide"
+ = link_to "Cancel", admin_applications_path, class: "btn btn-default"
diff --git a/app/views/admin/applications/edit.html.haml b/app/views/admin/applications/edit.html.haml
new file mode 100644
index 0000000000..e408ae2f29
--- /dev/null
+++ b/app/views/admin/applications/edit.html.haml
@@ -0,0 +1,3 @@
+%h3.page-title Edit application
+- @url = admin_application_path(@application)
+= render 'form', application: @application
\ No newline at end of file
diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml
new file mode 100644
index 0000000000..d550278710
--- /dev/null
+++ b/app/views/admin/applications/index.html.haml
@@ -0,0 +1,22 @@
+%h3.page-title
+ System OAuth applications
+%p.light
+ System OAuth application does not belong to certain user and can be managed only by admins
+%hr
+%p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success'
+%table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th Clients
+ %th
+ %th
+ %tbody.oauth-applications
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, admin_application_path(application)
+ %td= application.redirect_uri
+ %td= application.access_tokens.map(&:resource_owner_id).uniq.count
+ %td= link_to 'Edit', edit_admin_application_path(application), class: 'btn btn-link'
+ %td= render 'delete_form', application: application
diff --git a/app/views/admin/applications/new.html.haml b/app/views/admin/applications/new.html.haml
new file mode 100644
index 0000000000..7c62425f19
--- /dev/null
+++ b/app/views/admin/applications/new.html.haml
@@ -0,0 +1,3 @@
+%h3.page-title New application
+- @url = admin_applications_path
+= render 'form', application: @application
\ No newline at end of file
diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml
new file mode 100644
index 0000000000..2abe390ce1
--- /dev/null
+++ b/app/views/admin/applications/show.html.haml
@@ -0,0 +1,26 @@
+%h3.page-title
+ Application: #{@application.name}
+
+
+%table.table
+ %tr
+ %td
+ Application Id
+ %td
+ %code#application_id= @application.uid
+ %tr
+ %td
+ Secret:
+ %td
+ %code#secret= @application.secret
+
+ %tr
+ %td
+ Callback url
+ %td
+ - @application.redirect_uri.split.each do |uri|
+ %div
+ %span.monospace= uri
+.form-actions
+ = link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide pull-left'
+ = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 9dcf7b488e..8db2b2a709 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -25,7 +25,7 @@
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- data = process.strip.split(' ')
%tr
- %td= Settings.gitlab.user
+ %td= gitlab_config.user
- 5.times do
%td= data.shift
%td= data.join(' ')
@@ -36,7 +36,7 @@
If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
%p
%i.fa.fa-exclamation-circle
- If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{Settings.gitlab.user} -f sidekiq) and restart GitLab.
+ If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab.
diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
index 7b483ee655..c0afaf16d8 100644
--- a/app/views/admin/broadcast_messages/index.html.haml
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -52,7 +52,7 @@
%strong
#{broadcast_message.ends_at.to_s(:short)}
- = link_to [:admin, broadcast_message], method: :delete, remote: true, class: 'remove-row btn btn-tiny' do
+ = link_to [:admin, broadcast_message], method: :delete, remote: true, class: 'remove-row btn btn-xs' do
%i.fa.fa-times.cred
.message= broadcast_message.message
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 7427cea7e8..d1c586328a 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -1,69 +1,7 @@
-%h3.page-title
- Admin area
-%p.light
- You can manage projects, users and other GitLab data from here.
-%hr
.admin-dashboard
.row
- .col-sm-4
- .light-well
- %h4 Projects
- .data
- = link_to admin_projects_path do
- %h1= Project.count
- %hr
- = link_to 'New Project', new_project_path, class: "btn btn-new"
- .col-sm-4
- .light-well
- %h4 Users
- .data
- = link_to admin_users_path do
- %h1= User.count
- %hr
- = link_to 'New User', new_admin_user_path, class: "btn btn-new"
- .col-sm-4
- .light-well
- %h4 Groups
- .data
- = link_to admin_groups_path do
- %h1= Group.count
- %hr
- = link_to 'New Group', new_admin_group_path, class: "btn btn-new"
-
- .row.prepend-top-10
.col-md-4
- %h4 Latest projects
- %hr
- - @projects.each do |project|
- %p
- = link_to project.name_with_namespace, [:admin, project], class: 'str-truncated'
- %span.light.pull-right
- #{time_ago_with_tooltip(project.created_at)}
-
- .col-md-4
- %h4 Latest users
- %hr
- - @users.each do |user|
- %p
- = link_to [:admin, user], class: 'str-truncated' do
- = user.name
- %span.light.pull-right
- #{time_ago_with_tooltip(user.created_at)}
-
- .col-md-4
- %h4 Latest groups
- %hr
- - @groups.each do |group|
- %p
- = link_to [:admin, group], class: 'str-truncated' do
- = group.name
- %span.light.pull-right
- #{time_ago_with_tooltip(group.created_at)}
-
- %br
- .row
- .col-md-4
- %h4 Stats
+ %h4 Statistics
%hr
%p
Forks
@@ -94,9 +32,9 @@
%span.light.pull-right
= Milestone.count
%p
- Active users last 30 days
+ Active Users
%span.light.pull-right
- = User.where("current_sign_in_at > ?", 30.days.ago).count
+ = User.active.count
.col-md-4
%h4
Features
@@ -104,7 +42,7 @@
%p
Sign up
%span.light.pull-right
- = boolean_to_icon gitlab_config.signup_enabled
+ = boolean_to_icon signup_enabled?
%p
LDAP
%span.light.pull-right
@@ -112,7 +50,7 @@
%p
Gravatar
%span.light.pull-right
- = boolean_to_icon Gitlab.config.gravatar.enabled
+ = boolean_to_icon gravatar_enabled?
%p
OmniAuth
%span.light.pull-right
@@ -141,3 +79,59 @@
Rails
%span.pull-right
#{Rails::VERSION::STRING}
+ %hr
+ .row
+ .col-sm-4
+ .light-well
+ %h4 Projects
+ .data
+ = link_to admin_namespaces_projects_path do
+ %h1= Project.count
+ %hr
+ = link_to('New Project', new_project_path, class: "btn btn-new")
+ .col-sm-4
+ .light-well
+ %h4 Users
+ .data
+ = link_to admin_users_path do
+ %h1= User.count
+ %hr
+ = link_to 'New User', new_admin_user_path, class: "btn btn-new"
+ .col-sm-4
+ .light-well
+ %h4 Groups
+ .data
+ = link_to admin_groups_path do
+ %h1= Group.count
+ %hr
+ = link_to 'New Group', new_admin_group_path, class: "btn btn-new"
+
+ .row.prepend-top-10
+ .col-md-4
+ %h4 Latest projects
+ %hr
+ - @projects.each do |project|
+ %p
+ = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated'
+ %span.light.pull-right
+ #{time_ago_with_tooltip(project.created_at)}
+
+ .col-md-4
+ %h4 Latest users
+ %hr
+ - @users.each do |user|
+ %p
+ = link_to [:admin, user], class: 'str-truncated' do
+ = user.name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(user.created_at)}
+
+ .col-md-4
+ %h4 Latest groups
+ %hr
+ - @groups.each do |group|
+ %p
+ = link_to [:admin, group], class: 'str-truncated' do
+ = group.name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(group.created_at)}
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index c56863ce27..9e7751830a 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -2,55 +2,25 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
- .form-group.group_name_holder
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: "Example Group", class: "form-control"
- .form-group.group-description-holder
- = f.label :description, "Details", class: 'control-label'
- .col-sm-10
- = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
+ = render 'shared/group_form', f: f
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10
- %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
- %i.fa.fa-paperclip
- %span Choose File ...
-
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-group-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ = render 'shared/choose_group_avatar_button', f: f
- if @group.new_record?
.form-group
.col-sm-2
.col-sm-10
- .bs-callout.bs-callout-info
- %ul
- %li A group is a collection of several projects
- %li Groups are private by default
- %li Members of a group may only view projects they have permission to access
- %li Group project URLs are prefixed with the group namespace
- %li Existing projects may be moved into a group
+ .alert.alert-info
+ = render 'shared/group_tips'
.form-actions
= f.submit 'Create group', class: "btn btn-create"
= link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
- else
- .form-group.group_name_holder
- = f.label :path, class: 'control-label' do
- %span Group path
- .col-sm-10
- = f.text_field :path, placeholder: "example-group", class: "form-control danger"
- .bs-callout.bs-callout-danger
- %ul
- %li Changing group path can have unintended side effects.
- %li Renaming group path will rename directory for all related projects
- %li It will change web url for access group and group projects.
- %li It will change the git path to repositories under this group.
.form-actions
= f.submit 'Save changes', class: "btn btn-primary"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 09105679bd..4c53ff5570 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -8,9 +8,30 @@
%hr
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
+ = hidden_field_tag :sort, @sort
.form-group
= text_field_tag :name, params[:name], class: "form-control input-mn-300"
- = submit_tag "Search", class: "btn submit btn-primary"
+ = button_tag "Search", class: "btn submit btn-primary"
+
+ .pull-right
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %span.light sort:
+ - if @sort.present?
+ = sort_options_hash[@sort]
+ - else
+ = sort_title_recently_created
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to admin_groups_path(sort: sort_value_recently_created) do
+ = sort_title_recently_created
+ = link_to admin_groups_path(sort: sort_value_oldest_created) do
+ = sort_title_oldest_created
+ = link_to admin_groups_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to admin_groups_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
%hr
@@ -19,8 +40,8 @@
%li
.clearfix
.pull-right.prepend-top-10
- = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small"
- = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-small btn-remove"
+ = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-sm"
+ = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-sm btn-remove"
%h4
= link_to [:admin, group] do
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index c1a9214b77..7d29211807 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -12,7 +12,7 @@
Group info:
%ul.well-list
%li
- = image_tag group_icon(@group.path), class: "avatar s60"
+ = image_tag group_icon(@group), class: "avatar s60"
%li
%span.light Name:
%strong= @group.name
@@ -41,7 +41,7 @@
- @projects.each do |project|
%li
%strong
- = link_to project.name_with_namespace, [:admin, project]
+ = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
%span.label.label-gray
= repository_size(project)
%span.pull-right.light
@@ -58,13 +58,13 @@
Read more about project permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
- = form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do
+ = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div
= users_select_tag(:user_ids, multiple: true)
%div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
- = submit_tag 'Add users into group', class: "btn btn-create"
+ = button_tag 'Add users to group', class: "btn btn-create"
.panel.panel-default
.panel-heading
%h3.panel-title
@@ -74,13 +74,13 @@
%ul.well-list.group-users-list
- @members.each do |member|
- user = member.user
- %li{class: dom_class(user)}
+ %li{class: dom_class(member), id: dom_id(user)}
.list-item-name
%strong
= link_to user.name, admin_user_path(user)
%span.pull-right.light
= member.human_access
- = link_to group_group_members_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index 0c5db0805f..7a9dc113f2 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -33,5 +33,5 @@
%strong= hook.url
.pull-right
- = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small"
- = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
+ = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm"
+ = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
diff --git a/app/views/admin/keys/show.html.haml b/app/views/admin/keys/show.html.haml
new file mode 100644
index 0000000000..5b23027b3a
--- /dev/null
+++ b/app/views/admin/keys/show.html.haml
@@ -0,0 +1 @@
+= render "profiles/keys/key_details", admin: true
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index b3f8f012f0..384c6ee9af 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -1,68 +1,25 @@
+- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
+ Gitlab::ProductionLogger, Gitlab::SidekiqLogger]
%ul.nav.nav-tabs.log-tabs
- %li.active
- = link_to "githost.log", "#githost", 'data-toggle' => 'tab'
- %li
- = link_to "application.log", "#application", 'data-toggle' => 'tab'
- %li
- = link_to "production.log", "#production", 'data-toggle' => 'tab'
- %li
- = link_to "sidekiq.log", "#sidekiq", 'data-toggle' => 'tab'
-
+ - loggers.each do |klass|
+ %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
+ = link_to klass::file_name, "##{klass::file_name_noext}",
+ 'data-toggle' => 'tab'
%p.light To prevent performance issues admin logs output the last 2000 lines
.tab-content
- .tab-pane.active#githost
- .file-holder#README
- .file-title
- %i.fa.fa-file
- githost.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.fa.fa-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::GitLogger.read_latest.each do |line|
- %li
- %p= line
- .tab-pane#application
- .file-holder#README
- .file-title
- %i.fa.fa-file
- application.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.fa.fa-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::AppLogger.read_latest.each do |line|
- %li
- %p= line
- .tab-pane#production
- .file-holder#README
- .file-title
- %i.fa.fa-file
- production.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.fa.fa-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::Logger.read_latest_for('production.log').each do |line|
- %li
- %p= line
- .tab-pane#sidekiq
- .file-holder#README
- .file-title
- %i.fa.fa-file
- sidekiq.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.fa.fa-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::Logger.read_latest_for('sidekiq.log').each do |line|
- %li
- %p= line
+ - loggers.each do |klass|
+ .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
+ id: klass::file_name_noext }
+ .file-holder#README
+ .file-title
+ %i.fa.fa-file
+ = klass::file_name
+ .pull-right
+ = link_to '#', class: 'log-bottom' do
+ %i.fa.fa-arrow-down
+ Scroll down
+ .file-content.logs
+ %ol
+ - klass.read_latest.each do |line|
+ %li
+ %p= line
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 5ca6090f8d..3bbe10bc27 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -1,7 +1,9 @@
.row
- .col-md-3
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
+ %aside.col-md-3
.admin-filter
- = form_tag admin_projects_path, method: :get, class: '' do
+ = form_tag admin_namespaces_projects_path, method: :get, class: '' do
.form-group
= label_tag :name, 'Name:'
= text_field_tag :name, params[:name], class: "form-control"
@@ -13,15 +15,13 @@
.form-group
%strong Activity
.checkbox
- = label_tag :with_push, 'Not empty'
- = check_box_tag :with_push, 1, params[:with_push]
-
- %span.light Projects with push events
+ = label_tag :with_push do
+ = check_box_tag :with_push, 1, params[:with_push]
+ %span Projects with push events
.checkbox
- = label_tag :abandoned, 'Abandoned'
- = check_box_tag :abandoned, 1, params[:abandoned]
-
- %span.light No activity over 6 month
+ = label_tag :abandoned do
+ = check_box_tag :abandoned, 1, params[:abandoned]
+ %span No activity over 6 month
%fieldset
%strong Visibility level:
@@ -35,49 +35,47 @@
= label
%hr
= hidden_field_tag :sort, params[:sort]
- = submit_tag "Search", class: "btn submit btn-primary"
- = link_to "Reset", admin_projects_path, class: "btn btn-cancel"
+ = button_tag "Search", class: "btn submit btn-primary"
+ = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel"
- .col-md-9
+ %section.col-md-9
.panel.panel-default
.panel-heading
Projects (#{@projects.total_count})
.panel-head-actions
.dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
- = @sort.humanize
+ = sort_options_hash[@sort]
- else
- Name
+ = sort_title_recently_created
%b.caret
%ul.dropdown-menu
%li
- = link_to admin_projects_path(sort: nil) do
- Name
- = link_to admin_projects_path(sort: 'newest') do
- Newest
- = link_to admin_projects_path(sort: 'oldest') do
- Oldest
- = link_to admin_projects_path(sort: 'recently_updated') do
- Recently updated
- = link_to admin_projects_path(sort: 'last_updated') do
- Last updated
- = link_to admin_projects_path(sort: 'largest_repository') do
- Largest repository
- = link_to 'New Project', new_project_path, class: "btn btn-new"
+ = link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do
+ = sort_title_recently_created
+ = link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do
+ = sort_title_oldest_created
+ = link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
+ = link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do
+ = sort_title_largest_repo
+ = link_to 'New Project', new_project_path, class: "btn btn-sm btn-success"
%ul.well-list
- @projects.each do |project|
%li
.list-item-name
%span{ class: visibility_level_color(project.visibility_level) }
= visibility_level_icon(project.visibility_level)
- = link_to project.name_with_namespace, [:admin, project]
+ = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
.pull-right
%span.label.label-gray
= repository_size(project)
- = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
- = link_to 'Destroy', [project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-small btn-remove"
+ = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
+ = link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove"
- if @projects.blank?
.nothing-here-block 0 projects matches
= paginate @projects, theme: "gitlab"
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 6d53619985..077ee56908 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -79,11 +79,11 @@
.panel-heading
Transfer project
.panel-body
- = form_for @project, url: transfer_admin_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
+ = form_for @project, url: transfer_admin_namespace_project_path(@project.namespace, @project), method: :put, html: { class: 'form-horizontal' } do |f|
.form-group
- = f.label :namespace_id, "Namespace", class: 'control-label'
+ = f.label :new_namespace_id, "Namespace", class: 'control-label'
.col-sm-10
- = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large'
+ = namespace_select_tag :new_namespace_id, selected: params[:namespace_id], class: 'input-large'
.form-group
.col-sm-2
@@ -97,7 +97,7 @@
%strong #{@group.name}
group members (#{@group.group_members.count})
.pull-right
- = link_to admin_group_path(@group), class: 'btn btn-small' do
+ = link_to admin_group_path(@group), class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o
%ul.well-list
- @group_members.each do |member|
@@ -111,10 +111,10 @@
%small
(#{@project.users.count})
.pull-right
- = link_to project_team_index_path(@project), class: "btn btn-tiny" do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-xs" do
%i.fa.fa-pencil-square-o
Manage Access
- %ul.well-list.team_members
+ %ul.well-list.project_members
- @project_members.each do |project_member|
- user = project_member.user
%li.project_member
@@ -126,7 +126,7 @@
%span.light Owner
- else
%span.light= project_member.human_access
- = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-small btn-remove" do
+ = link_to namespace_project_project_member_path(@project.namespace, @project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do
%i.fa.fa-times
.panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml
new file mode 100644
index 0000000000..a953833b37
--- /dev/null
+++ b/app/views/admin/services/_form.html.haml
@@ -0,0 +1,81 @@
+%h3.page-title
+ = @service.title
+
+%p #{@service.description} template
+
+= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |f|
+ - if @service.errors.any?
+ #error_explanation
+ .alert.alert-danger
+ - @service.errors.full_messages.each do |msg|
+ %p= msg
+ - if @service.help.present?
+ .alert.alert-info
+ = preserve do
+ = markdown @service.help
+
+ - if @service.supported_events.length > 1
+ .form-group
+ = f.label :url, "Trigger", class: 'control-label'
+ .col-sm-10
+ - if @service.supported_events.include?("push")
+ %div
+ = f.check_box :push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :push_events, class: 'list-label' do
+ %strong Push events
+ %p.light
+ This url will be triggered by a push to the repository
+ - if @service.supported_events.include?("tag_push")
+ %div
+ = f.check_box :tag_push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :tag_push_events, class: 'list-label' do
+ %strong Tag push events
+ %p.light
+ This url will be triggered when a new tag is pushed to the repository
+ - if @service.supported_events.include?("issue")
+ %div
+ = f.check_box :issues_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :issues_events, class: 'list-label' do
+ %strong Issues events
+ %p.light
+ This url will be triggered when an issue is created
+ - if @service.supported_events.include?("merge_request")
+ %div
+ = f.check_box :merge_requests_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :merge_requests_events, class: 'list-label' do
+ %strong Merge Request events
+ %p.light
+ This url will be triggered when a merge request is created
+
+ - @service.fields.each do |field|
+ - name = field[:name]
+ - title = field[:title] || name.humanize
+ - value = @service.send(name) unless field[:type] == 'password'
+ - type = field[:type]
+ - placeholder = field[:placeholder]
+ - choices = field[:choices]
+ - default_choice = field[:default_choice]
+ - help = field[:help]
+
+ .form-group
+ = f.label name, title, class: "control-label"
+ .col-sm-10
+ - if type == 'text'
+ = f.text_field name, class: "form-control", placeholder: placeholder
+ - elsif type == 'textarea'
+ = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
+ - elsif type == 'checkbox'
+ = f.check_box name
+ - elsif type == 'select'
+ = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
+ - elsif type == 'password'
+ = f.password_field name, class: 'form-control'
+ - if help
+ %span.help-block= help
+
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml
new file mode 100644
index 0000000000..bcc5832792
--- /dev/null
+++ b/app/views/admin/services/edit.html.haml
@@ -0,0 +1 @@
+= render 'form'
diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml
new file mode 100644
index 0000000000..0093fb9776
--- /dev/null
+++ b/app/views/admin/services/index.html.haml
@@ -0,0 +1,22 @@
+%h3.page-title Service templates
+%p.light Service template allows you to set default values for project services
+
+%table.table
+ %thead
+ %tr
+ %th
+ %th Service
+ %th Description
+ %th Last edit
+ - @services.sort_by(&:title).each do |service|
+ %tr
+ %td
+ = icon("copy", class: 'clgray')
+ %td
+ = link_to edit_admin_application_settings_service_path(service.id) do
+ %strong= service.title
+ %td
+ = service.description
+ %td.light
+ = time_ago_in_words service.updated_at
+ ago
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 5c2664e14f..25c1730ef7 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -1,5 +1,7 @@
.row
- .col-md-3
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
+ %aside.col-md-3
.admin-filter
%ul.nav.nav-pills.nav-stacked
%li{class: "#{'active' unless params[:filter]}"}
@@ -27,12 +29,37 @@
%hr
= link_to 'Reset', admin_users_path, class: "btn btn-cancel"
- .col-md-9
+ %section.col-md-9
.panel.panel-default
.panel-heading
Users (#{@users.total_count})
.panel-head-actions
- = link_to 'New User', new_admin_user_path, class: "btn btn-new"
+ .dropdown.inline
+ %a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"}
+ %span.light sort:
+ - if @sort.present?
+ = sort_options_hash[@sort]
+ - else
+ = sort_title_name
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to admin_users_path(sort: sort_value_name) do
+ = sort_title_name
+ = link_to admin_users_path(sort: sort_value_recently_signin) do
+ = sort_title_recently_signin
+ = link_to admin_users_path(sort: sort_value_oldest_signin) do
+ = sort_title_oldest_signin
+ = link_to admin_users_path(sort: sort_value_recently_created) do
+ = sort_title_recently_created
+ = link_to admin_users_path(sort: sort_value_oldest_created) do
+ = sort_title_oldest_created
+ = link_to admin_users_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to admin_users_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
+
+ = link_to 'New User', new_admin_user_path, class: "btn btn-new btn-sm"
%ul.well-list
- @users.each do |user|
%li
@@ -51,11 +78,11 @@
%i.fa.fa-envelope
= mail_to user.email, user.email, class: 'light'
- = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-small"
+ = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-sm"
- unless user == current_user
- if user.blocked?
- = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-small success"
+ = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-sm success"
- else
- = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-small btn-remove"
- = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-small btn-remove"
+ = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-sm btn-remove"
+ = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-sm btn-remove"
= paginate @users, theme: "gitlab"
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 211d77d518..0a2934d3bd 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -20,6 +20,8 @@
%a{"data-toggle" => "tab", href: "#groups"} Groups
%li
%a{"data-toggle" => "tab", href: "#projects"} Projects
+ %li
+ %a{"data-toggle" => "tab", href: "#ssh-keys"} SSH keys
.tab-content
#account.tab-pane.active
@@ -44,7 +46,7 @@
%li
%span.light Secondary email:
%strong= email.email
- = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-tiny btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do
+ = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do
%i.fa.fa-times
%li
@@ -95,7 +97,7 @@
%li
%span.light LDAP uid:
%strong
- = @user.extern_uid
+ = @user.ldap_identity.extern_uid
- if @user.created_by
%li
@@ -106,45 +108,49 @@
.col-md-6
- unless @user == current_user
- if @user.blocked?
- .alert.alert-info
- %h4 This user is blocked
- %p Blocking user has the following effects:
- %ul
- %li User will not be able to login
- %li User will not be able to access git repositories
- %li User will be removed from joined projects and groups
- %li Personal projects will be left
- %li Owned groups will be left
- %br
- = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", data: { confirm: 'Are you sure?' }
+ .panel.panel-info
+ .panel-heading
+ This user is blocked
+ .panel-body
+ %p Blocking user has the following effects:
+ %ul
+ %li User will not be able to login
+ %li User will not be able to access git repositories
+ %li Personal projects will be left
+ %li Owned groups will be left
+ %br
+ = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
- else
- .alert.alert-warning
- %h4 Block this user
- %p Blocking user has the following effects:
- %ul
- %li User will not be able to login
- %li User will not be able to access git repositories
- %li User will be removed from joined projects and groups
- %li Personal projects will be left
- %li Owned groups will be left
- %br
- = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-remove"
+ .panel.panel-warning
+ .panel-heading
+ Block this user
+ .panel-body
+ %p Blocking user has the following effects:
+ %ul
+ %li User will not be able to login
+ %li User will not be able to access git repositories
+ %li User will be removed from joined projects and groups
+ %li Personal projects will be left
+ %li Owned groups will be left
+ %br
+ = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning"
- .alert.alert-danger
- %h4
+ .panel.panel-danger
+ .panel-heading
Remove user
- %p Deleting a user has the following effects:
- %ul
- %li All user content like authored issues, snippets, comments will be removed
- - rp = @user.personal_projects.count
- - unless rp.zero?
- %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
- - if @user.solo_owned_groups.present?
- %li
- Next groups with all content will be removed:
- %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
- %br
- = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove"
+ .panel-body
+ %p Deleting a user has the following effects:
+ %ul
+ %li All user content like authored issues, snippets, comments will be removed
+ - rp = @user.personal_projects.count
+ - unless rp.zero?
+ %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
+ - if @user.solo_owned_groups.present?
+ %li
+ Next groups with all content will be removed:
+ %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
+ %br
+ = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove"
#profile.tab-pane
.row
@@ -168,15 +174,15 @@
.panel.panel-default
.panel-heading Groups:
%ul.well-list
- - @user.group_members.each do |user_group|
- - group = user_group.group
+ - @user.group_members.each do |group_member|
+ - group = group_member.group
%li.group_member
- %span{class: ("list-item-name" unless user_group.owner?)}
+ %span{class: ("list-item-name" unless group_member.owner?)}
%strong= link_to group.name, admin_group_path(group)
.pull-right
- %span.light= user_group.human_access
- - unless user_group.owner?
- = link_to group_group_member_path(group, user_group), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ %span.light= group_member.human_access
+ - unless group_member.owner?
+ = link_to group_group_member_path(group, group_member), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-times.fa-inverse
- else
.nothing-here-block This user has no groups.
@@ -201,19 +207,21 @@
.panel-heading Joined projects (#{@joined_projects.count})
%ul.well-list
- @joined_projects.sort_by(&:name_with_namespace).each do |project|
- - tm = project.team.find_tm(@user.id)
+ - member = project.team.find_member(@user.id)
%li.project_member
.list-item-name
- = link_to admin_project_path(project), class: dom_class(project) do
+ = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do
= project.name_with_namespace
- - if tm
+ - if member
.pull-right
- - if tm.owner?
+ - if member.owner?
%span.light Owner
- else
- %span.light= tm.human_access
+ %span.light= member.human_access
- - if tm.respond_to? :project
- = link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do
+ - if member.respond_to? :project
+ = link_to namespace_project_project_member_path(project.namespace, project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do
%i.fa.fa-times
+ #ssh-keys.tab-pane
+ = render 'profiles/keys/key_table', admin: true
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index fdf96dd6f5..c1fc1602d0 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -1,9 +1,4 @@
= render "events/event_last_push", event: @last_push
= render 'shared/event_filter'
-
-- if @events.any?
- .content_list
-- else
- .nothing-here-block Projects activity will be displayed here
-
+.content_list
= spinner
diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml
deleted file mode 100644
index 5460cf56f2..0000000000
--- a/app/views/dashboard/_groups.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-.panel.panel-default
- .panel-heading.clearfix
- = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- - if current_user.can_create_group?
- = link_to new_group_path, class: "btn btn-new pull-right" do
- %i.fa.fa-plus
- New group
- %ul.well-list.dash-list
- - groups.each do |group|
- %li.group-row
- = link_to group_path(id: group.path), class: dom_class(group) do
- = image_tag group_icon(group.path), class: "avatar s24"
- %span.group-name.filter-title
- = truncate(group.name, length: 35)
- %span.arrow
- %i.fa.fa-angle-right
- - if groups.blank?
- %li
- .nothing-here-block You have no groups yet.
diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml
deleted file mode 100644
index 89ed510275..0000000000
--- a/app/views/dashboard/_project.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-= link_to project_path(project), class: dom_class(project) do
- .dash-project-access-icon
- = visibility_level_icon(project.visibility_level)
- %span.str-truncated
- %span.namespace-name
- - if project.namespace
- = project.namespace.human_name
- \/
- %span.project-name.filter-title
- = project.name
- %span.arrow
- %i.fa.fa-angle-right
diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml
index 3598425777..3634b2bfd7 100644
--- a/app/views/dashboard/_projects.html.haml
+++ b/app/views/dashboard/_projects.html.haml
@@ -1,24 +1,10 @@
.panel.panel-default
.panel-heading.clearfix
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- - if current_user.can_create_project?
- = link_to new_project_path, class: "btn btn-new pull-right" do
- %i.fa.fa-plus
- New project
+ .input-group
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
+ - if current_user.can_create_project?
+ .input-group-addon.dash-new-project
+ = link_to new_project_path do
+ %strong New project
- %ul.well-list.dash-list
- - projects.each do |project|
- %li.project-row
- = render "project", project: project
-
- - if projects.blank?
- %li
- .nothing-here-block There are no projects here.
- - if @projects_count > @projects_limit
- %li.bottom
- %span.light
- #{@projects_limit} of #{pluralize(@projects_count, 'project')} displayed.
- .pull-right
- = link_to projects_dashboard_path do
- Show all
- %i.fa.fa-angle-right
+ = render 'shared/projects_list', projects: @projects, projects_limit: 20
diff --git a/app/views/dashboard/_projects_filter.html.haml b/app/views/dashboard/_projects_filter.html.haml
deleted file mode 100644
index b65e882e69..0000000000
--- a/app/views/dashboard/_projects_filter.html.haml
+++ /dev/null
@@ -1,55 +0,0 @@
-%fieldset
- %ul.nav.nav-pills.nav-stacked
- = nav_tab :scope, nil do
- = link_to projects_dashboard_filter_path(scope: nil) do
- All
- %span.pull-right
- = current_user.authorized_projects.count
- = nav_tab :scope, 'personal' do
- = link_to projects_dashboard_filter_path(scope: 'personal') do
- Personal
- %span.pull-right
- = current_user.personal_projects.count
- = nav_tab :scope, 'joined' do
- = link_to projects_dashboard_filter_path(scope: 'joined') do
- Joined
- %span.pull-right
- = current_user.authorized_projects.joined(current_user).count
- = nav_tab :scope, 'owned' do
- = link_to projects_dashboard_filter_path(scope: 'owned') do
- Owned
- %span.pull-right
- = current_user.owned_projects.count
-
-%fieldset
- %legend Visibility
- %ul.nav.nav-pills.nav-stacked.nav-small.visibility-filter
- - Gitlab::VisibilityLevel.values.each do |level|
- %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
- = link_to projects_dashboard_filter_path(visibility_level: level) do
- = visibility_level_icon(level)
- = visibility_level_label(level)
-
-- if @groups.present?
- %fieldset
- %legend Groups
- %ul.nav.nav-pills.nav-stacked.nav-small
- - @groups.each do |group|
- %li{ class: (group.name == params[:group]) ? 'active' : 'light' }
- = link_to projects_dashboard_filter_path(group: group.name) do
- %i.fa.fa-folder-o
- = group.name
- %small.pull-right
- = group.projects.count
-
-
-
-- if @tags.present?
- %fieldset
- %legend Tags
- %ul.nav.nav-pills.nav-stacked.nav-small
- - @tags.each do |tag|
- %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
- = link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do
- %i.fa.fa-tag
- = tag.name
diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml
index add9eb7fa2..78f695be91 100644
--- a/app/views/dashboard/_sidebar.html.haml
+++ b/app/views/dashboard/_sidebar.html.haml
@@ -1,25 +1,3 @@
-%ul.nav.nav-tabs.dash-sidebar-tabs
- %li.active
- = link_to '#projects', 'data-toggle' => 'tab', id: 'sidebar-projects-tab' do
- Projects
- %span.badge= @projects_count
- %li
- = link_to '#groups', 'data-toggle' => 'tab', id: 'sidebar-groups-tab' do
- Groups
- %span.badge= @groups.count
-
-.tab-content
- .tab-pane.active#projects
- = render "projects", projects: @projects
- .tab-pane#groups
- = render "groups", groups: @groups
-
+= render "dashboard/projects", projects: @projects
.prepend-top-20
- %span.rss-icon
- = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
- %strong
- %i.fa.fa-rss
- News Feed
-
-%hr
-= render 'shared/promo'
+ = render 'shared/promo'
diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml
index 711e607f0b..4e7d663972 100644
--- a/app/views/dashboard/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/_zero_authorized_projects.html.haml
@@ -1,10 +1,11 @@
+- publicish_project_count = Project.publicish(current_user).count
%h3.page-title Welcome to GitLab!
%p.light Self hosted Git management application.
%hr
%div
.dashboard-intro-icon
%i.fa.fa-bookmark-o
- %div
+ .dashboard-intro-text
%p.slead
You don't have access to any projects right now.
%br
@@ -17,34 +18,36 @@
- if current_user.can_create_project?
.link_holder
= link_to new_project_path, class: "btn btn-new" do
- New project »
+ %i.fa.fa-plus
+ New Project
- if current_user.can_create_group?
%hr
%div
.dashboard-intro-icon
%i.fa.fa-users
- %div
+ .dashboard-intro-text
%p.slead
You can create a group for several dependent projects.
%br
Groups are the best way to manage projects and members.
.link_holder
= link_to new_group_path, class: "btn btn-new" do
- New group »
+ %i.fa.fa-plus
+ New Group
--if @publicish_project_count > 0
+-if publicish_project_count > 0
%hr
%div
.dashboard-intro-icon
%i.fa.fa-globe
- %div
+ .dashboard-intro-text
%p.slead
There are
- %strong= @publicish_project_count
+ %strong= publicish_project_count
public projects on this server.
%br
Public projects are an easy way to allow everyone to have read-only access.
.link_holder
- = link_to explore_projects_path, class: "btn btn-new" do
- Browse public projects »
+ = link_to trending_explore_projects_path, class: "btn btn-new" do
+ Browse public projects
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
new file mode 100644
index 0000000000..165db214d7
--- /dev/null
+++ b/app/views/dashboard/groups/index.html.haml
@@ -0,0 +1,40 @@
+%h3.page-title
+ Group Membership
+ - if current_user.can_create_group?
+ %span.pull-right
+ = link_to new_group_path, class: "btn btn-new" do
+ %i.fa.fa-plus
+ New Group
+%p.light
+ Group members have access to all group projects.
+%hr
+.panel.panel-default
+ .panel-heading
+ %strong Groups
+ (#{@group_members.count})
+ %ul.well-list
+ - @group_members.each do |group_member|
+ - group = group_member.group
+ %li
+ .pull-right
+ - if can?(current_user, :manage_group, group)
+ = link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
+ %i.fa.fa-cogs
+ Settings
+
+ - if can?(current_user, :destroy_group_member, group_member)
+ = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Remove user from group' do
+ %i.fa.fa-sign-out
+ Leave
+
+ = image_tag group_icon(group), class: "avatar s40 avatar-tile"
+ = link_to group, class: 'group-name' do
+ %strong= group.name
+
+ as
+ %strong #{group_member.human_access}
+
+ %div.light
+ #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
+
+= paginate @group_members
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index f541355778..72e9e361dc 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -1,24 +1,13 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues"
- xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml"
- xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html"
- xml.id issues_dashboard_url(:private_token => current_user.private_token)
+ xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
+ xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html"
+ xml.id issues_dashboard_url(private_token: current_user.private_token)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(issue.project, issue)
- xml.link :href => project_issue_url(issue.project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 7c1f1ddbb8..db19a46cb2 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -5,10 +5,6 @@
List all issues from all projects you have access to.
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/filter', entity: 'issue'
- .col-md-9
- = render 'shared/issues'
+.append-bottom-20
+ = render 'shared/issuable_filter'
+= render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index c96584c7b6..97a42461b4 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -5,10 +5,6 @@
%p.light
List all merge requests from all projects you have access to.
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/filter', entity: 'merge_request'
- .col-md-9
- = render 'shared/merge_requests'
+.append-bottom-20
+ = render 'shared/issuable_filter'
+= render 'shared/merge_requests'
diff --git a/app/views/dashboard/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml
new file mode 100644
index 0000000000..f689b9698e
--- /dev/null
+++ b/app/views/dashboard/milestones/_issue.html.haml
@@ -0,0 +1,10 @@
+%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
+ %span.milestone-row
+ - project = issue.project
+ %strong #{project.name_with_namespace} ·
+ = link_to [project.namespace.becomes(Namespace), project, issue] do
+ %span.cgray ##{issue.iid}
+ = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
+ .pull-right.assignee-icon
+ - if issue.assignee
+ = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_issues.html.haml b/app/views/dashboard/milestones/_issues.html.haml
new file mode 100644
index 0000000000..9f350b772b
--- /dev/null
+++ b/app/views/dashboard/milestones/_issues.html.haml
@@ -0,0 +1,6 @@
+.panel.panel-default
+ .panel-heading= title
+ %ul{ class: "well-list issues-sortable-list" }
+ - if issues
+ - issues.each do |issue|
+ = render 'issue', issue: issue
diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml
new file mode 100644
index 0000000000..8f5c4cce52
--- /dev/null
+++ b/app/views/dashboard/milestones/_merge_request.html.haml
@@ -0,0 +1,10 @@
+%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid }
+ %span.milestone-row
+ - project = merge_request.project
+ %strong #{project.name_with_namespace} ·
+ = link_to [project.namespace.becomes(Namespace), project, merge_request] do
+ %span.cgray ##{merge_request.iid}
+ = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
+ .pull-right.assignee-icon
+ - if merge_request.assignee
+ = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16"
diff --git a/app/views/dashboard/milestones/_merge_requests.html.haml b/app/views/dashboard/milestones/_merge_requests.html.haml
new file mode 100644
index 0000000000..50057e2c63
--- /dev/null
+++ b/app/views/dashboard/milestones/_merge_requests.html.haml
@@ -0,0 +1,6 @@
+.panel.panel-default
+ .panel-heading= title
+ %ul{ class: "well-list merge_requests-sortable-list" }
+ - if merge_requests
+ - merge_requests.each do |merge_request|
+ = render 'merge_request', merge_request: merge_request
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
new file mode 100644
index 0000000000..caf3b68586
--- /dev/null
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -0,0 +1,38 @@
+%h3.page-title
+ Milestones
+ %span.pull-right #{@dashboard_milestones.count} milestones
+
+%p.light
+ List all milestones from all projects you have access to.
+
+%hr
+
+= render 'shared/milestones_filter'
+.milestones
+ .panel.panel-default
+ %ul.well-list
+ - if @dashboard_milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
+ - else
+ - @dashboard_milestones.each do |milestone|
+ %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
+ %h4
+ = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
+ %div
+ %div
+ = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
+ = pluralize milestone.issue_count, 'Issue'
+
+ = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do
+ = pluralize milestone.merge_requests_count, 'Merge Request'
+
+ %span.light #{milestone.percent_complete}% complete
+ = milestone_progress_bar(milestone)
+ %div
+ %br
+ - milestone.milestones.each do |milestone|
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) do
+ %span.label.label-default
+ = milestone.project.name_with_namespace
+ = paginate @dashboard_milestones, theme: "gitlab"
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
new file mode 100644
index 0000000000..57cce9ab74
--- /dev/null
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -0,0 +1,81 @@
+%h4.page-title
+ .issue-box{ class: "issue-box-#{@dashboard_milestone.closed? ? 'closed' : 'open'}" }
+ - if @dashboard_milestone.closed?
+ Closed
+ - else
+ Open
+ Milestone #{@dashboard_milestone.title}
+
+%hr
+- if (@dashboard_milestone.total_items_count == @dashboard_milestone.closed_items_count) && @dashboard_milestone.active?
+ .alert.alert-success
+ %span All issues for this milestone are closed. You may close the milestone now.
+
+.description
+%table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - @dashboard_milestone.milestones.each do |milestone|
+ %tr
+ %td
+ = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
+ %td
+ = milestone.issues.opened.count
+ %td
+ - if milestone.closed?
+ Closed
+ - else
+ Open
+ %td
+ = milestone.expires_at
+
+.context
+ %p.lead
+ Progress:
+ #{@dashboard_milestone.closed_items_count} closed
+ –
+ #{@dashboard_milestone.open_items_count} open
+ = milestone_progress_bar(@dashboard_milestone)
+
+%ul.nav.nav-tabs
+ %li.active
+ = link_to '#tab-issues', 'data-toggle' => 'tab' do
+ Issues
+ %span.badge= @dashboard_milestone.issue_count
+ %li
+ = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
+ Merge Requests
+ %span.badge= @dashboard_milestone.merge_requests_count
+ %li
+ = link_to '#tab-participants', 'data-toggle' => 'tab' do
+ Participants
+ %span.badge= @dashboard_milestone.participants.count
+
+.tab-content
+ .tab-pane.active#tab-issues
+ .row
+ .col-md-6
+ = render 'issues', title: "Open", issues: @dashboard_milestone.opened_issues
+ .col-md-6
+ = render 'issues', title: "Closed", issues: @dashboard_milestone.closed_issues
+
+ .tab-pane#tab-merge-requests
+ .row
+ .col-md-6
+ = render 'merge_requests', title: "Open", merge_requests: @dashboard_milestone.opened_merge_requests
+ .col-md-6
+ = render 'merge_requests', title: "Closed", merge_requests: @dashboard_milestone.closed_merge_requests
+
+ .tab-pane#tab-participants
+ %ul.bordered-list
+ - @dashboard_milestone.participants.each do |user|
+ %li
+ = link_to user, title: user.name, class: "darken" do
+ = image_tag avatar_icon(user.email, 32), class: "avatar s32"
+ %strong= truncate(user.name, lenght: 40)
+ %br
+ %small.cgray= user.username
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
deleted file mode 100644
index f124c688be..0000000000
--- a/app/views/dashboard/projects.html.haml
+++ /dev/null
@@ -1,73 +0,0 @@
-%h3.page-title
- My Projects
-.pull-right
- .dropdown.inline
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
- %span.light sort:
- - if @sort.present?
- = @sort.humanize
- - else
- Name
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to projects_dashboard_filter_path(sort: nil) do
- Name
- = link_to projects_dashboard_filter_path(sort: 'newest') do
- Newest
- = link_to projects_dashboard_filter_path(sort: 'oldest') do
- Oldest
- = link_to projects_dashboard_filter_path(sort: 'recently_updated') do
- Recently updated
- = link_to projects_dashboard_filter_path(sort: 'last_updated') do
- Last updated
-%p.light
- All projects you have access to are listed here. Public projects are not included here unless you are a member
-%hr
-.row
- .col-md-3.hidden-sm.hidden-xs.side-filters
- = render "projects_filter"
- .col-md-9
- %ul.bordered-list.my-projects.top-list
- - @projects.each do |project|
- %li.my-project-row
- %h4.project-title
- .project-access-icon
- = visibility_level_icon(project.visibility_level)
- = link_to project_path(project), class: dom_class(project) do
- = project.name_with_namespace
-
- - if current_user.can_leave_project?(project)
- .pull-right
- = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do
- %i.fa.fa-sign-out
- Leave
-
- - if project.forked_from_project
- %small.pull-right
- %i.fa.fa-code-fork
- Forked from:
- = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project)
- .project-info
- .pull-right
- - if project.archived?
- %span.label
- %i.fa.fa-archive
- Archived
- - project.tags.each do |tag|
- %span.label.label-info
- %i.fa.fa-tag
- = tag.name
- - if project.description.present?
- %p= truncate project.description, length: 100
- .last-activity
- %span.light Last activity:
- %span.date= project_last_activity(project)
-
-
- - if @projects.blank?
- %li
- .nothing-here-block There are no projects here.
- .bottom
- = paginate @projects, theme: "gitlab"
-
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
new file mode 100644
index 0000000000..94de609256
--- /dev/null
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -0,0 +1,23 @@
+- if @projects.any?
+ .dashboard.row
+ %section.activities.col-md-8
+ = render 'dashboard/activities'
+ %aside.col-md-4
+ .panel.panel-default
+ .panel-heading.clearfix
+ .input-group
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
+ - if current_user.can_create_project?
+ .input-group-addon.dash-new-project
+ = link_to new_project_path do
+ %strong New project
+
+ = render 'shared/projects_list', projects: @projects,
+ projects_limit: 20, stars: true, avatar: false
+
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
+
+- else
+ %h3 You dont have starred projects yet
+ %p.slead Visit project page and press on star icon and it will appear on this page.
diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder
index f4cf24ccd9..da631ecb33 100644
--- a/app/views/dashboard/show.atom.builder
+++ b/app/views/dashboard/show.atom.builder
@@ -1,29 +1,12 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
- xml.link :href => dashboard_url(:atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => dashboard_url, :rel => "alternate", :type => "text/html"
+ xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml"
+ xml.link href: dashboard_url, rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
- if event.proper?
- xml.entry do
- event_link = event_feed_url(event)
- event_title = event_feed_title(event)
- event_summary = event_feed_summary(event)
-
- xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
- xml.link :href => event_link
- xml.title truncate(event_title, :length => 80)
- xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
- xml.author do |author|
- xml.name event.author_name
- xml.email event.author_email
- end
- xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? }
- end
- end
+ event_to_atom(xml, event)
end
end
diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml
index 10951af6a0..fa8946011b 100644
--- a/app/views/dashboard/show.html.haml
+++ b/app/views/dashboard/show.html.haml
@@ -1,12 +1,11 @@
-- if @has_authorized_projects
+- if @projects.any?
.dashboard.row
%section.activities.col-md-8
= render 'activities'
- %aside.side.col-md-4.left.responsive-side
+ %aside.col-md-4
= render 'sidebar'
-
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
- else
= render "zero_authorized_projects"
diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml
old mode 100755
new mode 100644
index 8d17f39eba..970ba14711
--- a/app/views/devise/confirmations/new.html.haml
+++ b/app/views/devise/confirmations/new.html.haml
@@ -7,7 +7,8 @@
= devise_error_messages!
.clearfix.append-bottom-20
= f.email_field :email, placeholder: 'Email', class: "form-control", required: true
- .clearfix.append-bottom-10
+ .clearfix
= f.submit "Resend confirmation instructions", class: 'btn btn-success'
- .login-footer
- = render 'devise/shared/sign_in_link'
+
+.clearfix.prepend-top-20
+ = render 'devise/shared/sign_in_link'
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 1326cc0aac..0640739b5d 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -10,9 +10,10 @@
= f.password_field :password, class: "form-control top", placeholder: "New password", required: true
%div
= f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true
- .clearfix.append-bottom-10
+ .clearfix
= f.submit "Change my password", class: "btn btn-primary"
- .login-footer
- %p
- = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name)
- = render 'devise/shared/sign_in_link'
+
+.clearfix.prepend-top-20
+ %p
+ = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name)
+ = render 'devise/shared/sign_in_link'
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
old mode 100755
new mode 100644
index b8af1b8693..e8820daf58
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -7,7 +7,8 @@
= devise_error_messages!
.clearfix.append-bottom-20
= f.email_field :email, placeholder: "Email", class: "form-control", required: true
- .clearfix.append-bottom-10
+ .clearfix
= f.submit "Reset password", class: "btn-primary btn"
- .login-footer
- = render 'devise/shared/sign_in_link'
+
+.clearfix.prepend-top-20
+ = render 'devise/shared/sign_in_link'
diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml
index d6a952f3dc..d3e37f7494 100644
--- a/app/views/devise/registrations/new.html.haml
+++ b/app/views/devise/registrations/new.html.haml
@@ -1,27 +1,3 @@
-.login-box
- .login-heading
- %h3 Sign up
- .login-body
- = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
- .devise-errors
- = devise_error_messages!
- %div
- = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
- %div
- = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
- %div
- = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
- %div
- = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true
- %div
- = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true
- %div
- = f.submit "Sign up", class: "btn-create btn"
- .login-footer
- %p
- %span.light
- Have an account?
- %strong
- = link_to "Sign in", new_session_path(resource_name)
- %p
- = link_to "Forgot your password?", new_password_path(resource_name)
+= render 'devise/shared/signup_box'
+
+= render 'devise/shared/sign_in_link'
\ No newline at end of file
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index e819847e5e..54a3972677 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -2,11 +2,11 @@
= f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus"
= f.password_field :password, class: "form-control bottom", placeholder: "Password"
- if devise_mapping.rememberable?
- .clearfix.append-bottom-10
- %label.checkbox.remember_me{for: "user_remember_me"}
+ .remember-me.checkbox
+ %label{for: "user_remember_me"}
= f.check_box :remember_me
%span Remember me
- .pull-right
- = link_to "Forgot your password?", new_password_path(resource_name)
+ .pull-right
+ = link_to "Forgot your password?", new_password_path(resource_name)
%div
= f.submit "Sign in", class: "btn btn-save"
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 6c5a878e90..812e22373a 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,5 +1,4 @@
-= form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do
- = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
+= form_tag(user_omniauth_callback_path(server['provider_name']), id: 'new_ldap_user' ) do
+ = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
- %br/
- = submit_tag "LDAP Sign in", class: "btn-save btn"
+ = button_tag "#{server['label']} Sign in", class: "btn-save btn"
diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml
deleted file mode 100644
index 15048a7806..0000000000
--- a/app/views/devise/sessions/_oauth_providers.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-- providers = (enabled_oauth_providers - [:ldap])
-- if providers.present?
- .bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'}
- %span Sign in with:
- - providers.each do |provider|
- %span
- - if default_providers.include?(provider)
- = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider)
- - else
- = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn"
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index b70b0d6617..89e4e229ac 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -1,42 +1,18 @@
-.login-box
- .login-heading
- %h3 Sign in
- .login-body
- - if ldap_enabled? && gitlab_config.signin_enabled
- %ul.nav.nav-tabs
- %li.active
- = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab'
- %li
- = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
- .tab-content
- %div#tab-ldap.tab-pane.active
- = render partial: 'devise/sessions/new_ldap'
- %div#tab-signin.tab-pane
- = render partial: 'devise/sessions/new_base'
+%div
+ - if signin_enabled? || ldap_enabled?
+ = render 'devise/shared/signin_box'
- - elsif ldap_enabled?
- = render partial: 'devise/sessions/new_ldap'
- - elsif gitlab_config.signin_enabled
- = render partial: 'devise/sessions/new_base'
- - else
- %div
- No authentication methods configured.
+ -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box
+ - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?
+ .clearfix.prepend-top-20
+ = render 'devise/shared/omniauth_box'
- = render 'devise/sessions/oauth_providers' if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?
+ -# Signup only makes sense if you can also sign-in
+ - if signin_enabled? && signup_enabled?
+ .prepend-top-20
+ = render 'devise/shared/signup_box'
- .login-footer
- - if gitlab_config.signup_enabled
- %p
- %span.light
- Don't have an account?
- %strong
- = link_to "Sign up", new_registration_path(resource_name)
-
- %p
- %span.light Did not receive confirmation email?
- = link_to "Send again", new_confirmation_path(resource_name)
-
-
- - if extra_config.has_key?('sign_in_text')
- %hr
- = markdown(extra_config.sign_in_text)
+ -# Show a message if none of the mechanisms above are enabled
+ - if !signin_enabled? && !ldap_enabled? && !(Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?)
+ %div
+ No authentication methods configured.
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
new file mode 100644
index 0000000000..4cd1c303b2
--- /dev/null
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -0,0 +1,10 @@
+%p
+ %span.light
+ Sign in with
+ - providers = additional_providers
+ - providers.each do |provider|
+ %span.light
+ - if default_providers.include?(provider)
+ = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider)
+ - else
+ = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn"
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
new file mode 100644
index 0000000000..c76574db45
--- /dev/null
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -0,0 +1,26 @@
+.login-box
+ - if signup_enabled?
+ .login-heading
+ %h3 Existing user? Sign in
+ - else
+ .login-heading
+ %h3 Sign in
+ .login-body
+ - if ldap_enabled?
+ %ul.nav.nav-tabs
+ - @ldap_servers.each_with_index do |server, i|
+ %li{class: (:active if i.zero?)}
+ = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
+ - if signin_enabled?
+ %li
+ = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
+ .tab-content
+ - @ldap_servers.each_with_index do |server, i|
+ %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
+ = render 'devise/sessions/new_ldap', server: server
+ - if signin_enabled?
+ %div#tab-signin.tab-pane
+ = render 'devise/sessions/new_base'
+
+ - elsif signin_enabled?
+ = render 'devise/sessions/new_base'
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
new file mode 100644
index 0000000000..dcf60c9043
--- /dev/null
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -0,0 +1,26 @@
+.login-box
+ - if signin_enabled?
+ .login-heading
+ %h3 New user? Create an account
+ - else
+ .login-heading
+ %h3 Create an account
+ .login-body
+ = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
+ .devise-errors
+ = devise_error_messages!
+ %div
+ = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
+ %div
+ = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
+ %div
+ = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
+ .form-group.append-bottom-20#password-strength
+ = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
+ %div
+ = f.submit "Sign up", class: "btn-create btn"
+
+.clearfix.prepend-top-20
+ %p
+ %span.light Did not receive confirmation email?
+ = link_to "Send again", new_confirmation_path(resource_name)
\ No newline at end of file
diff --git a/app/views/doorkeeper/applications/_delete_form.html.haml b/app/views/doorkeeper/applications/_delete_form.html.haml
new file mode 100644
index 0000000000..6a5c917049
--- /dev/null
+++ b/app/views/doorkeeper/applications/_delete_form.html.haml
@@ -0,0 +1,4 @@
+- submit_btn_css ||= 'btn btn-link btn-remove btn-sm'
+= form_tag oauth_application_path(application) do
+ %input{:name => "_method", :type => "hidden", :value => "delete"}/
+ = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css
\ No newline at end of file
diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml
new file mode 100644
index 0000000000..a5fec2fabd
--- /dev/null
+++ b/app/views/doorkeeper/applications/_form.html.haml
@@ -0,0 +1,24 @@
+= form_for application, url: doorkeeper_submit_path(application), html: {class: 'form-horizontal', role: 'form'} do |f|
+ - if application.errors.any?
+ .alert.alert-danger{"data-alert" => ""}
+ %p Whoops! Check your form for possible errors
+ = content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do
+ = f.label :name, class: 'col-sm-2 control-label'
+ .col-sm-10
+ = f.text_field :name, class: 'form-control'
+ = doorkeeper_errors_for application, :name
+ = content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do
+ = f.label :redirect_uri, class: 'col-sm-2 control-label'
+ .col-sm-10
+ = f.text_area :redirect_uri, class: 'form-control'
+ = doorkeeper_errors_for application, :redirect_uri
+ %span.help-block
+ Use one line per URI
+ - if Doorkeeper.configuration.native_redirect_uri
+ %span.help-block
+ Use
+ %code= Doorkeeper.configuration.native_redirect_uri
+ for local tests
+ .form-actions
+ = f.submit 'Submit', class: "btn btn-primary wide"
+ = link_to "Cancel", applications_profile_path, class: "btn btn-default"
diff --git a/app/views/doorkeeper/applications/edit.html.haml b/app/views/doorkeeper/applications/edit.html.haml
new file mode 100644
index 0000000000..61584eb9c4
--- /dev/null
+++ b/app/views/doorkeeper/applications/edit.html.haml
@@ -0,0 +1,2 @@
+%h3.page-title Edit application
+= render 'form', application: @application
\ No newline at end of file
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
new file mode 100644
index 0000000000..e5be4b4bca
--- /dev/null
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -0,0 +1,16 @@
+%h3.page-title Your applications
+%p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
+%table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td= application.redirect_uri
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link'
+ %td= render 'delete_form', application: application
\ No newline at end of file
diff --git a/app/views/doorkeeper/applications/new.html.haml b/app/views/doorkeeper/applications/new.html.haml
new file mode 100644
index 0000000000..655845e4af
--- /dev/null
+++ b/app/views/doorkeeper/applications/new.html.haml
@@ -0,0 +1,2 @@
+%h3.page-title New application
+= render 'form', application: @application
\ No newline at end of file
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
new file mode 100644
index 0000000000..82e78b4af1
--- /dev/null
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -0,0 +1,26 @@
+%h3.page-title
+ Application: #{@application.name}
+
+
+%table.table
+ %tr
+ %td
+ Application Id
+ %td
+ %code#application_id= @application.uid
+ %tr
+ %td
+ Secret:
+ %td
+ %code#secret= @application.secret
+
+ %tr
+ %td
+ Callback url
+ %td
+ - @application.redirect_uri.split.each do |uri|
+ %div
+ %span.monospace= uri
+.form-actions
+ = link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left'
+ = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10'
diff --git a/app/views/doorkeeper/authorizations/error.html.haml b/app/views/doorkeeper/authorizations/error.html.haml
new file mode 100644
index 0000000000..7561ec85ed
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/error.html.haml
@@ -0,0 +1,3 @@
+%h3.page-title An error has occurred
+%main{:role => "main"}
+ %pre= @pre_auth.error_response.body[:error_description]
\ No newline at end of file
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
new file mode 100644
index 0000000000..15f9ee266c
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -0,0 +1,28 @@
+%h3.page-title Authorize required
+%main{:role => "main"}
+ %p.h4
+ Authorize
+ %strong.text-info= @pre_auth.client.name
+ to use your account?
+ - if @pre_auth.scopes
+ #oauth-permissions
+ %p This application will be able to:
+ %ul.text-info
+ - @pre_auth.scopes.each do |scope|
+ %li= t scope, scope: [:doorkeeper, :scopes]
+ %hr/
+ .actions
+ = form_tag oauth_authorization_path, method: :post do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = submit_tag "Authorize", class: "btn btn-success wide pull-left"
+ = form_tag oauth_authorization_path, method: :delete do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = submit_tag "Deny", class: "btn btn-danger prepend-left-10"
\ No newline at end of file
diff --git a/app/views/doorkeeper/authorizations/show.html.haml b/app/views/doorkeeper/authorizations/show.html.haml
new file mode 100644
index 0000000000..9a40200719
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/show.html.haml
@@ -0,0 +1,3 @@
+%h3.page-title Authorization code:
+%main{:role => "main"}
+ %code#authorization_code= params[:code]
\ No newline at end of file
diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
new file mode 100644
index 0000000000..4bba72167e
--- /dev/null
+++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
@@ -0,0 +1,4 @@
+- submit_btn_css ||= 'btn btn-link btn-remove'
+= form_tag oauth_authorized_application_path(application) do
+ %input{:name => "_method", :type => "hidden", :value => "delete"}/
+ = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-sm'
\ No newline at end of file
diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml
new file mode 100644
index 0000000000..814cdc987e
--- /dev/null
+++ b/app/views/doorkeeper/authorized_applications/index.html.haml
@@ -0,0 +1,16 @@
+%header.page-header
+ %h1 Your authorized applications
+%main{:role => "main"}
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Application
+ %th Created At
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr
+ %td= application.name
+ %td= application.created_at.strftime('%Y-%m-%d %H:%M:%S')
+ %td= render 'delete_form', application: application
\ No newline at end of file
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index 0e03e116e7..c86ce9ae65 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,5 +1,5 @@
%li.commit
.commit-row-title
- = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: ''
+ = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''
= gfm event_commit_title(commit[:message]), project
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 6138331537..02b1dec753 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -3,13 +3,14 @@
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
- = cache event do
+ = cache [event, current_user] do
= image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
- if event.push?
= render "events/event/push", event: event
- - elsif event.note?
+ - elsif event.commented?
= render "events/event/note", event: event
+ - elsif event.created_project?
+ = render "events/event/created_project", event: event
- else
- = render "events/event/common", event: event
-
+ = render "events/event/common", event: event
\ No newline at end of file
diff --git a/app/views/events/_event_issue.atom.haml b/app/views/events/_event_issue.atom.haml
index eba2b63797..0edb61ea24 100644
--- a/app/views/events/_event_issue.atom.haml
+++ b/app/views/events/_event_issue.atom.haml
@@ -1,3 +1,3 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- if issue.description.present?
- = markdown issue.description
+ = markdown(issue.description, xhtml: true)
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index 4c9a39bcc2..d2f0005142 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -2,13 +2,13 @@
.event-last-push
.event-last-push-text
%span You pushed to
- = link_to project_commits_path(event.project, event.ref_name) do
+ = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
%strong= event.ref_name
at
%strong= link_to_project event.project
#{time_ago_with_tooltip(event.created_at)}
.pull-right
- = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-small" do
+ = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-sm" do
Create Merge Request
%hr
diff --git a/app/views/events/_event_merge_request.atom.haml b/app/views/events/_event_merge_request.atom.haml
index 0aea2d17d6..1a8b62abea 100644
--- a/app/views/events/_event_merge_request.atom.haml
+++ b/app/views/events/_event_merge_request.atom.haml
@@ -1,3 +1,3 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- if merge_request.description.present?
- = markdown merge_request.description
+ = markdown(merge_request.description, xhtml: true)
diff --git a/app/views/events/_event_note.atom.haml b/app/views/events/_event_note.atom.haml
index be0e05481e..b49c331ccf 100644
--- a/app/views/events/_event_note.atom.haml
+++ b/app/views/events/_event_note.atom.haml
@@ -1,2 +1,2 @@
%div{xmlns: "http://www.w3.org/1999/xhtml"}
- = markdown note.note
+ = markdown(note.note, xhtml: true)
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index 17228c430c..5d14def8f7 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -2,11 +2,11 @@
- event.commits.first(15).each do |commit|
%p
%strong= commit[:author][:name]
- = link_to "(##{commit[:id][0...8]})", project_commit_path(event.project, id: commit[:id])
+ = link_to "(##{truncate_sha(commit[:id])})", namespace_project_commit_path(event.project.namespace, event.project, id: commit[:id])
%i
at
= commit[:timestamp].to_time.to_s(:short)
- %blockquote= markdown(escape_once(commit[:message]))
+ %blockquote= markdown(escape_once(commit[:message]), xhtml: true)
- if event.commits_count > 15
%p
%i
diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml
index 3d62d47886..68c19df092 100644
--- a/app/views/events/_events.html.haml
+++ b/app/views/events/_events.html.haml
@@ -1 +1 @@
-= render @events
+= render partial: 'events/event', collection: @events
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index a9d3adf41d..a39e62e9da 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -1,15 +1,17 @@
.event-title
%span.author_name= link_to_author event
- %span.event_label{class: event.action_name}= event_action_name(event)
+ %span.event_label{class: event.action_name}
+ = event_action_name(event)
+
- if event.target
- %strong= link_to "##{event.target_iid}", [event.project, event.target]
- - else
- %strong= gfm event.target_title
- at
+ %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target]
+ at
+
- if event.project
= link_to_project event.project
- else
= event.project_name
+
- if event.target.respond_to?(:title)
.event-body
.event-note
diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml
new file mode 100644
index 0000000000..3c7153d235
--- /dev/null
+++ b/app/views/events/event/_created_project.html.haml
@@ -0,0 +1,27 @@
+.event-title
+ %span.author_name= link_to_author event
+ %span.event_label{class: event.action_name}
+ = event_action_name(event)
+
+ - if event.project
+ = link_to_project event.project
+ - else
+ = event.project_name
+
+- if current_user == event.author && !event.project.private? && twitter_sharing_enabled?
+ .event-body
+ .event-note
+ .md
+ %p
+ Congratulations! Why not share your accomplishment with the world?
+
+ %a.twitter-share-button{ |
+ href: "https://twitter.com/share", |
+ "data-url" => event.project.web_url, |
+ "data-text" => "I just created a new project in GitLab! GitLab is version control on your server.", |
+ "data-size" => "medium", |
+ "data-related" => "gitlab", |
+ "data-hashtags" => "gitlab", |
+ "data-count" => "none"}
+ Tweet
+ %script{src: "//platform.twitter.com/widgets.js"}
\ No newline at end of file
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 6ec8e54fba..4ef18c0906 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -1,6 +1,10 @@
.event-title
%span.author_name= link_to_author event
- %span.event_label commented on #{event_note_title_html(event)} at
+ %span.event_label
+ = event.action_name
+ = event_note_title_html(event)
+ at
+
- if event.project
= link_to_project event.project
- else
@@ -14,9 +18,9 @@
- note = event.target
- if note.attachment.url
- if note.attachment.image?
- = link_to note.attachment.secure_url, target: '_blank' do
- = image_tag note.attachment.secure_url, class: 'note-image-attach'
+ = link_to note.attachment.url, target: '_blank' do
+ = image_tag note.attachment.url, class: 'note-image-attach'
- else
- = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do
+ = link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do
%i.fa.fa-paperclip
= note.attachment_identifier
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 1bca64c7d5..489138887a 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -1,10 +1,10 @@
.event-title
%span.author_name= link_to_author event
- %span.event_label.pushed #{event.push_action_name} #{event.ref_type}
+ %span.event_label.pushed #{event.action_name} #{event.ref_type}
- if event.rm_ref?
%strong= event.ref_name
- else
- = link_to project_commits_path(event.project, event.ref_name) do
+ = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
%strong= event.ref_name
at
= link_to_project event.project
@@ -21,5 +21,5 @@
%li.commits-stat
- if event.commits_count > 2
%span ... and #{event.commits_count - 2} more commits.
- = link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do
- %strong Compare → #{event.commit_from[0..7]}...#{event.commit_to[0..7]}
+ = link_to namespace_project_compare_path(event.project.namespace, event.project, from: event.commit_from, to: event.commit_to) do
+ %strong Compare → #{truncate_sha(event.commit_from)}...#{truncate_sha(event.commit_to)}
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index c8243ff782..2ea6cb1865 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -1,32 +1,31 @@
.clearfix
.pull-left
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
+ = hidden_field_tag :sort, @sort
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search"
.form-group
- = submit_tag 'Search', class: "btn btn-primary wide"
+ = button_tag 'Search', class: "btn btn-primary wide"
.pull-right
.dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
- = @sort.humanize
+ = sort_options_hash[@sort]
- else
- Name
+ = sort_title_recently_created
%b.caret
%ul.dropdown-menu
%li
- = link_to explore_groups_path(sort: nil) do
- Name
- = link_to explore_groups_path(sort: 'newest') do
- Newest
- = link_to explore_groups_path(sort: 'oldest') do
- Oldest
- = link_to explore_groups_path(sort: 'recently_updated') do
- Recently updated
- = link_to explore_groups_path(sort: 'last_updated') do
- Last updated
+ = link_to explore_groups_path(sort: sort_value_recently_created) do
+ = sort_title_recently_created
+ = link_to explore_groups_path(sort: sort_value_oldest_created) do
+ = sort_title_oldest_created
+ = link_to explore_groups_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to explore_groups_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
%hr
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
new file mode 100644
index 0000000000..b3963a9d90
--- /dev/null
+++ b/app/views/explore/projects/_filter.html.haml
@@ -0,0 +1,67 @@
+.pull-left
+ = form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
+ .form-group
+ = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
+ .form-group
+ = button_tag 'Search', class: "btn btn-primary wide"
+
+.pull-right.hidden-sm.hidden-xs
+ - if current_user
+ .dropdown.inline.append-right-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-globe
+ %span.light Visibility:
+ - if params[:visibility_level].present?
+ = visibility_level_label(params[:visibility_level].to_i)
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to explore_projects_filter_path(visibility_level: nil) do
+ Any
+ - Gitlab::VisibilityLevel.values.each do |level|
+ %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' }
+ = link_to explore_projects_filter_path(visibility_level: level) do
+ = visibility_level_icon(level)
+ = visibility_level_label(level)
+
+ - if @tags.present?
+ .dropdown.inline.append-right-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-tags
+ %span.light Tags:
+ - if params[:tag].present?
+ = params[:tag]
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to explore_projects_filter_path(tag: nil) do
+ Any
+
+ - @tags.each do |tag|
+ %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
+ = link_to explore_projects_filter_path(tag: tag.name) do
+ %i.fa.fa-tag
+ = tag.name
+
+ .dropdown.inline
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.light sort:
+ - if @sort.present?
+ = sort_options_hash[@sort]
+ - else
+ = sort_title_recently_created
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to explore_projects_filter_path(sort: sort_value_recently_created) do
+ = sort_title_recently_created
+ = link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
+ = sort_title_oldest_created
+ = link_to explore_projects_filter_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml
index 4bc79d0a8c..d65fb52937 100644
--- a/app/views/explore/projects/_project.html.haml
+++ b/app/views/explore/projects/_project.html.haml
@@ -2,11 +2,10 @@
%h4.project-title
.project-access-icon
= visibility_level_icon(project.visibility_level)
- = link_to project.name_with_namespace, project
-
- - if current_page?(starred_explore_projects_path)
- %strong.pull-right
- = pluralize project.star_count, 'star'
+ = link_to project.name_with_namespace, [project.namespace.becomes(Namespace), project]
+ %span.pull-right
+ %i.fa.fa-star
+ = project.star_count
.project-info
- if project.description.present?
@@ -15,11 +14,11 @@
.repo-info
- unless project.empty_repo?
- = link_to pluralize(project.repository.round_commit_count, 'commit'), project_commits_path(project, project.default_branch)
+ = link_to pluralize(project.repository.round_commit_count, 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch)
·
- = link_to pluralize(project.repository.branch_names.count, 'branch'), project_branches_path(project)
+ = link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project)
·
- = link_to pluralize(project.repository.tag_names.count, 'tag'), project_tags_path(project)
+ = link_to pluralize(project.repository.tag_names.count, 'tag'), namespace_project_tags_path(project.namespace, project)
- else
%i.fa.fa-exclamation-triangle
Empty repository
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index c8bf78385e..5086b58cd0 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -1,32 +1,5 @@
.clearfix
- .pull-left
- = form_tag explore_projects_path, method: :get, class: 'form-inline form-tiny' do |f|
- .form-group
- = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
- .form-group
- = submit_tag 'Search', class: "btn btn-primary wide"
-
- .pull-right
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %span.light sort:
- - if @sort.present?
- = @sort.humanize
- - else
- Name
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to explore_projects_path(sort: nil) do
- Name
- = link_to explore_projects_path(sort: 'newest') do
- Newest
- = link_to explore_projects_path(sort: 'oldest') do
- Oldest
- = link_to explore_projects_path(sort: 'recently_updated') do
- Recently updated
- = link_to explore_projects_path(sort: 'last_updated') do
- Last updated
+ = render 'filter'
%hr
.public-projects
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index d4b1140551..420f069375 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -1,6 +1,6 @@
.explore-trending-block
%p.lead
- %i.fa.fa-comments-o
+ %i.fa.fa-star
See most starred projects
%hr
.public-projects
diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml
deleted file mode 100644
index 393be3f1d1..0000000000
--- a/app/views/groups/_filter.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-= form_tag group_filter_path(entity), method: 'get' do
- %fieldset
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if (params[:status] == 'active' || !params[:status]))}
- = link_to group_filter_path(entity, status: 'active') do
- Active
- %li{class: ("active" if params[:status] == 'closed')}
- = link_to group_filter_path(entity, status: 'closed') do
- Closed
- %li{class: ("active" if params[:status] == 'all')}
- = link_to group_filter_path(entity, status: 'all') do
- All
diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml
deleted file mode 100644
index e590ddbf93..0000000000
--- a/app/views/groups/_new_group_member.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-= form_for @users_group, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
- .form-group
- = f.label :user_ids, "People", class: 'control-label'
- .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
-
- .form-group
- = f.label :access_level, "Group Access", class: 'control-label'
- .col-sm-10= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @users_group.access_level), class: "project-access-select select2"
-
- .form-actions
- = f.submit 'Add users into group', class: "btn btn-create"
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 2c65b3049e..6f53e125c4 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -1,21 +1,10 @@
.panel.panel-default
- .panel-heading
- Projects (#{projects.count})
- - if can? current_user, :create_projects, @group
- .panel-head-actions
- = link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do
- %i.fa.fa-plus
- New project
- %ul.well-list
- - if projects.blank?
- .nothing-here-block This group has no projects yet
- - projects.each do |project|
- %li.project-row
- = link_to project_path(project), class: dom_class(project) do
- .dash-project-access-icon
- = visibility_level_icon(project.visibility_level)
- %span.str-truncated
- %span.project-name
- = project.name
- %span.arrow
- %i.fa.fa-angle-right
+ .panel-heading.clearfix
+ .input-group
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
+ - if can? current_user, :create_projects, @group
+ .input-group-addon.dash-new-project
+ = link_to new_project_path(namespace_id: @group.id) do
+ %strong New project
+
+ = render 'shared/projects_list', projects: @projects, projects_limit: 20
diff --git a/app/views/groups/_settings_nav.html.haml b/app/views/groups/_settings_nav.html.haml
index ec1fb4a2c0..e6aee22e52 100644
--- a/app/views/groups/_settings_nav.html.haml
+++ b/app/views/groups/_settings_nav.html.haml
@@ -1,10 +1,11 @@
-%ul.nav.nav-pills.nav-stacked.nav-stacked-menu
+%ul.sidebar-subnav
= nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group) do
+ = link_to edit_group_path(@group), title: 'Group' do
%i.fa.fa-pencil-square-o
- Group
+ %span
+ Group
= nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group) do
+ = link_to projects_group_path(@group), title: 'Projects' do
%i.fa.fa-folder
- Projects
-
+ %span
+ Projects
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 0b15affe78..49e7180bf9 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,56 +1,37 @@
-.row
- .col-md-2
- = render 'settings_nav'
- .col-md-10
- .panel.panel-default
- .panel-heading
- %strong= @group.name
- group settings:
- .panel-body
- = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- - if @group.errors.any?
- .alert.alert-danger
- %span= @group.errors.full_messages.first
- .form-group
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left"
+.panel.panel-default
+ .panel-heading
+ %strong= @group.name
+ group settings:
+ .panel-body
+ = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
+ - if @group.errors.any?
+ .alert.alert-danger
+ %span= @group.errors.full_messages.first
+ = render 'shared/group_form', f: f
- .form-group.group-description-holder
- = f.label :description, "Details", class: 'control-label'
- .col-sm-10
- = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160'
+ %p.light
+ - if @group.avatar?
+ You can change your group avatar here
+ - else
+ You can upload a group avatar here
+ = render 'shared/choose_group_avatar_button', f: f
+ - if @group.avatar?
+ %hr
+ = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
- .form-group
- .col-sm-2
- .col-sm-10
- = image_tag group_icon(@group.to_param), alt: '', class: 'avatar s160'
- %p.light
- - if @group.avatar?
- You can change your group avatar here
- - else
- You can upload a group avatar here
- %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
- %i.fa.fa-paperclip
- %span Choose File ...
-
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-group-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
- - if @group.avatar?
- %hr
- = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
+ .form-actions
+ = f.submit 'Save group', class: "btn btn-save"
- .form-actions
- = f.submit 'Save group', class: "btn btn-save"
+.panel.panel-danger
+ .panel-heading Remove group
+ .panel-body
+ %p
+ Removing group will cause all child projects and resources to be removed.
+ %br
+ %strong Removed group can not be restored!
- .panel.panel-danger
- .panel-heading Remove group
- .panel-body
- %p
- Removing group will cause all child projects and resources to be removed.
- %br
- %strong Removed group can not be restored!
-
- = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
+ = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index d05016d9c3..003025221b 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -1,6 +1,7 @@
- user = member.user
- return unless user
- show_roles = true if show_roles.nil?
+
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)}
= image_tag avatar_icon(user.email, 16), class: "avatar s16"
@@ -8,24 +9,29 @@
%span.cgray= user.username
- if user == current_user
%span.label.label-success It's you
+ - if user.blocked?
+ %label.label.label-danger
+ %strong Blocked
- if show_roles
%span.pull-right
%strong= member.human_access
- if show_controls
- - if can?(current_user, :modify, member)
- = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
+ - if can?(current_user, :modify_group_member, member)
+ = button_tag class: "btn-xs btn js-toggle-button",
+ title: 'Edit access level', type: 'button' do
%i.fa.fa-pencil-square-o
- - if can?(current_user, :destroy, member)
- - if current_user == member.user
- = link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ - if can?(current_user, :destroy_group_member, member)
+
+ - if current_user == user
+ = link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
- else
- = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
.edit-member.hide.js-toggle-content
= form_for [@group, member], remote: true do |f|
.alert.prepend-top-20
= f.select :access_level, options_for_select(GroupMember.access_level_roles, member.access_level)
- = f.submit 'Save', class: 'btn btn-save btn-small'
+ = f.submit 'Save', class: 'btn btn-save btn-sm'
diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml
new file mode 100644
index 0000000000..c4c29bb2e8
--- /dev/null
+++ b/app/views/groups/group_members/_new_group_member.html.haml
@@ -0,0 +1,15 @@
+= form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
+ .form-group
+ = f.label :user_ids, "People", class: 'control-label'
+ .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
+
+ .form-group
+ = f.label :access_level, "Group Access", class: 'control-label'
+ .col-sm-10
+ = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2"
+ .help-block
+ Read more about role permissions
+ %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+
+ .form-actions
+ = f.submit 'Add users to group', class: "btn btn-create"
diff --git a/app/views/groups/members.html.haml b/app/views/groups/group_members/index.html.haml
similarity index 84%
rename from app/views/groups/members.html.haml
rename to app/views/groups/group_members/index.html.haml
index ba554cd5ef..0d501fe7bd 100644
--- a/app/views/groups/members.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,4 +1,5 @@
- show_roles = should_user_see_group_roles?(current_user, @group)
+
%h3.page-title
Group members
- if show_roles
@@ -10,14 +11,14 @@
%hr
.clearfix.js-toggle-container
- = form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do
+ = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
- = submit_tag 'Search', class: 'btn'
+ = button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:manage_group, @group)
.pull-right
- = link_to '#', class: 'btn btn-new js-toggle-button' do
+ = button_tag class: 'btn btn-new js-toggle-button', type: 'button' do
Add members
%i.fa.fa-chevron-down
@@ -33,6 +34,7 @@
%ul.well-list
- @members.each do |member|
= render 'groups/group_members/group_member', member: member, show_roles: show_roles, show_controls: true
+
= paginate @members, theme: 'gitlab'
:coffeescript
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index f2005193f8..240001967f 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -7,18 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(issue.project, issue)
- xml.link :href => project_issue_url(issue.project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 1932ba2f64..6c0d89c4e7 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -9,10 +9,6 @@
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/filter', entity: 'issue'
- .col-md-9
- = render 'shared/issues'
+.append-bottom-20
+ = render 'shared/issuable_filter'
+= render 'shared/issues'
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 86d5acdaa3..1ad7490563 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -8,10 +8,6 @@
- if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/filter', entity: 'merge_request'
- .col-md-9
- = render 'shared/merge_requests'
+.append-bottom-20
+ = render 'shared/issuable_filter'
+= render 'shared/merge_requests'
diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml
index c95c2e8967..27d0c62df8 100644
--- a/app/views/groups/milestones/_issue.html.haml
+++ b/app/views/groups/milestones/_issue.html.haml
@@ -2,9 +2,9 @@
%span.milestone-row
- project = issue.project
%strong #{project.name} ·
- = link_to [project, issue] do
+ = link_to [project.namespace.becomes(Namespace), project, issue] do
%span.cgray ##{issue.iid}
- = link_to_gfm issue.title, [project, issue], title: issue.title
+ = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
= image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml
index e0c903bfdb..b2d2097dfa 100644
--- a/app/views/groups/milestones/_merge_request.html.haml
+++ b/app/views/groups/milestones/_merge_request.html.haml
@@ -2,9 +2,9 @@
%span.milestone-row
- project = merge_request.project
%strong #{project.name} ·
- = link_to [project, merge_request] do
+ = link_to [project.namespace.becomes(Namespace), project, merge_request] do
%span.cgray ##{merge_request.iid}
- = link_to_gfm merge_request.title, [project, merge_request], title: merge_request.title
+ = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
= image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16"
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 2727525f07..57dc235f5b 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -9,42 +9,38 @@
%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'groups/filter', entity: 'milestone'
- .col-md-9
- .panel.panel-default
- %ul.well-list
- - if @group_milestones.blank?
- %li
- .nothing-here-block No milestones to show
- - else
- - @group_milestones.each do |milestone|
- %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- .pull-right
- - if can?(current_user, :manage_group, @group)
- - if milestone.closed?
- = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen"
- - else
- = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-close"
- %h4
- = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
+= render 'shared/milestones_filter'
+.milestones
+ .panel.panel-default
+ %ul.well-list
+ - if @group_milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
+ - else
+ - @group_milestones.each do |milestone|
+ %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
+ .pull-right
+ - if can?(current_user, :manage_group, @group)
+ - if milestone.closed?
+ = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
+ - else
+ = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
+ %h4
+ = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
+ %div
%div
- %div
- = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
- = pluralize milestone.issue_count, 'Issue'
-
- = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
- = pluralize milestone.merge_requests_count, 'Merge Request'
-
- %span.light #{milestone.percent_complete}% complete
- .progress.progress-info
- .progress-bar{style: "width: #{milestone.percent_complete}%;"}
- %div
- %br
- - milestone.projects.each do |project|
+ = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
+ = pluralize milestone.issue_count, 'Issue'
+
+ = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do
+ = pluralize milestone.merge_requests_count, 'Merge Request'
+
+ %span.light #{milestone.percent_complete}% complete
+ = milestone_progress_bar(milestone)
+ %div
+ %br
+ - milestone.milestones.each do |milestone|
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) do
%span.label.label-default
- = project.name
- = paginate @group_milestones, theme: "gitlab"
+ = milestone.project.name
+ = paginate @group_milestones, theme: "gitlab"
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 411d1822be..fea70f5cbc 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,52 +1,51 @@
-%h3.page-title
+%h4.page-title
+ .issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" }
+ - if @group_milestone.closed?
+ Closed
+ - else
+ Open
Milestone #{@group_milestone.title}
.pull-right
- if can?(current_user, :manage_group, @group)
- if @group_milestone.active?
- = link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-close"
+ = link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
- else
- = link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen"
+ = link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
+%hr
- if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active?
.alert.alert-success
%span All issues for this milestone are closed. You may close the milestone now.
-.back-link
- = link_to group_milestones_path(@group) do
- ← To milestones list
-
-.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" }
- .state.clearfix
- .state-label
- - if @group_milestone.closed?
- Closed
- - else
- Open
-
- %h4.title
- = gfm escape_once(@group_milestone.title)
-
- .description
- - @group_milestone.milestones.each do |milestone|
- %hr
- %h4
- = link_to "#{milestone.project.name} - #{milestone.title}", project_milestone_path(milestone.project, milestone)
- %span.pull-right= milestone.expires_at
+.description
+%table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - @group_milestone.milestones.each do |milestone|
+ %tr
+ %td
+ = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
+ %td
+ = milestone.issues.opened.count
+ %td
- if milestone.closed?
- %span.label.label-danger #{milestone.state}
- = preserve do
- - if milestone.description.present?
- = milestone.description
+ Closed
+ - else
+ Open
+ %td
+ = milestone.expires_at
- .context
- %p
- Progress:
- #{@group_milestone.closed_items_count} closed
- –
- #{@group_milestone.open_items_count} open
-
- .progress.progress-info
- .progress-bar{style: "width: #{@group_milestone.percent_complete}%;"}
+.context
+ %p.lead
+ Progress:
+ #{@group_milestone.closed_items_count} closed
+ –
+ #{@group_milestone.open_items_count} open
+ = milestone_progress_bar(@group_milestone)
%ul.nav.nav-tabs
%li.active
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 235e299343..6e17cdaef6 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -2,37 +2,18 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
- .form-group
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true
- .form-group.group-description-holder
- = f.label :description, "Details", class: 'control-label'
- .col-sm-10
- = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2
+ = render 'shared/group_form', f: f, autofocus: true
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10
- %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
- %i.fa.fa-paperclip
- %span Choose File ...
-
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-group-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ = render 'shared/choose_group_avatar_button', f: f
.form-group
.col-sm-2
.col-sm-10
- %ul
- %li A group is a collection of several projects
- %li Groups are private by default
- %li Members of a group may only view projects they have permission to access
- %li Group project URLs are prefixed with the group namespace
- %li Existing projects may be moved into a group
+ = render 'shared/group_tips'
.form-actions
= f.submit 'Create group', class: "btn btn-create", tabindex: 3
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 65a66355c5..dd1fa3840d 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -1,29 +1,25 @@
-.row
- .col-md-2
- = render 'settings_nav'
- .col-md-10
- .panel.panel-default
- .panel-heading
- %strong= @group.name
- projects:
- - if can? current_user, :manage_group, @group
- .panel-head-actions
- = link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do
- %i.fa.fa-plus
- New Project
- %ul.well-list
- - @projects.each do |project|
- %li
- .list-item-name
- = visibility_level_icon(project.visibility_level)
- %strong= link_to project.name_with_namespace, project
- %span.label.label-gray
- = repository_size(project)
- .pull-right
- = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
- = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
- = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-small btn-remove"
- - if @projects.blank?
- .nothing-here-block This group has no projects yet
+.panel.panel-default
+ .panel-heading
+ %strong= @group.name
+ projects:
+ - if can? current_user, :manage_group, @group
+ .panel-head-actions
+ = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
+ %i.fa.fa-plus
+ New Project
+ %ul.well-list
+ - @projects.each do |project|
+ %li
+ .list-item-name
+ = visibility_level_icon(project.visibility_level)
+ %strong= link_to project.name_with_namespace, project
+ %span.label.label-gray
+ = repository_size(project)
+ .pull-right
+ = link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
+ = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
+ = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove"
+ - if @projects.blank?
+ .nothing-here-block This group has no projects yet
- = paginate @projects, theme: "gitlab"
+= paginate @projects, theme: "gitlab"
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index e07bb7d2fb..c78bd1bd26 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -1,28 +1,12 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Group feed - #{@group.name}"
- xml.link :href => group_path(@group, :atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => group_path(@group), :rel => "alternate", :type => "text/html"
+ xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml"
+ xml.link href: group_path(@group), rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
- if event.proper?
- xml.entry do
- event_link = event_feed_url(event)
- event_title = event_feed_title(event)
-
- xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
- xml.link :href => event_link
- xml.title truncate(event_title, :length => 80)
- xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
- xml.author do |author|
- xml.name event.author_name
- xml.email event.author_email
- end
- xml.summary event_title
- end
- end
+ event_to_atom(xml, event)
end
end
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index d876e87852..25efe973d4 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,37 +1,21 @@
.dashboard
- %section.activities.col-md-8.hidden-sm.hidden-xs
- - if current_user
- = render "events/event_last_push", event: @last_push
- = link_to dashboard_path, class: 'btn btn-tiny' do
- ← To dashboard
-
- %span.cgray
- Currently you are only seeing events from the
- = @group.name
- group
- %hr
- = render 'shared/event_filter'
- - if @events.any?
+ %div
+ = image_tag group_icon(@group), class: "avatar group-avatar s90"
+ .clearfix
+ %h2
+ = @group.name
+ - if @group.description.present?
+ %p
+ = escaped_autolink(@group.description)
+ %hr
+ .row
+ %section.activities.col-md-8
+ - if current_user
+ = render "events/event_last_push", event: @last_push
+ = render 'shared/event_filter'
.content_list
- - else
- .nothing-here-block Project activity will be displayed here
- = spinner
- %aside.side.col-md-4
- .light-well.append-bottom-20
- = image_tag group_icon(@group.path), class: "avatar s90"
- .clearfix.light
- %h3.page-title
- = @group.name
- - if @group.description.present?
- %p
- = escaped_autolink(@group.description)
- = render "projects", projects: @projects
- - if current_user
- .prepend-top-20
- = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do
- %strong
- %i.fa.fa-rss
- News Feed
-
- %hr
- = render 'shared/promo'
+ = spinner
+ %aside.side.col-md-4
+ = render "projects", projects: @projects
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index 7b8193abfd..af39dfeac5 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -42,3 +42,9 @@
%li
Use
= link_to 'shortcuts', '#', onclick: 'Shortcuts.showHelp(event)'
+ %li
+ Get a support
+ = link_to 'subscription', 'https://about.gitlab.com/pricing/'
+ %li
+ = link_to 'Compare', 'https://about.gitlab.com/features/#compare'
+ GitLab editions
diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml
index 67f9cc41cf..eca34dbff0 100644
--- a/app/views/help/show.html.haml
+++ b/app/views/help/show.html.haml
@@ -1,2 +1,2 @@
.documentation.wiki
- = markdown File.read(Rails.root.join('doc', @category, @file + '.md'))
+ = markdown File.read(Rails.root.join('doc', @category, @file + '.md')).gsub("$your_email", current_user.email)
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
new file mode 100644
index 0000000000..ed03f885dc
--- /dev/null
+++ b/app/views/help/ui.html.haml
@@ -0,0 +1,227 @@
+- lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare."
+
+.gitlab-ui-dev-kit
+ %h1 GitLab UI development kit
+ %p.light
+ Use page inspector in your browser to check element classes and structure
+ of examples below.
+ %hr
+ %ul
+ %li
+ = link_to 'Blocks', '#blocks'
+ %li
+ = link_to 'Lists', '#lists'
+ %li
+ = link_to 'Tables', '#tables'
+ %li
+ = link_to 'Buttons', '#buttons'
+ %li
+ = link_to 'Panels', '#panels'
+ %li
+ = link_to 'Alerts', '#alerts'
+ %li
+ = link_to 'Forms', '#forms'
+ %li
+ = link_to 'Files', '#file'
+ %li
+ = link_to 'Markdown', '#markdown'
+
+ %h2#blocks Blocks
+
+ %h3
+ %code .well
+
+
+ .well
+ %h4 Something
+ = lorem
+
+
+ %h2#lists Lists
+
+ %h3
+ %code .well-list
+ %ul.well-list
+ %li
+ One item
+ %li
+ One item
+ %li
+ One item
+
+ %h3
+ %code .panel .well-list
+
+ .panel.panel-default
+ .panel-heading My list
+ %ul.well-list
+ %li
+ One item
+ %li
+ One item
+ %li
+ One item
+
+ %h3
+ %code .bordered-list
+ %ul.bordered-list
+ %li
+ One item
+ %li
+ One item
+ %li
+ One item
+
+
+
+ %h2#tables Tables
+
+ .example
+ %table.table
+ %thead
+ %tr
+ %th #
+ %th First Name
+ %th Last Name
+ %th Username
+ %tbody
+ %tr
+ %td 1
+ %td Mark
+ %td Otto
+ %td @mdo
+ %tr
+ %td 2
+ %td Jacob
+ %td Thornton
+ %td @fat
+ %tr
+ %td 3
+ %td Larry
+ %td the Bird
+ %td @twitter
+
+
+ %h2#buttons Buttons
+
+ .example
+ %button.btn.btn-default{:type => "button"} Default
+ %button.btn.btn-primary{:type => "button"} Primary
+ %button.btn.btn-success{:type => "button"} Success
+ %button.btn.btn-info{:type => "button"} Info
+ %button.btn.btn-warning{:type => "button"} Warning
+ %button.btn.btn-danger{:type => "button"} Danger
+ %button.btn.btn-link{:type => "button"} Link
+
+ %h2#panels Panels
+
+ .row
+ .col-md-6
+ .panel.panel-success
+ .panel-heading Success
+ .panel-body
+ = lorem
+ .panel.panel-primary
+ .panel-heading Primary
+ .panel-body
+ = lorem
+ .panel.panel-info
+ .panel-heading Info
+ .panel-body
+ = lorem
+ .col-md-6
+ .panel.panel-warning
+ .panel-heading Warning
+ .panel-body
+ = lorem
+ .panel.panel-danger
+ .panel-heading Danger
+ .panel-body
+ = lorem
+
+ %h2#alert Alerts
+
+ .row
+ .col-md-6
+ .alert.alert-success
+ = lorem
+ .alert.alert-primary
+ = lorem
+ .alert.alert-info
+ = lorem
+ .col-md-6
+ .alert.alert-warning
+ = lorem
+ .alert.alert-danger
+ = lorem
+
+ %h2#forms Forms
+
+ %h3
+ %code form.horizontal-form
+
+ %form.form-horizontal
+ .form-group
+ %label.col-sm-2.control-label{:for => "inputEmail3"} Email
+ .col-sm-10
+ %input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/
+ .form-group
+ %label.col-sm-2.control-label{:for => "inputPassword3"} Password
+ .col-sm-10
+ %input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ %label
+ %input{:type => "checkbox"}/
+ Remember me
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ %button.btn.btn-default{:type => "submit"} Sign in
+
+ %h3
+ %code form
+
+ %form
+ .form-group
+ %label{:for => "exampleInputEmail1"} Email address
+ %input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/
+ .form-group
+ %label{:for => "exampleInputPassword1"} Password
+ %input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/
+ .checkbox
+ %label
+ %input{:type => "checkbox"}/
+ Remember me
+ %button.btn.btn-default{:type => "submit"} Sign in
+
+ %h2#file File
+ %h3
+ %code .file-holder
+
+ - blob = Snippet.new(content: "Wow\nSuch\nFile")
+ .example
+ .file-holder
+ .file-title
+ Awesome file
+ .file-actions
+ .btn-group
+ %a.btn Edit
+ %a.btn Remove
+ .file-contenta.code
+ = render 'shared/file_highlight', blob: blob
+
+
+ %h2#markdown Markdown
+ %h3
+ %code .md or .wiki and others
+
+ Markdown rendering has a bit different css and presented in next UI elements:
+
+ %ul
+ %li comment
+ %li issue, merge request description
+ %li wiki page
+ %li help page
+
+ You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown", "markdown")}.
diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml
new file mode 100644
index 0000000000..8d10722628
--- /dev/null
+++ b/app/views/import/base/create.js.haml
@@ -0,0 +1,25 @@
+- if @already_been_taken
+ :plain
+ target_field = $("tr#repo_#{@repo_id} .import-target")
+ origin_target = target_field.text()
+ project_name = "#{@project_name}"
+ origin_namespace = "#{@target_namespace}"
+ target_field.empty()
+ target_field.append("This namespace already been taken! Please choose another one
")
+ target_field.append(" ")
+ target_field.append("/" + project_name)
+ target_field.data("project_name", project_name)
+ target_field.find('input').prop("value", origin_namespace)
+- elsif @access_denied
+ :plain
+ job = $("tr#repo_#{@repo_id}")
+ job.find(".import-actions").html("Access denied! Please verify you can add deploy keys to this repository.
"")
+- else
+ :plain
+ job = $("tr#repo_#{@repo_id}")
+ job.attr("id", "project_#{@project.id}")
+ target_field = job.find(".import-target")
+ target_field.empty()
+ target_field.append('#{link_to @project.path_with_namespace, [@project.namespace.becomes(Namespace), @project]} ')
+ $("table.import-jobs tbody").prepend(job)
+ job.addClass("active").find(".import-actions").html(" started")
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
new file mode 100644
index 0000000000..9da3c920c6
--- /dev/null
+++ b/app/views/import/bitbucket/status.html.haml
@@ -0,0 +1,46 @@
+%h3.page-title
+ %i.fa.fa-bitbucket
+ Import projects from Bitbucket
+
+%p.light
+ Select projects you want to import.
+%hr
+%p
+ = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From Bitbucket
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"}
+ %td
+ = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
+ %td.import-target
+ = "#{repo["owner"]}/#{repo["slug"]}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ $ ->
+ new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}")
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
new file mode 100644
index 0000000000..9c4d91013e
--- /dev/null
+++ b/app/views/import/github/status.html.haml
@@ -0,0 +1,46 @@
+%h3.page-title
+ %i.fa.fa-github
+ Import projects from GitHub
+
+%p.light
+ Select projects you want to import.
+%hr
+%p
+ = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From GitHub
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
+ %td.import-target
+ = repo.full_name
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ $ ->
+ new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}")
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
new file mode 100644
index 0000000000..e809643d8d
--- /dev/null
+++ b/app/views/import/gitlab/status.html.haml
@@ -0,0 +1,46 @@
+%h3.page-title
+ %i.fa.fa-heart
+ Import projects from GitLab.com
+
+%p.light
+ Select projects you want to import.
+%hr
+%p
+ = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From GitLab.com
+ %th To this GitLab instance
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo["id"]}"}
+ %td
+ = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
+ %td.import-target
+ = repo["path_with_namespace"]
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ $ ->
+ new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}")
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
new file mode 100644
index 0000000000..645241a6c6
--- /dev/null
+++ b/app/views/import/gitorious/status.html.haml
@@ -0,0 +1,46 @@
+%h3.page-title
+ %i.icon-gitorious.icon-gitorious-big
+ Import projects from Gitorious.org
+
+%p.light
+ Select projects you want to import.
+%hr
+%p
+ = button_tag 'Import all projects', class: "btn btn-success js-import-all"
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From Gitorious.org
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank"
+ %td.import-target
+ = repo.full_name
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ $ ->
+ new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}")
diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml
new file mode 100644
index 0000000000..2ed51d87ca
--- /dev/null
+++ b/app/views/layouts/_collapse_button.html.haml
@@ -0,0 +1,4 @@
+- if nav_menu_collapsed?
+ = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
+- else
+ = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index fa6aecb666..d12145651a 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -1,12 +1,5 @@
%head
%meta{charset: "utf-8"}
-
- -# Go repository retrieval support
- -# Need to be the fist thing in the head
- -# Since Go is using an XML parser to process HTML5
- -# https://github.com/gitlabhq/gitlabhq/pull/5958#issuecomment-45397555
- - if controller_name == 'projects' && action_name == 'show'
- %meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"}
%meta{content: "GitLab Community Edition", name: "description"}
%title
@@ -18,7 +11,8 @@
= javascript_include_tag "application"
= csrf_meta_tags
= include_gon
- %meta{name: 'viewport', content: 'width=device-width, initial-scale=1.0'}
+ %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
+ %meta{name: 'theme-color', content: '#474D57'}
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
@@ -29,6 +23,6 @@
= auto_discovery_link_tag :atom, projects_url(:atom, private_token: current_user.private_token), title: "Dashboard feed"
- if @project && !@project.new_record?
- if current_controller?(:tree, :commits)
- = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}")
+ = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}")
- if current_controller?(:issues)
- = auto_discovery_link_tag(:atom, project_issues_url(@project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
+ = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
index 5dcaee2fa0..fc8a487ece 100644
--- a/app/views/layouts/_head_panel.html.haml
+++ b/app/views/layouts/_head_panel.html.haml
@@ -1,11 +1,9 @@
-%header.navbar.navbar-static-top.navbar-gitlab
+%header.navbar.navbar-fixed-top.navbar-gitlab
.navbar-inner
.container
%div.app_logo
- %span.separator
= link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do
- %h1 GITLAB
- %span.separator
+ = brand_header_logo
%h1.title= title
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
@@ -44,5 +42,7 @@
= link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do
%i.fa.fa-sign-out
%li.hidden-xs
- = link_to current_user, class: "profile-pic", id: 'profile-pic' do
- = image_tag avatar_icon(current_user.email, 26), alt: 'User activity'
+ = link_to current_user, class: "profile-pic has_bottom_tooltip", id: 'profile-pic', 'data-original-title' => 'Your profile' do
+ = image_tag avatar_icon(current_user.email, 60), alt: 'User activity'
+
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 353f7ce34f..3c58f10e75 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,3 +1,3 @@
:javascript
- GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project, type: @noteable.class, type_id: params[:id])}"
+ GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(@project.namespace, @project, type: @noteable.class, type_id: params[:id])}"
GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
new file mode 100644
index 0000000000..422966cdc5
--- /dev/null
+++ b/app/views/layouts/_page.html.haml
@@ -0,0 +1,23 @@
+- if defined?(sidebar)
+ .page-with-sidebar{ class: nav_sidebar_class }
+ = render "layouts/broadcast"
+ .sidebar-wrapper
+ = render(sidebar)
+ .collapse-nav
+ = render partial: 'layouts/collapse_button'
+ .content-wrapper
+ .container-fluid
+ .content
+ = render "layouts/flash"
+ .clearfix
+ = yield
+- else
+ .container.navless-container
+ .content
+ = yield
+
+= yield :embedded_scripts
+
+:coffeescript
+ $('.page-sidebar-collapsed .nav-sidebar a').tooltip placement: "right"
+
diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
index 9bfc14d16c..3d6d2bfc00 100644
--- a/app/views/layouts/_public_head_panel.html.haml
+++ b/app/views/layouts/_public_head_panel.html.haml
@@ -1,22 +1,22 @@
-%header.navbar.navbar-static-top.navbar-gitlab
+%header.navbar.navbar-fixed-top.navbar-gitlab
.navbar-inner
.container
%div.app_logo
- %span.separator
= link_to explore_root_path, class: "home" do
- %h1 GITLAB
- %span.separator
+ = brand_header_logo
%h1.title= title
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
%span.sr-only Toggle navigation
%i.fa.fa-bars
- .pull-right.hidden-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new'
+ - unless current_controller?('sessions')
+ .pull-right.hidden-xs
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new append-right-10'
- .navbar-collapse.collapse
- %ul.nav.navbar-nav
- %li.visible-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav
+ %li.visible-xs
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 5ab82122ad..04f7984685 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -4,11 +4,20 @@
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
- = hidden_field_tag :search_code, true
+
+ - if current_controller?(:issues)
+ = hidden_field_tag :scope, 'issues'
+ - elsif current_controller?(:merge_requests)
+ = hidden_field_tag :scope, 'merge_requests'
+ - elsif current_controller?(:wikis)
+ = hidden_field_tag :scope, 'wiki_blobs'
+ - else
+ = hidden_field_tag :search_code, true
+
- if @snippet || @snippets
= hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref
- = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test'
+ = button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
:javascript
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index 207ab22f4c..ab84e87c30 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -1,13 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Admin area"
- %body{class: "#{app_theme} admin", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/head_panel", title: "Admin area"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/admin'
- .container
- .content
- = render "layouts/flash"
- = yield
- = yield :embedded_scripts
+ %body{class: "#{app_theme} admin", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: link_to("Admin area", admin_root_path)
+ = render 'layouts/page', sidebar: 'layouts/nav/admin'
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 7d0819aa93..6bd8ac4adb 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,12 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Dashboard"
- %body{class: "#{app_theme} application", :'data-page' => body_data_page }
- = render "layouts/broadcast"
- = render "layouts/head_panel", title: "Dashboard"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/dashboard'
- .container
- .content
- = render "layouts/flash"
- = yield
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page }
+ = render "layouts/head_panel", title: link_to("Dashboard", root_path)
+ = render 'layouts/page', sidebar: 'layouts/nav/dashboard'
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 06de03eada..6f805f1c9d 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,35 +1,32 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body.ui_basic.login-page
- .container
- .content
- .login-title
- %h1= brand_title
- %hr
- .container
+ %body.ui_mars.login-page.application
+ = render "layouts/broadcast"
+ = render "layouts/public_head_panel", title: ''
+ .container.navless-container
.content
= render "layouts/flash"
- .row
- .col-md-7.brand-holder
- - if brand_item
- .brand-image
- = brand_image
- .brand_text
- = brand_text
- - else
- .brand-image.default-brand-image.hidden-sm.hidden-xs
- = image_tag 'brand_logo.png'
- .brand_text.hidden-xs
- %h2 Open source software to collaborate on code
-
- %p.lead
- Manage git repositories with fine grained access controls that keep your code secure.
- Perform code reviews and enhance collaboration with merge requests.
- Each project can also have an issue tracker and a wiki.
-
- .col-md-5
+ .row.prepend-top-20
+ .col-sm-5.pull-right
= yield
+ .col-sm-7.brand-holder.pull-left
+ %h1
+ = brand_title
+ - if brand_item
+ = brand_image
+ = brand_text
+ - else
+ %h3 Open source software to collaborate on code
+
+ %p
+ Manage git repositories with fine grained access controls that keep your code secure.
+ Perform code reviews and enhance collaboration with merge requests.
+ Each project can also have an issue tracker and a wiki.
+
+ - if extra_sign_in_text.present?
+ = markdown(extra_sign_in_text)
+
%hr
.container
.footer-links
diff --git a/app/views/layouts/doorkeeper/admin.html.haml b/app/views/layouts/doorkeeper/admin.html.haml
new file mode 100644
index 0000000000..bd9adfab66
--- /dev/null
+++ b/app/views/layouts/doorkeeper/admin.html.haml
@@ -0,0 +1,22 @@
+!!!
+%html
+ %head
+ %meta{:charset => "utf-8"}
+ %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}
+ %meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}
+ %title Doorkeeper
+ = stylesheet_link_tag "doorkeeper/admin/application"
+ = csrf_meta_tags
+ %body
+ .navbar.navbar-inverse.navbar-fixed-top{:role => "navigation"}
+ .container
+ .navbar-header
+ = link_to 'OAuth2 Provider', oauth_applications_path, class: 'navbar-brand'
+ %ul.nav.navbar-nav
+ = content_tag :li, class: "#{'active' if request.path == oauth_applications_path}" do
+ = link_to 'Applications', oauth_applications_path
+ .container
+ - if flash[:notice].present?
+ .alert.alert-info
+ = flash[:notice]
+ = yield
\ No newline at end of file
diff --git a/app/views/layouts/doorkeeper/application.html.haml b/app/views/layouts/doorkeeper/application.html.haml
new file mode 100644
index 0000000000..e5f37fad1f
--- /dev/null
+++ b/app/views/layouts/doorkeeper/application.html.haml
@@ -0,0 +1,15 @@
+!!!
+%html
+ %head
+ %title OAuth authorize required
+ %meta{:charset => "utf-8"}
+ %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}
+ %meta{:content => "width=device-width, initial-scale=1.0", :name => "viewport"}
+ = stylesheet_link_tag "doorkeeper/application"
+ = csrf_meta_tags
+ %body
+ #container
+ - if flash[:notice].present?
+ .alert.alert-info
+ = flash[:notice]
+ = yield
\ No newline at end of file
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 16df9c10fb..e51fd4cb82 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Error"
- %body{class: "#{app_theme} application"}
+ %body{class: "#{app_theme} application"}
= render "layouts/head_panel", title: "" if current_user
.container.navless-container
= render "layouts/flash"
diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml
index d023846c5e..2bd0b8d85c 100644
--- a/app/views/layouts/explore.html.haml
+++ b/app/views/layouts/explore.html.haml
@@ -2,12 +2,12 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: page_title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- if current_user
- = render "layouts/head_panel", title: page_title
+ = render "layouts/head_panel", title: link_to(page_title, explore_root_path)
- else
- = render "layouts/public_head_panel", title: page_title
+ = render "layouts/public_head_panel", title: link_to(page_title, explore_root_path)
.container.navless-container
.content
.explore-title
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index f22fb236cb..f4a6bee15f 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,12 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: group_head_title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/head_panel", title: "group: #{@group.name}"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/group'
- .container
- .content
- = render "layouts/flash"
- = yield
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: link_to(@group.name, group_path(@group))
+ = render 'layouts/page', sidebar: 'layouts/nav/group'
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index c57216f01c..2f38d596c6 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,19 +1,60 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do
- Overview
+ %i.fa.fa-dashboard
+ %span
+ Overview
= nav_link(controller: :projects) do
- = link_to "Projects", admin_projects_path
+ = link_to admin_namespaces_projects_path, title: 'Projects' do
+ %i.fa.fa-cube
+ %span
+ Projects
= nav_link(controller: :users) do
- = link_to "Users", admin_users_path
+ = link_to admin_users_path, title: 'Users' do
+ %i.fa.fa-user
+ %span
+ Users
= nav_link(controller: :groups) do
- = link_to "Groups", admin_groups_path
+ = link_to admin_groups_path, title: 'Groups' do
+ %i.fa.fa-group
+ %span
+ Groups
= nav_link(controller: :logs) do
- = link_to "Logs", admin_logs_path
+ = link_to admin_logs_path, title: 'Logs' do
+ %i.fa.fa-file-text
+ %span
+ Logs
= nav_link(controller: :broadcast_messages) do
- = link_to "Messages", admin_broadcast_messages_path
+ = link_to admin_broadcast_messages_path, title: 'Broadcast Messages' do
+ %i.fa.fa-bullhorn
+ %span
+ Messages
= nav_link(controller: :hooks) do
- = link_to "Hooks", admin_hooks_path
+ = link_to admin_hooks_path, title: 'Hooks' do
+ %i.fa.fa-external-link
+ %span
+ Hooks
= nav_link(controller: :background_jobs) do
- = link_to "Background Jobs", admin_background_jobs_path
+ = link_to admin_background_jobs_path, title: 'Background Jobs' do
+ %i.fa.fa-cog
+ %span
+ Background Jobs
+
+ = nav_link(controller: :applications) do
+ = link_to admin_applications_path, title: 'Applications' do
+ %i.fa.fa-cloud
+ %span
+ Applications
+
+ = nav_link(controller: :services) do
+ = link_to admin_application_settings_services_path, title: 'Service Templates' do
+ %i.fa.fa-copy
+ %span
+ Service Templates
+
+ = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
+ = link_to admin_application_settings_path, title: 'Settings' do
+ %i.fa.fa-cogs
+ %span
+ Settings
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index a6e9772d93..e4f630c6a1 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,18 +1,38 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
= link_to root_path, title: 'Home', class: 'shortcuts-activity' do
- Activity
- = nav_link(path: 'dashboard#projects') do
- = link_to projects_dashboard_path, class: 'shortcuts-projects' do
- Projects
+ %i.fa.fa-dashboard
+ %span
+ Your Projects
+ = nav_link(path: 'projects#starred') do
+ = link_to starred_dashboard_projects_path, title: 'Starred Projects' do
+ %i.fa.fa-star
+ %span
+ Starred Projects
+ = nav_link(controller: :groups) do
+ = link_to dashboard_groups_path, title: 'Groups' do
+ %i.fa.fa-group
+ %span
+ Groups
+ = nav_link(controller: :milestones) do
+ = link_to dashboard_milestones_path, title: 'Milestones' do
+ %i.fa.fa-clock-o
+ %span
+ Milestones
= nav_link(path: 'dashboard#issues') do
- = link_to issues_dashboard_path, class: 'shortcuts-issues' do
- Issues
- %span.count= current_user.assigned_issues.opened.count
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
+ %i.fa.fa-exclamation-circle
+ %span
+ Issues
+ %span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
- = link_to merge_requests_dashboard_path, class: 'shortcuts-merge_requests' do
- Merge Requests
- %span.count= current_user.assigned_merge_requests.opened.count
+ = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ %i.fa.fa-tasks
+ %span
+ Merge Requests
+ %span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :help) do
- = link_to "Help", help_path
-
+ = link_to help_path, title: 'Help' do
+ %i.fa.fa-question-circle
+ %span
+ Help
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 9095a843c9..32fe0e37df 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,25 +1,42 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: "Home" do
- Activity
- = nav_link(controller: [:group, :milestones]) do
- = link_to group_milestones_path(@group) do
- Milestones
+ %i.fa.fa-dashboard
+ %span
+ Activity
+ - if current_user
+ = nav_link(controller: [:group, :milestones]) do
+ = link_to group_milestones_path(@group), title: 'Milestones' do
+ %i.fa.fa-clock-o
+ %span
+ Milestones
= nav_link(path: 'groups#issues') do
- = link_to issues_group_path(@group) do
- Issues
- - if current_user
- %span.count= current_user.assigned_issues.opened.of_group(@group).count
+ = link_to issues_group_path(@group), title: 'Issues' do
+ %i.fa.fa-exclamation-circle
+ %span
+ Issues
+ - if current_user
+ %span.count= Issue.opened.of_group(@group).count
= nav_link(path: 'groups#merge_requests') do
- = link_to merge_requests_group_path(@group) do
- Merge Requests
- - if current_user
- %span.count= current_user.cared_merge_requests.opened.of_group(@group).count
- = nav_link(path: 'groups#members') do
- = link_to "Members", members_group_path(@group)
+ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
+ %i.fa.fa-tasks
+ %span
+ Merge Requests
+ - if current_user
+ %span.count= MergeRequest.opened.of_group(@group).count
+ = nav_link(controller: [:group_members]) do
+ = link_to group_group_members_path(@group), title: 'Members' do
+ %i.fa.fa-users
+ %span
+ Members
- if can?(current_user, :manage_group, @group)
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), class: "tab " do
- Settings
+ = nav_link(html_options: { class: "#{"active" if group_settings_page?} separate-item" }) do
+ = link_to edit_group_path(@group), title: 'Settings', class: "tab no-highlight" do
+ %i.fa.fa-cogs
+ %span
+ Settings
+ %i.fa.fa-angle-down
+ - if group_settings_page?
+ = render 'groups/settings_nav'
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 1de5ee99cf..d88e862829 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,26 +1,50 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: "Profile" do
- Profile
+ %i.fa.fa-user
+ %span
+ Profile
= nav_link(controller: :accounts) do
- = link_to "Account", profile_account_path
+ = link_to profile_account_path, title: 'Account' do
+ %i.fa.fa-gear
+ %span
+ Account
+ = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do
+ = link_to applications_profile_path, title: 'Applications' do
+ %i.fa.fa-cloud
+ %span
+ Applications
= nav_link(controller: :emails) do
- = link_to profile_emails_path do
- Emails
- %span.count= current_user.emails.count + 1
+ = link_to profile_emails_path, title: 'Emails' do
+ %i.fa.fa-envelope-o
+ %span
+ Emails
+ %span.count= current_user.emails.count + 1
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
- = link_to "Password", edit_profile_password_path
+ = link_to edit_profile_password_path, title: 'Password' do
+ %i.fa.fa-lock
+ %span
+ Password
= nav_link(controller: :notifications) do
- = link_to "Notifications", profile_notifications_path
- = nav_link(controller: :keys) do
- = link_to profile_keys_path do
- SSH Keys
- %span.count= current_user.keys.count
- = nav_link(path: 'profiles#design') do
- = link_to "Design", design_profile_path
- = nav_link(controller: :groups) do
- = link_to "Groups", profile_groups_path
- = nav_link(path: 'profiles#history') do
- = link_to "History", history_profile_path
+ = link_to profile_notifications_path, title: 'Notifications' do
+ %i.fa.fa-inbox
+ %span
+ Notifications
+ = nav_link(controller: :keys) do
+ = link_to profile_keys_path, title: 'SSH Keys' do
+ %i.fa.fa-key
+ %span
+ SSH Keys
+ %span.count= current_user.keys.count
+ = nav_link(path: 'profiles#design') do
+ = link_to design_profile_path, title: 'Design' do
+ %i.fa.fa-image
+ %span
+ Design
+ = nav_link(path: 'profiles#history') do
+ = link_to history_profile_path, title: 'History' do
+ %i.fa.fa-history
+ %span
+ History
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 6cb2a82bac..d340ab1796 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,45 +1,95 @@
-%ul.project-navigation
- = nav_link(path: 'projects#show', html_options: {class: "home"}) do
- = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
- Project
- - if project_nav_tab? :files
- = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
- = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref), class: 'shortcuts-tree'
+%ul.project-navigation.nav.nav-sidebar
+ - if @project_settings_nav
+ = nav_link do
+ = link_to project_path(@project), title: 'Back to project', class: "" do
+ %i.fa.fa-caret-square-o-left
+ %span
+ Back to project
- - if project_nav_tab? :commits
- = nav_link(controller: %w(commit commits compare repositories tags branches)) do
- = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref), class: 'shortcuts-commits'
+ %li.separate-item
- - if project_nav_tab? :network
- = nav_link(controller: %w(network)) do
- = link_to "Network", project_network_path(@project, @ref || @repository.root_ref), class: 'shortcuts-network'
+ = render 'projects/settings_nav'
- - if project_nav_tab? :graphs
- = nav_link(controller: %w(graphs)) do
- = link_to "Graphs", project_graph_path(@project, @ref || @repository.root_ref), class: 'shortcuts-graphs'
+ - else
+ = nav_link(path: 'projects#show', html_options: {class: "home"}) do
+ = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
+ %i.fa.fa-dashboard
+ %span
+ Project
+ - if project_nav_tab? :files
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree' do
+ %i.fa.fa-files-o
+ %span
+ Files
- - if project_nav_tab? :issues
- = nav_link(controller: %w(issues milestones labels)) do
- = link_to url_for_project_issues, class: 'shortcuts-issues' do
- Issues
- - if @project.used_default_issues_tracker?
- %span.count.issue_counter= @project.issues.opened.count
+ - if project_nav_tab? :commits
+ = nav_link(controller: %w(commit commits compare repositories tags branches)) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits' do
+ %i.fa.fa-history
+ %span
+ Commits
- - if project_nav_tab? :merge_requests
- = nav_link(controller: :merge_requests) do
- = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests' do
- Merge Requests
- %span.count.merge_counter= @project.merge_requests.opened.count
+ - if project_nav_tab? :network
+ = nav_link(controller: %w(network)) do
+ = link_to namespace_project_network_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network' do
+ %i.fa.fa-code-fork
+ %span
+ Network
- - if project_nav_tab? :wiki
- = nav_link(controller: :wikis) do
- = link_to 'Wiki', project_wiki_path(@project, :home), class: 'shortcuts-wiki'
+ - if project_nav_tab? :graphs
+ = nav_link(controller: %w(graphs)) do
+ = link_to namespace_project_graph_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs' do
+ %i.fa.fa-area-chart
+ %span
+ Graphs
- - if project_nav_tab? :snippets
- = nav_link(controller: :snippets) do
- = link_to 'Snippets', project_snippets_path(@project), class: 'shortcuts-snippets'
+ = nav_link(controller: :milestones) do
+ = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ %i.fa.fa-clock-o
+ %span
+ Milestones
- - if project_nav_tab? :settings
- = nav_link(html_options: {class: "#{project_tab_class}"}) do
- = link_to edit_project_path(@project), class: "stat-tab tab " do
- Settings
+ - if project_nav_tab? :issues
+ = nav_link(controller: :issues) do
+ = link_to url_for_project_issues, title: 'Issues', class: 'shortcuts-issues' do
+ %i.fa.fa-exclamation-circle
+ %span
+ Issues
+ - if @project.default_issues_tracker?
+ %span.count.issue_counter= @project.issues.opened.count
+
+ - if project_nav_tab? :merge_requests
+ = nav_link(controller: :merge_requests) do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ %i.fa.fa-tasks
+ %span
+ Merge Requests
+ %span.count.merge_counter= @project.merge_requests.opened.count
+
+ = nav_link(controller: :labels) do
+ = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ %i.fa.fa-tags
+ %span
+ Labels
+
+ - if project_nav_tab? :wiki
+ = nav_link(controller: :wikis) do
+ = link_to namespace_project_wiki_path(@project.namespace, @project, :home), title: 'Wiki', class: 'shortcuts-wiki' do
+ %i.fa.fa-book
+ %span
+ Wiki
+
+ - if project_nav_tab? :snippets
+ = nav_link(controller: :snippets) do
+ = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
+ %i.fa.fa-file-text-o
+ %span
+ Snippets
+
+ - if project_nav_tab? :settings
+ = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
+ = link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do
+ %i.fa.fa-cogs
+ %span
+ Settings
diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml
index 2c5fffe384..4d0278251a 100644
--- a/app/views/layouts/navless.html.haml
+++ b/app/views/layouts/navless.html.haml
@@ -1,9 +1,9 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- = render "layouts/head_panel", title: @title
+ = render "layouts/head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title
.container.navless-container
.content
= render "layouts/flash"
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index ab421d63f1..7eec93abdf 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -16,7 +16,19 @@
font-size:small;
color:#777
}
-
+ pre.commit-message {
+ white-space: pre-wrap;
+ }
+ .file-stats a {
+ text-decoration: none;
+ }
+ .file-stats .new-file {
+ color: #090;
+ }
+ .file-stats .deleted-file {
+ color: #B00;
+ }
+ #{add_email_highlight_css}
%body
%div.content
= yield
@@ -24,7 +36,8 @@
%p
\—
%br
- - if @project
- You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team.
- if @target_url
#{link_to "View it on GitLab", @target_url}
+ = email_action @target_url
+ - if @project && !@disable_footer
+ You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team.
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 1d0ab84d26..2b5be7fc37 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,12 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Profile"
- %body{class: "#{app_theme} profile", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/head_panel", title: "Profile"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/profile'
- .container
- .content
- = render "layouts/flash"
- = yield
+ %body{class: "#{app_theme} profile", :'data-page' => body_data_page}
+ = render "layouts/head_panel", title: link_to("Profile", profile_path)
+ = render 'layouts/page', sidebar: 'layouts/nav/profile'
diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml
index c8b8f4ba97..0a0039dec1 100644
--- a/app/views/layouts/project_settings.html.haml
+++ b/app/views/layouts/project_settings.html.haml
@@ -1,19 +1,8 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
- %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
- = render "layouts/broadcast"
+ %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
- - if can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/project'
- .container
- .content
- = render "layouts/flash"
- .row
- .col-md-2
- = render "projects/settings_nav"
- .col-md-10
- = yield
+ - @project_settings_nav = true
+ = render 'layouts/page', sidebar: 'layouts/nav/project'
diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml
index 8ad2f16594..dde0964f47 100644
--- a/app/views/layouts/projects.html.haml
+++ b/app/views/layouts/projects.html.haml
@@ -1,16 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: project_head_title
- %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
- = render "layouts/broadcast"
+ %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete"
- - if can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/project'
- .container
- .content
- = render "layouts/flash"
- = yield
- = yield :embedded_scripts
+ = render 'layouts/page', sidebar: 'layouts/nav/project'
diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml
index a289b78472..b9b1d03e08 100644
--- a/app/views/layouts/public_group.html.haml
+++ b/app/views/layouts/public_group.html.haml
@@ -1,10 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: group_head_title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/public_head_panel", title: "group: #{@group.name}"
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/group'
- .container
- .content= yield
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/public_head_panel", title: link_to(@group.name, group_path(@group))
+ = render 'layouts/page', sidebar: 'layouts/nav/group'
diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml
index 2a9230244f..04fa7c84e7 100644
--- a/app/views/layouts/public_projects.html.haml
+++ b/app/views/layouts/public_projects.html.haml
@@ -1,10 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/broadcast"
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/public_head_panel", title: project_title(@project)
- %nav.main-nav.navbar-collapse.collapse
- .container= render 'layouts/nav/project'
- .container
- .content= yield
+ = render 'layouts/page', sidebar: 'layouts/nav/project'
diff --git a/app/views/layouts/public_users.html.haml b/app/views/layouts/public_users.html.haml
index 4aa258fea0..71c16bd168 100644
--- a/app/views/layouts/public_users.html.haml
+++ b/app/views/layouts/public_users.html.haml
@@ -1,8 +1,6 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: @title
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
- = render "layouts/broadcast"
- = render "layouts/public_head_panel", title: @title
- .container.navless-container
- .content= yield
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ = render "layouts/public_head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title
+ = render 'layouts/page'
diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml
index 084ff7ec83..f9d8db06e1 100644
--- a/app/views/layouts/search.html.haml
+++ b/app/views/layouts/search.html.haml
@@ -1,9 +1,9 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "Search"
- %body{class: "#{app_theme} application", :'data-page' => body_data_page}
+ %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast"
- = render "layouts/head_panel", title: "Search"
+ = render "layouts/head_panel", title: link_to("Search", search_path)
.container.navless-container
.content
= render "layouts/flash"
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 5272dfa0ed..778a78acf5 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -1,2 +1,2 @@
%div
- = markdown(@note.note)
+ = replace_image_links_with_base64(markdown(@note.note), @note.project)
diff --git a/app/views/notify/_reassigned_issuable_email.text.erb b/app/views/notify/_reassigned_issuable_email.text.erb
index 817d030c36..855d37429d 100644
--- a/app/views/notify/_reassigned_issuable_email.text.erb
+++ b/app/views/notify/_reassigned_issuable_email.text.erb
@@ -1,6 +1,6 @@
Reassigned <%= issuable.class.model_name.human.titleize %> <%= issuable.iid %>
-<%= url_for([issuable.project, issuable, {only_path: false}]) %>
+<%= url_for([issuable.project.namespace.becomes(Namespace), issuable.project, issuable, {only_path: false}]) %>
Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee -%>
to <%= "#{issuable.assignee_id ? issuable.assignee_name : 'Unassigned'}" %>
diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
index 49f160a0d5..ac703b31ed 100644
--- a/app/views/notify/closed_issue_email.text.haml
+++ b/app/views/notify/closed_issue_email.text.haml
@@ -1,3 +1,3 @@
= "Issue was closed by #{@updated_by.name}"
-Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)}
+Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index d6b76e906c..59db86b08b 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,6 +1,6 @@
= "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}"
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/group_access_granted_email.html.haml b/app/views/notify/group_access_granted_email.html.haml
index 823ebf7734..f1916d624b 100644
--- a/app/views/notify/group_access_granted_email.html.haml
+++ b/app/views/notify/group_access_granted_email.html.haml
@@ -1,4 +1,4 @@
%p
- = "You have been granted #{@membership.human_access} access to group"
+ = "You have been granted #{@group_member.human_access} access to group"
= link_to group_url(@group) do
= @group.name
diff --git a/app/views/notify/group_access_granted_email.text.erb b/app/views/notify/group_access_granted_email.text.erb
index 331bb98d5c..ef9617bfc1 100644
--- a/app/views/notify/group_access_granted_email.text.erb
+++ b/app/views/notify/group_access_granted_email.text.erb
@@ -1,4 +1,4 @@
-You have been granted <%= @membership.human_access %> access to group <%= @group.name %>
+You have been granted <%= @group_member.human_access %> access to group <%= @group.name %>
<%= url_for(group_url(@group)) %>
diff --git a/app/views/notify/issue_status_changed_email.text.erb b/app/views/notify/issue_status_changed_email.text.erb
index 4200881f7e..e6ab3fcde7 100644
--- a/app/views/notify/issue_status_changed_email.text.erb
+++ b/app/views/notify/issue_status_changed_email.text.erb
@@ -1,4 +1,4 @@
Issue was <%= @issue_status %> by <%= @updated_by.name %>
-Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index 8750bf86e2..b96dd0fd8a 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,6 +1,6 @@
= "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}"
-Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 360da60bc3..9db75bdb19 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,6 +1,6 @@
= "Merge Request ##{@merge_request.iid} was merged"
-Merge Request Url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
+Merge Request Url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml
index f2f8eee18c..03cbee9460 100644
--- a/app/views/notify/new_issue_email.html.haml
+++ b/app/views/notify/new_issue_email.html.haml
@@ -1,5 +1,5 @@
-if @issue.description
- = markdown(@issue.description)
+ = replace_image_links_with_base64(markdown(@issue.description), @issue.project)
- if @issue.assignee_id.present?
%p
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index d36f54eb1c..0cc6293549 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -1,5 +1,5 @@
New Issue was created.
-Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Asignee: <%= @issue.assignee_name %>
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index f02d5111b2..729a7bb505 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -6,4 +6,4 @@
Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name}
-if @merge_request.description
- = markdown(@merge_request.description)
+ = replace_image_links_with_base64(markdown(@merge_request.description), @merge_request.project)
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index 16be4bb619..f08039ad04 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,6 +1,6 @@
New Merge Request #<%= @merge_request.iid %>
-<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
+<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %>
diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml
index deb0822d8f..63b0cbbd20 100644
--- a/app/views/notify/new_ssh_key_email.html.haml
+++ b/app/views/notify/new_ssh_key_email.html.haml
@@ -6,5 +6,5 @@
title:
%code= @key.title
%p
- If this key was added in error, you can remove it here:
+ If this key was added in error, you can remove it under
= link_to "SSH Keys", profile_keys_url
diff --git a/app/views/notify/new_ssh_key_email.text.erb b/app/views/notify/new_ssh_key_email.text.erb
index 5f0080c2b7..05b551c89a 100644
--- a/app/views/notify/new_ssh_key_email.text.erb
+++ b/app/views/notify/new_ssh_key_email.text.erb
@@ -2,6 +2,6 @@ Hi <%= @user.name %>!
A new public key was added to your account:
-title.................. <%= @key.title %>
+Title: <%= @key.title %>
-If this key was added in error, you can remove it here: <%= profile_keys_url %>
+If this key was added in error, you can remove it at <%= profile_keys_url %>
diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb
index aab8e5cfb6..aaeaf5fdf7 100644
--- a/app/views/notify/note_commit_email.text.erb
+++ b/app/views/notify/note_commit_email.text.erb
@@ -1,6 +1,6 @@
New comment for Commit <%= @commit.short_id %>
-<%= url_for(project_commit_url(@note.project, id: @commit.id, anchor: "note_#{@note.id}")) %>
+<%= url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %>
Author: <%= @note.author_name %>
diff --git a/app/views/notify/note_issue_email.text.erb b/app/views/notify/note_issue_email.text.erb
index 8a61f54a33..e33cbcd70f 100644
--- a/app/views/notify/note_issue_email.text.erb
+++ b/app/views/notify/note_issue_email.text.erb
@@ -1,6 +1,6 @@
New comment for Issue <%= @issue.iid %>
-<%= url_for(project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")) %>
+<%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue, anchor: "note_#{@note.id}")) %>
Author: <%= @note.author_name %>
diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb
index 79e72ca16c..1d1411992a 100644
--- a/app/views/notify/note_merge_request_email.text.erb
+++ b/app/views/notify/note_merge_request_email.text.erb
@@ -1,6 +1,6 @@
New comment for Merge Request <%= @merge_request.iid %>
-<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
+<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %>
<%= @note.author_name %>
diff --git a/app/views/notify/project_access_granted_email.html.haml b/app/views/notify/project_access_granted_email.html.haml
index 4596205f39..dfc30a2d36 100644
--- a/app/views/notify/project_access_granted_email.html.haml
+++ b/app/views/notify/project_access_granted_email.html.haml
@@ -1,5 +1,5 @@
%p
= "You have been granted #{@project_member.human_access} access to project"
%p
- = link_to project_url(@project) do
+ = link_to namespace_project_url(@project.namespace, @project) do
= @project.name_with_namespace
diff --git a/app/views/notify/project_access_granted_email.text.erb b/app/views/notify/project_access_granted_email.text.erb
index de24feb802..68eb1611ba 100644
--- a/app/views/notify/project_access_granted_email.text.erb
+++ b/app/views/notify/project_access_granted_email.text.erb
@@ -1,4 +1,4 @@
You have been granted <%= @project_member.human_access %> access to project <%= @project.name_with_namespace %>
-<%= url_for(project_url(@project)) %>
+<%= url_for(namespace_project_url(@project.namespace, @project)) %>
diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml
index fe248584e5..f53de2de28 100644
--- a/app/views/notify/project_was_moved_email.html.haml
+++ b/app/views/notify/project_was_moved_email.html.haml
@@ -2,7 +2,7 @@
Project was moved to another location
%p
The project is now located under
- = link_to project_url(@project) do
+ = link_to namespace_project_url(@project.namespace, @project) do
= @project.name_with_namespace
%p
To update the remote url in your local repository run (for ssh):
diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb
index 664148fb3b..b3f18b35a4 100644
--- a/app/views/notify/project_was_moved_email.text.erb
+++ b/app/views/notify/project_was_moved_email.text.erb
@@ -1,7 +1,7 @@
Project was moved to another location
The project is now located under
-<%= project_url(@project) %>
+<%= namespace_project_url(@project.namespace, @project) %>
To update the remote url in your local repository run (for ssh):
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 3cf50bf082..039b92df2b 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -1,28 +1,66 @@
-%h3 #{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, project_url(@project)}
+%h3 #{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}
-%h4 Commits:
+- if @reverse_compare
+ %p
+ %strong WARNING:
+ The push did not contain any new commits, but force pushed to delete the commits and changes below.
+
+%h4
+ = @reverse_compare ? "Deleted commits:" : "Commits:"
%ul
- @commits.each do |commit|
%li
- %strong #{link_to commit.short_id, project_commit_url(@project, commit)}
- %span by #{commit.author_name}
- %pre #{commit.safe_message}
+ %strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)}
+ %div
+ %span by #{commit.author_name}
+ %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
+ %pre.commit-message
+ = commit.safe_message
-%h4 Changes:
-- @diffs.each do |diff|
- %li
- %strong
- - if diff.old_path == diff.new_path
- = diff.new_path
- - elsif diff.new_path && diff.old_path
- #{diff.old_path} → #{diff.new_path}
- - else
- = diff.new_path || diff.old_path
- %hr
- %pre
- = diff.diff
- %br
+%h4 #{pluralize @diffs.count, "changed file"}:
+
+%ul
+ - @diffs.each_with_index do |diff, i|
+ %li.file-stats
+ %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" }
+ - if diff.deleted_file
+ %span.deleted-file
+ −
+ = diff.old_path
+ - elsif diff.renamed_file
+ = diff.old_path
+ →
+ = diff.new_path
+ - elsif diff.new_file
+ %span.new-file
+ +
+ = diff.new_path
+ - else
+ = diff.new_path
+
+- unless @disable_diffs
+ %h4 Changes:
+ - @diffs.each_with_index do |diff, i|
+ %li{id: "diff-#{i}"}
+ %a{href: @target_url + "#diff-#{i}"}
+ - if diff.deleted_file
+ %strong
+ = diff.old_path
+ deleted
+ - elsif diff.renamed_file
+ %strong
+ = diff.old_path
+ →
+ %strong
+ = diff.new_path
+ - else
+ %strong
+ = diff.new_path
+ %hr
+ %pre
+ = color_email_diff(diff.diff)
+ %br
- if @compare.timeout
%h5 Huge diff. To prevent performance issues changes are hidden
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index 6f5f9eda2c..8d67a42234 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -1,25 +1,47 @@
-#{@author.name} pushed to #{@branch} at #{link_to @project.name_with_namespace, project_url(@project)}
-
+#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
\
-Commits:
+\
+- if @reverse_compare
+ WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below.
+ \
+ \
+= @reverse_compare ? "Deleted commits:" : "Commits:"
- @commits.each do |commit|
- #{link_to commit.short_id, project_commit_url(@project, commit)} by #{commit.author_name}
+ #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
#{commit.safe_message}
\- - - - -
\
\
-Changes:
-- @diffs.each do |diff|
- \
- \=====================================
- - if diff.old_path == diff.new_path
- = diff.new_path
- - elsif diff.new_path && diff.old_path
- #{diff.old_path} → #{diff.new_path}
- - else
- = diff.new_path || diff.old_path
- \=====================================
- != diff.diff
+#{pluralize @diffs.count, "changed file"}:
\
+- @diffs.each do |diff|
+ - if diff.deleted_file
+ \- − #{diff.old_path}
+ - elsif diff.renamed_file
+ \- #{diff.old_path} → #{diff.new_path}
+ - elsif diff.new_file
+ \- + #{diff.new_path}
+ - else
+ \- #{diff.new_path}
+- unless @disable_diffs
+ \
+ \
+ Changes:
+ - @diffs.each do |diff|
+ \
+ \=====================================
+ - if diff.deleted_file
+ #{diff.old_path} deleted
+ - elsif diff.renamed_file
+ #{diff.old_path} → #{diff.new_path}
+ - else
+ = diff.new_path
+ \=====================================
+ != diff.diff
- if @compare.timeout
+ \
+ \
Huge diff. To prevent performance issues it was hidden
+\
+\
+View it on GitLab: #{@target_url}
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a21dcff41c..6bafcb5655 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,5 +1,5 @@
%h3.page-title
- Account settings
+ Account Settings
%p.light
You can change your username and private token here.
- if current_user.ldap_user?
@@ -10,7 +10,7 @@
.account-page
%fieldset.update-token
%legend
- Private token
+ Reset Private token
%div
= form_for @user, url: reset_private_token_profile_path, method: :put do |f|
.data
@@ -25,7 +25,7 @@
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "form-control"
%div
- = f.submit 'Reset', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token"
+ = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
= f.submit 'Generate', class: "btn success btn-build-token"
@@ -43,7 +43,7 @@
- if show_profile_username_tab?
%fieldset.update-username
%legend
- Username
+ Change Username
= form_for @user, url: update_username_profile_path, method: :put, remote: true do |f|
%p
Changing your username will change path to all personal projects!
@@ -57,7 +57,7 @@
%p.light
= user_url(@user)
%div
- = f.submit 'Save username', class: "btn btn-save"
+ = f.submit 'Save username', class: "btn btn-warning"
- if show_profile_remove_tab?
%fieldset.remove-account
@@ -75,3 +75,4 @@
The following groups will be abandoned. You should transfer or remove them:
%strong #{current_user.solo_owned_groups.map(&:name).join(', ')}
= link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove"
+
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
new file mode 100644
index 0000000000..97e98948f3
--- /dev/null
+++ b/app/views/profiles/applications.html.haml
@@ -0,0 +1,49 @@
+%h3.page-title
+ Application Settings
+%p.light
+ OAuth2 protocol settings below.
+
+%fieldset.oauth-applications
+ %legend Your applications
+ %p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
+ - if @applications.any?
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th Clients
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td
+ - application.redirect_uri.split.each do |uri|
+ %div= uri
+ %td= application.access_tokens.count
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
+ %td= render 'doorkeeper/applications/delete_form', application: application
+
+%fieldset.oauth-authorized-applications.prepend-top-20
+ %legend Authorized applications
+
+ - if @authorized_tokens.any?
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Authorized At
+ %th Scope
+ %th
+ %tbody
+ - @authorized_apps.each do |app|
+ - token = app.authorized_tokens.order('created_at desc').first
+ %tr{:id => "application_#{app.id}"}
+ %td= app.name
+ %td= token.created_at
+ %td= token.scopes
+ %td= render 'doorkeeper/authorized_applications/delete_form', application: app
+ - else
+ %p.light You dont have any authorized applications
diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml
index 0d8075b7d4..cc00d08d03 100644
--- a/app/views/profiles/design.html.haml
+++ b/app/views/profiles/design.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
- My appearance settings
+ Design Settings
%p.light
- Appearance settings saved to your profile and available across all devices
+ Appearance settings will be saved to your profile and made available across all devices.
%hr
= form_for @user, url: profile_path, remote: true, method: :put do |f|
@@ -33,6 +33,11 @@
.prev.violet
= f.radio_button :theme_id, 5
Violet
+
+ = label_tag do
+ .prev.blue
+ = f.radio_button :theme_id, 6
+ Blue
%br
.clearfix
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index ca980db2f3..9d8f33cbba 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,9 +1,13 @@
%h3.page-title
- My email addresses
+ Email Settings
%p.light
Your
%b Primary Email
- will be used for account notifications, avatar detection and web based operations, such as edits and merges.
+ will be used for avatar detection and web based operations, such as edits and merges.
+ %br
+ Your
+ %b Notification Email
+ will be used for account notifications.
%br
All email addresses will be used to identify your commits.
@@ -21,7 +25,7 @@
%strong= email.email
%span.cgray
added #{time_ago_with_tooltip(email.created_at)}
- = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-small btn-remove pull-right'
+ = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right'
%h4 Add email address
= form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f|
@@ -30,4 +34,4 @@
.col-sm-10
= f.text_field :email, class: 'form-control'
.form-actions
- = f.submit 'Add', class: 'btn btn-create'
+ = f.submit 'Add email address', class: 'btn btn-create'
diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml
deleted file mode 100644
index e9ffca8faf..0000000000
--- a/app/views/profiles/groups/index.html.haml
+++ /dev/null
@@ -1,39 +0,0 @@
-%h3.page-title
- Group membership
- - if current_user.can_create_group?
- %span.pull-right
- = link_to new_group_path, class: "btn btn-new" do
- %i.fa.fa-plus
- New Group
-%p.light
- Group members have access to all a group's projects
-%hr
-.panel.panel-default
- .panel-heading
- %strong Groups
- (#{@user_groups.count})
- %ul.well-list
- - @user_groups.each do |user_group|
- - group = user_group.group
- %li
- .pull-right
- - if can?(current_user, :manage_group, group)
- = link_to edit_group_path(group), class: "btn-small btn btn-grouped" do
- %i.fa.fa-cogs
- Settings
-
- - if can?(current_user, :destroy, user_group)
- = link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn btn-grouped", title: 'Remove user from group' do
- %i.fa.fa-sign-out
- Leave
-
- = link_to group, class: 'group-name' do
- %strong= group.name
-
- as
- %strong #{user_group.human_access}
-
- %div.light
- #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
-
-= paginate @user_groups
diff --git a/app/views/profiles/history.html.haml b/app/views/profiles/history.html.haml
index 3951c47b5f..9cafe03b8b 100644
--- a/app/views/profiles/history.html.haml
+++ b/app/views/profiles/history.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
- Account history
+ My Account History
%p.light
- All events created by your account are listed here
+ All events created by your account are listed below.
%hr
.profile_history
= render @events
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 81411a7565..fe5770f45c 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -1,9 +1,12 @@
-%li
- = link_to profile_key_path(key) do
- %strong= key.title
- %span
- (#{key.fingerprint})
- %span.cgray
- added #{time_ago_with_tooltip(key.created_at)}
-
- = link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right"
+%tr
+ %td
+ = link_to path_to_key(key, is_admin) do
+ %strong= key.title
+ %td
+ %span
+ (#{key.fingerprint})
+ %td
+ %span.cgray
+ added #{time_ago_with_tooltip(key.created_at)}
+ %td
+ = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right"
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
new file mode 100644
index 0000000000..8bac22a2e1
--- /dev/null
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -0,0 +1,22 @@
+- is_admin = defined?(admin) ? true : false
+.row
+ .col-md-4
+ .panel.panel-default
+ .panel-heading
+ SSH Key
+ %ul.well-list
+ %li
+ %span.light Title:
+ %strong= @key.title
+ %li
+ %span.light Created on:
+ %strong= @key.created_at.stamp("Aug 21, 2011")
+
+ .col-md-8
+ %p
+ %span.light Fingerprint:
+ %strong= @key.fingerprint
+ %pre.well-pre
+ = @key.key
+ .pull-right
+ = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml
new file mode 100644
index 0000000000..ef0075aad3
--- /dev/null
+++ b/app/views/profiles/keys/_key_table.html.haml
@@ -0,0 +1,19 @@
+- is_admin = defined?(admin) ? true : false
+.panel.panel-default
+ - if @keys.any?
+ %table.table
+ %thead.panel-heading
+ %tr
+ %th Title
+ %th Fingerprint
+ %th Added at
+ %th
+ %tbody
+ - @keys.each do |key|
+ = render 'profiles/keys/key', key: key, is_admin: is_admin
+ - else
+ .nothing-here-block
+ - if is_admin
+ User has no ssh keys
+ - else
+ There are no SSH keys with access to your account.
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index a322f82f23..965d5e032f 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,22 +1,12 @@
%h3.page-title
- My SSH keys
+ SSH Keys Settings
.pull-right
= link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
%p.light
- SSH keys allow you to establish a secure connection between your computer and GitLab
+ My SSH keys: #{@keys.count}
%br
Before you can add an SSH key you need to
- = link_to "generate it", help_page_path("ssh", "ssh")
+ = link_to "generate it.", help_page_path("ssh", "README")
%hr
-
-.panel.panel-default
- .panel-heading
- SSH Keys (#{@keys.count})
- %ul.well-list#keys-table
- = render @keys
- - if @keys.blank?
- %li
- .nothing-here-block There are no SSH keys with access to your account.
-
-
+= render 'key_table'
diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
index c02b47b0ad..ccec716d0c 100644
--- a/app/views/profiles/keys/new.html.haml
+++ b/app/views/profiles/keys/new.html.haml
@@ -8,9 +8,9 @@
$('#key_key').on('focusout', function(){
var title = $('#key_title'),
val = $('#key_key').val(),
- key_mail = val.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+|\.[a-zA-Z0-9._-]+)/gi);
+ comment = val.match(/^\S+ \S+ (.+)$/);
- if( key_mail && key_mail.length > 0 && title.val() == '' ){
- $('#key_title').val( key_mail );
+ if( comment && comment.length > 1 && title.val() == '' ){
+ $('#key_title').val( comment[1] );
}
});
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index c4fc1bb269..cfd5329896 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,22 +1 @@
-.row
- .col-md-4
- .panel.panel-default
- .panel-heading
- SSH Key
- %ul.well-list
- %li
- %span.light Title:
- %strong= @key.title
- %li
- %span.light Created on:
- %strong= @key.created_at.stamp("Aug 21, 2011")
-
- .col-md-8
- %p
- %span.light Fingerprint:
- %strong= @key.fingerprint
- %pre.well-pre
- = @key.key
-
-.pull-right
- = link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
+= render "key_details"
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index f84de4430c..273e72f8a4 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,49 +1,76 @@
%h3.page-title
- Notifications settings
+ Notifications Settings
%p.light
- GitLab uses the email specified in your profile for notifications
+ These are your global notification settings.
%hr
-= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications form-horizontal global-notifications-form' do
+
+= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f|
+ -if @user.errors.any?
+ %div.alert.alert-danger
+ %ul
+ - @user.errors.full_messages.each do |msg|
+ %li= msg
+
= hidden_field_tag :notification_type, 'global'
- = label_tag :notification_level, 'Notification level', class: 'control-label'
- .col-sm-10
- .radio
- = label_tag nil, class: '' do
- = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit'
- .level-title
- Disabled
- %p You will not get any notifications via email
+ .form-group
+ = f.label :notification_email, class: "control-label"
+ .col-sm-10
+ = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "form-control"
- .radio
- = label_tag nil, class: '' do
- = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit'
- .level-title
- Participating
- %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
+ .form-group
+ = f.label :notification_level, class: 'control-label'
+ .col-sm-10
+ .radio
+ = f.label :notification_level, value: Notification::N_DISABLED do
+ = f.radio_button :notification_level, Notification::N_DISABLED
+ .level-title
+ Disabled
+ %p You will not get any notifications via email
- .radio
- = label_tag nil, class: '' do
- = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit'
- .level-title
- Watch
- %p You will receive all notifications from projects in which you participate
+ .radio
+ = f.label :notification_level, value: Notification::N_MENTION do
+ = f.radio_button :notification_level, Notification::N_MENTION
+ .level-title
+ Mention
+ %p You will receive notifications only for comments in which you were @mentioned
+
+ .radio
+ = f.label :notification_level, value: Notification::N_PARTICIPATING do
+ = f.radio_button :notification_level, Notification::N_PARTICIPATING
+ .level-title
+ Participating
+ %p You will only receive notifications from related resources (e.g. from your commits or assigned issues)
+
+ .radio
+ = f.label :notification_level, value: Notification::N_WATCH do
+ = f.radio_button :notification_level, Notification::N_WATCH
+ .level-title
+ Watch
+ %p You will receive all notifications from projects in which you participate
+
+ .form-actions
+ = f.submit 'Save changes', class: "btn btn-create"
.clearfix
%hr
- %p
- You can also specify notification level per group or per project
- %br
- By default all projects and groups uses notification level set above
.row.all-notifications
.col-md-6
+ %p
+ You can also specify notification level per group or per project.
+ %br
+ By default, all projects and groups will use the notification level set above.
%h4 Groups:
%ul.bordered-list
- - @group_members.each do |users_group|
- - notification = Notification.new(users_group)
- = render 'settings', type: 'group', membership: users_group, notification: notification
+ - @group_members.each do |group_member|
+ - notification = Notification.new(group_member)
+ = render 'settings', type: 'group', membership: group_member, notification: notification
.col-md-6
+ %p
+ To specify the notification level per project of a group you belong to,
+ %br
+ you need to be a member of the project itself, not only its group.
%h4 Projects:
%ul.bordered-list
- @project_members.each do |project_member|
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 2a7d317aa3..4b04b113e8 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,25 +1,30 @@
-%h3.page-title Password
+%h3.page-title Password Settings
%p.light
- Change your password or recover your current one.
+ - if @user.password_automatically_set?
+ Set your password.
+ - else
+ Change your password or recover your current one.
%hr
.update-password
= form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f|
%div
%p.slead
- You must provide current password in order to change it.
- %br
- After a successful password update you will be redirected to login page where you should login with your new password
+ - unless @user.password_automatically_set?
+ You must provide current password in order to change it.
+ %br
+ After a successful password update, you will be redirected to the login page where you can log in with your new password.
-if @user.errors.any?
.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
- .form-group
- = f.label :current_password, class: 'control-label'
- .col-sm-10
- = f.password_field :current_password, required: true, class: 'form-control'
- %div
- = link_to "Forgot your password?", reset_profile_password_path, method: :put
+ - unless @user.password_automatically_set?
+ .form-group
+ = f.label :current_password, class: 'control-label'
+ .col-sm-10
+ = f.password_field :current_password, required: true, class: 'form-control'
+ %div
+ = link_to "Forgot your password?", reset_profile_password_path, method: :put
.form-group
= f.label :password, 'New password', class: 'control-label'
@@ -30,4 +35,4 @@
.col-sm-10
= f.password_field :password_confirmation, required: true, class: 'form-control'
.form-actions
- = f.submit 'Save password', class: "btn btn-save"
+ = f.submit 'Save password', class: "btn btn-create"
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index aef7348fd2..8bed6e0dbe 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -10,10 +10,11 @@
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
-
- .form-group
- = f.label :current_password, class: 'control-label'
- .col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
+
+ - unless @user.password_automatically_set?
+ .form-group
+ = f.label :current_password, class: 'control-label'
+ .col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
.form-group
= f.label :password, class: 'control-label'
.col-sm-10= f.password_field :password, required: true, class: 'form-control'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index d6b52f8615..409b6b5a19 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
- Profile settings
+ Profile Settings
%p.light
- This information appears on your profile.
+ This information will appear on your profile.
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
%hr
@@ -56,7 +56,7 @@
.form-group
= f.label :bio, class: "control-label"
.col-sm-10
- = f.text_area :bio, rows: 6, class: "form-control", maxlength: 250
+ = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250
%span.help-block Tell us about yourself in fewer than 250 characters.
.col-md-5
@@ -77,22 +77,26 @@
%br
or change it at #{link_to "gravatar.com", "http://gravatar.com"}
%hr
- %a.choose-btn.btn.btn-small.js-choose-user-avatar-button
+ %a.choose-btn.btn.btn-sm.js-choose-user-avatar-button
%i.fa.fa-paperclip
%span Choose File ...
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-user-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ .light The maximum file size allowed is 200KB.
- if @user.avatar?
%hr
- = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
+ = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
- if @user.public_profile?
- .bs-callout.bs-callout-info
+ .alert.alert-info
%h4 Public profile
%p Your profile is publicly visible because you joined public project(s)
- .form-actions
- = f.submit 'Save changes', class: "btn btn-save"
+ .row
+ .col-md-7
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/profiles/update.js.erb b/app/views/profiles/update.js.erb
index 04b5cf4827..e664ac2a52 100644
--- a/app/views/profiles/update.js.erb
+++ b/app/views/profiles/update.js.erb
@@ -1,6 +1,6 @@
// Remove body class for any previous theme, re-add current one
-$('body').removeClass('ui_basic ui_mars ui_modern ui_gray ui_color')
-$('body').addClass('<%= app_theme %>')
+$('body').removeClass('ui_basic ui_mars ui_modern ui_gray ui_color light_theme dark_theme')
+$('body').addClass('<%= app_theme %> <%= theme_type %>')
// Re-render the header to reflect the new theme
$('header').html('<%= escape_javascript(render("layouts/head_panel", title: "Profile")) %>')
diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml
new file mode 100644
index 0000000000..07d4d60276
--- /dev/null
+++ b/app/views/projects/_bitbucket_import_modal.html.haml
@@ -0,0 +1,13 @@
+%div#bitbucket_import_modal.modal.hide
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 Import projects from Bitbucket
+ .modal-body
+ To enable importing projects from Bitbucket,
+ - if current_user.admin?
+ you need to
+ - else
+ your GitLab administrator needs to
+ == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md'}.
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
new file mode 100644
index 0000000000..fd8320adb8
--- /dev/null
+++ b/app/views/projects/_commit_button.html.haml
@@ -0,0 +1,9 @@
+.form-actions
+ .commit-button-annotation
+ = button_tag 'Commit Changes',
+ class: 'btn commit-btn js-commit-button btn-create'
+ .message
+ to branch
+ %strong= ref
+ = link_to 'Cancel', cancel_path,
+ class: 'btn btn-cancel', data: {confirm: leave_edit_message}
diff --git a/app/views/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml
index 6ff4697033..f4f4c2662c 100644
--- a/app/views/projects/_dropdown.html.haml
+++ b/app/views/projects/_dropdown.html.haml
@@ -9,24 +9,24 @@
New issue
- if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project)
%li
- = link_to new_project_merge_request_path(@project), title: "New Merge Request" do
+ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), title: "New Merge Request" do
New merge request
- if @project.snippets_enabled && can?(current_user, :write_snippet, @project)
%li
- = link_to new_project_snippet_path(@project), title: "New Snippet" do
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
New snippet
- - if can?(current_user, :admin_team_member, @project)
+ - if can?(current_user, :admin_project_member, @project)
%li
- = link_to new_project_team_member_path(@project), title: "New project member" do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do
New project member
- if can? current_user, :push_code, @project
%li.divider
%li
- = link_to new_project_branch_path(@project) do
+ = link_to new_namespace_project_branch_path(@project.namespace, @project) do
%i.fa.fa-code-fork
Git branch
%li
- = link_to new_project_tag_path(@project) do
+ = link_to new_namespace_project_tag_path(@project.namespace, @project) do
%i.fa.fa-tag
Git tag
diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml
new file mode 100644
index 0000000000..e88a0f7d68
--- /dev/null
+++ b/app/views/projects/_github_import_modal.html.haml
@@ -0,0 +1,13 @@
+%div#github_import_modal.modal.hide
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 Import projects from GitHub
+ .modal-body
+ To enable importing projects from GitHub,
+ - if current_user.admin?
+ you need to
+ - else
+ your GitLab administrator needs to
+ == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md'}.
\ No newline at end of file
diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml
new file mode 100644
index 0000000000..52212b6ae0
--- /dev/null
+++ b/app/views/projects/_gitlab_import_modal.html.haml
@@ -0,0 +1,13 @@
+%div#gitlab_import_modal.modal.hide
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3 Import projects from GitLab.com
+ .modal-body
+ To enable importing projects from GitLab.com,
+ - if current_user.admin?
+ you need to
+ - else
+ your GitLab administrator needs to
+ == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md'}.
\ No newline at end of file
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 672a91e0ee..c0e13e67be 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,26 +1,28 @@
- empty_repo = @project.empty_repo?
.project-home-panel{:class => ("empty-project" if empty_repo)}
+ .project-identicon-holder
+ = project_icon(@project, alt: '', class: 'avatar project-avatar')
.project-home-row
.project-home-desc
- if @project.description.present?
= escaped_autolink(@project.description)
- if can?(current_user, :admin_project, @project)
–
- = link_to 'Edit', edit_project_path
+ = link_to 'Edit', edit_namespace_project_path
- elsif !@project.empty_repo? && @repository.readme
- readme = @repository.readme
–
- = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
= readme.name
.star-fork-buttons
- unless @project.empty_repo?
.fork-buttons
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- - if current_user.already_forked?(@project)
- = link_to project_path(current_user.fork_of(@project)), title: 'Go to my fork' do
+ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to my fork' do
= link_to_toggle_fork
- else
- = link_to fork_project_path(@project), title: "Fork project", method: "POST" do
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project" do
= link_to_toggle_fork
.star-buttons
@@ -31,7 +33,7 @@
- else
= link_to_toggle_star('You must sign in to star a project.', false, false)
- .project-home-row
+ .project-home-row.hidden-xs
- if current_user && !empty_repo
.project-home-dropdown
= render "dropdown"
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index 6cdfab933b..a7cd129b63 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -14,17 +14,20 @@
.form-group.issuable-description
= f.label :description, 'Description', class: 'control-label'
.col-sm-10
- = render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control'
- .col-sm-12.hint
- .pull-left
- Parsed with
- #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
- .pull-right
- Attach images (JPG, PNG, GIF) by dragging & dropping
- or #{link_to 'selecting them', '#', class: 'markdown-selector' }.
- .clearfix
- .error-alert
+
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render 'projects/zen', f: f, attr: :description,
+ classes: 'description form-control'
+ .col-sm-12.hint
+ .pull-left
+ Parsed with
+ #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
+ .pull-right
+ Attach files by dragging & dropping
+ or #{link_to 'selecting them', '#', class: 'markdown-selector' }.
+
+ .clearfix
+ .error-alert
%hr
.form-group
.issue-assignee
@@ -47,23 +50,32 @@
= f.select(:milestone_id, milestone_options(issuable),
{ include_blank: 'Select milestone' }, { class: 'select2' })
- else
+ .prepend-top-10
%span.light No open milestones available.
- = link_to 'Create new milestone', new_project_milestone_path(issuable.project)
+ - if can? current_user, :admin_milestone, issuable.project
+ = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
.form-group
= f.label :label_ids, class: 'control-label' do
- %i.icon-tag
+ %i.fa.fa-tag
Labels
.col-sm-10
- if issuable.project.labels.any?
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2'
- else
+ .prepend-top-10
%span.light No labels yet.
- = link_to 'Create new label', new_project_label_path(issuable.project)
+ - if can? current_user, :admin_label, issuable.project
+ = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank
.form-actions
+ - if !issuable.project.empty_repo? && contribution_guide_url(issuable.project) && !issuable.persisted?
+ %p
+ Please review the
+ %strong #{link_to 'guidelines for contribution', contribution_guide_url(issuable.project)}
+ to this repository.
- if issuable.new_record?
= f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create'
- else
@@ -72,4 +84,4 @@
- cancel_project = issuable.source_project
- else
- cancel_project = issuable.project
- = link_to 'Cancel', [cancel_project, issuable], class: 'btn btn-cancel'
+ = link_to 'Cancel', [cancel_project.namespace.becomes(Namespace), cancel_project, issuable], class: 'btn btn-cancel'
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
new file mode 100644
index 0000000000..f356a25dbf
--- /dev/null
+++ b/app/views/projects/_md_preview.html.haml
@@ -0,0 +1,13 @@
+%ul.nav.nav-tabs
+ %li.active
+ = link_to '#md-write-holder', class: 'js-md-write-button' do
+ Write
+ %li
+ = link_to '#md-preview-holder', class: 'js-md-preview-button',
+ data: { url: markdown_preview_namespace_project_path(@project.namespace, @project) } do
+ Preview
+%div
+ .md-write-holder
+ = yield
+ .md-preview-holder.hide
+ .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml
index 2008f8c558..281a84a3d3 100644
--- a/app/views/projects/_settings_nav.html.haml
+++ b/app/views/projects/_settings_nav.html.haml
@@ -1,25 +1,31 @@
-%ul.nav.nav-pills.nav-stacked.nav-stacked-menu.append-bottom-20.project-settings-nav
+%ul.project-settings-nav.sidebar-subnav
= nav_link(path: 'projects#edit') do
- = link_to edit_project_path(@project), class: "stat-tab tab " do
+ = link_to edit_project_path(@project), title: 'Project', class: "stat-tab tab " do
%i.fa.fa-pencil-square-o
- Project
- = nav_link(controller: [:team_members, :teams]) do
- = link_to project_team_index_path(@project), class: "team-tab tab" do
+ %span
+ Project
+ = nav_link(controller: [:project_members, :teams]) do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: "team-tab tab" do
%i.fa.fa-users
- Members
+ %span
+ Members
= nav_link(controller: :deploy_keys) do
- = link_to project_deploy_keys_path(@project) do
+ = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
%i.fa.fa-key
- Deploy Keys
+ %span
+ Deploy Keys
= nav_link(controller: :hooks) do
- = link_to project_hooks_path(@project) do
+ = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do
%i.fa.fa-link
- Web Hooks
+ %span
+ Web Hooks
= nav_link(controller: :services) do
- = link_to project_services_path(@project) do
+ = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
%i.fa.fa-cogs
- Services
+ %span
+ Services
= nav_link(controller: :protected_branches) do
- = link_to project_protected_branches_path(@project) do
+ = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
%i.fa.fa-lock
- Protected branches
+ %span
+ Protected branches
diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml
index 5f34e66b3e..42c8e68522 100644
--- a/app/views/projects/_visibility_level.html.haml
+++ b/app/views/projects/_visibility_level.html.haml
@@ -7,8 +7,8 @@
- Gitlab::VisibilityLevel.values.each do |level|
.radio
- restricted = restricted_visibility_levels.include?(level)
- = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted
= label :project_visibility_level, level do
+ = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted
= visibility_level_icon(level)
.option-title
= visibility_level_label(level)
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index 2bbc49e8eb..cf1c55ecca 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -1,7 +1,10 @@
.zennable
- %input#zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
+ %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
.zen-backdrop
- classes << ' js-gfm-input markdown-area'
= f.text_area attr, class: classes, placeholder: 'Leave a comment'
- %label{ for: 'zen-toggle-comment', class: 'expand' } Edit in fullscreen
- %label{ for: 'zen-toggle-comment', class: 'collapse' }
+ = link_to nil, class: 'zen-enter-link', tabindex: '-1' do
+ %i.fa.fa-expand
+ Edit in fullscreen
+ = link_to nil, class: 'zen-leave-link' do
+ %i.fa.fa-compress
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index e5cde488c3..e6a859fea8 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -4,10 +4,11 @@
.file-holder
.file-title
%i.fa.fa-file
- %span.file_name
+ %strong
= @path
- %small= number_to_human_size @blob.size
- %span.options= render "projects/blob/actions"
+ %small= number_to_human_size @blob.size
+ .file-actions
+ = render "projects/blob/actions"
.file-content.blame.highlight
%table
- @blame.each do |commit, lines, since|
@@ -15,20 +16,20 @@
%tr
%td.blame-commit
%span.commit
- = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id"
+ = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "commit_short_id"
= commit_author_link(commit, avatar: true, size: 16)
- = link_to_gfm truncate(commit.title, length: 20), project_commit_path(@project, commit.id), class: "row_title"
+ = link_to_gfm truncate(commit.title, length: 20), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "row_title"
%td.lines.blame-numbers
%pre
- (since...(since + lines.count)).each do |i|
= i
\
%td.lines
- %pre
- %code{ class: highlightjs_class(@blob.name) }
+ %pre{class: 'code highlight white'}
+ %code
:erb
<% lines.each do |line| %>
- <%= line %>
+ <%= highlight(@blob.name, line, true).html_safe %>
<% end %>
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index 64c19a5780..13f8271b97 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -1,27 +1,22 @@
.btn-group.tree-btn-group
- -# only show edit link for text files
- - if @blob.text?
- - if allowed_tree_edit?
- = link_to 'Edit', project_edit_tree_path(@project, @id),
- class: 'btn btn-small'
- - else
- %span.btn.btn-small.disabled Edit
- = link_to 'Raw', project_raw_path(@project, @id),
- class: 'btn btn-small', target: '_blank'
+ = edit_blob_link(@project, @ref, @path)
+ = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
+ class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files
- if @blob.text?
- - if current_page? project_blame_path(@project, @id)
- = link_to 'Normal View', project_blob_path(@project, @id),
- class: 'btn btn-small'
+ - if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
+ = link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
+ class: 'btn btn-sm'
- else
- = link_to 'Blame', project_blame_path(@project, @id),
- class: 'btn btn-small' unless @blob.empty?
- = link_to 'History', project_commits_path(@project, @id),
- class: 'btn btn-small'
+ = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id),
+ class: 'btn btn-sm' unless @blob.empty?
+ = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
+ class: 'btn btn-sm'
- if @ref != @commit.sha
- = link_to 'Permalink', project_blob_path(@project,
- tree_join(@commit.sha, @path)), class: 'btn btn-small'
+ = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
+ tree_join(@commit.sha, @path)), class: 'btn btn-sm'
- if allowed_tree_edit?
- = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do
+ = button_tag class: 'remove-blob btn btn-sm btn-remove',
+ 'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do
Remove
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 492dff437b..ba60bd9286 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -1,32 +1,34 @@
%ul.breadcrumb.repo-breadcrumb
%li
%i.fa.fa-angle-right
- = link_to project_tree_path(@project, @ref) do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
- tree_breadcrumbs(@tree, 6) do |title, path|
%li
- if path
- if path.end_with?(@path)
- = link_to project_blob_path(@project, path) do
+ = link_to namespace_project_blob_path(@project.namespace, @project, path) do
%strong
= truncate(title, length: 40)
- else
- = link_to truncate(title, length: 40), project_tree_path(@project, path)
+ = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
= link_to title, '#'
-%ul.blob-commit-info.bs-callout.bs-callout-info.hidden-xs
- - blob_commit = @repository.last_commit_for_path(@commit.id, @blob.path)
+%ul.blob-commit-info.well.hidden-xs
+ - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project
%div#tree-content-holder.tree-content-holder
%article.file-holder
- .file-title.clearfix
+ .file-title
%i.fa.fa-file
- %span.file_name
+ %strong
= blob.name
- %small= number_to_human_size blob.size
- %span.options.hidden-xs= render "actions"
+ %small
+ = number_to_human_size(blob.size)
+ .file-actions.hidden-xs
+ = render "actions"
- if blob.text?
= render "text", blob: blob
- elsif blob.image?
diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml
index c24eeea493..f2c5e95ecf 100644
--- a/app/views/projects/blob/_download.html.haml
+++ b/app/views/projects/blob/_download.html.haml
@@ -1,6 +1,6 @@
.file-content.blob_file.blob-no-preview
.center
- = link_to project_raw_path(@project, @id) do
+ = link_to namespace_project_raw_path(@project.namespace, @project, @id) do
%h1.light
%i.fa.fa-download
%h4
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
new file mode 100644
index 0000000000..96f188e4aa
--- /dev/null
+++ b/app/views/projects/blob/_editor.html.haml
@@ -0,0 +1,25 @@
+.file-holder.file
+ .file-title
+ .editor-ref
+ %i.fa.fa-code-fork
+ = ref
+ %span.editor-file-name
+ - if @path
+ %span.monospace
+ = @path
+
+ - if current_action?(:new) || current_action?(:create)
+ \/
+ = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
+ required: true, class: 'form-control new-file-name'
+ .pull-right
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
+
+ .file-content.code
+ %pre.js-edit-mode-pane#editor
+ = params[:content] || local_assigns[:blob_data]
+ - if local_assigns[:path]
+ .js-edit-mode-pane#preview.hide
+ .center
+ %h2
+ %i.icon-spinner.icon-spin
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index da84dc4b6c..09559a4967 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -9,13 +9,13 @@
%strong= @ref
.modal-body
- = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal' do
+ = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal' do
= render 'shared/commit_message_container', params: params,
placeholder: 'Removed this file because...'
.form-group
.col-sm-2
.col-sm-10
- = submit_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
+ = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml
index 7cbea7c3eb..f6bd62f239 100644
--- a/app/views/projects/blob/_text.html.haml
+++ b/app/views/projects/blob/_text.html.haml
@@ -8,6 +8,6 @@
- else
.file-content.code
- unless blob.empty?
- = render 'shared/file_hljs', blob: blob
+ = render 'shared/file_highlight', blob: blob
- else
.nothing-here-block Empty file
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
new file mode 100644
index 0000000000..1f61a0b940
--- /dev/null
+++ b/app/views/projects/blob/edit.html.haml
@@ -0,0 +1,31 @@
+.file-editor
+ %ul.nav.nav-tabs.js-edit-mode
+ %li.active
+ = link_to '#editor' do
+ %i.fa.fa-edit
+ Edit file
+
+ %li
+ = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
+ %i.fa.fa-eye
+ = editing_preview_title(@blob.name)
+
+ = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: "form-horizontal") do
+ = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
+ = render 'shared/commit_message_container', params: params,
+ placeholder: "Update #{@blob.name}"
+
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
+
+ = hidden_field_tag 'last_commit', @last_commit
+ = hidden_field_tag 'content', '', id: "file-content"
+ = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
+ = render 'projects/commit_button', ref: @ref,
+ cancel_path: @after_edit_path
+
+:javascript
+ blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
new file mode 100644
index 0000000000..d78a01f642
--- /dev/null
+++ b/app/views/projects/blob/new.html.haml
@@ -0,0 +1,19 @@
+%h3.page-title New file
+.file-editor
+ = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file') do
+ = render 'projects/blob/editor', ref: @ref
+ = render 'shared/commit_message_container', params: params,
+ placeholder: 'Add new file'
+
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
+
+ = hidden_field_tag 'content', '', id: 'file-content'
+ = render 'projects/commit_button', ref: @ref,
+ cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
+
+:javascript
+ blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
diff --git a/app/views/projects/edit_tree/preview.html.haml b/app/views/projects/blob/preview.html.haml
similarity index 100%
rename from app/views/projects/edit_tree/preview.html.haml
rename to app/views/projects/blob/preview.html.haml
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 8e58f3c247..0de8c509f2 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,7 +1,7 @@
- commit = @repository.commit(branch.target)
%li(class="js-branch-#{branch.name}")
%h4
- = link_to project_tree_path(@project, branch.name) do
+ = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
%strong.str-truncated= branch.name
- if branch.name == @repository.root_ref
%span.label.label-info default
@@ -11,14 +11,14 @@
protected
.pull-right
- if can?(current_user, :download_code, @project)
- = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-small'
+ = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-sm'
- if branch.name != @repository.root_ref
- = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-small', method: :post, title: "Compare" do
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-sm', method: :post, title: "Compare" do
%i.fa.fa-files-o
Compare
- if can_remove_branch?(@project, branch.name)
- = link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
+ = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-sm btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do
%i.fa.fa-trash-o
- if commit
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 9f2b1b5929..a313ffcf27 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -3,12 +3,12 @@
Branches
.pull-right
- if can? current_user, :push_code, @project
- = link_to new_project_branch_path(@project), class: 'btn btn-create' do
+ = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
%i.fa.fa-add-sign
New branch
.dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
= @sort.humanize
@@ -17,12 +17,12 @@
%b.caret
%ul.dropdown-menu
%li
- = link_to project_branches_path(sort: nil) do
+ = link_to namespace_project_branches_path(sort: nil) do
Name
- = link_to project_branches_path(sort: 'recently_updated') do
- Recently updated
- = link_to project_branches_path(sort: 'last_updated') do
- Last updated
+ = link_to namespace_project_branches_path(sort: 'recently_updated') do
+ = sort_title_recently_updated
+ = link_to namespace_project_branches_path(sort: 'last_updated') do
+ = sort_title_oldest_updated
%hr
- unless @branches.empty?
%ul.bordered-list.top-list.all-branches
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index f5a530d95f..e5fcb98c68 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -5,7 +5,7 @@
%h3.page-title
%i.fa.fa-code-fork
New branch
-= form_tag project_branches_path, method: :post, class: "form-horizontal" do
+= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal" do
.form-group
= label_tag :branch_name, 'Name for new branch', class: 'control-label'
.col-sm-10
@@ -15,10 +15,11 @@
.col-sm-10
= text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
.form-actions
- = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
+ = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
+ = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
+ disableButtonIfAnyEmptyField($("#new-branch-form"), ".form-control", ".btn-create");
var availableTags = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 0b6b6af4f9..7409f702c5 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -10,15 +10,15 @@
Download as
%span.caret
%ul.dropdown-menu
- %li= link_to "Email Patches", project_commit_path(@project, @commit, format: :patch)
- %li= link_to "Plain Diff", project_commit_path(@project, @commit, format: :diff)
- = link_to project_tree_path(@project, @commit), class: "btn btn-primary btn-grouped" do
+ %li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
+ %li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
+ = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-primary btn-grouped" do
%span Browse Code »
%div
%p
%span.light Commit
- = link_to @commit.id, project_commit_path(@project, @commit)
+ = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit)
.commit-info-row
%span.light Authored by
%strong
@@ -35,20 +35,10 @@
.commit-info-row
%span.cgray= pluralize(@commit.parents.count, "parent")
- @commit.parents.each do |parent|
- = link_to parent.id[0...10], project_commit_path(@project, parent)
+ = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent)
-- if @branches.any?
- .commit-info-row
- %span.cgray
- Exists in
- %span
- - branch = commit_default_branch(@project, @branches)
- = link_to(branch, project_tree_path(@project, branch))
- - if @branches.any?
- and in
- = link_to("#{pluralize(@branches.count, "other branch")}", "#", class: "js-details-expand")
- %span.js-details-content.hide
- = commit_branches_links(@project, @branches)
+.commit-info-row.branches
+ %i.fa.fa-spinner.fa-spin
.commit-box
%h3.commit-title
@@ -56,3 +46,7 @@
- if @commit.description.present?
%pre.commit-description
= preserve(gfm(escape_once(@commit.description)))
+
+:coffeescript
+ $ ->
+ $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}")
diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml
new file mode 100644
index 0000000000..82aac1fbd1
--- /dev/null
+++ b/app/views/projects/commit/branches.html.haml
@@ -0,0 +1,16 @@
+- if @branches.any?
+ %span
+ - branch = commit_default_branch(@project, @branches)
+ = link_to(namespace_project_tree_path(@project.namespace, @project, branch)) do
+ %span.label.label-gray
+ %i.fa.fa-code-fork
+ = branch
+ - if @branches.any? || @tags.any?
+ = link_to("#", class: "js-details-expand") do
+ %span.label.label-gray
+ \...
+ %span.js-details-content.hide
+ - if @branches.any?
+ = commit_branches_links(@project, @branches)
+ - if @tags.any?
+ = commit_tags_links(@project, @tags)
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 68852ba973..4c853f577e 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -1,13 +1,12 @@
%li.commit.js-toggle-container
.commit-row-title
- = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
-
- %span.str-truncated
- = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
+ %strong.str-truncated
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description?
%a.text-expander.js-toggle-button ...
- = link_to_browse_code(project, commit)
+ .pull-right
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
.notes_count
- if @note_counts
@@ -17,8 +16,9 @@
- note_count = notes.count
- if note_count > 0
- %span.label.label-gray
- %i.fa.fa-comment= note_count
+ %span.light
+ %i.fa.fa-comments
+ = note_count
- if commit.description?
.commit-row-description.js-toggle-content
@@ -26,6 +26,8 @@
= preserve(gfm(escape_once(commit.description)))
.commit-row-info
- = commit_author_link(commit, avatar: true, size: 16)
+ = commit_author_link(commit, avatar: true, size: 24)
+ authored
.committed_ago
#{time_ago_with_tooltip(commit.committed_date)}
+ = link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index d57659065a..0cd9ce1f37 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -1,11 +1,15 @@
+- unless defined?(project)
+ - project = @project
+
- @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
.row.commits-row
- .col-md-2
- %h4
+ .col-md-2.hidden-xs.hidden-sm
+ %h5.commits-row-date
%i.fa.fa-calendar
%span= day.stamp("28 Aug, 2010")
- %p= pluralize(commits.count, 'commit')
- .col-md-10
+ .light
+ = pluralize(commits.count, 'commit')
+ .col-md-10.col-sm-12
%ul.bordered-list
- = render commits, project: @project
+ = render commits, project: project
%hr.lists-separator
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index 0c9d906481..83e4d24cf5 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,15 +1,15 @@
%ul.nav.nav-tabs
= nav_link(controller: [:commit, :commits]) do
- = link_to 'Commits', project_commits_path(@project, @repository.root_ref)
+ = link_to 'Commits', namespace_project_commits_path(@project.namespace, @project, @repository.root_ref)
= nav_link(controller: :compare) do
- = link_to 'Compare', project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref)
+ = link_to 'Compare', namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref)
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project) do
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
Branches
%span.badge.js-totalbranch-count= @repository.branches.size
= nav_link(controller: :tags) do
- = link_to project_tags_path(@project) do
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
Tags
%span.badge.js-totaltags-count= @repository.tags.length
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
index b36369b428..c03bc3f9df 100644
--- a/app/views/projects/commits/_inline_commit.html.haml
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -1,8 +1,8 @@
%li.commit.inline-commit
.commit-row-title
- = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id"
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
%span.str-truncated
- = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
.pull-right
#{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 32c82edb24..9211de72b1 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -1,15 +1,15 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Recent commits to #{@project.name}:#{@ref}"
- xml.link :href => project_commits_url(@project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => project_commits_url(@project, @ref), :rel => "alternate", :type => "text/html"
- xml.id project_commits_url(@project, @ref)
+ xml.link :href => namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml"
+ xml.link :href => namespace_project_commits_url(@project.namespace, @project, @ref), :rel => "alternate", :type => "text/html"
+ xml.id namespace_project_commits_url(@project.namespace, @project, @ref)
xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any?
@commits.each do |commit|
xml.entry do
- xml.id project_commit_url(@project, :id => commit.id)
- xml.link :href => project_commit_url(@project, :id => commit.id)
+ xml.id namespace_project_commit_url(@project.namespace, @project, :id => commit.id)
+ xml.link :href => namespace_project_commit_url(@project.namespace, @project, :id => commit.id)
xml.title truncate(commit.title, :length => 80)
xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")
xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(commit.author_email)
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 5717c24c27..7ea855e1a4 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -5,17 +5,15 @@
- if current_user && current_user.private_token
.commits-feed-holder.hidden-xs.hidden-sm
- = link_to project_commits_path(@project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'btn' do
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'btn' do
%i.fa.fa-rss
Commits feed
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
- %li.active
- commits
%div{id: dom_id(@project)}
- #commits-list= render "commits"
+ #commits-list= render "commits", project: @project
.clear
= spinner
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index da6157cf1b..dfb1dded9e 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,4 +1,4 @@
-= form_tag project_compare_index_path(@project), method: :post, class: 'form-inline' do
+= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline' do
.clearfix.append-bottom-20
- if params[:to] && params[:from]
= link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
@@ -12,7 +12,7 @@
%span.input-group-addon to
= text_field_tag :to, params[:to], class: "form-control"
- = submit_tag "Compare", class: "btn btn-create commits-compare-btn"
+ = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if compare_to_mr_button?
= link_to compare_mr_path, class: 'prepend-left-10 btn' do
%strong Make a merge request
diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml
deleted file mode 100644
index 89710d3a09..0000000000
--- a/app/views/projects/create.js.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- if @project.saved?
- - if @project.import?
- :plain
- location.href = "#{import_project_path(@project)}";
- - else
- :plain
- location.href = "#{project_path(@project)}";
-- else
- :plain
- $(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
- $('.project-submit').enable();
- $('.save-project-loader').hide();
- $('.project-edit-container').show();
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index a0345dbd9c..a2faa9d5e2 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -1,19 +1,20 @@
%li
.pull-right
- if @available_keys.include?(deploy_key)
- = link_to enable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
+ = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
%i.fa.fa-plus
Enable
- else
- if deploy_key.projects.count > 1
- = link_to disable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do
+ = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
%i.fa.fa-power-off
Disable
- else
- = link_to 'Remove', project_deploy_key_path(@project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-small pull-right"
+ = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-sm pull-right"
- = link_to project_deploy_key_path(deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first, deploy_key) do
+ - key_project = deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first
+ = link_to namespace_project_deploy_key_path(key_project.namespace, key_project, deploy_key) do
%i.fa.fa-key
%strong= deploy_key.title
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 162ef05b36..91675b3738 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,5 +1,5 @@
%div
- = form_for [@project, @key], url: project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f|
-if @key.errors.any?
.alert.alert-danger
%ul
@@ -19,5 +19,5 @@
.form-actions
= f.submit 'Create', class: "btn-create btn"
- = link_to "Cancel", project_deploy_keys_path(@project), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
index 6f475e0b39..c02a18146e 100644
--- a/app/views/projects/deploy_keys/index.html.haml
+++ b/app/views/projects/deploy_keys/index.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
Deploy keys allow read-only access to the repository
- = link_to new_project_deploy_key_path(@project), class: "btn btn-new pull-right", title: "New Deploy Key" do
+ = link_to new_namespace_project_deploy_key_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Deploy Key" do
%i.fa.fa-plus
New Deploy Key
@@ -20,7 +20,7 @@
= render @enabled_keys
- if @enabled_keys.blank?
.light-well
- .nothing-here-block Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one
+ .nothing-here-block Create a #{link_to 'new deploy key', new_namespace_project_deploy_key_path(@project.namespace, @project)} or add an existing one
.col-md-6.available-keys
%h5
%strong Deploy keys
diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml
index c66e6bc69c..405b5bcd0d 100644
--- a/app/views/projects/deploy_keys/show.html.haml
+++ b/app/views/projects/deploy_keys/show.html.haml
@@ -5,9 +5,9 @@
created on
= @key.created_at.stamp("Aug 21, 2011")
.back-link
- = link_to project_deploy_keys_path(@project) do
+ = link_to namespace_project_deploy_keys_path(@project.namespace, @project) do
← To keys list
%hr
%pre= @key.key
.pull-right
- = link_to 'Remove', project_deploy_key_path(@project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key"
+ = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key"
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 334ea1ba82..48d4c33ce8 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -2,15 +2,9 @@
.col-md-8
= render 'projects/diffs/stats', diffs: diffs
.col-md-4
- %ul.nav.nav-tabs
- %li.pull-right{class: params[:view] == 'parallel' ? 'active' : ''}
- - params_copy = params.dup
- - params_copy[:view] = 'parallel'
- = link_to "Side-by-side Diff", url_for(params_copy), {id: "commit-diff-viewtype"}
- %li.pull-right{class: params[:view] != 'parallel' ? 'active' : ''}
- - params_copy[:view] = 'inline'
- = link_to "Inline Diff", url_for(params_copy), {id: "commit-diff-viewtype"}
-
+ .btn-group.pull-right
+ = inline_diff_btn
+ = parallel_diff_btn
- if show_diff_size_warning?(diffs)
= render 'projects/diffs/warning', diffs: diffs
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c415ae2ddc..36d98b2671 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,16 +1,22 @@
- blob = project.repository.blob_for_diff(@commit, diff_file.diff)
- return unless blob
-- blob_diff_path = diff_project_blob_path(project, tree_join(@commit.id, diff_file.file_path))
+- blob_diff_path = namespace_project_blob_diff_path(project.namespace, project, tree_join(@commit.id, diff_file.file_path))
.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }}
.diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"}
- if diff_file.deleted_file
- %span= diff_file.old_path
+ %span="#{diff_file.old_path} deleted"
.diff-btn-group
- if @commit.parent_ids.present?
= view_file_btn(@commit.parent_id, diff_file, project)
+ - elsif diff_file.diff.submodule?
+ - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
+ = submodule_link(submodule_item, @commit.id)
- else
- %span= diff_file.new_path
+ - if diff_file.renamed_file
+ %span= "#{diff_file.old_path} renamed to #{diff_file.new_path}"
+ - else
+ %span= diff_file.new_path
- if diff_file.mode_changed?
%span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
@@ -21,15 +27,15 @@
= check_box_tag nil, 1, false, class: 'js-toggle-diff-line-wrap'
Wrap text
- = link_to '#', class: 'js-toggle-diff-comments btn btn-small' do
+ = link_to '#', class: 'js-toggle-diff-comments btn btn-sm' do
%i.fa.fa-chevron-down
- Diff comments
+ Show/Hide comments
- if @merge_request && @merge_request.source_project
- = link_to project_edit_tree_path(@merge_request.source_project, tree_join(@merge_request.source_branch, diff_file.new_path), from_merge_request_id: @merge_request.id), { class: 'btn btn-small' } do
- Edit
-
+ = edit_blob_link(@merge_request.source_project,
+ @merge_request.source_branch, diff_file.new_path,
+ after: ' ', from_merge_request_id: @merge_request.id)
= view_file_btn(@commit.id, diff_file, project)
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 900646dd0a..058b71b21f 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -10,7 +10,7 @@
%div.two-up.view
%span.wrap
.frame.deleted
- %a{href: project_blob_path(@project, tree_join(@commit.parent_id, diff.old_path))}
+ %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))}
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}"
@@ -22,7 +22,7 @@
%span.meta-height
%span.wrap
.frame.added
- %a{href: project_blob_path(@project, tree_join(@commit.id, diff.new_path))}
+ %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}"
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 20e51d18da..d387ec2f75 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -9,7 +9,7 @@
and
%strong.cred #{@commit.stats.deletions} deletions
- = link_to '#', class: 'btn btn-small js-toggle-button' do
+ = link_to '#', class: 'btn btn-sm js-toggle-button' do
Show diff stats
%i.fa.fa-chevron-down
.file-stats.js-toggle-content.hide
@@ -26,7 +26,7 @@
%a{href: "#diff-#{i}"}
%i.fa.fa-minus
= diff.old_path
- \->
+ →
= diff.new_path
- elsif diff.new_file
%span.new-file
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 86ed6bbeaa..47abbba2eb 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -1,19 +1,19 @@
-.bs-callout.bs-callout-warning
+.alert.alert-warning
%h4
Too many changes.
.pull-right
- unless diff_hard_limit_enabled?
- = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-small btn-warning"
+ = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-sm btn-warning"
- if current_controller?(:commit) or current_controller?(:merge_requests)
- if current_controller?(:commit)
- = link_to "Plain diff", project_commit_path(@project, @commit, format: :diff), class: "btn btn-warning btn-small"
- = link_to "Email patch", project_commit_path(@project, @commit, format: :patch), class: "btn btn-warning btn-small"
+ = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-warning btn-sm"
+ = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-warning btn-sm"
- elsif @merge_request && @merge_request.persisted?
- = link_to "Plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "btn btn-warning btn-small"
- = link_to "Email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "btn btn-warning btn-small"
+ = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-warning btn-sm"
+ = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-warning btn-sm"
%p
To preserve performance only
%strong #{allowed_diff_size} of #{diffs.size}
- files displayed.
+ files are displayed.
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f48f4bb295..e0d75113a5 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -3,17 +3,17 @@
.project-edit-content
%div
%h3.page-title
- Project settings:
- %p.light Some settings, such as "Transfer Project", are hidden inside the danger area below.
+ Project settings
%hr
.panel-body
- = form_for @project, remote: true, html: { class: "edit_project form-horizontal" } do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal" }, authenticity_token: true do |f|
+
%fieldset
.form-group.project_name_holder
= f.label :name, class: 'control-label' do
Project name
.col-sm-10
- = f.text_field :name, placeholder: "Example Project", class: "form-control"
+ = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
.form-group
@@ -50,15 +50,6 @@
= f.check_box :issues_enabled
%span.descr Lightweight issue tracking system for this project
- - if Project.issues_tracker.values.count > 1
- .form-group
- = f.label :issues_tracker, "Issues tracker", class: 'control-label'
- .col-sm-10= f.select(:issues_tracker, project_issues_trackers(@project.issues_tracker), {}, { disabled: !@project.issues_enabled })
-
- .form-group
- = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label'
- .col-sm-10= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id?, class: 'form-control'
-
.form-group
= f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
.col-sm-10
@@ -80,6 +71,32 @@
= f.check_box :snippets_enabled
%span.descr Share code pastes with others out of git repository
+ %fieldset.features
+ %legend
+ Project avatar:
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ - if @project.avatar?
+ = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
+ %p.light
+ - if @project.avatar_in_git
+ Project avatar in repository: #{ @project.avatar_in_git }
+ %p.light
+ - if @project.avatar?
+ You can change your project avatar here
+ - else
+ You can upload a project avatar here
+ %a.choose-btn.btn.btn-sm.js-choose-project-avatar-button
+ %i.icon-paper-clip
+ %span Choose File ...
+
+ %span.file_name.js-avatar-filename File name...
+ = f.file_field :avatar, class: "js-project-avatar-input hidden"
+ .light The maximum file size allowed is 200KB.
+ - if @project.avatar?
+ %hr
+ = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
.form-actions
= f.submit 'Save changes', class: "btn btn-save"
@@ -99,7 +116,7 @@
The project can be committed to.
%br
%strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive', unarchive_project_path(@project),
+ = link_to 'Unarchive', unarchive_namespace_project_path(@project.namespace, @project),
data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
method: :post, class: "btn btn-success"
- else
@@ -113,7 +130,7 @@
It is hidden from the dashboard and doesn't show up in searches.
%br
%strong Archived projects cannot be committed to!
- = link_to 'Archive', archive_project_path(@project),
+ = link_to 'Archive', archive_namespace_project_path(@project.namespace, @project),
data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
method: :post, class: "btn btn-warning"
- else
@@ -123,13 +140,21 @@
.panel-heading Rename repository
.errors-holder
.panel-body
- = form_for(@project, html: { class: 'form-horizontal' }) do |f|
+ = form_for([@project.namespace.becomes(Namespace), @project], html: { class: 'form-horizontal' }) do |f|
+ .form-group.project_name_holder
+ = f.label :name, class: 'control-label' do
+ Project name
+ .col-sm-9
+ .form-group
+ = f.text_field :name, placeholder: "Example Project", class: "form-control"
.form-group
= f.label :path, class: 'control-label' do
%span Path
.col-sm-9
.form-group
.input-group
+ .input-group-addon
+ #{URI.join(root_url, @project.namespace.path)}/
= f.text_field :path, class: 'form-control'
%span.input-group-addon .git
%ul
@@ -143,13 +168,13 @@
.panel-heading Transfer project
.errors-holder
.panel-body
- = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
+ = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f|
.form-group
- = f.label :namespace_id, class: 'control-label' do
+ = label_tag :new_namespace_id, nil, class: 'control-label' do
%span Namespace
.col-sm-10
.form-group
- = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' }
+ = select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' }
%ul
%li Be careful. Changing the project's namespace can have unintended side effects.
%li You can only transfer the project to namespaces you manage.
@@ -163,7 +188,7 @@
.panel.panel-default.panel.panel-danger
.panel-heading Remove project
.panel-body
- = form_tag(project_path(@project), method: :delete, html: { class: 'form-horizontal'}) do
+ = form_tag(namespace_project_path(@project.namespace, @project), method: :delete, html: { class: 'form-horizontal'}) do
%p
Removing the project will delete its repository and all related resources including issues, merge requests etc.
%br
diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml
deleted file mode 100644
index a863f7420a..0000000000
--- a/app/views/projects/edit_tree/show.html.haml
+++ /dev/null
@@ -1,77 +0,0 @@
-.file-editor
- %ul.nav.nav-tabs.js-edit-mode
- %li.active
- = link_to 'Edit', '#editor'
- %li
- = link_to editing_preview_title(@blob.name), '#preview', 'data-preview-url' => preview_project_edit_tree_path(@project, @id)
-
- = form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do
- .file-holder.file
- .file-title
- %i.fa.fa-file
- %span.file_name
- %span.monospace.light #{@ref}:
- = @path
- %span.options
- .btn-group.tree-btn-group
- = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message }
- .file-content.code
- %pre.js-edit-mode-pane#editor
- .js-edit-mode-pane#preview.hide
- .center
- %h2
- %i.fa.fa-spinner.fa-spin
- = render 'shared/commit_message_container', params: params,
- placeholder: "Update #{@blob.name}"
- .form-actions
- = hidden_field_tag 'last_commit', @last_commit
- = hidden_field_tag 'content', '', id: "file-content"
- = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
- .commit-button-annotation
- = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary'
- .message
- to branch
- %strong= @ref
- = link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message}
-
-:javascript
- ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace")
- ace.config.loadModule("ace/ext/searchbox");
- var ace_mode = "#{@blob.language.try(:ace_mode)}";
- var editor = ace.edit("editor");
- editor.setValue("#{escape_javascript(@blob.data)}");
- if (ace_mode) {
- editor.getSession().setMode('ace/mode/' + ace_mode);
- }
-
- disableButtonIfEmptyField("#commit_message", ".js-commit-button");
-
- $(".js-commit-button").click(function(){
- $("#file-content").val(editor.getValue());
- $(".file-editor form").submit();
- });
-
- var editModePanes = $('.js-edit-mode-pane'),
- editModeLinks = $('.js-edit-mode a');
-
- editModeLinks.click(function(event) {
- event.preventDefault();
-
- var currentLink = $(this),
- paneId = currentLink.attr('href'),
- currentPane = editModePanes.filter(paneId);
-
- editModeLinks.parent().removeClass('active hover');
- currentLink.parent().addClass('active hover');
- editModePanes.hide();
-
- if (paneId == '#preview') {
- currentPane.fadeIn(200);
- $.post(currentLink.data('preview-url'), { content: editor.getValue() }, function(response) {
- currentPane.empty().append(response);
- })
- } else {
- currentPane.fadeIn(200);
- editor.focus()
- }
- })
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 59f19c8b7a..49806ceaa9 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,5 +1,20 @@
+- if current_user && can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+ = render 'shared/no_password'
+
= render "home_panel"
+.center.well
+ %h3
+ The repository for this project is empty
+ %h4
+ You can
+ = link_to namespace_project_new_blob_path(@project.namespace, @project, 'master'), class: 'btn btn-new btn-lg' do
+ add a file
+ or do a push via the command line.
+
+%h4
+ %strong Command line instructions
%div.git-empty
%fieldset
%legend Git global setup
@@ -31,4 +46,4 @@
- if can? current_user, :remove_project, @project
.prepend-top-20
- = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
+ = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml
deleted file mode 100644
index d8f5c7b98d..0000000000
--- a/app/views/projects/fork.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-.alert.alert-danger.alert-block
- %h4
- %i.fa.fa-code-fork
- Fork Error!
- %p
- You tried to fork
- = link_to_project @project
- but it failed for the following reason:
-
-
- - if @forked_project && @forked_project.errors.any?
- %p
- –
- = @forked_project.errors.full_messages.first
-
- %p
- = link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do
- %i.fa.fa-code-fork
- Try to Fork again
diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml
new file mode 100644
index 0000000000..8eb4f79597
--- /dev/null
+++ b/app/views/projects/forks/error.html.haml
@@ -0,0 +1,20 @@
+- if @forked_project && !@forked_project.saved?
+ .alert.alert-danger.alert-block
+ %h4
+ %i.fa.fa-code-fork
+ Fork Error!
+ %p
+ You tried to fork
+ = link_to_project @project
+ but it failed for the following reason:
+
+
+ - if @forked_project && @forked_project.errors.any?
+ %p
+ –
+ = @forked_project.errors.full_messages.first
+
+ %p
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
+ %i.fa.fa-code-fork
+ Try to Fork again
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
new file mode 100644
index 0000000000..5a6c46f320
--- /dev/null
+++ b/app/views/projects/forks/new.html.haml
@@ -0,0 +1,39 @@
+%h3.page-title Fork project
+%p.lead
+ Click to fork the project to a user or group
+%hr
+
+.fork-namespaces
+ - @namespaces.in_groups_of(6, false) do |group|
+ .row
+ - group.each do |namespace|
+ .col-md-2.col-sm-3
+ - if fork = namespace.find_fork_of(@project)
+ .thumbnail.fork-exists-thumbnail
+ = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
+ = image_tag namespace_icon(namespace, 200)
+ .caption
+ %h4=namespace.human_name
+ %p
+ = namespace.path
+ - else
+ .thumbnail.fork-thumbnail
+ = link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
+ = image_tag namespace_icon(namespace, 200)
+ .caption
+ %h4=namespace.human_name
+ %p
+ = namespace.path
+
+ %p.light
+ Fork is a copy of a project repository.
+ %br
+ Forking a repository allows you to do changes without affecting the original project.
+
+.save-project-loader.hide
+ .center
+ %h2
+ %i.fa.fa-spinner.fa-spin
+ Forking repository
+ %p Please wait a moment, this page will automatically refresh when ready.
+
diff --git a/app/views/projects/go_import.html.haml b/app/views/projects/go_import.html.haml
new file mode 100644
index 0000000000..87ac75a350
--- /dev/null
+++ b/app/views/projects/go_import.html.haml
@@ -0,0 +1,5 @@
+!!! 5
+%html
+ %head
+ - web_url = [Gitlab.config.gitlab.url, @namespace, @id].join('/')
+ %meta{name: "go-import", content: "#{web_url.split('://')[1]} git #{web_url}.git"}
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 9f37a760e6..9383df1330 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -1,5 +1,5 @@
%ul.nav.nav-tabs
= nav_link(action: :show) do
- = link_to 'Contributors', project_graph_path
+ = link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do
- = link_to 'Commits', commits_project_graph_path
+ = link_to 'Commits', commits_namespace_project_graph_path
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index a189a48713..4a5d09b950 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -1,7 +1,7 @@
= render 'head'
%p.lead
- Commits statistic for
+ Commit statistics for
%strong #{@repository.root_ref}
#{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 9a003c87f6..bbaddba31b 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -7,7 +7,7 @@
%hr.clearfix
-= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-horizontal' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
-if @hook.errors.any?
.alert.alert-danger
- @hook.errors.full_messages.each do |msg|
@@ -58,8 +58,8 @@
- @hooks.each do |hook|
%li
.pull-right
- = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small btn-grouped"
- = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small btn-grouped"
+ = link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
+ = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
.clearfix
%span.monospace= hook.url
%p
diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml
deleted file mode 100644
index 1f7fd26c64..0000000000
--- a/app/views/projects/import.html.haml
+++ /dev/null
@@ -1,30 +0,0 @@
-- if @project.import_in_progress?
- .save-project-loader
- .center
- %h2
- %i.fa.fa-spinner.fa-spin
- Import in progress.
- %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
- %p Please wait while we import the repository for you. Refresh at will.
- :javascript
- new ProjectImport();
-
-- elsif @project.import_failed?
- .save-project-loader
- .center
- %h2
- Import failed. Retry?
- %hr
- - if can?(current_user, :admin_project, @project)
- = form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
- .form-group.import-url-data
- = f.label :import_url, class: 'control-label' do
- %span Import existing repo
- .col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
- .bs-callout.bs-callout-info
- This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
- %br
- The import will time out after 4 minutes. For big repositories, use a clone/push combination.
- .form-actions
- = f.submit 'Retry import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
new file mode 100644
index 0000000000..f1248ac2af
--- /dev/null
+++ b/app/views/projects/imports/new.html.haml
@@ -0,0 +1,21 @@
+%h3.page-title
+ - if @project.import_failed?
+ Import failed. Retry?
+ - else
+ Import repository
+
+%hr
+
+= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f|
+ .form-group.import-url-data
+ = f.label :import_url, class: 'control-label' do
+ %span Import existing git repo
+ .col-sm-10
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
+ .alert.alert-info
+ This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
+ %br
+ The import will time out after 4 minutes. For big repositories, use a clone/push combination.
+ For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
+ .form-actions
+ = f.submit 'Start import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml
new file mode 100644
index 0000000000..2d1fdafed2
--- /dev/null
+++ b/app/views/projects/imports/show.html.haml
@@ -0,0 +1,9 @@
+.save-project-loader
+ .center
+ %h2
+ %i.fa.fa-spinner.fa-spin
+ Import in progress.
+ %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
+ %p Please wait while we import the repository for you. Refresh at will.
+ :javascript
+ new ProjectImport();
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
new file mode 100644
index 0000000000..fc3e35640d
--- /dev/null
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -0,0 +1,37 @@
+- content_for :note_actions do
+ - if can?(current_user, :modify_issue, @issue)
+ - if @issue.closed?
+ = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue'
+ - else
+ = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue"
+.row
+ %section.col-md-9
+ .participants
+ %span= pluralize(@issue.participants.count, 'participant')
+ - @issue.participants.each do |participant|
+ = link_to_member(@project, participant, name: false, size: 24)
+
+ .voting_notes#notes= render "projects/notes/notes_with_form"
+ %aside.col-md-3
+ .issuable-affix
+ .clearfix
+ %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @issue)
+ %hr
+ .context
+ = render partial: 'issue_context', locals: { issue: @issue }
+ %hr
+ .clearfix
+ .votes-holder
+ %h6 Votes
+ #votes= render 'votes/votes_block', votable: @issue
+
+ - if @issue.labels.any?
+ %hr
+ %h6 Labels
+ .issue-show-labels
+ - @issue.labels.each do |label|
+ = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do
+ = render_colored_label(label)
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 64a28d8da4..7d7217eb2a 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,14 +1,8 @@
%div.issue-form-holder
- %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}"
+ %h3.page-title= @issue.new_record? ? "Create Issue" : "Edit Issue ##{@issue.iid}"
%hr
- - if @repository.exists? && !@repository.empty? && @repository.contribution_guide && !@issue.persisted?
- - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name))
- .row
- .col-sm-10.col-sm-offset-2
- .alert.alert-info
- = "Please review the #{link_to "guidelines for contribution", contribution_guide_url} to this repository.".html_safe
- = form_for [@project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f|
= render 'projects/issuable_form', f: f, issuable: @issue
:javascript
@@ -17,4 +11,4 @@
e.preventDefault();
});
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
deleted file mode 100644
index 1d2f3ed811..0000000000
--- a/app/views/projects/issues/_head.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-%ul.nav.nav-tabs
- = nav_link(controller: :issues) do
- = link_to project_issues_path(@project), class: "tab" do
- Browse Issues
- = nav_link(controller: :milestones) do
- = link_to 'Milestones', project_milestones_path(@project), class: "tab"
- = nav_link(controller: :labels) do
- = link_to 'Labels', project_labels_path(@project), class: "tab"
-
- - if current_controller?(:milestones)
- %li.pull-right
- %button.btn.btn-default.sidebar-expand-button
- %i.icon.fa.fa-list
-
- - if current_controller?(:issues)
- - if current_user
- %li
- = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
- %i.fa.fa-rss
-
- %li.pull-right
- .pull-right
- %button.btn.btn-default.sidebar-expand-button
- %i.icon.fa.fa-list
- = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
- .append-right-10.hidden-xs.hidden-sm
- = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
- = hidden_field_tag :state, params['state']
- = hidden_field_tag :scope, params['scope']
- = hidden_field_tag :assignee_id, params['assignee_id']
- = hidden_field_tag :milestone_id, params['milestone_id']
- = hidden_field_tag :label_id, params['label_id']
- - if can? current_user, :write_issue, @project
- = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
- %i.fa.fa-plus
- New Issue
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index b125706781..3b50ce0135 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,27 +1,27 @@
-%li{ id: dom_id(issue), class: issue_css_classes(issue), url: project_issue_path(issue.project, issue) }
+%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue) }
- if controller.controller_name == 'issues'
.issue-check
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue)
.issue-title
- %span.light= "##{issue.iid}"
%span.str-truncated
- = link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title"
- - if issue.closed?
- %small.pull-right
- CLOSED
+ = link_to_gfm issue.title, issue_path(issue), class: "row_title"
+ .pull-right.light
+ - if issue.closed?
+ %span
+ CLOSED
+ - if issue.notes.any?
+
+ %span
+ %i.fa.fa-comments
+ = issue.notes.count
.issue-info
+ %span.light= "##{issue.iid}"
- if issue.assignee
assigned to #{link_to_member(@project, issue.assignee)}
- - else
- unassigned
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
- - if issue.notes.any?
- %span
- %i.fa.fa-comments
- = issue.notes.count
- if issue.milestone
%span
%i.fa.fa-clock-o
@@ -30,21 +30,21 @@
%span.task-status
= issue.task_status
- .pull-right
+ .pull-right.issue-updated-at
%small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')}
.issue-labels
- issue.labels.each do |label|
- = link_to project_issues_path(issue.project, label_name: label.name) do
+ = link_to namespace_project_issues_path(issue.project.namespace, issue.project, label_name: label.name) do
= render_colored_label(label)
.issue-actions
- if can? current_user, :modify_issue, issue
- if issue.closed?
- = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue btn-reopen", remote: true
+ = link_to 'Reopen', issue_path(issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-sm btn-grouped reopen_issue btn-reopen", remote: true
- else
- = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue btn-close", remote: true
- = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do
+ = link_to 'Close', issue_path(issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-sm btn-grouped close_issue btn-close", remote: true
+ = link_to edit_namespace_project_issue_path(issue.project.namespace, issue.project, issue), class: "btn btn-sm edit-issue-link btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
index 8c3f082338..cb4846a41d 100644
--- a/app/views/projects/issues/_issue_context.html.haml
+++ b/app/views/projects/issues/_issue_context.html.haml
@@ -1,24 +1,48 @@
-= form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f|
- .row
- .col-sm-6
- %strong.append-right-10
+= form_for [@project.namespace.becomes(Namespace), @project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f|
+ %div.prepend-top-20
+ .issuable-context-title
+ %label
Assignee:
-
- - if can?(current_user, :modify_issue, @issue)
- = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id)
- - elsif issue.assignee
- = link_to_member(@project, @issue.assignee)
+ - if issue.assignee
+ %strong= link_to_member(@project, @issue.assignee, size: 24)
- else
- None
+ none
+ - if can?(current_user, :modify_issue, @issue)
+ = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id)
- .col-sm-6.text-right
- %strong.append-right-10
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
Milestone:
- - if can?(current_user, :modify_issue, @issue)
- = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
- = hidden_field_tag :issue_context
- = f.submit class: 'btn'
- - elsif issue.milestone
- = link_to issue.milestone.title, project_milestone_path
+ - if issue.milestone
+ %span.back-to-milestone
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @issue.milestone) do
+ %strong
+ %i.fa.fa-clock-o
+ = @issue.milestone.title
- else
- None
+ none
+ - if can?(current_user, :modify_issue, @issue)
+ = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
+ = hidden_field_tag :issue_context
+ = f.submit class: 'btn'
+
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
+ Subscription:
+ %button.btn.btn-block.subscribe-button
+ %i.fa.fa-eye
+ %span= @issue.subscribed?(current_user) ? "Unsubscribe" : "Subscribe"
+ - subscribtion_status = @issue.subscribed?(current_user) ? "subscribed" : "unsubscribed"
+ .subscription-status{"data-status" => subscribtion_status}
+ .description-block.unsubscribed{class: ( "hidden" if @issue.subscribed?(current_user) )}
+ You're not receiving notifications from this thread.
+ .description-block.subscribed{class: ( "hidden" unless @issue.subscribed?(current_user) )}
+ You're receiving notifications because you're subscribed to this thread.
+
+:coffeescript
+ $ ->
+ new Subscription("#{toggle_subscription_namespace_project_issue_path(@issue.project.namespace, @project, @issue)}")
+
+
\ No newline at end of file
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 0bff8bdbea..5d243adb5f 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,66 +1,3 @@
-.append-bottom-10
- .check-all-holder
- = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left"
- .issues-filters
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
-
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-clock-o
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
- .pull-right
- = render 'shared/sort_dropdown'
-
- .clearfix
- .issues_bulk_update.hide
- = form_tag bulk_update_project_issues_path(@project), method: :post do
- = select_tag('update[status]', options_for_select([['Open', 'open'], ['Closed', 'closed']]), prompt: "Status")
- = project_users_select_tag('update[assignee_id]', placeholder: 'Assignee')
- = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
- = hidden_field_tag 'update[issues_ids]', []
- = hidden_field_tag :status, params[:status]
- = button_tag "Update issues", class: "btn update_selected_issues btn-save"
-
.panel.panel-default
%ul.well-list.issues-list
= render @issues
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 012ba23595..126f2c07fa 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,23 +1,12 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@project.name} issues"
- xml.link :href => project_issues_url(@project, :atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => project_issues_url(@project), :rel => "alternate", :type => "text/html"
- xml.id project_issues_url(@project)
+ xml.link :href => namespace_project_issues_url(@project.namespace, @project, :atom), :rel => "self", :type => "application/atom+xml"
+ xml.link :href => namespace_project_issues_url(@project.namespace, @project), :rel => "alternate", :type => "text/html"
+ xml.id namespace_project_issues_url(@project.namespace, @project)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(@project, issue)
- xml.link :href => project_issue_url(@project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 4ec362b306..2cb94d10b6 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,9 +1,29 @@
-= render "head"
-.row
- .fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/project_filter', project_entities_path: project_issues_path(@project),
- labels: true, redirect: 'issues', entity: 'issue'
- .col-md-9.issues-holder
- = render "issues"
+.append-bottom-10
+ .pull-right
+ .pull-left
+ - if current_user
+ .hidden-xs.pull-left
+ = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
+ %i.fa.fa-rss
+
+ = render 'shared/issuable_search_form', path: namespace_project_issues_path(@project.namespace, @project)
+
+ - if can? current_user, :write_issue, @project
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
+ %i.fa.fa-plus
+ New Issue
+
+ = render 'shared/issuable_filter'
+
+ .clearfix
+ .issues_bulk_update.hide
+ = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
+ = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status")
+ = project_users_select_tag('update[assignee_id]', placeholder: 'Assignee')
+ = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone")
+ = hidden_field_tag 'update[issues_ids]', []
+ = hidden_field_tag :state_event, params[:state_event]
+ = button_tag "Update issues", class: "btn update_selected_issues btn-save"
+
+.issues-holder
+ = render "issues"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 71eb0d5c86..bd28d8a1db 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,75 +1,40 @@
-%h3.page-title
- Issue ##{@issue.iid}
+.issue
+ .issue-details
+ %h4.page-title
+ .issue-box{ class: issue_box_class(@issue) }
+ - if @issue.closed?
+ Closed
+ - else
+ Open
+ Issue ##{@issue.iid}
+ %small.creator
+ · created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
- %span.pull-right.issue-btn-group
- - if can?(current_user, :write_issue, @project)
- = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do
- %i.fa.fa-plus
- New Issue
- - if can?(current_user, :modify_issue, @issue)
- - if @issue.closed?
- = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
- - else
- = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
+ .pull-right
+ - if can?(current_user, :write_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-grouped new-issue-link", title: "New Issue", id: "new_issue_link" do
+ %i.fa.fa-plus
+ New Issue
+ - if can?(current_user, :modify_issue, @issue)
+ - if @issue.closed?
+ = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen"
+ - else
+ = link_to 'Close', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue"
- = link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped" do
- %i.fa.fa-pencil-square-o
- Edit
+ = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: "btn btn-grouped issuable-edit" do
+ %i.fa.fa-pencil-square-o
+ Edit
-.clearfix
- .votes-holder
- #votes= render 'votes/votes_block', votable: @issue
+ %hr
+ %h2.issue-title
+ = gfm escape_once(@issue.title)
+ %div
+ - if @issue.description.present?
+ .description
+ .wiki
+ = preserve do
+ = markdown(@issue.description, parse_tasks: true)
- .back-link
- = link_to project_issues_path(@project) do
- ← To issues list
- %span.milestone-nav-link
- - if @issue.milestone
- |
- %span.light Milestone
- = link_to project_milestone_path(@project, @issue.milestone) do
- = @issue.milestone.title
-
-.issue-box{ class: issue_box_class(@issue) }
- .state.clearfix
- .state-label
- - if @issue.closed?
- Closed
- - else
- Open
-
- .creator
- Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)}
-
- %h4.title
- = gfm escape_once(@issue.title)
-
- - if @issue.description.present?
- .description
- .wiki
- = preserve do
- = markdown(@issue.description, parse_tasks: true)
- .context
- %cite.cgray
- = render partial: 'issue_context', locals: { issue: @issue }
-
-
-- content_for :note_actions do
- - if can?(current_user, :modify_issue, @issue)
- - if @issue.closed?
- = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue'
- - else
- = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue"
-
-.participants
- %cite.cgray
- = pluralize(@issue.participants.count, 'participant')
- - @issue.participants.each do |participant|
- = link_to_member(@project, participant, name: false, size: 24)
-
- .issue-show-labels.pull-right
- - @issue.labels.each do |label|
- = link_to project_issues_path(@project, label_name: label.name) do
- = render_colored_label(label)
-
-.voting_notes#notes= render "projects/notes/notes_with_form"
+ %hr
+ .issue-discussion
+ = render "projects/issues/discussion"
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index 5199e9fc61..82c0e65375 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -3,8 +3,15 @@
:plain
$("##{dom_id(@issue)}").fadeOut();
- elsif params[:issue_context]
- $('.issue-box .context').effect('highlight');
+ $('.context').html("#{escape_javascript(render partial: 'issue_context', locals: { issue: @issue })}");
+ $('.context').effect('highlight');
- if @issue.milestone
- $('.milestone-nav-link').replaceWith("| Milestone #{escape_javascript(link_to @issue.milestone.title, project_milestone_path(@issue.project, @issue.milestone))} ")
+ $('.milestone-nav-link').replaceWith("| Milestone #{escape_javascript(link_to @issue.milestone.title, namespace_project_milestone_path(@issue.project.namespace, @issue.project, @issue.milestone))} ")
- else
$('.milestone-nav-link').html('')
+
+
+$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
+$('.edit-issue.inline-update input[type="submit"]').hide();
+new ProjectUsersSelect();
+new Issue();
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 72a01e1c27..2305fce112 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -1,8 +1,8 @@
-= form_for [@project, @label], html: { class: 'form-horizontal label-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form' } do |f|
-if @label.errors.any?
.row
.col-sm-10.col-sm-offset-2
- .bs-callout.bs-callout-danger
+ .alert.alert-danger
- @label.errors.full_messages.each do |msg|
%span= msg
%br
@@ -16,7 +16,7 @@
.col-sm-10
.input-group
.input-group-addon.label-color-preview
- = f.text_field :color, placeholder: "#AA33EE", class: "form-control"
+ = f.color_field :color, placeholder: "#AA33EE", class: "form-control"
.help-block
6 character hex values starting with a # sign.
%br
@@ -29,5 +29,5 @@
.form-actions
= f.submit 'Save', class: 'btn btn-save js-save-button'
- = link_to "Cancel", project_labels_path(@project), class: 'btn btn-cancel'
+ = link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel'
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 03a8f0921b..8282945286 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -2,9 +2,9 @@
= render_colored_label(label)
.pull-right
%strong.append-right-20
- = link_to project_issues_path(@project, label_name: label.name) do
+ = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do
= pluralize label.open_issues_count, 'open issue'
- if can? current_user, :admin_label, @project
- = link_to 'Edit', edit_project_label_path(@project, label), class: 'btn'
- = link_to 'Remove', project_label_path(@project, label), class: 'btn btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+ = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn'
+ = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 52435c5d89..e003d1dfe7 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -2,7 +2,7 @@
Edit label
%span.light #{@label.name}
.back-link
- = link_to project_labels_path(@project) do
+ = link_to namespace_project_labels_path(@project.namespace, @project) do
← To labels list
%hr
= render 'form'
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 06568278de..0700e72d39 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,7 +1,5 @@
-= render "projects/issues/head"
-
- if can? current_user, :admin_label, @project
- = link_to new_project_label_path(@project), class: "pull-right btn btn-new" do
+ = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do
New label
%h3.page-title
Labels
@@ -14,4 +12,4 @@
= paginate @labels, theme: 'gitlab'
- else
.light-well
- .nothing-here-block Create first label or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels
+ .nothing-here-block Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 850da0b192..0683ed5d4f 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,6 +1,6 @@
%h3 New label
.back-link
- = link_to project_labels_path(@project) do
+ = link_to namespace_project_labels_path(@project.namespace, @project) do
← To labels list
%hr
= render 'form'
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
new file mode 100644
index 0000000000..79a093dc77
--- /dev/null
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -0,0 +1,33 @@
+- content_for :note_actions do
+ - if can?(current_user, :modify_merge_request, @merge_request)
+ - if @merge_request.open?
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
+ - if @merge_request.closed?
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
+
+.row
+ %section.col-md-9
+ = render "projects/merge_requests/show/participants"
+ = render "projects/notes/notes_with_form"
+ %aside.col-md-3
+ .issuable-affix
+ .clearfix
+ %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @merge_request)
+ %hr
+ .context
+ = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
+ %hr
+ .votes-holder
+ %h6 Votes
+ #votes= render 'votes/votes_block', votable: @merge_request
+
+ - if @merge_request.labels.any?
+ %hr
+ %h6 Labels
+ .merge-request-show-labels
+ - @merge_request.labels.each do |label|
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project, label_name: label.name) do
+ = render_colored_label(label)
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index d52e64666a..1c7160bce5 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f|
.merge-request-form-info
= render 'projects/issuable_form', f: f, issuable: @merge_request
@@ -9,4 +9,4 @@
e.preventDefault();
});
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
index 35a86e6511..19e4dab874 100644
--- a/app/views/projects/merge_requests/_head.html.haml
+++ b/app/views/projects/merge_requests/_head.html.haml
@@ -1,5 +1,5 @@
.top-tabs
- = link_to project_merge_requests_path(@project), class: "tab #{'active' if current_page?(project_merge_requests_path(@project)) }" do
+ = link_to namespace_project_merge_requests_path(@project.namespace, @project), class: "tab #{'active' if current_page?(namespace_project_merge_requests_path(@project.namespace, @project)) }" do
%span
Merge Requests
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 1ee2e1bdae..1eba1a96b7 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,28 +1,34 @@
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
- %span.light= "##{merge_request.iid}"
- = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title"
- - if merge_request.merged?
- %small.pull-right
- %i.fa.fa-check
- MERGED
- - else
- %span.pull-right
- - if merge_request.for_fork?
- %span.light
- #{merge_request.source_project_namespace}:
- = truncate merge_request.source_branch, length: 25
- %i.fa.fa-angle-right.light
- = merge_request.target_branch
+ %span.str-truncated
+ = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
+ .pull-right.light
+ - if merge_request.merged?
+ %span
+ %i.fa.fa-check
+ MERGED
+ - elsif merge_request.closed?
+ %span
+ %i.fa.fa-close
+ CLOSED
+ - else
+ %span.hidden-xs.hidden-sm
+ %span.label-branch<
+ %i.fa.fa-code-fork
+ %span= merge_request.target_branch
+ - if merge_request.notes.any?
+
+ %span
+ %i.fa.fa-comments
+ = merge_request.mr_and_commit_notes.count
.merge-request-info
- - if merge_request.author
- authored by #{link_to_member(merge_request.source_project, merge_request.author)}
+ %span.light= "##{merge_request.iid}"
+ - if merge_request.assignee
+ assigned to #{link_to_member(merge_request.source_project, merge_request.assignee)}
+ - else
+ Unassigned
- if merge_request.votes_count > 0
= render 'votes/votes_inline', votable: merge_request
- - if merge_request.notes.any?
- %span
- %i.fa.fa-comments
- = merge_request.mr_and_commit_notes.count
- if merge_request.milestone_id?
%span
%i.fa.fa-clock-o
@@ -31,10 +37,11 @@
%span.task-status
= merge_request.task_status
- .pull-right
+
+ .pull-right.hidden-xs
%small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
.merge-request-labels
- merge_request.labels.each do |label|
- = link_to project_merge_requests_path(merge_request.project, label_name: label.name) do
+ = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, label_name: label.name) do
= render_colored_label(label)
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
new file mode 100644
index 0000000000..b8a0ca9a42
--- /dev/null
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -0,0 +1,13 @@
+.panel.panel-default
+ %ul.well-list.mr-list
+ = render @merge_requests
+ - if @merge_requests.blank?
+ %li
+ .nothing-here-block No merge requests to show
+
+- if @merge_requests.present?
+ .pull-right
+ %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
+
+ = paginate @merge_requests, theme: "gitlab"
+
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 9972617215..17e76059fd 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -1,7 +1,7 @@
%h3.page-title Compare branches for new Merge Request
%hr
-= form_for [@project, @merge_request], url: new_project_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline" } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline" } do |f|
.hide.alert.alert-danger.mr-compare-errors
.merge-request-branches.row
.col-md-6
@@ -60,19 +60,19 @@
, target_branch = $("#merge_request_target_branch")
, target_project = $("#merge_request_target_project_id");
- $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() });
- $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
+ $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: source_branch.val() });
+ $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
target_project.on("change", function() {
- $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() });
+ $.get("#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: $(this).val() });
});
source_branch.on("change", function() {
- $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() });
+ $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: $(this).val() });
$(".mr-compare-errors").fadeOut();
$(".mr-compare-btn").enable();
});
target_branch.on("change", function() {
- $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
+ $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
$(".mr-compare-errors").fadeOut();
$(".mr-compare-btn").enable();
});
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index d4666eacd7..bf80afe878 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -7,75 +7,105 @@
%strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
%span.pull-right
- = link_to 'Change branches', new_project_merge_request_path(@project)
+ = link_to 'Change branches', new_namespace_project_merge_request_path(@project.namespace, @project)
-= form_for [@project, @merge_request], html: { class: "merge-request-form gfm-form" } do |f|
- .panel.panel-default
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: "merge-request-form form-horizontal gfm-form" } do |f|
+ .merge-request-form-info
+ .form-group
+ = f.label :title, class: 'control-label' do
+ %strong Title *
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, autofocus: true, class: 'form-control pad js-gfm-input', required: true
+ .form-group.issuable-description
+ = f.label :description, 'Description', class: 'control-label'
+ .col-sm-10
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
- .panel-body
- .form-group
- .light
- = f.label :title do
- Title *
- = f.text_field :title, class: "form-control input-lg js-gfm-input", maxlength: 255, rows: 5, required: true
- .form-group
- .light
- = f.label :description, "Description"
- = render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control'
- .clearfix.hint
- .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
- .error-alert
- .form-group
- .issue-assignee
- = f.label :assignee_id do
- %i.fa.fa-user
- Assign to
- %div
- = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
-
- = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
- .form-group
- .issue-milestone
- = f.label :milestone_id do
- %i.fa.fa-clock-o
- Milestone
- %div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'})
- .form-group
- = f.label :label_ids do
- %i.fa.fa-tag
- Labels
- %div
- = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2'
+ .col-sm-12-hint
+ .pull-left
+ Parsed with
+ #{link_to 'Gitlab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}.
+ .pull-right
+ Attach files by dragging & dropping
+ or #{link_to 'selecting them', '#', class: 'markdown-selector'}.
- .panel-footer
+ .clearfix
+ .error-alert
+ %hr
+ .form-group
+ .issue-assignee
+ = f.label :assignee_id, class: 'control-label' do
+ %i.fa.fa-user
+ Assign to
+ .col-sm-10
+ = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id)
+
+ = link_to 'Assign to me', '#', class: 'btn assign-to-me-link'
+ .form-group
+ .issue-milestone
+ = f.label :milestone_id, class: 'control-label' do
+ %i.fa.fa-clock-o
+ Milestone
+ .col-sm-10
+ - if milestone_options(@merge_request).present?
+ = f.select(:milestone_id, milestone_options(@merge_request), {include_blank: 'Select milestone'}, {class: 'select2'})
+ - else
+ %span.light No open milestones available.
+
+ - if can? current_user, :admin_milestone, @merge_request.target_project
+ = link_to 'Create new milestone', new_namespace_project_milestone_path(@merge_request.target_project.namespace, @merge_request.target_project), target: :blank
+ .form-group
+ = f.label :label_ids, class: 'control-label' do
+ %i.fa.fa-tag
+ Labels
+ .col-sm-10
+ - if @merge_request.target_project.labels.any?
+ = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, {selected: @merge_request.label_ids}, multiple: true, class: 'select2'
+ - else
+ %span.light No labels yet.
+
+ - if can? current_user, :admin_label, @merge_request.target_project
+ = link_to 'Create new label', new_namespace_project_label_path(@merge_request.target_project.namespace, @merge_request.target_project), target: :blank
+
+ .form-actions
- if contribution_guide_url(@target_project)
%p
Please review the
- %strong #{link_to "guidelines for contribution", contribution_guide_url(@target_project)}
+ %strong #{link_to 'guidelines for contribution', contribution_guide_url(@target_project)}
to this repository.
= f.hidden_field :source_project_id
+ = f.hidden_field :source_branch
= f.hidden_field :target_project_id
= f.hidden_field :target_branch
- = f.hidden_field :source_branch
- = f.submit 'Submit merge request', class: "btn btn-create"
+ = f.submit 'Submit merge request', class: 'btn btn-create'
-.mr-compare
- = render "projects/commits/commit_list"
-
- %h4 Changes
- - if @diffs.present?
- = render "projects/diffs/diffs", diffs: @diffs, project: @project
- - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- .bs-callout.bs-callout-danger
- %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
- %p To preserve performance the line changes are not shown.
- - else
- .bs-callout.bs-callout-danger
- %h4 This comparison includes huge diff.
- %p To preserve performance the line changes are not shown.
+.mr-compare.merge-request
+ %ul.nav.nav-tabs.merge-request-tabs
+ %li.commits-tab{data: {action: 'commits'}}
+ = link_to url_for(params) do
+ %i.fa.fa-history
+ Commits
+ %span.badge= @commits.size
+ %li.diffs-tab{data: {action: 'diffs'}}
+ = link_to url_for(params) do
+ %i.fa.fa-list-alt
+ Changes
+ %span.badge= @diffs.size
+ .commits.tab-content
+ = render "projects/commits/commits", project: @project
+ .diffs.tab-content
+ - if @diffs.present?
+ = render "projects/diffs/diffs", diffs: @diffs, project: @project
+ - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
+ .alert.alert-danger
+ %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
+ %p To preserve performance the line changes are not shown.
+ - else
+ .alert.alert-danger
+ %h4 This comparison includes a huge diff.
+ %p To preserve performance the line changes are not shown.
:javascript
$('.assign-to-me-link').on('click', function(e){
@@ -83,4 +113,11 @@
e.preventDefault();
});
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
+
+:javascript
+ var merge_request
+ merge_request = new MergeRequest({
+ action: 'commits'
+ });
+
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 947e8f58ae..ca4ceecb22 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,46 +1,76 @@
-.merge-request
- = render "projects/merge_requests/show/mr_title"
- = render "projects/merge_requests/show/how_to_merge"
- = render "projects/merge_requests/show/mr_box"
- = render "projects/merge_requests/show/state_widget"
- = render "projects/merge_requests/show/commits"
- = render "projects/merge_requests/show/participants"
+.merge-request{'data-url' => merge_request_path(@merge_request)}
+ .merge-request-details
+ = render "projects/merge_requests/show/mr_title"
+ %hr
+ = render "projects/merge_requests/show/mr_box"
+ %hr
+ .append-bottom-20
+ .slead
+ %span From
+ - if @merge_request.for_fork?
+ %strong.label-branch<
+ - if @merge_request.source_project
+ = link_to @merge_request.source_project_namespace, namespace_project_path(@merge_request.source_project.namespace, @merge_request.source_project)
+ - else
+ \ #{@merge_request.source_project_namespace}
+ \:#{@merge_request.source_branch}
+ %span into
+ %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
+ - else
+ %strong.label-branch #{@merge_request.source_branch}
+ %span into
+ %strong.label-branch #{@merge_request.target_branch}
+ - if @merge_request.open?
+ %span.pull-right
+ .btn-group
+ %a.btn.dropdown-toggle{ data: {toggle: :dropdown} }
+ %i.fa.fa-download
+ Download as
+ %span.caret
+ %ul.dropdown-menu
+ %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
+ %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
+
+ = render "projects/merge_requests/show/how_to_merge"
+ = render "projects/merge_requests/show/state_widget"
- if @commits.present?
- %ul.nav.nav-pills.merge-request-tabs
+ %ul.nav.nav-tabs.merge-request-tabs
%li.notes-tab{data: {action: 'notes'}}
- = link_to project_merge_request_path(@project, @merge_request) do
- %i.fa.fa-comment
+ = link_to merge_request_path(@merge_request) do
+ %i.fa.fa-comments
Discussion
%span.badge= @merge_request.mr_and_commit_notes.count
+ %li.commits-tab{data: {action: 'commits'}}
+ = link_to merge_request_path(@merge_request), title: 'Commits' do
+ %i.fa.fa-history
+ Commits
+ %span.badge= @commits.size
%li.diffs-tab{data: {action: 'diffs'}}
- = link_to diffs_project_merge_request_path(@project, @merge_request) do
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) do
%i.fa.fa-list-alt
Changes
%span.badge= @merge_request.diffs.size
- - content_for :note_actions do
- - if can?(current_user, :modify_merge_request, @merge_request)
- - unless @merge_request.closed? || @merge_request.merged?
- = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
- - if @merge_request.closed?
- = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
-
+ .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
+ = render "projects/merge_requests/discussion"
+ .commits.tab-content
+ = render "projects/merge_requests/show/commits"
.diffs.tab-content
- if current_page?(action: 'diffs')
= render "projects/merge_requests/show/diffs"
- .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
- = render "projects/notes/notes_with_form"
+
.mr-loading-status
= spinner
+
:javascript
var merge_request;
merge_request = new MergeRequest({
- url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}",
+ url_to_automerge_check: "#{automerge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"},
- url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}",
+ url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
ci_enable: #{@project.ci_service ? "true" : "false"},
current_status: "#{@merge_request.merge_status_name}",
action: "#{controller.action_name}"
diff --git a/app/views/projects/merge_requests/automerge.js.haml b/app/views/projects/merge_requests/automerge.js.haml
index e01ff662e7..a53cbb150a 100644
--- a/app/views/projects/merge_requests/automerge.js.haml
+++ b/app/views/projects/merge_requests/automerge.js.haml
@@ -1,7 +1,6 @@
-if @status
:plain
- location.reload();
+ merge_request.mergeInProgress();
-else
:plain
merge_request.alreadyOrCannotBeMerged()
-
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index be638d7cac..d7992bdd19 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,78 +1,11 @@
-- if can? current_user, :write_merge_request, @project
- = link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do
- %i.fa.fa-plus
- New Merge Request
-%h3.page-title
- Merge Requests
-%hr
-.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
- labels: true, redirect: 'merge_requests', entity: 'merge_request'
- .col-md-9
- .mr-filters.append-bottom-10
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
+.append-bottom-10
+ .pull-right
+ = render 'shared/issuable_search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-clock-o
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
- .pull-right
- = render 'shared/sort_dropdown'
-
- .panel.panel-default
- %ul.well-list.mr-list
- = render @merge_requests
- - if @merge_requests.blank?
- %li
- .nothing-here-block No merge requests to show
- - if @merge_requests.present?
- .pull-right
- %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter
-
- = paginate @merge_requests, theme: "gitlab"
-
-:javascript
- $(merge_requestsPage);
+ - if can? current_user, :write_merge_request, @project
+ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new pull-left", title: "New Merge Request" do
+ %i.fa.fa-plus
+ New Merge Request
+ = render 'shared/issuable_filter'
+.merge-requests-holder
+ = render 'merge_requests'
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index a658740387..3b7f283daf 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,30 +1 @@
-- if @commits.present?
- .panel.panel-default
- .panel-heading
- %i.fa.fa-list
- Commits (#{@commits.count})
- .commits.mr-commits
- - if @commits.count > 8
- %ul.first-commits.well-list
- - @commits.first(8).each do |commit|
- = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
- %li.bottom
- 8 of #{@commits.count} commits displayed.
- %strong
- %a.show-all-commits Click here to show all
- - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- %ul.all-commits.hide.well-list
- - @commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE).each do |commit|
- = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
- %li
- other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues.
- - else
- %ul.all-commits.hide.well-list
- - @commits.each do |commit|
- = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project
-
- - else
- %ul.well-list
- - @commits.each do |commit|
- = render "projects/commits/commit", commit: commit, project: @merge_request.source_project
-
+= render "projects/commits/commits", project: @merge_request.source_project
diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml
index 089302e358..753c7e0e61 100644
--- a/app/views/projects/merge_requests/show/_context.html.haml
+++ b/app/views/projects/merge_requests/show/_context.html.haml
@@ -1,24 +1,50 @@
-= form_for [@project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f|
- .row
- .col-sm-6
- %strong.append-right-10
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f|
+ %div.prepend-top-20
+ .issuable-context-title
+ %label
Assignee:
-
+ - if @merge_request.assignee
+ %strong= link_to_member(@project, @merge_request.assignee, size: 24)
+ - else
+ none
+ .issuable-context-selectbox
- if can?(current_user, :modify_merge_request, @merge_request)
= project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id)
- - elsif merge_request.assignee
- = link_to_member(@project, @merge_request.assignee)
- - else
- None
- .col-sm-6.text-right
- %strong.append-right-10
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
Milestone:
+ - if @merge_request.milestone
+ %span.back-to-milestone
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @merge_request.milestone) do
+ %strong
+ %i.fa.fa-clock-o
+ = @merge_request.milestone.title
+ - else
+ none
+ .issuable-context-selectbox
- if can?(current_user, :modify_merge_request, @merge_request)
= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
= hidden_field_tag :merge_request_context
= f.submit class: 'btn'
- - elsif merge_request.milestone
- = link_to merge_request.milestone.title, project_milestone_path
- - else
- None
+
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
+ Subscription:
+ %button.btn.btn-block.subscribe-button
+ %i.fa.fa-eye
+ %span= @merge_request.subscribed?(current_user) ? "Unsubscribe" : "Subscribe"
+ - subscribtion_status = @merge_request.subscribed?(current_user) ? "subscribed" : "unsubscribed"
+ .subscription-status{"data-status" => subscribtion_status}
+ .description-block.unsubscribed{class: ( "hidden" if @merge_request.subscribed?(current_user) )}
+ You're not receiving notifications from this thread.
+ .description-block.subscribed{class: ( "hidden" unless @merge_request.subscribed?(current_user) )}
+ You're receiving notifications because you're subscribed to this thread.
+
+:coffeescript
+ $ ->
+ new Subscription("#{toggle_subscription_namespace_project_merge_request_path(@merge_request.project.namespace, @project, @merge_request)}")
+
+
\ No newline at end of file
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index d361c5f579..786b5f3906 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -3,10 +3,10 @@
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
- else
- .bs-callout.bs-callout-warning
+ .alert.alert-warning
%h4
Changes view for this comparison is extremely large.
%p
You can
- = link_to "download it", project_merge_request_path(@merge_request.target_project, @merge_request, format: :diff), class: "vlink"
+ = link_to "download it", merge_request_path(@merge_request, format: :diff), class: "vlink"
instead.
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 9540453ce3..63db4b3096 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -10,11 +10,11 @@
- target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
%p
%strong Step 1.
- Checkout the branch we are going to merge and pull in the code
+ Fetch the code and create a new branch pointing to it
%pre.dark
:preserve
- git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch}
- git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
+ git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
+ git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD
%p
%strong Step 2.
Merge the branch and push the changes to GitLab
diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index 213e14268c..9f51f84d40 100644
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -12,29 +12,25 @@
- if @show_merge_controls
.automerge_widget.can_be_merged.hide
.clearfix
- = form_for [:automerge, @project, @merge_request], remote: true, method: :post do |f|
- %h4
- You can accept this request automatically.
- .accept-merge-holder.clearfix
- .js-toggle-container
- %p
- You can
- %strong= link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message"
- before accepting merge request
- .js-toggle-content.hide
- = render 'shared/commit_message_container', params: params,
- text: @merge_request.merge_commit_message,
- rows: 14, hint: true
- .accept-group
- .pull-left
- = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
- - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
- .remove_branch_holder.pull-left
- = label_tag :should_remove_source_branch, class: "checkbox" do
- = check_box_tag :should_remove_source_branch
- Remove source-branch
+ = form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post do |f|
+ .accept-merge-holder.clearfix.js-toggle-container
+ .accept-action
+ = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request"
+ - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
+ .accept-control.checkbox
+ = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
+ = check_box_tag :should_remove_source_branch
+ Remove source-branch
+ .accept-control
+ = link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do
+ %i.fa.fa-edit
+ Modify commit message
+ .js-toggle-content.hide.prepend-top-20
+ = render 'shared/commit_message_container', params: params,
+ text: @merge_request.merge_commit_message,
+ rows: 14, hint: true
- %hr
+ %br
.light
If you still want to merge this request manually - use
%strong
@@ -49,10 +45,17 @@
.automerge_widget.cannot_be_merged.hide
%h4
This request can't be merged with GitLab.
- %p
You should do it manually with
%strong
- = link_to "command line", "#modal_merge_info", class: "how_to_merge_link", title: "How To Merge", "data-toggle" => "modal"
+ = link_to "#modal_merge_info", class: "underlined-link how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" do
+ command line
+
+ %p
+ %button.btn.disabled
+ %i.fa.fa-warning
+ Accept Merge Request
+
+ This usually happens when git can not resolve conflicts between branches automatically.
.automerge_widget.unchecked
%p
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index 7e5a4eda50..ada9ae58b8 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,25 +1,9 @@
-.issue-box{ class: issue_box_class(@merge_request) }
- .state.clearfix
- .state-label
- - if @merge_request.merged?
- Merged
- - elsif @merge_request.closed?
- Closed
- - else
- Open
-
- .creator
- Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
-
- %h4.title
- = gfm escape_once(@merge_request.title)
+%h2.issue-title
+ = gfm escape_once(@merge_request.title)
+%div
- if @merge_request.description.present?
.description
.wiki
= preserve do
= markdown(@merge_request.description, parse_tasks: true)
-
- .context
- %cite.cgray
- = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml
index 941b15d3b3..85a7103f3b 100644
--- a/app/views/projects/merge_requests/show/_mr_ci.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml
@@ -3,21 +3,21 @@
%i.fa.fa-check
%span CI build passed
for #{@merge_request.last_commit_short_sha}.
- = link_to "Build page", ci_build_details_path(@merge_request)
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
.ci_widget.ci-failed{style: "display:none"}
%i.fa.fa-times
%span CI build failed
for #{@merge_request.last_commit_short_sha}.
- = link_to "Build page", ci_build_details_path(@merge_request)
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
- [:running, :pending].each do |status|
.ci_widget{class: "ci-#{status}", style: "display:none"}
%i.fa.fa-clock-o
%span CI build #{status}
for #{@merge_request.last_commit_short_sha}.
- = link_to "Build page", ci_build_details_path(@merge_request)
+ = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
.ci_widget
%i.fa.fa-spinner
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 6fe765248e..46e92a9c55 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,45 +1,22 @@
-%h3.page-title
+%h4.page-title
+ .issue-box{ class: issue_box_class(@merge_request) }
+ - if @merge_request.merged?
+ Merged
+ - elsif @merge_request.closed?
+ Closed
+ - else
+ Open
= "Merge Request ##{@merge_request.iid}"
+ %small.creator
+ ·
+ created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
- %span.pull-right.issue-btn-group
+ .issue-btn-group.pull-right
- if can?(current_user, :modify_merge_request, @merge_request)
- if @merge_request.open?
- .btn-group.pull-left
- %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} }
- %i.fa.fa-download
- Download as
- %span.caret
- %ul.dropdown-menu
- %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch)
- %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff)
-
- = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
-
- = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped", id:"edit_merge_request" do
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
+ = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do
%i.fa.fa-pencil-square-o
Edit
- if @merge_request.closed?
- = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
-
-.votes-holder.hidden-sm.hidden-xs
- #votes= render 'votes/votes_block', votable: @merge_request
-
-.back-link
- = link_to project_merge_requests_path(@project) do
- ← To merge requests
-
- %span.prepend-left-20
- %span From
- - if @merge_request.for_fork?
- %strong.label-branch<
- - if @merge_request.source_project
- = link_to @merge_request.source_project_namespace, project_path(@merge_request.source_project)
- - else
- \ #{@merge_request.source_project_namespace}
- \:#{@merge_request.source_branch}
- %span into
- %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
- - else
- %strong.label-branch #{@merge_request.source_branch}
- %span into
- %strong.label-branch #{@merge_request.target_branch}
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/projects/merge_requests/show/_participants.html.haml
index b709c89cec..4f34af1737 100644
--- a/app/views/projects/merge_requests/show/_participants.html.haml
+++ b/app/views/projects/merge_requests/show/_participants.html.haml
@@ -1,9 +1,4 @@
.participants
- %cite.cgray #{@merge_request.participants.count} participants
+ %span #{@merge_request.participants.count} participants
- @merge_request.participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
-
- .merge-request-show-labels.pull-right
- - @merge_request.labels.each do |label|
- = link_to project_merge_requests_path(@project, label_name: label.name) do
- = render_colored_label(label)
diff --git a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
index 4fe5935bcf..59cb85edfc 100644
--- a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
+++ b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml
@@ -4,7 +4,7 @@
- elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && @merge_request.merged?
.remove_source_branch_widget
%p Changes merged into #{@merge_request.target_branch}. You can remove source branch now
- = link_to project_branch_path(@merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-small remove_source_branch" do
+ = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
%i.fa.fa-times
Remove Source Branch
@@ -12,6 +12,6 @@
Failed to remove source branch '#{@merge_request.source_branch}'
.remove_source_branch_in_progress.hide
- %i.fa.fa-refresh.fa-spin
+ %i.fa.fa-spinner.fa-spin
Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded.
diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml
index 2b58c865b2..a4f2a89096 100644
--- a/app/views/projects/merge_requests/show/_state_widget.html.haml
+++ b/app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -11,16 +11,26 @@
- if @merge_request.closed?
%h4
- Closed by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
+ Closed
+ - if @merge_request.closed_event
+ by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
%p Changes were not merged into target branch
- if @merge_request.merged?
%h4
- Merged by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
+ Merged
+ - if @merge_request.merge_event
+ by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
= render "projects/merge_requests/show/remove_source_branch"
+ - if @merge_request.locked?
+ %h4
+ Merge in progress...
+ %p
+ GitLab tries to merge it right now. During this time merge request is locked and can not be closed.
+
- unless @commits.any?
%h4 Nothing to merge
%p
@@ -38,4 +48,3 @@
Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
= succeed '.' do
!= gfm(issues_sentence(@closes_issues))
-
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index 6452cc6382..f5cc98c7fa 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -1,2 +1,8 @@
- if params[:merge_request_context]
- $('.issue-box .context').effect('highlight');
+ $('.context').html("#{escape_javascript(render partial: 'projects/merge_requests/show/context', locals: { issue: @issue })}");
+ $('.context').effect('highlight');
+
+ new ProjectUsersSelect();
+
+ $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true});
+ merge_request = new MergeRequest();
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 5fb01a11cc..95b7070ce5 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,11 +1,11 @@
%h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.iid}"
.back-link
- = link_to project_milestones_path(@project) do
+ = link_to namespace_project_milestones_path(@project.namespace, @project) do
← To milestones
%hr
-= form_for [@project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form'} do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form'} do |f|
-if @milestone.errors.any?
.alert.alert-danger
%ul
@@ -18,13 +18,14 @@
.col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control"
%p.hint Required
- .form-group
+ .form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
- .hint
- .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
- .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ .hint
+ .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
+ .pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
.clearfix
.error-alert
.col-md-6
@@ -37,10 +38,10 @@
.form-actions
- if @milestone.new_record?
= f.submit 'Create milestone', class: "btn-create btn"
- = link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_milestones_path(@project.namespace, @project), class: "btn btn-cancel"
-else
= f.submit 'Save changes', class: "btn-save btn"
- = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel"
:javascript
@@ -50,4 +51,4 @@
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml
index b5ec0fc988..26c83841a2 100644
--- a/app/views/projects/milestones/_issue.html.haml
+++ b/app/views/projects/milestones/_issue.html.haml
@@ -1,8 +1,8 @@
-%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => project_issue_path(@project, issue) }
+%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
%span.str-truncated
- = link_to [@project, issue] do
+ = link_to [@project.namespace.becomes(Namespace), @project, issue] do
%span.cgray ##{issue.iid}
- = link_to_gfm issue.title, [@project, issue], title: issue.title
+ = link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
= image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
index d54cb3f8e7..42fbd0cd2c 100644
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ b/app/views/projects/milestones/_merge_request.html.haml
@@ -1,5 +1,8 @@
-%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => project_merge_request_path(@project, merge_request) }
+%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) }
%span.str-truncated
- = link_to [@project, merge_request] do
+ = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do
%span.cgray ##{merge_request.iid}
- = link_to_gfm merge_request.title, [@project, merge_request], title: merge_request.title
+ = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
+ .pull-right.assignee-icon
+ - if merge_request.assignee
+ = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16"
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 1002b9513f..7039c85bb2 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,12 +1,12 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
.pull-right
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link btn-grouped" do
+ = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-sm edit-milestone-link btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
- = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-close"
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close"
%h4
- = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone)
+ = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- if milestone.expired? and not milestone.closed?
%span.cred (Expired)
%small
@@ -16,12 +16,11 @@
- else
%div
%div
- = link_to project_issues_path(milestone.project, milestone_id: milestone.id) do
+ = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
= pluralize milestone.issues.count, 'Issue'
- = link_to project_merge_requests_path(milestone.project, milestone_id: milestone.id) do
+ = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do
= pluralize milestone.merge_requests.count, 'Merge Request'
%span.light #{milestone.percent_complete}% complete
- .progress.progress-info
- .progress-bar{style: "width: #{milestone.percent_complete}%;"}
+ = milestone_progress_bar(milestone)
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 03367b7cdb..d3eab8d6d7 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,33 +1,17 @@
-= render "projects/issues/head"
-.milestones_content
- %h3.page-title
- Milestones
- - if can? current_user, :admin_milestone, @project
- = link_to new_project_milestone_path(@project), class: "pull-right btn btn-new", title: "New Milestone" do
- %i.fa.fa-plus
- New Milestone
+.pull-right
+ - if can? current_user, :admin_milestone, @project
+ = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
+ %i.fa.fa-plus
+ New Milestone
+= render 'shared/milestones_filter'
- .row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
- %i.fa.fa-list.fa-2x
- .col-md-3.responsive-side
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if (params[:f] == "active" || !params[:f]))}
- = link_to project_milestones_path(@project, f: "active") do
- Active
- %li{class: ("active" if params[:f] == "closed")}
- = link_to project_milestones_path(@project, f: "closed") do
- Closed
- %li{class: ("active" if params[:f] == "all")}
- = link_to project_milestones_path(@project, f: "all") do
- All
- .col-md-9
- .panel.panel-default
- %ul.well-list
- = render @milestones
+.milestones
+ .panel.panel-default
+ %ul.well-list
+ = render @milestones
- - if @milestones.blank?
- %li
- .nothing-here-block No milestones to show
+ - if @milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
- = paginate @milestones, theme: "gitlab"
+ = paginate @milestones, theme: "gitlab"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 8263f7530a..110d896734 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,57 +1,49 @@
-= render "projects/issues/head"
-%h3.page-title
+%h4.page-title
+ .issue-box{ class: issue_box_class(@milestone) }
+ - if @milestone.closed?
+ Closed
+ - elsif @milestone.expired?
+ Expired
+ - else
+ Open
Milestone ##{@milestone.iid}
+ %small.creator
+ = @milestone.expires_at
.pull-right
- if can?(current_user, :admin_milestone, @project)
- = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do
+ = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
- if @milestone.active?
- = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
- else
- = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+ = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
.alert.alert-success
%span All issues for this milestone are closed. You may close milestone now.
-.back-link
- = link_to project_milestones_path(@project) do
- ← To milestones list
-
-
-.issue-box{ class: issue_box_class(@milestone) }
- .state.clearfix
- .state-label
- - if @milestone.closed?
- Closed
- - elsif @milestone.expired?
- Expired
- - else
- Open
- .creator
- = @milestone.expires_at
-
- %h4.title
- = gfm escape_once(@milestone.title)
-
+%h3.issue-title
+ = gfm escape_once(@milestone.title)
+%div
- if @milestone.description.present?
.description
.wiki
= preserve do
= markdown @milestone.description
- .context
- %p
- Progress:
- #{@milestone.closed_items_count} closed
- –
- #{@milestone.open_items_count} open
-
- %span.light #{@milestone.percent_complete}% complete
- %span.pull-right= @milestone.expires_at
- .progress.progress-info
- .progress-bar{style: "width: #{@milestone.percent_complete}%;"}
+%hr
+.context
+ %p.lead
+ Progress:
+ #{@milestone.closed_items_count} closed
+ –
+ #{@milestone.open_items_count} open
+
+ %span.light #{@milestone.percent_complete}% complete
+ %span.pull-right= @milestone.expires_at
+ = milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
@@ -69,10 +61,10 @@
%span.badge= @users.count
.pull-right
- = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small btn-grouped", title: "New Issue" do
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
%i.fa.fa-plus
New Issue
- = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link btn-grouped"
+ = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped"
.tab-content
.tab-pane.active#tab-issues
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 4a21b84fb8..c36bad1e94 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,7 +1,7 @@
= render "head"
.project-network
.controls
- = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f|
+ = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
= text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha'
= button_tag class: 'btn btn-success btn-search-sha' do
%i.fa.fa-search
@@ -18,8 +18,8 @@
disableButtonIfEmptyField('#extended_sha1', '.btn-search-sha')
network_graph = new Network({
- url: '#{project_network_path(@project, @ref, @options.merge(format: :json))}',
- commit_url: '#{project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")}',
+ url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}',
+ commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}',
ref: '#{@ref}',
commit_id: '#{@commit.id}'
})
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 6c986050c4..7fc612c0c7 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -3,12 +3,15 @@
= render 'projects/errors'
.project-edit-content
- = form_for @project, remote: true, html: { class: 'new_project form-horizontal' } do |f|
+ = form_for @project, html: { class: 'new_project form-horizontal' } do |f|
.form-group.project-name-holder
- = f.label :name, class: 'control-label' do
- %strong Project name
+ = f.label :path, class: 'control-label' do
+ %strong Project path
.col-sm-10
- = f.text_field :name, placeholder: "Example Project", class: "form-control", tabindex: 1, autofocus: true
+ .input-group
+ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true
+ .input-group-addon
+ \.git
- if current_user.can_select_namespace?
.form-group
@@ -18,40 +21,66 @@
= f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2}
%hr
- .js-toggle-container
- .form-group
- .col-sm-2
- .col-sm-10
- = link_to "#", class: 'js-toggle-button' do
- %i.fa.fa-pencil-square-o
- %span Customize repository name?
- .js-toggle-content.hide
- .form-group
- = f.label :path, class: 'control-label' do
- %span Repository name
- .col-sm-10
- .input-group
- = f.text_field :path, class: 'form-control'
- %span.input-group-addon .git
-
.js-toggle-container
.form-group
.col-sm-2
.col-sm-10
= link_to "#", class: 'js-toggle-button' do
%i.fa.fa-upload
- %span Import existing repository?
+ %span Import existing repository by URL
.js-toggle-content.hide
.form-group.import-url-data
= f.label :import_url, class: 'control-label' do
- %span Import existing repo
+ %span Import existing git repo
.col-sm-10
= f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
- .bs-callout.bs-callout-info
+ .alert.alert-info.prepend-top-10
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
- %hr
+ For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
+
+
+ .project-import.form-group
+ %label.control-label Import projects from
+ .col-sm-10
+ - if github_import_enabled?
+ = link_to status_import_github_path, class: 'btn' do
+ %i.fa.fa-github
+ GitHub
+ - else
+ = link_to '#', class: 'how_to_import_link light btn' do
+ %i.fa.fa-github
+ GitHub
+ = render 'github_import_modal'
+
+
+ - if bitbucket_import_enabled?
+ = link_to status_import_bitbucket_path, class: 'btn' do
+ %i.fa.fa-bitbucket
+ Bitbucket
+ - else
+ = link_to '#', class: 'how_to_import_link light btn' do
+ %i.fa.fa-bitbucket
+ Bitbucket
+ = render 'bitbucket_import_modal'
+
+ - unless request.host == 'gitlab.com'
+ - if gitlab_import_enabled?
+ = link_to status_import_gitlab_path, class: 'btn' do
+ %i.fa.fa-heart
+ GitLab.com
+ - else
+ = link_to '#', class: 'how_to_import_link light btn' do
+ %i.fa.fa-heart
+ GitLab.com
+ = render 'gitlab_import_modal'
+
+ = link_to new_import_gitorious_path, class: 'btn' do
+ %i.icon-gitorious.icon-gitorious-small
+ Gitorious.org
+
+ %hr.prepend-botton-10
.form-group
= f.label :description, class: 'control-label' do
@@ -68,7 +97,7 @@
.pull-right
.light
Need a group for several dependent projects?
- = link_to new_group_path, class: "btn btn-tiny" do
+ = link_to new_group_path, class: "btn btn-xs" do
Create a group
.save-project-loader.hide
@@ -77,3 +106,11 @@
%i.fa.fa-spinner.fa-spin
Creating project & repository.
%p Please wait a moment, this page will automatically refresh when ready.
+
+:coffeescript
+ $ ->
+ $('.how_to_import_link').bind 'click', (e) ->
+ e.preventDefault()
+ import_modal = $(this).parent().find(".modal").show()
+ $('.modal-header .close').bind 'click', ->
+ $(".modal").hide()
diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml
deleted file mode 100644
index 49c504c104..0000000000
--- a/app/views/projects/new_tree/show.html.haml
+++ /dev/null
@@ -1,48 +0,0 @@
-%h3.page-title New file
-%hr
-.file-editor
- = form_tag(project_new_tree_path(@project, @id), method: :put, class: 'form-horizontal form-new-file') do
- .form-group.commit_message-group
- = label_tag 'file_name', class: 'control-label' do
- File name
- .col-sm-10
- .input-group
- %span.input-group-addon
- = @path[-1] == "/" ? @path : @path + "/"
- = text_field_tag 'file_name', params[:file_name], placeholder: "sample.rb", required: true, class: 'form-control'
- %span.input-group-addon
- on
- %span= @ref
-
- .form-group.commit_message-group
- = label_tag :encoding, class: "control-label" do
- Encoding
- .col-sm-10
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
- = render 'shared/commit_message_container', params: params,
- placeholder: 'Add new file'
- .file-holder
- .file-title
- %i.fa.fa-file
- .file-content.code
- %pre#editor= params[:content]
-
- .form-actions
- = hidden_field_tag 'content', '', id: "file-content"
- .commit-button-annotation
- = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create'
- .message
- to branch
- %strong= @ref
- = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message}
-
-:javascript
- ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
- var editor = ace.edit("editor");
-
- disableButtonIfAnyEmptyField($('.form-new-file'), '.form-control', '.btn-create')
-
- $(".js-commit-button").click(function(){
- $("#file-content").val(editor.getValue());
- $(".file-editor form").submit();
- });
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
new file mode 100644
index 0000000000..720957e833
--- /dev/null
+++ b/app/views/projects/no_repo.html.haml
@@ -0,0 +1,22 @@
+%h2
+ %i.fa.fa-warning
+ No repository
+
+%p.slead
+ The repository for this project does not exist.
+ %br
+ This means you can not push code until you create an empty repository or import existing one.
+%hr
+
+.no-repo-actions
+ = link_to namespace_project_repository_path(@project.namespace, @project), method: :post, class: 'btn btn-primary' do
+ Create empty bare repository
+
+ %strong.prepend-left-10.append-right-10 or
+
+ = link_to new_namespace_project_import_path(@project.namespace, @project), class: 'btn' do
+ Import repository
+
+- if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
new file mode 100644
index 0000000000..acb3991d29
--- /dev/null
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -0,0 +1,15 @@
+.note-edit-form
+ = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f|
+ = note_target_fields(note)
+ = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do
+ = render 'projects/zen', f: f, attr: :note,
+ classes: 'note_text js-note-text'
+
+ .comment-hints.clearfix
+ .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
+ .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
+
+ .note-form-actions
+ .buttons
+ = f.submit 'Save Comment', class: "btn btn-primary btn-save btn-grouped js-comment-button"
+ = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel"
\ No newline at end of file
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index c68b3817e7..be96c30214 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,27 +1,18 @@
-= form_for [@project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f|
- = note_target_fields
+= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f|
+ = note_target_fields(@note)
= f.hidden_field :commit_id
= f.hidden_field :line_code
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
- %ul.nav.nav-tabs
- %li.active
- = link_to '#note-write-holder', class: 'js-note-write-button' do
- Write
- %li
- = link_to '#note-preview-holder', class: 'js-note-preview-button', data: { url: preview_project_notes_path(@project) } do
- Preview
- %div
- .note-write-holder
- = render 'projects/zen', f: f, attr: :note,
- classes: 'note_text js-note-text'
- .light.clearfix
- .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
+ = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do
+ = render 'projects/zen', f: f, attr: :note,
+ classes: 'note_text js-note-text'
+
+ .comment-hints.clearfix
+ .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
+ .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
- .note-preview-holder.hide
- .js-note-preview
.note-form-actions
.buttons
@@ -29,13 +20,5 @@
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
- .note-form-option
- %a.choose-btn.btn.js-choose-note-attachment-button
- %i.fa.fa-paperclip
- %span Choose File ...
-
- %span.file_name.js-attachment-filename File name...
- = f.file_field :attachment, class: "js-note-attachment-input hidden"
-
:javascript
- window.project_image_path_upload = "#{upload_image_project_path @project}";
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 814bf19970..f3d00a6f06 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,7 +1,10 @@
-%li.timeline-entry{ id: dom_id(note), class: dom_class(note), data: { discussion: note.discussion_id } }
+%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
.timeline-entry-inner
.timeline-icon
- = image_tag avatar_icon(note.author_email), class: "avatar s40"
+ - if note.system
+ %span.fa.fa-circle
+ - else
+ = image_tag avatar_icon(note.author_email), class: "avatar s40"
.timeline-content
.note-header
.note-actions
@@ -14,56 +17,53 @@
%i.fa.fa-pencil-square-o
Edit
- = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do
+ = link_to namespace_project_note_path(@project.namespace, @project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do
%i.fa.fa-trash-o.cred
Remove
+ - if note.system
+ = image_tag avatar_icon(note.author_email), class: "avatar s16"
= link_to_member(@project, note.author, avatar: false)
+ %span.author-username
+ = '@' + note.author.username
%span.note-last-update
= note_timestamp(note)
- - if note.upvote?
- %span.vote.upvote.label.label-success
- %i.fa.fa-thumbs-up
- \+1
- - if note.downvote?
- %span.vote.downvote.label.label-danger
- %i.fa.fa-thumbs-down
- \-1
+ - if note.superceded?(@notes)
+ - if note.upvote?
+ %span.vote.upvote.label.label-gray.strikethrough
+ %i.fa.fa-thumbs-up
+ \+1
+ - if note.downvote?
+ %span.vote.downvote.label.label-gray.strikethrough
+ %i.fa.fa-thumbs-down
+ \-1
+ - else
+ - if note.upvote?
+ %span.vote.upvote.label.label-success
+ %i.fa.fa-thumbs-up
+ \+1
+ - if note.downvote?
+ %span.vote.downvote.label.label-danger
+ %i.fa.fa-thumbs-down
+ \-1
.note-body
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
-
- .note-edit-form
- = form_for note, url: project_note_path(@project, note), method: :put, remote: true, authenticity_token: true do |f|
- = f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on'
-
- .form-actions.clearfix
- = f.submit 'Save changes', class: "btn btn-primary btn-save js-comment-button"
-
- .note-form-option
- %a.choose-btn.btn.js-choose-note-attachment-button
- %i.fa.fa-paperclip
- %span Choose File ...
-
- %span.file_name.js-attachment-filename File name...
- = f.file_field :attachment, class: "js-note-attachment-input hidden"
-
- = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel"
-
+ = render 'projects/notes/edit_form', note: note
- if note.attachment.url
.note-attachment
- if note.attachment.image?
- = link_to note.attachment.secure_url, target: '_blank' do
- = image_tag note.attachment.secure_url, class: 'note-image-attach'
- .attachment.pull-right
- = link_to note.attachment.secure_url, target: "_blank" do
+ = link_to note.attachment.url, target: '_blank' do
+ = image_tag note.attachment.url, class: 'note-image-attach'
+ .attachment
+ = link_to note.attachment.url, target: "_blank" do
%i.fa.fa-paperclip
= note.attachment_identifier
- = link_to delete_attachment_project_note_path(@project, note),
+ = link_to delete_attachment_namespace_project_note_path(@project.namespace, @project, note),
title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do
%i.fa.fa-trash-o.cred
.clear
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 04ee17a40a..813e37276b 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -7,4 +7,4 @@
= render "projects/notes/form"
:javascript
- new Notes("#{project_notes_path(target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i})
+ new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i})
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index 52c06ec172..7c6f724317 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -8,7 +8,7 @@
%div
= link_to_member(@project, note.author, avatar: false)
started a discussion
- = link_to diffs_project_merge_request_path(note.project, note.noteable, anchor: note.line_code) do
+ = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do
%strong on the diff
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 94f16a5f02..62609cfc1c 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -8,7 +8,7 @@
%div
= link_to_member(@project, note.author, avatar: false)
started a discussion on commit
- = link_to(note.noteable.short_id, project_commit_path(note.project, note.noteable), class: 'monospace')
+ = link_to(note.noteable.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace')
.last-update.hide.js-toggle-content
- last_note = discussion_notes.last
last updated by
diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml
index da71220af1..f717c77a89 100644
--- a/app/views/projects/notes/discussions/_diff.html.haml
+++ b/app/views/projects/notes/discussions/_diff.html.haml
@@ -19,9 +19,11 @@
%td.new_line= "..."
%td.line_content.matched= line.text
- else
- %td.old_line= raw(line.type == "new" ? " " : line.old_pos)
- %td.new_line= raw(line.type == "old" ? " " : line.new_pos)
- %td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw "#{line.text} "
+ %td.old_line{class: line.type == "new" ? "new" : "old"}
+ = raw(line.type == "new" ? " " : line.old_pos)
+ %td.new_line{class: line.type == "new" ? "new" : "old"}
+ = raw(line.type == "old" ? " " : line.new_pos)
+ %td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text)
- if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
new file mode 100644
index 0000000000..43e92437cf
--- /dev/null
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -0,0 +1,16 @@
+.panel.panel-default
+ .panel-heading
+ %strong #{@group.name}
+ group members
+ %small
+ (#{members.count})
+ .panel-head-actions
+ = link_to group_group_members_path(@group), class: 'btn btn-sm' do
+ %i.fa.fa-pencil-square-o
+ Edit group members
+ %ul.well-list
+ - members.each do |member|
+ = render 'groups/group_members/group_member', member: member, show_controls: false
+ - if members.count > 20
+ %li
+ and #{members.count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(@group)}
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
new file mode 100644
index 0000000000..0f824bdabf
--- /dev/null
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -0,0 +1,15 @@
+= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f|
+ .form-group
+ = f.label :user_ids, "People", class: 'control-label'
+ .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
+
+ .form-group
+ = f.label :access_level, "Project Access", class: 'control-label'
+ .col-sm-10
+ = select_tag :access_level, options_for_select(ProjectMember.access_roles, @project_member.access_level), class: "project-access-select select2"
+ .help-block
+ Read more about role permissions
+ %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+
+ .form-actions
+ = f.submit 'Add users to project', class: "btn btn-create"
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
new file mode 100644
index 0000000000..1f31d84dd1
--- /dev/null
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -0,0 +1,35 @@
+- user = member.user
+- return unless user
+
+%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
+ %span.list-item-name
+ = image_tag avatar_icon(user.email, 16), class: "avatar s16"
+ %strong= user.name
+ %span.cgray= user.username
+ - if user == current_user
+ %span.label.label-success It's you
+ - if user.blocked?
+ %label.label.label-danger
+ %strong Blocked
+
+ - if current_user_can_admin_project
+ - unless @project.personal? && user == current_user
+ .pull-right
+ %strong= member.human_access
+ = button_tag class: "btn-xs btn js-toggle-button",
+ title: 'Edit access level', type: 'button' do
+ %i.fa.fa-pencil-square-o
+
+
+ - if current_user == user
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: "Leave project?"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do
+ %i.fa.fa-minus.fa-inverse
+ - else
+ = link_to namespace_project_project_member_path(@project.namespace, @project, user), data: { confirm: remove_from_project_team_message(@project, user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from team' do
+ %i.fa.fa-minus.fa-inverse
+
+ .edit-member.hide.js-toggle-content
+ = form_for member, as: :project_member, url: namespace_project_project_member_path(@project.namespace, @project, member.user), remote: true do |f|
+ .alert.prepend-top-20
+ = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level)
+ = f.submit 'Save', class: 'btn btn-save btn-sm'
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
new file mode 100644
index 0000000000..615c425e59
--- /dev/null
+++ b/app/views/projects/project_members/_team.html.haml
@@ -0,0 +1,11 @@
+- can_admin_project = can?(current_user, :admin_project, @project)
+
+.panel.panel-default.prepend-top-20
+ .panel-heading
+ %strong #{@project.name}
+ project members
+ %small
+ (#{members.count})
+ %ul.well-list
+ - members.each do |project_member|
+ = render 'project_member', member: project_member, current_user_can_admin_project: can_admin_project
diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/project_members/import.html.haml
similarity index 58%
rename from app/views/projects/team_members/import.html.haml
rename to app/views/projects/project_members/import.html.haml
index 510b579fe2..293754cd0c 100644
--- a/app/views/projects/team_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -3,12 +3,12 @@
%p.light
Only project members will be imported. Group members will be skipped.
%hr
-= form_tag apply_import_project_team_members_path(@project), method: 'post', class: 'form-horizontal' do
+= form_tag apply_import_namespace_project_project_members_path(@project.namespace, @project), method: 'post', class: 'form-horizontal' do
.form-group
= label_tag :source_project_id, "Project", class: 'control-label'
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
- = submit_tag 'Import project members', class: "btn btn-create"
- = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
+ = button_tag 'Import project members', class: "btn btn-create"
+ = link_to "Cancel", namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
new file mode 100644
index 0000000000..36a6f6a155
--- /dev/null
+++ b/app/views/projects/project_members/index.html.haml
@@ -0,0 +1,35 @@
+%h3.page-title
+ Users with access to this project
+
+%p.light
+ Read more about project permissions
+ %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+
+%hr
+
+.clearfix.js-toggle-container
+ = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
+ = button_tag 'Search', class: 'btn'
+
+ - if can?(current_user, :admin_project_member, @project)
+ %span.pull-right
+ = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do
+ Add members
+ %i.fa.fa-chevron-down
+ = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
+ Import members
+
+ .js-toggle-content.hide.new-group-member-holder
+ = render "new_project_member"
+
+= render "team", members: @project_members
+
+- if @group
+ = render "group_members", members: @group_members
+
+:coffeescript
+ $('form.member-search-form').on 'submit', (event) ->
+ event.preventDefault()
+ Turbolinks.visit @.action + '?' + $(@).serialize()
diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml
new file mode 100644
index 0000000000..811b185882
--- /dev/null
+++ b/app/views/projects/project_members/update.js.haml
@@ -0,0 +1,3 @@
+- can_admin_project = can?(current_user, :admin_project, @project)
+:plain
+ $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member, current_user_can_admin_project: can_admin_project))}');
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
new file mode 100644
index 0000000000..bb49f4de87
--- /dev/null
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -0,0 +1,34 @@
+- unless @branches.empty?
+ %br
+ %h4 Already Protected:
+ %table.table.protected-branches-list
+ %thead
+ %tr.no-border
+ %th Branch
+ %th Developers can push
+ %th Last commit
+ %th
+
+ %tbody
+ - @branches.each do |branch|
+ - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
+ %tr
+ %td
+ = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do
+ %strong= branch.name
+ - if @project.root_ref?(branch.name)
+ %span.label.label-info default
+ %td
+ = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url
+ %td
+ - if commit = branch.commit
+ = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do
+ = commit.short_id
+ ·
+ #{time_ago_with_tooltip(commit.committed_date)}
+ - else
+ (branch was removed from repository)
+ %td
+ .pull-right
+ - if can? current_user, :admin_project, @project
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm"
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 49a3ef4c8a..cfe2808417 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -1,17 +1,17 @@
%h3.page-title Protected branches
-%p.light This ability keeps stable branches secure and forces developers to use code reviews
+%p.light Keep stable branches secure and force developers to use Merge Requests
%hr
-.bs-callout.bs-callout-info
+.alert.alert-info
%p Protected branches are designed to
%ul
%li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
- %li prevents anyone from force pushing to the branch
- %li prevents anyone from deleting the branch
+ %li prevent anyone from force pushing to the branch
+ %li prevent anyone from deleting the branch
%p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
- if can? current_user, :admin_project, @project
- = form_for [@project, @protected_branch], html: { class: 'form-horizontal' } do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f|
-if @protected_branch.errors.any?
.alert.alert-danger
%ul
@@ -22,29 +22,14 @@
= f.label :name, "Branch", class: 'control-label'
.col-sm-10
= f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"})
+ .form-group
+ = f.label :developers_can_push, class: 'control-label' do
+ Developers can push
+ .col-sm-10
+ .checkbox
+ = f.check_box :developers_can_push
+ %span.descr Allow developers to push to this branch
.form-actions
= f.submit 'Protect', class: "btn-create btn"
-- unless @branches.empty?
- %h5 Already Protected:
- %ul.bordered-list.protected-branches-list
- - @branches.each do |branch|
- %li
- %h4
- = link_to project_commits_path(@project, branch.name) do
- %strong= branch.name
- - if @project.root_ref?(branch.name)
- %span.label.label-info default
- %span.label.label-success
- %i.fa.fa-lock
- .pull-right
- - if can? current_user, :admin_project, @project
- = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small"
+= render 'branches_list'
- - if commit = branch.commit
- = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do
- = commit.short_id
- %span.light
- = gfm escape_once(truncate(commit.title, length: 40))
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml
index 948a21aa81..49ce6c0888 100644
--- a/app/views/projects/refs/logs_tree.js.haml
+++ b/app/views/projects/refs/logs_tree.js.haml
@@ -11,9 +11,9 @@
- if @logs.present?
:plain
var current_url = location.href.replace(/\/?$/, '/');
- var log_url = '#{project_tree_url(@project, tree_join(@ref, @path || '/'))}'.replace(/\/?$/, '/');
+ var log_url = '#{namespace_project_tree_url(@project.namespace, @project, tree_join(@ref, @path || '/'))}'.replace(/\/?$/, '/');
if(current_url == log_url) {
// Load 10 more commit log for each file in tree
// if we still on the same page
- ajaxGet('#{logs_file_project_ref_path(@project, @ref, @path || '/', offset: (@offset + @limit))}');
+ ajaxGet('#{logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '/', offset: (@offset + @limit))}');
}
diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml
index ce69adeb48..26669fb00a 100644
--- a/app/views/projects/repositories/_download_archive.html.haml
+++ b/app/views/projects/repositories/_download_archive.html.haml
@@ -3,7 +3,7 @@
- split_button = split_button || false
- if split_button == true
%span.btn-group{class: btn_class}
- = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' }
@@ -12,26 +12,26 @@
Select Archive Format
%ul.dropdown-menu{ role: 'menu' }
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do
%i.fa.fa-download
%span Download zip
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.gz
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar.bz2
%li
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar'), rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do
%i.fa.fa-download
%span Download tar
- else
%span.btn-group{class: btn_class}
- = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-download
%span zip
- = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do
%i.fa.fa-download
%span tar.gz
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index c77ffff43f..f3526ad074 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -1,7 +1,7 @@
- commit = update
%tr
%td
- = link_to project_commits_path(@project, commit.head.name) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, commit.head.name) do
%strong
= commit.head.name
- if @project.root_ref?(commit.head.name)
@@ -9,7 +9,7 @@
%td
%div
- = link_to project_commits_path(@project, commit.id) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
= gfm escape_once(truncate(commit.title, length: 40))
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 16d59d1fe9..bb983229b1 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -5,12 +5,12 @@
%p= @service.description
.back-link
- = link_to project_services_path(@project) do
+ = link_to namespace_project_services_path(@project.namespace, @project) do
← to services
%hr
-= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
+= form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
- if @service.errors.any?
.alert.alert-danger
%ul
@@ -18,24 +18,72 @@
%li= msg
- if @service.help.present?
- .bs-callout
- = @service.help
+ .alert.alert-info
+ = preserve do
+ = markdown @service.help
.form-group
= f.label :active, "Active", class: "control-label"
.col-sm-10
= f.check_box :active
+ - if @service.supported_events.length > 1
+ .form-group
+ = f.label :url, "Trigger", class: 'control-label'
+ .col-sm-10
+ - if @service.supported_events.include?("push")
+ %div
+ = f.check_box :push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :push_events, class: 'list-label' do
+ %strong Push events
+ %p.light
+ This url will be triggered by a push to the repository
+ - if @service.supported_events.include?("tag_push")
+ %div
+ = f.check_box :tag_push_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :tag_push_events, class: 'list-label' do
+ %strong Tag push events
+ %p.light
+ This url will be triggered when a new tag is pushed to the repository
+ - if @service.supported_events.include?("note")
+ %div
+ = f.check_box :note_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :note_events, class: 'list-label' do
+ %strong Comments
+ %p.light
+ This url will be triggered when someone adds a comment
+ - if @service.supported_events.include?("issue")
+ %div
+ = f.check_box :issues_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :issues_events, class: 'list-label' do
+ %strong Issues events
+ %p.light
+ This url will be triggered when an issue is created
+ - if @service.supported_events.include?("merge_request")
+ %div
+ = f.check_box :merge_requests_events, class: 'pull-left'
+ .prepend-left-20
+ = f.label :merge_requests_events, class: 'list-label' do
+ %strong Merge Request events
+ %p.light
+ This url will be triggered when a merge request is created
+
- @service.fields.each do |field|
- name = field[:name]
- - value = @service.send(name)
+ - title = field[:title] || name.humanize
+ - value = service_field_value(field[:type], @service.send(name))
- type = field[:type]
- placeholder = field[:placeholder]
- choices = field[:choices]
- default_choice = field[:default_choice]
+ - help = field[:help]
.form-group
- = f.label name, class: "control-label"
+ = f.label name, title, class: "control-label"
.col-sm-10
- if type == 'text'
= f.text_field name, class: "form-control", placeholder: placeholder
@@ -45,9 +93,13 @@
= f.check_box name
- elsif type == 'select'
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
+ - elsif type == 'password'
+ = f.password_field name, placeholder: value, class: 'form-control'
+ - if help
+ %span.help-block= help
.form-actions
= f.submit 'Save', class: 'btn btn-save'
- if @service.valid? && @service.activated? && @service.can_test?
- = link_to 'Test settings', test_project_service_path(@project, @service.to_param), class: 'btn'
+ = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: 'btn'
diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml
index 7271dd830c..0d3ccb6bb8 100644
--- a/app/views/projects/services/index.html.haml
+++ b/app/views/projects/services/index.html.haml
@@ -1,13 +1,22 @@
%h3.page-title Project services
%p.light Project services allow you to integrate GitLab with other applications
-%hr
-%ul.bordered-list
+%table.table
+ %thead
+ %tr
+ %th
+ %th Service
+ %th Description
+ %th Last edit
- @services.sort_by(&:title).each do |service|
- %li
- %h4
- = link_to edit_project_service_path(@project, service.to_param) do
- = service.title
- .pull-right
- = boolean_to_icon service.activated?
- %p= service.description
+ %tr
+ %td
+ = boolean_to_icon service.activated?
+ %td
+ = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
+ %strong= service.title
+ %td
+ = service.description
+ %td.light
+ = time_ago_in_words service.updated_at
+ ago
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 9b06ebe95a..74b0739565 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,3 +1,7 @@
+- if current_user && can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+ = render 'shared/no_password'
+
= render "home_panel"
- readme = @repository.readme
@@ -11,20 +15,22 @@
Readme
.project-home-links
- unless @project.empty_repo?
- = link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
- = link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), project_branches_path(@project)
- = link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), project_tags_path(@project)
+ = link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref)
+ = link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), namespace_project_branches_path(@project.namespace, @project)
+ = link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), namespace_project_tags_path(@project.namespace, @project)
%span.light.prepend-left-20= repository_size
.tab-content
.tab-pane.active#tab-activity
.row
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
%section.col-md-9
= render "events/event_last_push", event: @last_push
= render 'shared/event_filter'
.content_list
= spinner
- %aside.col-md-3.project-side.hidden-sm.hidden-xs
+ %aside.col-md-3.project-side
.clearfix
- if @project.archived?
.alert.alert-warning
@@ -34,19 +40,19 @@
%p Repository is read-only
- if @project.forked_from_project
- .alert.alert-success
+ .well
%i.fa.fa-code-fork.project-fork-icon
Forked from:
%br
- = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
+ = link_to @project.forked_from_project.name_with_namespace, namespace_project_path(@project.namespace, @project.forked_from_project)
- unless @project.empty_repo?
- = link_to project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do
Compare code
- if @repository.version
- version = @repository.version
- = link_to project_blob_path(@project, tree_join(@repository.root_ref, version.name)), class: 'btn btn-block' do
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, version.name)), class: 'btn btn-block' do
Version:
%span.count
= @repository.blob_by_oid(version.id).data
@@ -65,16 +71,16 @@
- @project.ci_services.each do |ci_service|
- if ci_service.active? && ci_service.respond_to?(:builds_path)
- if ci_service.respond_to?(:status_img_path)
- = link_to ci_service.builds_path do
+ = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do
= image_tag ci_service.status_img_path, alt: "build status"
- else
%span.light CI provided by
- = link_to ci_service.title, ci_service.builds_path
+ = link_to ci_service.title, ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink'
- if readme
.tab-pane#tab-readme
%article.readme-holder#README
- = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
%h4.readme-file-title
%i.fa.fa-file
= readme.name
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index f6a5bf9e4f..2d4d5d030a 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -1,4 +1,4 @@
%h3.page-title
Edit snippet
%hr
-= render "shared/snippets/form", url: project_snippet_path(@project, @snippet)
+= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet)
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index e60f9a4432..e2d8ec673a 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,7 +1,7 @@
%h3.page-title
Snippets
- if can? current_user, :write_project_snippet, @project
- = link_to new_project_snippet_path(@project), class: "btn btn-new pull-right", title: "New Snippet" do
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Snippet" do
Add new snippet
%p.light
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 10f684b631..bb659dba0c 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -1,4 +1,4 @@
%h3.page-title
New snippet
%hr
-= render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
+= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet)
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index ada0d30c49..d19689a105 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -2,7 +2,7 @@
= @snippet.title
.pull-right
- = link_to new_project_snippet_path(@project), class: "btn btn-new", title: "New Snippet" do
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
Add new snippet
%hr
@@ -17,21 +17,21 @@
= @snippet.author_name
.back-link
- = link_to project_snippets_path(@project) do
+ = link_to namespace_project_snippets_path(@project.namespace, @project) do
← project snippets
.file-holder
.file-title
%i.fa.fa-file
- %span.file_name
+ %strong
= @snippet.file_name
- .options
+ .file-actions
.btn-group
- if can?(current_user, :modify_project_snippet, @snippet)
- = link_to "edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-small", title: 'Edit Snippet'
- = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-small", target: "_blank"
+ = link_to "edit", edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", title: 'Edit Snippet'
+ = link_to "raw", raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
- if can?(current_user, :admin_project_snippet, @snippet)
- = link_to "remove", project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-small btn-remove", title: 'Delete Snippet'
+ = link_to "remove", namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet'
= render 'shared/snippets/blob'
%div#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index bce105a033..f22308e54b 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -1,14 +1,17 @@
- commit = @repository.commit(tag.target)
%li
%h4
- = link_to project_commits_path(@project, tag.name), class: "" do
+ = link_to namespace_project_commits_path(@project.namespace, @project, tag.name), class: "" do
%i.fa.fa-tag
= tag.name
+ - if tag.message.present?
+
+ = strip_gpg_signature(tag.message)
.pull-right
- if can? current_user, :download_code, @project
- = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small'
+ = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-sm'
- if can?(current_user, :admin_project, @project)
- = link_to project_tag_path(@project, tag.name), class: 'btn btn-small btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do
+ = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-sm btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do
%i.fa.fa-trash-o
- if commit
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index ac74e3b6d3..f1bc2bc9a2 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -4,7 +4,7 @@
Git Tags
- if can? current_user, :push_code, @project
.pull-right
- = link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do
+ = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
%i.fa.fa-add-sign
New tag
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index aa08b39776..655044438d 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -5,7 +5,7 @@
%h3.page-title
%i.fa.fa-code-fork
New tag
-= form_tag project_tags_path, method: :post, class: "form-horizontal" do
+= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal" do
.form-group
= label_tag :tag_name, 'Name for new tag', class: 'control-label'
.col-sm-10
@@ -21,10 +21,11 @@
= text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
.light (Optional) Entering a message will create an annotated tag.
.form-actions
- = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
+ = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
+ = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
+ disableButtonIfAnyEmptyField($("#new-tag-form"), ".form-control", ".btn-create");
var availableTags = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({
diff --git a/app/views/projects/team_members/_form.html.haml b/app/views/projects/team_members/_form.html.haml
deleted file mode 100644
index 2bf61fa12b..0000000000
--- a/app/views/projects/team_members/_form.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-%h3.page-title
- New project member(s)
-
-= form_for @user_project_relation, as: :project_member, url: project_team_members_path(@project), html: { class: "form-horizontal users-project-form" } do |f|
- -if @user_project_relation.errors.any?
- .alert.alert-danger
- %ul
- - @user_project_relation.errors.full_messages.each do |msg|
- %li= msg
-
- %p 1. Choose people you want in the project
- .form-group
- = f.label :user_ids, "People", class: 'control-label'
- .col-sm-10
- = users_select_tag(:user_ids, multiple: true)
-
- %p 2. Set access level for them
- .form-group
- = f.label :access_level, "Project Access", class: 'control-label'
- .col-sm-10= select_tag :access_level, options_for_select(Gitlab::Access.options, @user_project_relation.access_level), class: "project-access-select select2"
-
- .form-actions
- = f.submit 'Add users', class: "btn btn-create"
- = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/team_members/_group_members.html.haml b/app/views/projects/team_members/_group_members.html.haml
deleted file mode 100644
index df3c914fde..0000000000
--- a/app/views/projects/team_members/_group_members.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-- group_users_count = @group.group_members.count
-.panel.panel-default
- .panel-heading
- %strong #{@group.name}
- group members (#{group_users_count})
- .pull-right
- = link_to members_group_path(@group), class: 'btn btn-small' do
- %i.fa.fa-pencil-square-o
- %ul.well-list
- - @group.group_members.order('access_level DESC').limit(20).each do |member|
- = render 'groups/group_members/group_member', member: member, show_controls: false
- - if group_users_count > 20
- %li
- and #{group_users_count - 20} more. For full list visit #{link_to 'group members page', members_group_path(@group)}
diff --git a/app/views/projects/team_members/_team.html.haml b/app/views/projects/team_members/_team.html.haml
deleted file mode 100644
index 0e5b817613..0000000000
--- a/app/views/projects/team_members/_team.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-.team-table
- - can_admin_project = (can? current_user, :admin_project, @project)
- .panel.panel-default
- .panel-heading
- %strong #{@project.name}
- project members (#{members.count})
- %ul.well-list
- - members.each do |team_member|
- = render 'team_member', member: team_member, current_user_can_admin_project: can_admin_project
diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml
deleted file mode 100644
index 5f29b58de3..0000000000
--- a/app/views/projects/team_members/_team_member.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-- user = member.user
-%li{id: dom_id(user), class: "team_member_row access-#{member.human_access.downcase}"}
- .pull-right
- - if current_user_can_admin_project
- - unless @project.personal? && user == current_user
- .pull-left
- = form_for(member, as: :project_member, url: project_team_member_path(@project, member.user)) do |f|
- = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "medium project-access-select span2 trigger-submit"
-
- = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
- %i.fa.fa-minus.fa-inverse
- = image_tag avatar_icon(user.email, 32), class: "avatar s32"
- %p
- %strong= user.name
- %span.cgray= user.username
-
-
diff --git a/app/views/projects/team_members/index.html.haml b/app/views/projects/team_members/index.html.haml
deleted file mode 100644
index ecb7c689e8..0000000000
--- a/app/views/projects/team_members/index.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-%h3.page-title
- Users with access to this project
-
- - if can? current_user, :admin_team_member, @project
- %span.pull-right
- = link_to new_project_team_member_path(@project), class: "btn btn-new btn-grouped", title: "New project member" do
- New project member
- = link_to import_project_team_members_path(@project), class: "btn btn-grouped", title: "Import members from another project" do
- Import members
-
-%p.light
- Read more about project permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-= render "team", members: @project_members
-- if @group
- = render "group_members"
diff --git a/app/views/projects/team_members/new.html.haml b/app/views/projects/team_members/new.html.haml
deleted file mode 100644
index b1bc3ba0eb..0000000000
--- a/app/views/projects/team_members/new.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= render "form"
diff --git a/app/views/projects/team_members/update.js.haml b/app/views/projects/team_members/update.js.haml
deleted file mode 100644
index c68fe9574a..0000000000
--- a/app/views/projects/team_members/update.js.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-- if @user_project_relation.valid?
- :plain
- $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#529214"}, 1000);;
-- else
- :plain
- $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#D12F19"}, 1000);;
diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml
index 10b0de98c0..17b9fecfeb 100644
--- a/app/views/projects/transfer.js.haml
+++ b/app/views/projects/transfer.js.haml
@@ -1,7 +1,2 @@
-- if @project.errors[:namespace_id].present?
- :plain
- $("#tab-transfer .errors-holder").replaceWith(errorMessage('#{escape_javascript(@project.errors[:namespace_id].first)}'));
- $("#tab-transfer .form-actions input").removeAttr('disabled').removeClass('disabled');
-- else
- :plain
- location.href = "#{edit_project_path(@project)}";
+:plain
+ location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index 393ef0e24b..b253fe896e 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -2,7 +2,7 @@
%td.tree-item-file-name
= tree_icon(type)
%span.str-truncated
- = link_to blob_item.name, project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name))
+ = link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name))
%td.tree_time_ago.cgray
= render 'spinner'
%td.hidden-xs.tree_commit
diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml
index a8ec9df2c8..20c70cac69 100644
--- a/app/views/projects/tree/_submodule_item.html.haml
+++ b/app/views/projects/tree/_submodule_item.html.haml
@@ -1,14 +1,6 @@
-- tree, commit = submodule_links(submodule_item)
%tr{ class: "tree-item" }
%td.tree-item-file-name
%i.fa.fa-archive
- %span
- = link_to truncate(submodule_item.name, length: 40), tree
- @
- %span.monospace
- - if commit.nil?
- #{submodule_item.id[0..10]}
- - else
- = link_to "#{submodule_item.id[0..10]}", commit
+ = submodule_link(submodule_item, @ref)
%td
%td.hidden-xs
diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml
index 1159fcadff..d304690d16 100644
--- a/app/views/projects/tree/_tree.html.haml
+++ b/app/views/projects/tree/_tree.html.haml
@@ -1,16 +1,16 @@
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to project_tree_path(@project, @ref) do
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
- tree_breadcrumbs(tree, 6) do |title, path|
%li
- if path
- = link_to truncate(title, length: 40), project_tree_path(@project, path)
+ = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
= link_to title, '#'
- if current_user && can_push_branch?(@project, @ref)
%li
- = link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do
+ = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'New file', id: 'new-file-link' do
%small
%i.fa.fa-plus
@@ -27,15 +27,15 @@
%i.fa.fa-angle-right
%small.light
- = link_to @commit.short_id, project_commit_path(@project, @commit)
+ = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit)
–
= truncate(@commit.title, length: 50)
- = link_to 'History', project_commits_path(@project, @id), class: 'pull-right'
+ = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right'
- if @path.present?
%tr.tree-item
%td.tree-item-file-name
- = link_to "..", project_tree_path(@project, up_dir_path(tree)), class: 'prepend-left-10'
+ = link_to "..", namespace_project_tree_path(@project.namespace, @project, up_dir_path), class: 'prepend-left-10'
%td
%td.hidden-xs
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index bd50dd4d9a..50521264a6 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,3 +1,3 @@
%span.str-truncated
%span.tree_author= commit_author_link(commit, avatar: true, size: 16)
- = link_to_gfm commit.title, project_commit_path(@project, commit.id), class: "tree-commit-link"
+ = link_to_gfm commit.title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index f8cecf9be1..94342bc9b2 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -2,7 +2,8 @@
%td.tree-item-file-name
= tree_icon(type)
%span.str-truncated
- = link_to tree_item.name, project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name))
+ - path = flatten_tree(tree_item)
+ = link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path))
%td.tree_time_ago.cgray
= render 'spinner'
%td.hidden-xs.tree_commit
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index fc4616da6e..feca145369 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -3,7 +3,7 @@
- if can? current_user, :download_code, @project
.tree-download-holder
- = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group-small pull-right hidden-xs hidden-sm', split_button: true
+ = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group-sm pull-right hidden-xs hidden-sm', split_button: true
#tree-holder.tree-holder.clearfix
= render "tree", tree: @tree
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index cbb21f2b9f..4f3f4cab8d 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -1,6 +1,6 @@
- if @project.valid?
:plain
- location.href = "#{edit_project_path(@project)}";
+ location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
- else
:plain
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index f37c086716..9fbfa0b1ae 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f|
-if @page.errors.any?
#error_explanation
.alert.alert-danger
@@ -19,13 +19,15 @@
%code [Link Title](page-slug)
\.
- .form-group
+ .form-group.wiki-content
= f.label :content, class: 'control-label'
.col-sm-10
- = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
- .col-sm-12.hint
- .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
- .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
+ .col-sm-12.hint
+ .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
+ .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }.
+
.clearfix
.error-alert
.form-group
@@ -35,11 +37,10 @@
.form-actions
- if @page && @page.persisted?
= f.submit 'Save changes', class: "btn-save btn"
- = link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel"
- else
= f.submit 'Create page', class: "btn-create btn"
- = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
:javascript
- window.project_image_path_upload = "#{upload_image_project_path @project}";
-
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 30410bc95e..633214a4e8 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,8 +1,8 @@
%span.pull-right
- if (@page && @page.persisted?)
- = link_to history_project_wiki_path(@project, @page), class: "btn btn-grouped" do
+ = link_to history_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
Page History
- if can?(current_user, :write_wiki, @project)
- = link_to edit_project_wiki_path(@project, @page), class: "btn btn-grouped" do
+ = link_to edit_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index 90539fde58..693c3facb3 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,12 +1,12 @@
%ul.nav.nav-tabs
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
- = link_to 'Home', project_wiki_path(@project, :home)
+ = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
= nav_link(path: 'wikis#pages') do
- = link_to 'Pages', pages_project_wikis_path(@project)
+ = link_to 'Pages', pages_namespace_project_wikis_path(@project.namespace, @project)
= nav_link(path: 'wikis#git_access') do
- = link_to git_access_project_wikis_path(@project) do
+ = link_to git_access_namespace_project_wikis_path(@project.namespace, @project) do
%i.fa.fa-download
Git Access
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 1ce292a02d..6834969de8 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -7,7 +7,7 @@
.modal-body
= label_tag :new_wiki_path do
%span Page slug
- = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project)
+ = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project)
%p.hint
Please don't use spaces.
.modal-footer
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 5347caf000..566850cb78 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -9,5 +9,5 @@
.pull-right
- if @page.persisted? && can?(current_user, :admin_wiki, @project)
- = link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-small btn-remove" do
+ = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-sm btn-remove" do
Delete this page
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index d3a66c48c9..91291f753f 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,7 +1,7 @@
= render 'nav'
%h3.page-title
%span.light History for
- = link_to @page.title, project_wiki_path(@project, @page)
+ = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
%table.table
%thead
@@ -12,18 +12,19 @@
%th Last updated
%th Format
%tbody
- - @page.versions.each do |version|
+ - @page.versions.each_with_index do |version, index|
- commit = version
%tr
%td
- = link_to project_wiki_path(@project, @page, version_id: commit.id) do
- = commit.id[0..10]
+ = link_to project_wiki_path_with_version(@project, @page,
+ commit.id, index == 0) do
+ = truncate_sha(commit.id)
%td
= commit.author.name
%td
= commit.message
%td
- #{time_ago_with_tooltip(version.date)}
+ #{time_ago_with_tooltip(version.authored_date)}
%td
%strong
= @page.page.wiki.page(@page.page.name, commit.id).try(:format)
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 264b48ec36..ee233d9086 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -5,7 +5,7 @@
- @wiki_pages.each do |wiki_page|
%li
%h4
- = link_to wiki_page.title, project_wiki_path(@project, wiki_page)
+ = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
%small (#{wiki_page.format})
.pull-right
%small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index ede4fef9e2..a6263e93f6 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -5,7 +5,7 @@
- if @page.historical?
.warning_message
This is an old version of this page.
- You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", history_project_wiki_path(@project, @page)}.
+ You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", history_namespace_project_wiki_path(@project.namespace, @project, @page)}.
%hr
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index eca69ce50b..ffc145497a 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -1,5 +1,5 @@
.dropdown.inline
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
+ %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%i.fa.fa-tags
%span.light Group:
- if @group.present?
@@ -17,7 +17,7 @@
= group.name
.dropdown.inline.prepend-left-10.project-filter
- %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"}
+ %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%i.fa.fa-tags
%span.light Project:
- if @project.present?
diff --git a/app/views/search/_project_filter.html.haml b/app/views/search/_project_filter.html.haml
index c201b3d6c4..ad933502a2 100644
--- a/app/views/search/_project_filter.html.haml
+++ b/app/views/search/_project_filter.html.haml
@@ -25,6 +25,7 @@
= @search_results.notes_count
%li{class: ("active" if @scope == 'wiki_blobs')}
= link_to search_filter_path(scope: 'wiki_blobs') do
+ %i.fa.fa-book
Wiki
.pull-right
= @search_results.wiki_blobs_count
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 58bcff9dbe..796dd752a4 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -2,7 +2,7 @@
#{@search_results.total_count} results found
- unless @show_snippets
- if @project
- for #{link_to @project.name_with_namespace, @project}
+ for #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]}
- elsif @group
for #{link_to @group.name, @group}
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index b46b4832e1..84e9be82c4 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,9 +1,9 @@
.blob-result
.file-holder
.file-title
- = link_to project_blob_path(@project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do
+ = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do
%i.fa.fa-file
%strong
= blob.filename
.file-content.code.term
- = render 'shared/file_hljs', blob: blob, first_line_number: blob.startline
+ = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, user_color_scheme_class: 'white'
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index 7868f95826..ce8ddff955 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -1,6 +1,6 @@
.search-result-row
%h4
- = link_to [issue.project, issue] do
+ = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
%span.term.str-truncated= issue.title
.pull-right ##{issue.iid}
- if issue.description.present?
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 56b185283b..2efa616d66 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -1,6 +1,6 @@
.search-result-row
%h4
- = link_to [merge_request.target_project, merge_request] do
+ = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
%span.term.str-truncated= merge_request.title
.pull-right ##{merge_request.iid}
- if merge_request.description.present?
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index f2327cd69c..5fcba2b7e9 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -9,15 +9,15 @@
= link_to project do
= project.name_with_namespace
·
- = link_to project_commit_path(project, note.commit_id, anchor: dom_id(note)) do
- Commit #{note.commit_id[0..8]}
+ = link_to namespace_project_commit_path(project.namespace, project, note.commit_id, anchor: dom_id(note)) do
+ Commit #{truncate_sha(note.commit_id)}
- else
= link_to project do
= project.name_with_namespace
·
%span #{note.noteable_type.titleize} ##{note.noteable.iid}
·
- = link_to [project, note.noteable, anchor: dom_id(note)] do
+ = link_to [project.namespace.becomes(Namespace), project, note.noteable, anchor: dom_id(note)] do
= note.noteable.title
.note-search-result
diff --git a/app/views/search/results/_project.html.haml b/app/views/search/results/_project.html.haml
index 301b65eca2..195cf06c8e 100644
--- a/app/views/search/results/_project.html.haml
+++ b/app/views/search/results/_project.html.haml
@@ -1,6 +1,6 @@
.search-result-row
%h4
- = link_to project do
+ = link_to [project.namespace.becomes(Namespace), project] do
%span.term= project.name_with_namespace
- if project.description.present?
%span.light.term= project.description
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index 6fc2cdf636..8af393777f 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -13,12 +13,6 @@
.file-title
%i.fa.fa-file
%strong= snippet_blob[:snippet_object].file_name
- %span.options
- .btn-group.tree-btn-group.pull-right
- - if snippet_blob[:snippet_object].author == current_user
- = link_to "Edit", edit_snippet_path(snippet_blob[:snippet_object]), class: "btn btn-tiny", title: 'Edit Snippet'
- = link_to "Delete", snippet_path(snippet_blob[:snippet_object]), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-tiny", title: 'Delete Snippet'
- = link_to "Raw", raw_snippet_path(snippet_blob[:snippet_object]), class: "btn btn-tiny", target: "_blank"
- if gitlab_markdown?(snippet_blob[:snippet_object].file_name)
.file-content.wiki
- snippet_blob[:snippet_chunks].each do |snippet|
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index f7e5ee5e20..c414acb6a1 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -11,7 +11,7 @@
%small.pull-right.cgray
- if snippet_title.project_id?
- = link_to snippet_title.project.name_with_namespace, project_path(snippet_title.project)
+ = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project)
.snippet-info
= "##{snippet_title.id}"
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index e361074b6a..f9c5810e3d 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,9 +1,9 @@
.blob-result
.file-holder
.file-title
- = link_to project_wiki_path(@project, wiki_blob.filename) do
+ = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.filename) do
%i.fa.fa-file
%strong
= wiki_blob.filename
.file-content.code.term
- = render 'shared/file_hljs', blob: wiki_blob, first_line_number: wiki_blob.startline
+ = render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline, user_color_scheme_class: 'white'
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index bae57917a4..5b4816e4c4 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -6,7 +6,7 @@
.col-sm-6
= search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search"
.col-sm-4
- = submit_tag 'Search', class: "btn btn-create"
+ = button_tag 'Search', class: "btn btn-create"
.form-group
.col-sm-2
- unless params[:snippets].eql? 'true'
diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml
new file mode 100644
index 0000000000..000532b1c9
--- /dev/null
+++ b/app/views/shared/_choose_group_avatar_button.html.haml
@@ -0,0 +1,7 @@
+%a.choose-btn.btn.btn-sm.js-choose-group-avatar-button
+ %i.fa.fa-paperclip
+ %span Choose File ...
+
+%span.file_name.js-avatar-filename File name...
+= f.file_field :avatar, class: 'js-group-avatar-input hidden'
+.light The maximum file size allowed is 200KB.
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 1cc6043f56..a1121750ca 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -1,8 +1,20 @@
- project = project || @project
.git-clone-holder.input-group
.input-group-btn
- %button{class: "btn #{ 'active' if default_clone_protocol == 'ssh' }", :"data-clone" => project.ssh_url_to_repo} SSH
- %button{class: "btn #{ 'active' if default_clone_protocol == 'http' }", :"data-clone" => project.http_url_to_repo}= gitlab_config.protocol.upcase
+ %button{ |
+ class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
+ :"data-clone" => project.ssh_url_to_repo, |
+ :"data-title" => "Add an SSH key to your profile to pull or push via SSH",
+ :"data-html" => "true",
+ :"data-container" => "body"}
+ SSH
+ %button{ |
+ class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
+ :"data-clone" => project.http_url_to_repo, |
+ :"data-title" => "Set a password on your account to pull or push via #{gitlab_config.protocol.upcase}",
+ :"data-html" => "true",
+ :"data-container" => "body"}
+ = gitlab_config.protocol.upcase
= text_field_tag :project_clone, default_url_to_repo(project), class: "one_click_select form-control", readonly: true
- if project.kind_of?(Project)
.input-group-addon
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index ee0b57fbe5..d07a9e2b92 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,5 +1,19 @@
-.event_filter
+%ul.nav.nav-pills.event_filter
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
+
+ - if current_user
+ - if current_controller?(:dashboard)
+ %li.pull-right
+ = link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do
+ %i.fa.fa-rss
+ News Feed
+
+ - if current_controller?(:groups)
+ %li.pull-right
+ = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do
+ %i.fa.fa-rss
+ News Feed
+%hr
diff --git a/app/views/shared/_file_hljs.html.haml b/app/views/shared/_file_highlight.html.haml
similarity index 66%
rename from app/views/shared/_file_hljs.html.haml
rename to app/views/shared/_file_highlight.html.haml
index 444c948b02..fba69dd0f3 100644
--- a/app/views/shared/_file_hljs.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,4 +1,4 @@
-%div.highlighted-data{class: user_color_scheme_class}
+.file-content.code{class: user_color_scheme_class}
.line-numbers
- if blob.data.present?
- blob.data.lines.to_a.size.times do |index|
@@ -7,7 +7,5 @@
= link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do
%i.fa.fa-link
= i
- .highlight
- %pre
- %code{ class: highlightjs_class(blob.name) }
- #{blob.data}
+ :preserve
+ #{highlight(blob.name, blob.data)}
diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml
deleted file mode 100644
index d366dd97a7..0000000000
--- a/app/views/shared/_filter.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-.side-filters
- = form_tag filter_path(entity), method: 'get' do
- - if current_user
- %fieldset.scope-filter
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:scope] == 'assigned-to-me')}
- = link_to filter_path(entity, scope: 'assigned-to-me') do
- Assigned to me
- %span.pull-right
- = assigned_entities_count(current_user, entity, @group)
- %li{class: ("active" if params[:scope] == 'authored')}
- = link_to filter_path(entity, scope: 'authored') do
- Created by me
- %span.pull-right
- = authored_entities_count(current_user, entity, @group)
- %li{class: ("active" if params[:scope] == 'all')}
- = link_to filter_path(entity, scope: 'all') do
- Everyone's
- %span.pull-right
- = authorized_entities_count(current_user, entity, @group)
-
- %fieldset.status-filter
- %legend State
- %ul.nav.nav-pills
- %li{class: ("active" if params[:state] == 'opened')}
- = link_to filter_path(entity, state: 'opened') do
- Open
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to filter_path(entity, state: 'closed') do
- Closed
- %li{class: ("active" if params[:state] == 'all')}
- = link_to filter_path(entity, state: 'all') do
- All
-
- %fieldset
- %legend Projects
- %ul.nav.nav-pills.nav-stacked.nav-small
- - @projects.each do |project|
- - unless entities_per_project(project, entity).zero?
- %li{class: ("active" if params[:project_id] == project.id.to_s)}
- = link_to filter_path(entity, project_id: project.id) do
- = project.name_with_namespace
- %small.pull-right= entities_per_project(project, entity)
-
- %fieldset
- - if params[:state].present? || params[:project_id].present?
- = link_to filter_path(entity, state: nil, project_id: nil), class: 'pull-right cgray' do
- %i.fa.fa-times
- %strong Clear filter
-
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
new file mode 100644
index 0000000000..b34dd53e3b
--- /dev/null
+++ b/app/views/shared/_group_form.html.haml
@@ -0,0 +1,29 @@
+- if @group.persisted?
+ .form-group
+ = f.label :name, class: 'control-label' do
+ Group name
+ .col-sm-10
+ = f.text_field :name, placeholder: 'open-source', class: 'form-control'
+
+.form-group
+ = f.label :path, class: 'control-label' do
+ Group path
+ .col-sm-10
+ .input-group
+ .input-group-addon
+ = root_url
+ = f.text_field :path, placeholder: 'open-source', class: 'form-control',
+ autofocus: local_assigns[:autofocus] || false
+ - if @group.persisted?
+ .alert.alert-danger
+ %ul
+ %li Changing group path can have unintended side effects.
+ %li Renaming group path will rename directory for all related projects
+ %li It will change web url for access group and group projects.
+ %li It will change the git path to repositories under this group.
+
+.form-group.group-description-holder
+ = f.label :description, 'Details', class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, maxlength: 250,
+ class: 'form-control js-gfm-input', rows: 4
diff --git a/app/views/shared/_group_tips.html.haml b/app/views/shared/_group_tips.html.haml
new file mode 100644
index 0000000000..e5cf783beb
--- /dev/null
+++ b/app/views/shared/_group_tips.html.haml
@@ -0,0 +1,6 @@
+%ul
+ %li A group is a collection of several projects
+ %li Groups are private by default
+ %li Members of a group may only view projects they have permission to access
+ %li Group project URLs are prefixed with the group namespace
+ %li Existing projects may be moved into a group
diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml
new file mode 100644
index 0000000000..5412b9ef0f
--- /dev/null
+++ b/app/views/shared/_issuable_filter.html.haml
@@ -0,0 +1,119 @@
+.issues-filters
+ .issues-state-filters
+ %ul.nav.nav-tabs
+ %li{class: ("active" if params[:state] == 'opened')}
+ = link_to page_filter_path(state: 'opened') do
+ %i.fa.fa-exclamation-circle
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to page_filter_path(state: 'closed') do
+ %i.fa.fa-check-circle
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to page_filter_path(state: 'all') do
+ %i.fa.fa-compass
+ All
+
+ %div
+ - if controller.controller_name == 'issues'
+ .check-all-holder
+ = check_box_tag "check_all_issues", nil, false,
+ class: "check_all_issues left",
+ disabled: !can?(current_user, :modify_issue, @project)
+ .issues-other-filters
+ .dropdown.inline.assignee-filter
+ %button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
+ %i.fa.fa-user
+ %span.light assignee:
+ - if @assignee.present?
+ %strong= @assignee.name
+ - elsif params[:assignee_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to page_filter_path(assignee_id: nil) do
+ Any
+ = link_to page_filter_path(assignee_id: 0) do
+ Unassigned
+ - @assignees.sort_by(&:name).each do |user|
+ %li
+ = link_to page_filter_path(assignee_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
+
+ .dropdown.inline.prepend-left-10.author-filter
+ %button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
+ %i.fa.fa-user
+ %span.light author:
+ - if @author.present?
+ %strong= @author.name
+ - elsif params[:author_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to page_filter_path(author_id: nil) do
+ Any
+ = link_to page_filter_path(author_id: 0) do
+ Unassigned
+ - @authors.sort_by(&:name).each do |user|
+ %li
+ = link_to page_filter_path(author_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
+
+ .dropdown.inline.prepend-left-10.milestone-filter
+ %button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
+ %i.fa.fa-clock-o
+ %span.light milestone:
+ - if @milestone.present?
+ %strong= @milestone.title
+ - elsif params[:milestone_id] == "0"
+ None (backlog)
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to page_filter_path(milestone_id: nil) do
+ Any
+ = link_to page_filter_path(milestone_id: 0) do
+ None (backlog)
+ - @milestones.each do |milestone|
+ %li
+ = link_to page_filter_path(milestone_id: milestone.id) do
+ %strong= milestone.title
+ %small.light= milestone.expires_at
+
+ - if @project
+ .dropdown.inline.prepend-left-10.labels-filter
+ %button.dropdown-toggle.btn{type: 'button', "data-toggle" => "dropdown"}
+ %i.fa.fa-tags
+ %span.light label:
+ - if params[:label_name].present?
+ %strong= params[:label_name]
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to page_filter_path(label_name: nil) do
+ Any
+ - if @project.labels.any?
+ - @project.labels.each do |label|
+ %li
+ = link_to page_filter_path(label_name: label.name) do
+ = render_colored_label(label)
+ - else
+ %li
+ = link_to generate_namespace_project_labels_path(@project.namespace, @project, redirect: request.original_url), method: :post do
+ %i.fa.fa-plus-circle
+ Create default labels
+
+ .pull-right
+ = render 'shared/sort_dropdown'
diff --git a/app/views/shared/_issuable_search_form.html.haml b/app/views/shared/_issuable_search_form.html.haml
new file mode 100644
index 0000000000..639d203dcd
--- /dev/null
+++ b/app/views/shared/_issuable_search_form.html.haml
@@ -0,0 +1,9 @@
+= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do
+ .append-right-10.hidden-xs.hidden-sm
+ = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
+ = hidden_field_tag :state, params['state']
+ = hidden_field_tag :scope, params['scope']
+ = hidden_field_tag :assignee_id, params['assignee_id']
+ = hidden_field_tag :author_id, params['author_id']
+ = hidden_field_tag :milestone_id, params['milestone_id']
+ = hidden_field_tag :label_id, params['label_id']
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index e976f897dc..0dbb6a0439 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -4,7 +4,7 @@
- project = group[0]
.panel-heading
= link_to_project project
- = link_to 'show all', project_issues_path(project), class: 'pull-right'
+ = link_to 'show all', namespace_project_issues_path(project.namespace, project), class: 'pull-right'
%ul.well-list.issues-list
- group[1].each do |issue|
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index 39a1ee38f8..c02c5af008 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -4,7 +4,7 @@
- project = group[0]
.panel-heading
= link_to_project project
- = link_to 'show all', project_merge_requests_path(project), class: 'pull-right'
+ = link_to 'show all', namespace_project_merge_requests_path(project.namespace, project), class: 'pull-right'
%ul.well-list.mr-list
- group[1].each do |merge_request|
= render 'projects/merge_requests/merge_request', merge_request: merge_request
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
new file mode 100644
index 0000000000..f685ae7726
--- /dev/null
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -0,0 +1,14 @@
+.milestones-filters.append-bottom-10
+ %ul.nav.nav-tabs
+ %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
+ = link_to milestones_filter_path(state: 'opened') do
+ %i.fa.fa-exclamation-circle
+ Open
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to milestones_filter_path(state: 'closed') do
+ %i.fa.fa-check-circle
+ Closed
+ %li{class: ("active" if params[:state] == 'all')}
+ = link_to milestones_filter_path(state: 'all') do
+ %i.fa.fa-compass
+ All
diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml
new file mode 100644
index 0000000000..a43bf33751
--- /dev/null
+++ b/app/views/shared/_no_password.html.haml
@@ -0,0 +1,8 @@
+- if cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && current_user.require_password?
+ .no-password-message.alert.alert-warning.hidden-xs
+ You won't be able to pull or push project code via #{gitlab_config.protocol.upcase} until you #{link_to 'set a password', edit_profile_password_path} on your account
+
+ .pull-right
+ = link_to "Don't show again", profile_path(user: {hide_no_password: true}), method: :put
+ |
+ = link_to 'Remind later', '#', class: 'hide-no-password-message'
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index e70eb4d01b..089179e677 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,14 +1,8 @@
-- if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key
- .no-ssh-key-message
- .container
- You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile
- .pull-right.hidden-xs
- = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true
- |
- = link_to 'Remind later', '#', class: 'hide-no-ssh-message'
- .links-xs.visible-xs
- = link_to "Add key", new_profile_key_path
- |
- = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true
- |
- = link_to 'Later', '#', class: 'hide-no-ssh-message'
+- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+ .no-ssh-key-message.alert.alert-warning.hidden-xs
+ You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path, class: 'alert-link'} to your profile
+
+ .pull-right
+ = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link'
+ |
+ = link_to 'Remind later', '#', class: 'hide-no-ssh-message alert-link'
diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml
new file mode 100644
index 0000000000..0eba1fe075
--- /dev/null
+++ b/app/views/shared/_outdated_browser.html.haml
@@ -0,0 +1,8 @@
+- if outdated_browser?
+ - link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers"
+ .browser-alert
+ GitLab may not work properly because you are using an outdated web browser.
+ %br
+ Please install a
+ = link_to 'supported web browser', link
+ for a better experience.
diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml
new file mode 100644
index 0000000000..8746970c23
--- /dev/null
+++ b/app/views/shared/_project.html.haml
@@ -0,0 +1,21 @@
+= cache [project, controller.controller_name, controller.action_name] do
+ = link_to project_path(project), class: dom_class(project) do
+ - if avatar
+ .dash-project-avatar
+ = project_icon(project, alt: '', class: 'avatar project-avatar s40')
+ .dash-project-access-icon
+ = visibility_level_icon(project.visibility_level)
+ %span.str-truncated
+ %span.namespace-name
+ - if project.namespace
+ = project.namespace.human_name
+ \/
+ %span.project-name.filter-title
+ = project.name
+ - if stars
+ %span.pull-right.light
+ %i.fa.fa-star
+ = project.star_count
+ - else
+ %span.arrow
+ %i.fa.fa-angle-right
diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml
deleted file mode 100644
index ea6a49e150..0000000000
--- a/app/views/shared/_project_filter.html.haml
+++ /dev/null
@@ -1,64 +0,0 @@
-.side-filters
- = form_tag project_entities_path, method: 'get' do
- - if current_user
- %fieldset
- %ul.nav.nav-pills.nav-stacked
- %li{class: ("active" if params[:scope] == 'all')}
- = link_to project_filter_path(scope: 'all') do
- Everyone's
- %span.pull-right
- = authorized_entities_count(current_user, entity, @project)
- %li{class: ("active" if params[:scope] == 'assigned-to-me')}
- = link_to project_filter_path(scope: 'assigned-to-me') do
- Assigned to me
- %span.pull-right
- = assigned_entities_count(current_user, entity, @project)
- %li{class: ("active" if params[:scope] == 'created-by-me')}
- = link_to project_filter_path(scope: 'created-by-me') do
- Created by me
- %span.pull-right
- = authored_entities_count(current_user, entity, @project)
-
- %fieldset
- %legend State
- %ul.nav.nav-pills
- %li{class: ("active" if params[:state] == 'opened')}
- = link_to project_filter_path(state: 'opened') do
- Open
- %li{class: ("active" if params[:state] == 'closed')}
- = link_to project_filter_path(state: 'closed') do
- Closed
- %li{class: ("active" if params[:state] == 'all')}
- = link_to project_filter_path(state: 'all') do
- All
-
- - if defined?(labels)
- %fieldset
- %legend
- Labels
- %small.pull-right
- = link_to project_labels_path(@project), class: 'light' do
- %i.fa.fa-pencil-square-o
- %ul.nav.nav-pills.nav-stacked.nav-small.labels-filter
- - @project.labels.order_by_name.each do |label|
- %li{class: label_filter_class(label.name)}
- = link_to labels_filter_path(label.name) do
- = render_colored_label(label)
- - if selected_label?(label.name)
- .pull-right
- %i.fa.fa-times
-
- - if @project.labels.empty?
- .light-well
- Create first label at
- = link_to 'labels page', project_labels_path(@project)
- %br
- or #{link_to 'generate', generate_project_labels_path(@project, redirect: redirect), method: :post} default set of labels
-
- %fieldset
- - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any?
- = link_to project_entities_path, class: 'cgray pull-right' do
- %i.fa.fa-times
- %strong Clear filter
-
-
diff --git a/app/views/shared/_projects_list.html.haml b/app/views/shared/_projects_list.html.haml
new file mode 100644
index 0000000000..4c58092af4
--- /dev/null
+++ b/app/views/shared/_projects_list.html.haml
@@ -0,0 +1,17 @@
+- projects_limit = 20 unless local_assigns[:projects_limit]
+- avatar = true unless local_assigns[:avatar] == false
+- stars = false unless local_assigns[:stars] == true
+%ul.well-list.projects-list
+ - projects.each_with_index do |project, i|
+ %li{class: (i >= projects_limit) ? 'project-row hide' : 'project-row'}
+ = render "shared/project", project: project, avatar: avatar, stars: stars
+ - if projects.blank?
+ %li
+ .nothing-here-block There are no projects here.
+ - if projects.count > projects_limit
+ %li.bottom
+ %span.light
+ #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
+ %span
+ = link_to '#', class: 'js-expand' do
+ Show all
diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml
index 3400c345c4..3596aabe30 100644
--- a/app/views/shared/_promo.html.haml
+++ b/app/views/shared/_promo.html.haml
@@ -1,5 +1,5 @@
.gitlab-promo
= link_to 'Homepage', promo_url
= link_to "Blog", promo_url + '/blog/'
- = link_to "@gitlabhq", "https://twitter.com/gitlabhq"
+ = link_to "@gitlab", "https://twitter.com/gitlab"
= link_to "Requests", "http://feedback.gitlab.com/"
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 4d9534f49b..eb2e1919e1 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,4 +1,4 @@
-= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
+= form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do
= select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm"
= hidden_field_tag :destination, destination
- if defined?(path)
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 7b37b39780..af3d35de32 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,22 +1,22 @@
.dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
- = @sort
+ = sort_options_hash[@sort]
- else
- Newest
+ = sort_title_recently_created
%b.caret
- %ul.dropdown-menu
+ %ul.dropdown-menu.dropdown-menu-align-right
%li
- = link_to project_filter_path(sort: 'newest') do
- Newest
- = link_to project_filter_path(sort: 'oldest') do
- Oldest
- = link_to project_filter_path(sort: 'recently_updated') do
- Recently updated
- = link_to project_filter_path(sort: 'last_updated') do
- Last updated
- = link_to project_filter_path(sort: 'milestone_due_soon') do
- Milestone due soon
- = link_to project_filter_path(sort: 'milestone_due_later') do
- Milestone due later
+ = link_to page_filter_path(sort: sort_value_recently_created) do
+ = sort_title_recently_created
+ = link_to page_filter_path(sort: sort_value_oldest_created) do
+ = sort_title_oldest_created
+ = link_to page_filter_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to page_filter_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
+ = link_to page_filter_path(sort: sort_value_milestone_soon) do
+ = sort_title_milestone_soon
+ = link_to page_filter_path(sort: sort_value_milestone_later) do
+ = sort_title_milestone_later
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index 8cec6168ab..30458793fd 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -8,7 +8,7 @@
= render_markup(@snippet.file_name, @snippet.data)
- else
.file-content.code
- = render 'shared/file_hljs', blob: @snippet
+ = render 'shared/file_highlight', blob: @snippet
- else
.file-content.code
.nothing-here-block Empty file
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index f729f129e4..4e0663ea20 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -30,7 +30,7 @@
= f.submit 'Save', class: "btn-save btn"
- if @snippet.respond_to?(:project)
- = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
- else
= link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml
index c584dd8dfb..5bb2866434 100644
--- a/app/views/snippets/_snippet.html.haml
+++ b/app/views/snippets/_snippet.html.haml
@@ -11,7 +11,7 @@
%small.pull-right.cgray
- if snippet.project_id?
- = link_to snippet.project.name_with_namespace, project_path(snippet.project)
+ = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
.snippet-info
= "##{snippet.id}"
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index f5bc543de1..edfa2092df 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -31,13 +31,13 @@
.file-holder
.file-title
%i.fa.fa-file
- %span.file_name
+ %strong
= @snippet.file_name
- .options
+ .file-actions
.btn-group
- if can?(current_user, :modify_personal_snippet, @snippet)
- = link_to "edit", edit_snippet_path(@snippet), class: "btn btn-small", title: 'Edit Snippet'
- = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-small", target: "_blank"
+ = link_to "edit", edit_snippet_path(@snippet), class: "btn btn-sm", title: 'Edit Snippet'
+ = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
- if can?(current_user, :admin_personal_snippet, @snippet)
- = link_to "remove", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-small btn-remove", title: 'Delete Snippet'
+ = link_to "remove", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet'
= render 'shared/snippets/blob'
diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml
index 67f3a68aa2..df524cd18b 100644
--- a/app/views/snippets/user_index.html.haml
+++ b/app/views/snippets/user_index.html.haml
@@ -5,7 +5,7 @@
\/
Snippets
- if current_user
- = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do
+ = link_to new_snippet_path, class: "btn btn-sm add_new pull-right", title: "New Snippet" do
Add new snippet
%hr
diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml
index 09b2985d49..f360fbb3d5 100644
--- a/app/views/users/_groups.html.haml
+++ b/app/views/users/_groups.html.haml
@@ -1,3 +1,4 @@
-- groups.each do |group|
- = link_to group, class: 'profile-groups-avatars', :title => group.name do
- = image_tag group_icon(group.path)
+.clearfix
+ - groups.each do |group|
+ = link_to group, class: 'profile-groups-avatars inline', title: group.name do
+ = image_tag group_icon(group), class: 'avatar group-avatar s40'
diff --git a/app/views/users/_profile.html.haml b/app/views/users/_profile.html.haml
index 3b44959baa..0a70b73807 100644
--- a/app/views/users/_profile.html.haml
+++ b/app/views/users/_profile.html.haml
@@ -12,7 +12,7 @@
- unless user.linkedin.blank?
%li
%span.light LinkedIn:
- %strong= user.linkedin
+ %strong= link_to user.linkedin, "http://www.linkedin.com/in/#{user.linkedin}"
- unless user.twitter.blank?
%li
%span.light Twitter:
diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml
index 1d38f8e8ab..6c7779be30 100644
--- a/app/views/users/_projects.html.haml
+++ b/app/views/users/_projects.html.haml
@@ -1,6 +1,13 @@
-.panel.panel-default
- .panel-heading Personal projects
- %ul.well-list
- - projects.each do |project|
- %li
- = link_to_project project
+- if @contributed_projects.present?
+ .panel.panel-default
+ .panel-heading Projects contributed to
+ = render 'shared/projects_list',
+ projects: @contributed_projects.sort_by(&:star_count).reverse,
+ projects_limit: 5, stars: true, avatar: false
+
+- if @projects.present?
+ .panel.panel-default
+ .panel-heading Personal projects
+ = render 'shared/projects_list',
+ projects: @projects.sort_by(&:star_count).reverse,
+ projects_limit: 10, stars: true, avatar: false
diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml
new file mode 100644
index 0000000000..1d1c974da2
--- /dev/null
+++ b/app/views/users/calendar.html.haml
@@ -0,0 +1,8 @@
+%h4 Commits calendar
+#cal-heatmap.calendar
+ :javascript
+ new calendar(
+ #{@timestamps.to_json},
+ #{@starting_year},
+ #{@starting_month}
+ );
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
new file mode 100644
index 0000000000..8fe30b2363
--- /dev/null
+++ b/app/views/users/show.atom.builder
@@ -0,0 +1,12 @@
+xml.instruct!
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
+ xml.title "Activity feed for #{@user.name}"
+ xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
+ xml.link href: user_url(@user), rel: "alternate", type: "text/html"
+ xml.id projects_url
+ xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+
+ @events.each do |event|
+ event_to_atom(xml, event)
+ end
+end
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index cb49c030af..abd6b22978 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,7 +1,9 @@
.row
- .col-md-8
+ = link_to '#aside', class: 'show-aside' do
+ %i.fa.fa-angle-left
+ %section.col-md-8
%h3.page-title
- = image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: ''
+ = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
= @user.name
- if @user == current_user
.pull-right
@@ -15,12 +17,29 @@
.clearfix
- if @groups.any?
- %h4 Groups:
+ %h4 Groups
= render 'groups', groups: @groups
%hr
- %h4 User Activity:
+
+ .hidden-xs
+ .user-calendar
+ %h4.center.light
+ %i.fa.fa-spinner.fa-spin
+ %hr
+ %h4
+ User Activity
+
+ - if current_user
+ %span.rss-icon.pull-right
+ = link_to user_path(@user, :atom, { private_token: current_user.private_token }) do
+ %strong
+ %i.fa.fa-rss
+
= render @events
- .col-md-4
+ %aside.col-md-4
= render 'profile', user: @user
- - if @projects.present?
- = render 'projects', projects: @projects
+ = render 'projects'
+
+:coffeescript
+ $ ->
+ $(".user-calendar").load("#{user_calendar_path}")
diff --git a/app/workers/auto_merge_worker.rb b/app/workers/auto_merge_worker.rb
new file mode 100644
index 0000000000..a6dd73eee5
--- /dev/null
+++ b/app/workers/auto_merge_worker.rb
@@ -0,0 +1,13 @@
+class AutoMergeWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(merge_request_id, current_user_id, params)
+ params = params.with_indifferent_access
+ current_user = User.find(current_user_id)
+ merge_request = MergeRequest.find(merge_request_id)
+ merge_request.should_remove_source_branch = params[:should_remove_source_branch]
+ merge_request.automerge!(current_user, params[:commit_message])
+ end
+end
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 2947c8e3ec..e59ca81def 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -1,25 +1,44 @@
class EmailsOnPushWorker
include Sidekiq::Worker
- def perform(project_id, recipients, push_data)
+ def perform(project_id, recipients, push_data, send_from_committer_email = false, disable_diffs = false)
project = Project.find(project_id)
before_sha = push_data["before"]
after_sha = push_data["after"]
branch = push_data["ref"]
author_id = push_data["user_id"]
- if before_sha =~ /^000000/ || after_sha =~ /^000000/
+ if Gitlab::Git.blank_ref?(before_sha) || Gitlab::Git.blank_ref?(after_sha)
# skip if new branch was pushed or branch was removed
return true
end
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
- # Do not send emails if git compare failed
- return false unless compare && compare.commits.present?
+ return false if compare.same
+
+ if compare.commits.empty?
+ compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha)
+
+ reverse_compare = true
+
+ return false if compare.commits.empty?
+ end
recipients.split(" ").each do |recipient|
- Notify.repository_push_email(project_id, recipient, author_id, branch, compare).deliver
+ Notify.repository_push_email(
+ project_id,
+ recipient,
+ author_id,
+ branch,
+ compare,
+ reverse_compare,
+ send_from_committer_email,
+ disable_diffs
+ ).deliver
end
+ ensure
+ compare = nil
+ GC.start
end
end
diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb
new file mode 100644
index 0000000000..e1a99d9cad
--- /dev/null
+++ b/app/workers/irker_worker.rb
@@ -0,0 +1,169 @@
+require 'json'
+require 'socket'
+
+class IrkerWorker
+ include Sidekiq::Worker
+
+ def perform(project_id, chans, colors, push_data, settings)
+ project = Project.find(project_id)
+
+ # Get config parameters
+ return false unless init_perform settings, chans, colors
+
+ repo_name = push_data['repository']['name']
+ committer = push_data['user_name']
+ branch = push_data['ref'].gsub(%r'refs/[^/]*/', '')
+
+ if @colors
+ repo_name = "\x0304#{repo_name}\x0f"
+ branch = "\x0305#{branch}\x0f"
+ end
+
+ # Firsts messages are for branch creation/deletion
+ send_branch_updates push_data, project, repo_name, committer, branch
+
+ # Next messages are for commits
+ send_commits push_data, project, repo_name, committer, branch
+
+ close_connection
+ true
+ end
+
+ private
+
+ def init_perform(set, chans, colors)
+ @colors = colors
+ @channels = chans
+ start_connection set['server_ip'], set['server_port']
+ end
+
+ def start_connection(irker_server, irker_port)
+ begin
+ @socket = TCPSocket.new irker_server, irker_port
+ rescue Errno::ECONNREFUSED => e
+ logger.fatal "Can't connect to Irker daemon: #{e}"
+ return false
+ end
+ true
+ end
+
+ def sendtoirker(privmsg)
+ to_send = { to: @channels, privmsg: privmsg }
+ @socket.puts JSON.dump(to_send)
+ end
+
+ def close_connection
+ @socket.close
+ end
+
+ def send_branch_updates(push_data, project, repo_name, committer, branch)
+ if push_data['before'] == Gitlab::Git::BLANK_SHA
+ send_new_branch project, repo_name, committer, branch
+ elsif push_data['after'] == Gitlab::Git::BLANK_SHA
+ send_del_branch repo_name, committer, branch
+ end
+ end
+
+ def send_new_branch(project, repo_name, committer, branch)
+ repo_path = project.path_with_namespace
+ newbranch = "#{Gitlab.config.gitlab.url}/#{repo_path}/branches"
+ newbranch = "\x0302\x1f#{newbranch}\x0f" if @colors
+
+ privmsg = "[#{repo_name}] #{committer} has created a new branch "
+ privmsg += "#{branch}: #{newbranch}"
+ sendtoirker privmsg
+ end
+
+ def send_del_branch(repo_name, committer, branch)
+ privmsg = "[#{repo_name}] #{committer} has deleted the branch #{branch}"
+ sendtoirker privmsg
+ end
+
+ def send_commits(push_data, project, repo_name, committer, branch)
+ return if push_data['total_commits_count'] == 0
+
+ # Next message is for number of commit pushed, if any
+ if push_data['before'] == Gitlab::Git::BLANK_SHA
+ # Tweak on push_data["before"] in order to have a nice compare URL
+ push_data['before'] = before_on_new_branch push_data, project
+ end
+
+ send_commits_count(push_data, project, repo_name, committer, branch)
+
+ # One message per commit, limited by 3 messages (same limit as the
+ # github irc hook)
+ commits = push_data['commits'].first(3)
+ commits.each do |hook_attrs|
+ send_one_commit project, hook_attrs, repo_name, branch
+ end
+ end
+
+ def before_on_new_branch(push_data, project)
+ commit = commit_from_id project, push_data['commits'][0]['id']
+ parents = commit.parents
+ # Return old value if there's no new one
+ return push_data['before'] if parents.empty?
+ # Or return the first parent-commit
+ parents[0].id
+ end
+
+ def send_commits_count(data, project, repo, committer, branch)
+ url = compare_url data, project.path_with_namespace
+ commits = colorize_commits data['total_commits_count']
+
+ new_commits = 'new commit'
+ new_commits += 's' if data['total_commits_count'] > 1
+
+ sendtoirker "[#{repo}] #{committer} pushed #{commits} #{new_commits} " \
+ "to #{branch}: #{url}"
+ end
+
+ def compare_url(data, repo_path)
+ sha1 = Commit::truncate_sha(data['before'])
+ sha2 = Commit::truncate_sha(data['after'])
+ compare_url = "#{Gitlab.config.gitlab.url}/#{repo_path}/compare"
+ compare_url += "/#{sha1}...#{sha2}"
+ colorize_url compare_url
+ end
+
+ def send_one_commit(project, hook_attrs, repo_name, branch)
+ commit = commit_from_id project, hook_attrs['id']
+ sha = colorize_sha Commit::truncate_sha(hook_attrs['id'])
+ author = hook_attrs['author']['name']
+ files = colorize_nb_files(files_count commit)
+ title = commit.title
+
+ sendtoirker "#{repo_name}/#{branch} #{sha} #{author} (#{files}): #{title}"
+ end
+
+ def commit_from_id(project, id)
+ commit = Gitlab::Git::Commit.find(project.repository, id)
+ Commit.new(commit)
+ end
+
+ def files_count(commit)
+ files = "#{commit.diffs.count} file"
+ files += 's' if commit.diffs.count > 1
+ files
+ end
+
+ def colorize_sha(sha)
+ sha = "\x0314#{sha}\x0f" if @colors
+ sha
+ end
+
+ def colorize_nb_files(nb_files)
+ nb_files = "\x0312#{nb_files}\x0f" if @colors
+ nb_files
+ end
+
+ def colorize_url(url)
+ url = "\x0302\x1f#{url}\x0f" if @colors
+ url
+ end
+
+ def colorize_commits(commits)
+ commits = "\x02#{commits}\x0f" if @colors
+ commits
+ end
+end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 1406cba2db..0c3ee6ba4f 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -21,7 +21,9 @@ class PostReceive
return false
end
- changes = changes.lines if changes.kind_of?(String)
+ changes = Base64.decode64(changes) unless changes.include?(" ")
+ changes = utf8_encode_changes(changes)
+ changes = changes.lines
changes.each do |change|
oldrev, newrev, ref = change.strip.split(' ')
@@ -33,7 +35,7 @@ class PostReceive
return false
end
- if tag?(ref)
+ if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(project, @user, oldrev, newrev, ref)
else
GitPushService.new.execute(project, @user, oldrev, newrev, ref)
@@ -41,13 +43,20 @@ class PostReceive
end
end
+ def utf8_encode_changes(changes)
+ changes = changes.dup
+
+ changes.force_encoding("UTF-8")
+ return changes if changes.valid_encoding?
+
+ # Convert non-UTF-8 branch/tag names to UTF-8 so they can be dumped as JSON.
+ detection = CharlockHolmes::EncodingDetector.detect(changes)
+ return changes unless detection && detection[:encoding]
+
+ CharlockHolmes::Converter.convert(changes, detection[:encoding], 'UTF-8')
+ end
+
def log(message)
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
-
- private
-
- def tag?(ref)
- !!(/refs\/tags\/(.*)/.match(ref))
- end
end
diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb
new file mode 100644
index 0000000000..64d39c4d3f
--- /dev/null
+++ b/app/workers/project_service_worker.rb
@@ -0,0 +1,10 @@
+class ProjectServiceWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :project_web_hook
+
+ def perform(hook_id, data)
+ data = data.with_indifferent_access
+ Service.find(hook_id).execute(data)
+ end
+end
diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb
index 9f9b9b1df5..73085c046b 100644
--- a/app/workers/project_web_hook_worker.rb
+++ b/app/workers/project_web_hook_worker.rb
@@ -4,6 +4,7 @@ class ProjectWebHookWorker
sidekiq_options queue: :project_web_hook
def perform(hook_id, data)
- WebHook.find(hook_id).execute data
+ data = data.with_indifferent_access
+ WebHook.find(hook_id).execute(data)
end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 01586150cd..437640d230 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -6,17 +6,27 @@ class RepositoryImportWorker
def perform(project_id)
project = Project.find(project_id)
- result = gitlab_shell.send(:import_repository,
+
+ import_result = gitlab_shell.send(:import_repository,
project.path_with_namespace,
project.import_url)
+ return project.import_fail unless import_result
- if result
- project.import_finish
- project.save
- project.satellite.create unless project.satellite.exists?
- project.update_repository_size
- else
- project.import_fail
- end
+ data_import_result = if project.import_type == 'github'
+ Gitlab::GithubImport::Importer.new(project).execute
+ elsif project.import_type == 'gitlab'
+ Gitlab::GitlabImport::Importer.new(project).execute
+ elsif project.import_type == 'bitbucket'
+ Gitlab::BitbucketImport::Importer.new(project).execute
+ else
+ true
+ end
+ return project.import_fail unless data_import_result
+
+ project.import_finish
+ project.save
+ project.satellite.create unless project.satellite.exists?
+ project.update_repository_size
+ Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end
end
diff --git a/bin/pkgr_before_precompile.sh b/bin/pkgr_before_precompile.sh
index 283abb6a0c..5a2007f4ab 100755
--- a/bin/pkgr_before_precompile.sh
+++ b/bin/pkgr_before_precompile.sh
@@ -18,6 +18,3 @@ rm config/resque.yml
# Set default unicorn.rb file
echo "" > config/unicorn.rb
-
-# Required for assets precompilation
-sudo service postgresql start
diff --git a/bin/rspec b/bin/rspec
index 41e37089ac..20060ebd79 100755
--- a/bin/rspec
+++ b/bin/rspec
@@ -4,4 +4,4 @@ begin
rescue LoadError
end
require 'bundler/setup'
-load Gem.bin_path('rspec', 'rspec')
+load Gem.bin_path('rspec-core', 'rspec')
diff --git a/config/application.rb b/config/application.rb
index e36df913d0..fa399533e5 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -12,21 +12,16 @@ module Gitlab
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
- config.autoload_paths += %W(#{config.root}/lib
- #{config.root}/app/finders
- #{config.root}/app/models/hooks
- #{config.root}/app/models/concerns
- #{config.root}/app/models/project_services
- #{config.root}/app/models/members)
+ config.autoload_paths.push(*%W(#{config.root}/lib
+ #{config.root}/app/models/hooks
+ #{config.root}/app/models/concerns
+ #{config.root}/app/models/project_services
+ #{config.root}/app/models/members))
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
- # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
- # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
- # config.time_zone = 'Central Time (US & Canada)'
-
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
@@ -36,7 +31,7 @@ module Gitlab
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
- config.filter_parameters += [:password]
+ config.filter_parameters.push(:password, :password_confirmation, :private_token)
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
@@ -55,6 +50,8 @@ module Gitlab
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
+ config.action_view.sanitized_allowed_protocols = %w(smb)
+
# Relative url support
# Uncomment and customize the last line to run in a non-root path
# WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this.
@@ -75,7 +72,10 @@ module Gitlab
config.middleware.use Rack::Cors do
allow do
origins '*'
- resource '/api/*', headers: :any, methods: [:get, :post, :options, :put, :delete]
+ resource '/api/*',
+ headers: :any,
+ methods: [:get, :post, :options, :put, :delete],
+ expose: ['Link']
end
end
@@ -97,5 +97,8 @@ module Gitlab
redis_config_hash[:namespace] = 'cache:gitlab'
config.cache_store = :redis_store, redis_config_hash
+
+ # This is needed for gitlab-shell
+ ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
end
end
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 78bf543402..3316ece387 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -11,8 +11,9 @@ Gitlab::Application.configure do
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = false
- # Compress JavaScripts and CSS
- config.assets.compress = true
+ # Compress JavaScripts and CSS.
+ config.assets.js_compressor = :uglifier
+ # config.assets.css_compressor = :sass
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = true
@@ -74,7 +75,6 @@ Gitlab::Application.configure do
config.action_mailer.raise_delivery_errors = true
config.eager_load = true
- config.assets.js_compressor = :uglifier
config.allow_concurrency = false
end
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 25b082b98d..2d5e7addcd 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -5,7 +5,7 @@ Gitlab::Application.configure do
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
- config.cache_classes = true
+ config.cache_classes = false
# Configure static asset server for tests with Cache-Control for performance
config.serve_static_assets = true
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 857643c006..a85db10e01 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -33,14 +33,20 @@ production: &base
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
# user: git
+ ## Date & Time settings
+ # Uncomment and customize if you want to change the default time zone of GitLab application.
+ # To see all available zones, run `bundle exec rake time:zones:all RAILS_ENV=production`
+ # time_zone: 'UTC'
+
## Email settings
+ # Uncomment and set to false if you need to disable email sending from GitLab (default: true)
+ # email_enabled: true
# Email address used in the "From" field in mails sent by GitLab
email_from: example@example.com
+ email_display_name: GitLab
- # Email server smtp settings are in [a separate file](initializers/smtp_settings.rb.sample).
+ # Email server smtp settings are in config/initializers/smtp_settings.rb.sample
- ## User settings
- default_projects_limit: 10
# default_can_create_group: false # default: true
# username_changing_enabled: false # default: true - User can change her username/namespace
## Default theme
@@ -51,26 +57,12 @@ production: &base
## COLOR = 5
# default_theme: 2 # default: 2
- ## Users can create accounts
- # This also allows normal users to sign up for accounts themselves
- # default: false - By default GitLab administrators must create all new accounts
- # signup_enabled: true
-
- ## Standard login settings
- # The standard login can be disabled to force login via LDAP
- # default: true - If set to false the standard login form won't be shown on the sign-in page
- # signin_enabled: false
-
- # Restrict setting visibility levels for non-admin users.
- # The default is to allow all levels.
- # restricted_visibility_levels: [ "public" ]
-
## Automatic issue closing
# If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
# This happens when the commit is pushed or merged into the default branch of a project.
# When not specified the default issue_closing_pattern as specified below will be used.
# Tip: you can test your closing pattern at http://rubular.com
- # issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)'
+ # issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)'
## Default project features settings
default_projects_features:
@@ -119,6 +111,7 @@ production: &base
# new_issue_url: "http://jira.sample/secure/CreateIssue.jspa"
## Gravatar
+ ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar:
enabled: true # Use user avatar image from Gravatar.com (default: true)
# gravatar urls: possible placeholders: %{hash} %{size} %{email}
@@ -134,43 +127,61 @@ production: &base
# bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: false
- host: '_your_ldap_server'
- port: 636
- uid: 'sAMAccountName'
- method: 'ssl' # "tls" or "ssl" or "plain"
- bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
- password: '_the_password_of_the_bind_user'
+ servers:
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
- # This setting specifies if LDAP server is Active Directory LDAP server.
- # For non AD servers it skips the AD specific queries.
- # If your LDAP server is not AD, set this to false.
- active_directory: true
+ host: '_your_ldap_server'
+ port: 389
+ uid: 'sAMAccountName'
+ method: 'plain' # "tls" or "ssl" or "plain"
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
- # If allow_username_or_email_login is enabled, GitLab will ignore everything
- # after the first '@' in the LDAP username submitted by the user on login.
- #
- # Example:
- # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
- # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
- #
- # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
- # disable this setting, because the userPrincipalName contains an '@'.
- allow_username_or_email_login: false
+ # This setting specifies if LDAP server is Active Directory LDAP server.
+ # For non AD servers it skips the AD specific queries.
+ # If your LDAP server is not AD, set this to false.
+ active_directory: true
- # Base where we can search for users
- #
- # Ex. ou=People,dc=gitlab,dc=example
- #
- base: ''
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
- # Filter LDAP users
- #
- # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
- # Ex. (employeeType=developer)
- #
- # Note: GitLab does not support omniauth-ldap's custom filter syntax.
- #
- user_filter: ''
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ user_filter: ''
+
+ # GitLab EE only: add more LDAP servers
+ # Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+ # so that GitLab can remember which LDAP server a user belongs to.
+ # uswest2:
+ # label:
+ # host:
+ # ....
## OmniAuth settings
@@ -193,14 +204,19 @@ production: &base
# arguments, followed by optional 'args' which can be either a hash or an array.
# Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
providers:
- # - { name: 'google_oauth2', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET',
+ # - { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET',
# args: { access_type: 'offline', approval_prompt: '' } }
- # - { name: 'twitter', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET'}
- # - { name: 'github', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET',
+ # - { name: 'twitter', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET'}
+ # - { name: 'github', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET',
# args: { scope: 'user:email' } }
+ # - { name: 'gitlab', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET',
+ # args: { scope: 'api' } }
+ # - { name: 'bitbucket', app_id: 'YOUR_APP_ID',
+ # app_secret: 'YOUR_APP_SECRET'}
@@ -267,10 +283,19 @@ production: &base
# piwik_url: '_your_piwik_url'
# piwik_site_id: '_your_piwik_site_id'
- ## Text under sign-in page (Markdown enabled)
- # sign_in_text: |
- # 
- # [Learn more about CompanyName](http://www.companydomain.com/)
+ rack_attack:
+ git_basic_auth:
+ # Whitelist requests from 127.0.0.1 for web proxies (NGINX/Apache) with incorrect headers
+ # ip_whitelist: ["127.0.0.1"]
+ #
+ # Limit the number of Git HTTP authentication attempts per IP
+ # maxretry: 10
+ #
+ # Reset the auth attempt counter per IP after 60 seconds
+ # findtime: 60
+ #
+ # Ban an IP for one hour (3600s) after too many auth attempts
+ # bantime: 3600
development:
<<: *base
@@ -299,6 +324,20 @@ test:
project_url: "http://redmine/projects/:issues_tracker_id"
issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
+ ldap:
+ enabled: false
+ servers:
+ main:
+ label: ldap
+ host: 127.0.0.1
+ port: 3890
+ uid: 'uid'
+ method: 'plain' # "tls" or "ssl" or "plain"
+ base: 'dc=example,dc=com'
+ user_filter: ''
+ group_base: 'ou=groups,dc=example,dc=com'
+ admin_group: ''
+ sync_ssh_keys: false
staging:
<<: *base
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 0d11ae6f33..70af7a829c 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -13,7 +13,11 @@ class Settings < Settingslogic
if gitlab_shell.ssh_port != 22
"ssh://#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:#{gitlab_shell.ssh_port}/"
else
- "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:"
+ if gitlab_shell.ssh_host.include? ':'
+ "[#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}]:"
+ else
+ "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:"
+ end
end
end
@@ -56,9 +60,25 @@ end
# Default settings
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
-Settings.ldap['allow_username_or_email_login'] = false if Settings.ldap['allow_username_or_email_login'].nil?
-Settings.ldap['active_directory'] = true if Settings.ldap['active_directory'].nil?
+# backwards compatibility, we only have one host
+if Settings.ldap['enabled'] || Rails.env.test?
+ if Settings.ldap['host'].present?
+ server = Settings.ldap.except('sync_time')
+ server['provider_name'] = 'ldap'
+ Settings.ldap['servers'] = {
+ 'ldap' => server
+ }
+ end
+
+ Settings.ldap['servers'].each do |key, server|
+ server['label'] ||= 'LDAP'
+ server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil?
+ server['active_directory'] = true if server['active_directory'].nil?
+ server['provider_name'] ||= "ldap#{key}".downcase
+ server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
+ end
+end
Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
@@ -71,6 +91,7 @@ Settings['issues_tracker'] ||= {}
#
Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_projects_limit'] ||= 10
+Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
Settings.gitlab['default_theme'] = Gitlab::Theme::MARS if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= 'localhost'
@@ -79,7 +100,9 @@ Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
+Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
+Settings.gitlab['email_display_name'] ||= "GitLab"
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git'
Settings.gitlab['user_home'] ||= begin
@@ -87,11 +110,13 @@ Settings.gitlab['user_home'] ||= begin
rescue ArgumentError # no user configured
'/home/' + Settings.gitlab['user']
end
-Settings.gitlab['signup_enabled'] ||= false
+Settings.gitlab['time_zone'] ||= nil
+Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
+Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
@@ -130,7 +155,7 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s
Settings['backup'] ||= Settingslogic.new({})
Settings.backup['keep_time'] ||= 0
Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root)
-Settings.backup['upload'] ||= Settingslogic.new({'remote_directory' => nil, 'connection' => nil})
+Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
# Convert upload connection settings to use symbol keys, to make Fog happy
if Settings.backup['upload']['connection']
Settings.backup['upload']['connection'] = Hash[Settings.backup['upload']['connection'].map { |k, v| [k.to_sym, v] }]
@@ -153,6 +178,16 @@ Settings.satellites['timeout'] ||= 30
#
Settings['extra'] ||= Settingslogic.new({})
+#
+# Rack::Attack settings
+#
+Settings['rack_attack'] ||= Settingslogic.new({})
+Settings.rack_attack['git_basic_auth'] ||= Settingslogic.new({})
+Settings.rack_attack.git_basic_auth['ip_whitelist'] ||= %w{127.0.0.1}
+Settings.rack_attack.git_basic_auth['maxretry'] ||= 10
+Settings.rack_attack.git_basic_auth['findtime'] ||= 1.minute
+Settings.rack_attack.git_basic_auth['bantime'] ||= 1.hour
+
#
# Testing settings
#
diff --git a/config/initializers/4_sidekiq.rb b/config/initializers/4_sidekiq.rb
index 228b14cb52..e856499732 100644
--- a/config/initializers/4_sidekiq.rb
+++ b/config/initializers/4_sidekiq.rb
@@ -14,7 +14,8 @@ Sidekiq.configure_server do |config|
}
config.server_middleware do |chain|
- chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger
+ chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
+ chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
end
end
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
index a7ee3c5982..b634028756 100644
--- a/config/initializers/6_rack_profiler.rb
+++ b/config/initializers/6_rack_profiler.rb
@@ -3,4 +3,6 @@ if Rails.env == 'development'
# initialization is skipped so trigger it
Rack::MiniProfilerRails.initialize!(Rails.application)
+ Rack::MiniProfiler.config.position = 'right'
+ Rack::MiniProfiler.config.start_hidden = true
end
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
new file mode 100644
index 0000000000..8f6c567310
--- /dev/null
+++ b/config/initializers/7_omniauth.rb
@@ -0,0 +1,12 @@
+if Gitlab::LDAP::Config.enabled?
+ module OmniAuth::Strategies
+ server = Gitlab.config.ldap.servers.values.first
+ klass = server['provider_class']
+ const_set(klass, Class.new(LDAP)) unless klass == 'LDAP'
+ end
+
+ OmniauthCallbacksController.class_eval do
+ server = Gitlab.config.ldap.servers.values.first
+ alias_method server['provider_name'], :ldap
+ end
+end
diff --git a/config/initializers/acts_as_taggable_on_patch.rb b/config/initializers/acts_as_taggable_on_patch.rb
index baa77fde39..0d535cb5ca 100644
--- a/config/initializers/acts_as_taggable_on_patch.rb
+++ b/config/initializers/acts_as_taggable_on_patch.rb
@@ -42,11 +42,12 @@ module ActsAsTaggableOn::Taggable
elsif options.delete(:any)
# get tags, drop out if nothing returned (we need at least one)
- tags = if options.delete(:wild)
- ActsAsTaggableOn::Tag.named_like_any(tag_list)
- else
- ActsAsTaggableOn::Tag.named_any(tag_list)
- end
+ tags =
+ if options.delete(:wild)
+ ActsAsTaggableOn::Tag.named_like_any(tag_list)
+ else
+ ActsAsTaggableOn::Tag.named_any(tag_list)
+ end
return empty_result unless tags.length > 0
@@ -68,12 +69,12 @@ module ActsAsTaggableOn::Taggable
select_clause = "DISTINCT #{table_name}.*" unless context and tag_types.one?
if owned_by
- tagging_join << " AND " +
- sanitize_sql([
- "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
- owned_by.id,
- owned_by.class.base_class.to_s
- ])
+ tagging_join << " AND " +
+ sanitize_sql([
+ "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
+ owned_by.id,
+ owned_by.class.base_class.to_s
+ ])
end
joins << tagging_join
@@ -92,12 +93,12 @@ module ActsAsTaggableOn::Taggable
tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context
if owned_by
- tagging_join << " AND " +
- sanitize_sql([
- "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
- owned_by.id,
- owned_by.class.base_class.to_s
- ])
+ tagging_join << " AND " +
+ sanitize_sql([
+ "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?",
+ owned_by.id,
+ owned_by.class.base_class.to_s
+ ])
end
joins << tagging_join
diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb
index d0065b63e5..bfb8656df5 100644
--- a/config/initializers/carrierwave.rb
+++ b/config/initializers/carrierwave.rb
@@ -12,22 +12,30 @@ if File.exists?(aws_file)
aws_secret_access_key: AWS_CONFIG['secret_access_key'], # required
region: AWS_CONFIG['region'], # optional, defaults to 'us-east-1'
}
- config.fog_directory = AWS_CONFIG['bucket'] # required
- config.fog_public = false # optional, defaults to true
- config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {}
- config.fog_authenticated_url_expiration = 1 << 29 # optional time (in seconds) that authenticated urls will be valid.
- # when fog_public is false and provider is AWS or Google, defaults to 600
+
+ # required
+ config.fog_directory = AWS_CONFIG['bucket']
+
+ # optional, defaults to true
+ config.fog_public = false
+
+ # optional, defaults to {}
+ config.fog_attributes = { 'Cache-Control'=>'max-age=315576000' }
+
+ # optional time (in seconds) that authenticated urls will be valid.
+ # when fog_public is false and provider is AWS or Google, defaults to 600
+ config.fog_authenticated_url_expiration = 1 << 29
end
# Mocking Fog requests, based on: https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Test-Fog-based-uploaders
if Rails.env.test?
Fog.mock!
connection = ::Fog::Storage.new(
- :aws_access_key_id => AWS_CONFIG['access_key_id'],
- :aws_secret_access_key => AWS_CONFIG['secret_access_key'],
- :provider => 'AWS',
- :region => AWS_CONFIG['region']
+ aws_access_key_id: AWS_CONFIG['access_key_id'],
+ aws_secret_access_key: AWS_CONFIG['secret_access_key'],
+ provider: 'AWS',
+ region: AWS_CONFIG['region']
)
- connection.directories.create(:key => AWS_CONFIG['bucket'])
+ connection.directories.create(key: AWS_CONFIG['bucket'])
end
end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 34f4f38698..79abe3c695 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -145,7 +145,8 @@ Devise.setup do |config|
# Time interval you can reset your password with a reset password key.
# Don't put a too small interval or your users won't have the time to
# change their passwords.
- config.reset_password_within = 2.hours
+ # When someone else invites you to GitLab this time is also used so it should be pretty long.
+ config.reset_password_within = 2.days
# ==> Configuration for :encryptable
# Allow you to use another encryption algorithm besides bcrypt (default). You can use
@@ -204,22 +205,24 @@ Devise.setup do |config|
# manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
- if Gitlab.config.ldap.enabled
- if Gitlab.config.ldap.allow_username_or_email_login
- email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
- else
- email_stripping_proc = ->(name) {name}
- end
+ if Gitlab::LDAP::Config.enabled?
+ Gitlab.config.ldap.servers.values.each do |server|
+ if server['allow_username_or_email_login']
+ email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')}
+ else
+ email_stripping_proc = ->(name) {name}
+ end
- config.omniauth :ldap,
- host: Gitlab.config.ldap['host'],
- base: Gitlab.config.ldap['base'],
- uid: Gitlab.config.ldap['uid'],
- port: Gitlab.config.ldap['port'],
- method: Gitlab.config.ldap['method'],
- bind_dn: Gitlab.config.ldap['bind_dn'],
- password: Gitlab.config.ldap['password'],
- name_proc: email_stripping_proc
+ config.omniauth server['provider_name'],
+ host: server['host'],
+ base: server['base'],
+ uid: server['uid'],
+ port: server['port'],
+ method: server['method'],
+ bind_dn: server['bind_dn'],
+ password: server['password'],
+ name_proc: email_stripping_proc
+ end
end
Gitlab.config.omniauth.providers.each do |provider|
diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb
new file mode 100644
index 0000000000..c76a6b8b19
--- /dev/null
+++ b/config/initializers/disable_email_interceptor.rb
@@ -0,0 +1,2 @@
+# Interceptor in lib/disable_email_interceptor.rb
+ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
new file mode 100644
index 0000000000..9da7ebf429
--- /dev/null
+++ b/config/initializers/doorkeeper.rb
@@ -0,0 +1,102 @@
+Doorkeeper.configure do
+ # Change the ORM that doorkeeper will use.
+ # Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper
+ orm :active_record
+
+ # This block will be called to check whether the resource owner is authenticated or not.
+ resource_owner_authenticator do
+ # Put your resource owner authentication logic here.
+ # Example implementation:
+ current_user || redirect_to(new_user_session_url)
+ end
+
+ resource_owner_from_credentials do |routes|
+ u = User.find_by(email: params[:username])
+ u if u && u.valid_password?(params[:password])
+ end
+
+ # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
+ # admin_authenticator do
+ # # Put your admin authentication logic here.
+ # # Example implementation:
+ # Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url)
+ # end
+
+ # Authorization Code expiration time (default 10 minutes).
+ # authorization_code_expires_in 10.minutes
+
+ # Access token expiration time (default 2 hours).
+ # If you want to disable expiration, set this to nil.
+ access_token_expires_in nil
+
+ # Reuse access token for the same resource owner within an application (disabled by default)
+ # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
+ # reuse_access_token
+
+ # Issue access tokens with refresh token (disabled by default)
+ use_refresh_token
+
+ # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled
+ # by default in non-development environments). OAuth2 delegates security in
+ # communication to the HTTPS protocol so it is wise to keep this enabled.
+ #
+ force_ssl_in_redirect_uri false
+
+ # Provide support for an owner to be assigned to each registered application (disabled by default)
+ # Optional parameter confirmation: true (default false) if you want to enforce ownership of
+ # a registered application
+ # Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
+ enable_application_owner confirmation: false
+
+ # Define access token scopes for your provider
+ # For more information go to
+ # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
+ default_scopes :api
+ #optional_scopes :write, :update
+
+ # Change the way client credentials are retrieved from the request object.
+ # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
+ # falls back to the `:client_id` and `:client_secret` params from the `params` object.
+ # Check out the wiki for more information on customization
+ # client_credentials :from_basic, :from_params
+
+ # Change the way access token is authenticated from the request object.
+ # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
+ # falls back to the `:access_token` or `:bearer_token` params from the `params` object.
+ # Check out the wiki for more information on customization
+ access_token_methods :from_access_token_param, :from_bearer_authorization, :from_bearer_param
+
+ # Change the native redirect uri for client apps
+ # When clients register with the following redirect uri, they won't be redirected to any server and the authorization code will be displayed within the provider
+ # The value can be any string. Use nil to disable this feature. When disabled, clients must provide a valid URL
+ # (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
+ #
+ native_redirect_uri nil#'urn:ietf:wg:oauth:2.0:oob'
+
+ # Specify what grant flows are enabled in array of Strings. The valid
+ # strings and the flows they enable are:
+ #
+ # "authorization_code" => Authorization Code Grant Flow
+ # "implicit" => Implicit Grant Flow
+ # "password" => Resource Owner Password Credentials Grant Flow
+ # "client_credentials" => Client Credentials Grant Flow
+ #
+ # If not specified, Doorkeeper enables all the four grant flows.
+ #
+ # grant_flows %w(authorization_code implicit password client_credentials)
+
+ # Under some circumstances you might want to have applications auto-approved,
+ # so that the user skips the authorization step.
+ # For example if dealing with trusted a application.
+ # skip_authorization do |resource_owner, client|
+ # client.superapp? or resource_owner.admin?
+ # end
+
+ # WWW-Authenticate Realm (default "Doorkeeper").
+ # realm "Doorkeeper"
+
+ # Allow dynamic query parameters (disabled by default)
+ # Some applications require dynamic query parameters on their request_uri
+ # set to true if you want this to be allowed
+ # wildcard_redirect_uri false
+end
diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb
new file mode 100644
index 0000000000..e7c9f0ba7c
--- /dev/null
+++ b/config/initializers/gitlab_shell_secret_token.rb
@@ -0,0 +1,19 @@
+# Be sure to restart your server when you modify this file.
+
+require 'securerandom'
+
+# Your secret key for verifying the gitlab_shell.
+
+
+secret_file = Rails.root.join('.gitlab_shell_secret')
+gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret')
+
+unless File.exist? secret_file
+ # Generate a new token of 16 random hexadecimal characters and store it in secret_file.
+ token = SecureRandom.hex(16)
+ File.write(secret_file, token)
+end
+
+if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink)
+ FileUtils.symlink(secret_file, gitlab_shell_symlink)
+end
diff --git a/config/initializers/public_key.rb b/config/initializers/public_key.rb
new file mode 100644
index 0000000000..75d74e3625
--- /dev/null
+++ b/config/initializers/public_key.rb
@@ -0,0 +1,2 @@
+path = File.expand_path("~/.ssh/id_rsa.pub")
+Gitlab::BitbucketImport.public_key = File.read(path) if File.exist?(path)
diff --git a/config/initializers/rack_attack_git_basic_auth.rb b/config/initializers/rack_attack_git_basic_auth.rb
new file mode 100644
index 0000000000..bbbfed6832
--- /dev/null
+++ b/config/initializers/rack_attack_git_basic_auth.rb
@@ -0,0 +1,12 @@
+unless Rails.env.test?
+ # Tell the Rack::Attack Rack middleware to maintain an IP blacklist. We will
+ # update the blacklist from Grack::Auth#authenticate_user.
+ Rack::Attack.blacklist('Git HTTP Basic Auth') do |req|
+ Rack::Attack::Allow2Ban.filter(req.ip, Gitlab.config.rack_attack.git_basic_auth) do
+ # This block only gets run if the IP was not already banned.
+ # Return false, meaning that we do not see anything wrong with the
+ # request at this time
+ false
+ end
+ end
+end
diff --git a/config/initializers/redis-store-fix-expiry.rb b/config/initializers/redis-store-fix-expiry.rb
new file mode 100644
index 0000000000..fce0a13533
--- /dev/null
+++ b/config/initializers/redis-store-fix-expiry.rb
@@ -0,0 +1,44 @@
+# Monkey-patch Redis::Store to make 'setex' and 'expire' work with namespacing
+
+module Gitlab
+ class Redis
+ class Store
+ module Namespace
+ # Redis::Store#setex in redis-store 1.1.4 does not respect namespaces;
+ # this new method does.
+ def setex(key, expires_in, value, options=nil)
+ namespace(key) { |key| super(key, expires_in, value) }
+ end
+
+ # Redis::Store#expire in redis-store 1.1.4 does not respect namespaces;
+ # this new method does.
+ def expire(key, expires_in)
+ namespace(key) { |key| super(key, expires_in) }
+ end
+
+ private
+
+ # Our new definitions of #setex and #expire above assume that the
+ # #namespace method exists. Because we cannot be sure of that, we
+ # re-implement the #namespace method from Redis::Store::Namespace so
+ # that it is available for all Redis::Store instances, whether they use
+ # namespacing or not.
+ #
+ # Based on lib/redis/store/namespace.rb L49-51 (redis-store 1.1.4)
+ def namespace(key)
+ if @namespace
+ yield interpolate(key)
+ else
+ # This Redis::Store instance does not use a namespace so we should
+ # just pass through the key.
+ yield key
+ end
+ end
+ end
+ end
+ end
+end
+
+Redis::Store.class_eval do
+ include Gitlab::Redis::Store::Namespace
+end
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
index 3711b03796..f0fe2fdfa4 100644
--- a/config/initializers/smtp_settings.rb.sample
+++ b/config/initializers/smtp_settings.rb.sample
@@ -1,8 +1,11 @@
-# To enable smtp email delivery for your GitLab instance do next:
+# To enable smtp email delivery for your GitLab instance do the following:
# 1. Rename this file to smtp_settings.rb
# 2. Edit settings inside this file
# 3. Restart GitLab instance
#
+# For full list of options and their values see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+#
+
if Rails.env.production?
Gitlab::Application.config.action_mailer.delivery_method = :smtp
@@ -13,6 +16,7 @@ if Rails.env.production?
password: "123456",
domain: "gitlab.company.com",
authentication: :login,
- enable_starttls_auto: true
+ enable_starttls_auto: true,
+ openssl_verify_mode: 'peer' # See ActionMailer documentation for other possible options
}
end
diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb
new file mode 100644
index 0000000000..d9042c652b
--- /dev/null
+++ b/config/initializers/static_files.rb
@@ -0,0 +1,15 @@
+app = Rails.application
+
+if app.config.serve_static_assets
+ # The `ActionDispatch::Static` middleware intercepts requests for static files
+ # by checking if they exist in the `/public` directory.
+ # We're replacing it with our `Gitlab::Middleware::Static` that does the same,
+ # except ignoring `/uploads`, letting those go through to the GitLab Rails app.
+
+ app.config.middleware.swap(
+ ActionDispatch::Static,
+ Gitlab::Middleware::Static,
+ app.paths["public"].first,
+ app.config.static_cache_control
+ )
+end
diff --git a/config/initializers/time_zone.rb b/config/initializers/time_zone.rb
new file mode 100644
index 0000000000..ee246e67d6
--- /dev/null
+++ b/config/initializers/time_zone.rb
@@ -0,0 +1 @@
+Time.zone = Gitlab.config.gitlab.time_zone || Time.zone
diff --git a/config/initializers/timeout.rb b/config/initializers/timeout.rb
new file mode 100644
index 0000000000..bc88595cf2
--- /dev/null
+++ b/config/initializers/timeout.rb
@@ -0,0 +1,8 @@
+# Slowpoke extends Rack::Timeout to gracefully kill Unicorn workers so they can clean up state.
+Slowpoke.timeout = 60
+
+# The `Rack::Timeout` middleware kills requests after 60 seconds (as set above).
+# We're replacing it with our `Gitlab::Middleware::Timeout` that does the same,
+# except ignoring Git-over-HTTP requests, letting those take as long as they need.
+
+Rails.application.config.middleware.swap(Rack::Timeout, Gitlab::Middleware::Timeout)
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
new file mode 100644
index 0000000000..c5b6b75e7f
--- /dev/null
+++ b/config/locales/doorkeeper.en.yml
@@ -0,0 +1,73 @@
+en:
+ activerecord:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ mongoid:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ mongo_mapper:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'cannot contain a fragment.'
+ invalid_uri: 'must be a valid URI.'
+ relative_uri: 'must be an absolute URI.'
+ doorkeeper:
+ errors:
+ messages:
+ # Common error messages
+ invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
+ invalid_redirect_uri: 'The redirect uri included is not valid.'
+ unauthorized_client: 'The client is not authorized to perform this request using this method.'
+ access_denied: 'The resource owner or authorization server denied the request.'
+ invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
+ server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
+ temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
+
+ #configuration error messages
+ credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
+ resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.'
+
+ # Access grant errors
+ unsupported_response_type: 'The authorization server does not support this response type.'
+
+ # Access token errors
+ invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.'
+ invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
+ unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.'
+
+ # Password Access token errors
+ invalid_resource_owner: 'The provided resource owner credentials are not valid, or resource owner cannot be found'
+
+ invalid_token:
+ revoked: "The access token was revoked"
+ expired: "The access token expired"
+ unknown: "The access token is invalid"
+ scopes:
+ api: Access your API
+
+ flash:
+ applications:
+ create:
+ notice: 'Application created.'
+ destroy:
+ notice: 'Application deleted.'
+ update:
+ notice: 'Application updated.'
+ authorized_applications:
+ destroy:
+ notice: 'Application revoked.'
diff --git a/config/routes.rb b/config/routes.rb
index 2534153758..e65ef30afb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,11 +2,15 @@ require 'sidekiq/web'
require 'api/api'
Gitlab::Application.routes.draw do
- #
+ use_doorkeeper do
+ controllers applications: 'oauth/applications',
+ authorized_applications: 'oauth/authorized_applications',
+ authorizations: 'oauth/authorizations'
+ end
+
# Search
- #
- get 'search' => "search#show"
- get 'search/autocomplete' => "search#autocomplete", as: :search_autocomplete
+ get 'search' => 'search#show'
+ get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
# API
API::API.logger Rails.logger
@@ -15,9 +19,9 @@ Gitlab::Application.routes.draw do
# Get all keys of user
get ':username.keys' => 'profiles/keys#get_keys' , constraints: { username: /.*/ }
- constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? }
+ constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? }
constraints constraint do
- mount Sidekiq::Web, at: "/admin/sidekiq", as: :sidekiq
+ mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
end
# Enable Grack support
@@ -28,26 +32,75 @@ Gitlab::Application.routes.draw do
receive_pack: Gitlab.config.gitlab_shell.receive_pack
}), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post]
- #
# Help
- #
-
get 'help' => 'help#index'
get 'help/:category/:file' => 'help#show', as: :help_page
get 'help/shortcuts'
+ get 'help/ui' => 'help#ui'
#
# Global snippets
#
resources :snippets do
member do
- get "raw"
+ get 'raw'
end
end
- get "/s/:username" => "snippets#user_index", as: :user_snippets, constraints: { username: /.*/ }
+ get '/s/:username' => 'snippets#user_index', as: :user_snippets, constraints: { username: /.*/ }
+
#
- # Explroe area
+ # Import
+ #
+ namespace :import do
+ resource :github, only: [:create, :new], controller: :github do
+ get :status
+ get :callback
+ get :jobs
+ end
+
+ resource :gitlab, only: [:create, :new], controller: :gitlab do
+ get :status
+ get :callback
+ get :jobs
+ end
+
+ resource :bitbucket, only: [:create, :new], controller: :bitbucket do
+ get :status
+ get :callback
+ get :jobs
+ end
+
+ resource :gitorious, only: [:create, :new], controller: :gitorious do
+ get :status
+ get :callback
+ get :jobs
+ end
+ end
+
+ #
+ # Uploads
+ #
+
+ scope path: :uploads do
+ # Note attachments and User/Group/Project avatars
+ get ":model/:mounted_as/:id/:filename",
+ to: "uploads#show",
+ constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /.+/ }
+
+ # Project markdown uploads
+ get ":namespace_id/:project_id/:secret/:filename",
+ to: "projects/uploads#show",
+ constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /.+/ }
+ end
+
+ # Redirect old note attachments path to new uploads path.
+ get "files/note/:id/:filename",
+ to: redirect("uploads/note/attachment/%{id}/%{filename}"),
+ constraints: { filename: /.+/ }
+
+ #
+ # Explore area
#
namespace :explore do
resources :projects, only: [:index] do
@@ -58,23 +111,19 @@ Gitlab::Application.routes.draw do
end
resources :groups, only: [:index]
- root to: "projects#trending"
+ root to: 'projects#trending'
end
# Compatibility with old routing
- get 'public' => "explore/projects#index"
- get 'public/projects' => "explore/projects#index"
-
- #
- # Attachments serving
- #
- get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ }
+ get 'public' => 'explore/projects#index'
+ get 'public/projects' => 'explore/projects#index'
#
# Admin Area
#
namespace :admin do
resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
+ resources :keys, only: [:show, :destroy]
member do
put :team_update
put :block
@@ -83,9 +132,11 @@ Gitlab::Application.routes.draw do
end
end
+ resources :applications
+
resources :groups, constraints: { id: /[^\/]+/ } do
member do
- put :project_teams_update
+ put :members_update
end
end
@@ -97,13 +148,26 @@ Gitlab::Application.routes.draw do
resource :logs, only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
- resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] do
- member do
- put :transfer
+ resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
+ root to: 'projects#index', as: :projects
+
+ resources(:projects,
+ path: '/',
+ constraints: { id: /[a-zA-Z.0-9_\-]+/ },
+ only: [:index, :show]) do
+ root to: 'projects#show'
+
+ member do
+ put :transfer
+ end
end
end
- root to: "dashboard#index"
+ resource :application_settings, only: [:show, :update] do
+ resources :services
+ end
+
+ root to: 'dashboard#index'
end
#
@@ -113,6 +177,7 @@ Gitlab::Application.routes.draw do
member do
get :history
get :design
+ get :applications
put :reset_private_token
put :update_username
@@ -128,43 +193,55 @@ Gitlab::Application.routes.draw do
end
resources :keys
resources :emails, only: [:index, :create, :destroy]
- resources :groups, only: [:index] do
- member do
- delete :leave
- end
- end
resource :avatar, only: [:destroy]
end
end
- match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get
+ get 'u/:username/calendar' => 'users#calendar', as: :user_calendar,
+ constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
+
+ get '/u/:username' => 'users#show', as: :user,
+ constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
#
# Dashboard Area
#
- resource :dashboard, controller: "dashboard", only: [:show] do
+ resource :dashboard, controller: 'dashboard', only: [:show] do
member do
- get :projects
get :issues
get :merge_requests
end
+
+ scope module: :dashboard do
+ resources :milestones, only: [:index, :show]
+
+ resources :groups, only: [:index]
+
+ resources :projects, only: [] do
+ collection do
+ get :starred
+ end
+ end
+ end
end
#
# Groups Area
#
- resources :groups, constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} do
+ resources :groups, constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } do
member do
get :issues
get :merge_requests
- get :members
get :projects
end
scope module: :groups do
- resources :group_members, only: [:create, :update, :destroy]
+ resources :group_members, only: [:index, :create, :update, :destroy] do
+ delete :leave, on: :collection
+ end
+
resource :avatar, only: [:destroy]
- resources :milestones
+ resources :milestones, only: [:index, :show, :update]
end
end
@@ -173,170 +250,228 @@ Gitlab::Application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions, confirmations: :confirmations }
devise_scope :user do
- get "/users/auth/:provider/omniauth_error" => "omniauth_callbacks#omniauth_error", as: :omniauth_error
+ get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
end
+
+ root to: "dashboard#show"
+
#
# Project Area
#
- resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do
- member do
- put :transfer
- post :fork
- post :archive
- post :unarchive
- post :upload_image
- post :toggle_star
- get :autocomplete_sources
- get :import
- put :retry_import
- end
+ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
+ resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+/ }, except:
+ [:new, :create, :index], path: "/") do
+ member do
+ put :transfer
+ post :archive
+ post :unarchive
+ post :toggle_star
+ post :markdown_preview
+ get :autocomplete_sources
+ end
- scope module: :projects do
- resources :blob, only: [:show, :destroy], constraints: { id: /.+/ } do
- get :diff, on: :member
- end
- resources :raw, only: [:show], constraints: {id: /.+/}
- resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
- resources :edit_tree, only: [:show, :update], constraints: { id: /.+/ }, path: 'edit' do
- post :preview, on: :member
- end
- resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new'
- resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
- resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
- resources :compare, only: [:index, :create]
- resources :blame, only: [:show], constraints: {id: /.+/}
- resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
- resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} do
- member do
- get :commits
+ scope module: :projects do
+ # Blob routes:
+ get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob'
+ post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob'
+ get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob'
+ put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob'
+ post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob'
+
+ scope do
+ get(
+ '/blob/*id/diff',
+ to: 'blob#diff',
+ constraints: { id: /.+/, format: false },
+ as: :blob_diff
+ )
+ get(
+ '/blob/*id',
+ to: 'blob#show',
+ constraints: { id: /.+/, format: false },
+ as: :blob
+ )
+ delete(
+ '/blob/*id',
+ to: 'blob#destroy',
+ constraints: { id: /.+/, format: false }
+ )
end
- end
- match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
+ scope do
+ get(
+ '/raw/*id',
+ to: 'raw#show',
+ constraints: { id: /.+/, format: /(html|js)/ },
+ as: :raw
+ )
+ end
- resources :snippets, constraints: {id: /\d+/} do
+ scope do
+ get(
+ '/tree/*id',
+ to: 'tree#show',
+ constraints: { id: /.+/, format: /(html|js)/ },
+ as: :tree
+ )
+ end
+ resource :avatar, only: [:show, :destroy]
+
+ resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do
+ get :branches, on: :member
+ end
+
+ resources :commits, only: [:show], constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
+ resources :compare, only: [:index, :create]
+
+ scope do
+ get(
+ '/blame/*id',
+ to: 'blame#show',
+ constraints: { id: /.+/, format: /(html|js)/ },
+ as: :blame
+ )
+ end
+
+ resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }
+ resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do
member do
- get "raw"
+ get :commits
end
end
- resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do
- collection do
- get :pages
- put ':id' => 'wikis#update'
- get :git_access
+ get '/compare/:from...:to' => 'compare#show', :as => 'compare',
+ :constraints => { from: /.+/, to: /.+/ }
+
+ resources :snippets, constraints: { id: /\d+/ } do
+ member do
+ get 'raw'
+ end
end
- member do
- get "history"
- end
- end
+ resources :wikis, only: [:show, :edit, :destroy, :create], constraints: { id: /[a-zA-Z.0-9_\-\/]+/ } do
+ collection do
+ get :pages
+ put ':id' => 'wikis#update'
+ get :git_access
+ end
- resource :repository, only: [:show] do
- member do
- get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex }
- end
- end
-
- resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
- member do
- get :test
- end
- end
-
- resources :deploy_keys, constraints: {id: /\d+/} do
- member do
- put :enable
- put :disable
- end
- end
-
- resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
- resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
- resources :protected_branches, only: [:index, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
-
- resources :refs, only: [] do
- collection do
- get "switch"
+ member do
+ get 'history'
+ end
end
- member do
- # tree viewer logs
- get "logs_tree", constraints: { id: Gitlab::Regex.git_reference_regex }
- get "logs_tree/:path" => "refs#logs_tree",
- as: :logs_file,
- constraints: {
- id: Gitlab::Regex.git_reference_regex,
+ resource :repository, only: [:show, :create] do
+ member do
+ get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
+ end
+ end
+
+ resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do
+ member do
+ get :test
+ end
+ end
+
+ resources :deploy_keys, constraints: { id: /\d+/ } do
+ member do
+ put :enable
+ put :disable
+ end
+ end
+
+ resource :fork, only: [:new, :create]
+ resource :import, only: [:new, :create, :show]
+
+ resources :refs, only: [] do
+ collection do
+ get 'switch'
+ end
+
+ member do
+ # tree viewer logs
+ get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
+ get 'logs_tree/:path' => 'refs#logs_tree', as: :logs_file, constraints: {
+ id: Gitlab::Regex.git_reference_regex,
path: /.*/
}
+ end
+ end
+
+ resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do
+ member do
+ get :diffs
+ post :automerge
+ get :automerge_check
+ get :ci_status
+ post :toggle_subscription
+ end
+
+ collection do
+ get :branch_from
+ get :branch_to
+ get :update_branches
+ end
+ end
+
+ resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
+ resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
+ resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex }
+
+ resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do
+ member do
+ get :test
+ end
+ end
+
+ resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do
+ member do
+ put :sort_issues
+ put :sort_merge_requests
+ end
+ end
+
+ resources :labels, constraints: { id: /\d+/ } do
+ collection do
+ post :generate
+ end
+ end
+
+ resources :issues, constraints: { id: /\d+/ }, except: [:destroy] do
+ member do
+ post :toggle_subscription
+ end
+ collection do
+ post :bulk_update
+ end
+ end
+
+ resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do
+ collection do
+ delete :leave
+
+ # Used for import team
+ # from another project
+ get :import
+ post :apply_import
+ end
+ end
+
+ resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
+ member do
+ delete :delete_attachment
+ end
+ end
+
+ resources :uploads, only: [:create] do
+ collection do
+ get ":secret/:filename", action: :show, as: :show, constraints: { filename: /.+/ }
+ end
end
end
- resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do
- member do
- get :diffs
- post :automerge
- get :automerge_check
- get :ci_status
- end
-
- collection do
- get :branch_from
- get :branch_to
- get :update_branches
- end
- end
-
- resources :hooks, only: [:index, :create, :destroy], constraints: {id: /\d+/} do
- member do
- get :test
- end
- end
-
- resources :team, controller: 'team_members', only: [:index]
- resources :milestones, except: [:destroy], constraints: {id: /\d+/} do
- member do
- put :sort_issues
- put :sort_merge_requests
- end
- end
-
- resources :labels, constraints: {id: /\d+/} do
- collection do
- post :generate
- end
- end
-
- resources :issues, constraints: {id: /\d+/}, except: [:destroy] do
- collection do
- post :bulk_update
- end
- end
-
- resources :team_members, except: [:index, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do
- collection do
- delete :leave
-
- # Used for import team
- # from another project
- get :import
- post :apply_import
- end
- end
-
- resources :notes, only: [:index, :create, :destroy, :update], constraints: {id: /\d+/} do
- member do
- delete :delete_attachment
- end
-
- collection do
- post :preview
- end
- end
end
end
- get ':id' => "namespaces#show", constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
-
- root to: "dashboard#show"
+ get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
end
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index 6833082d68..3aee718097 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -13,9 +13,10 @@
#
# ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
-# Use at least one worker per core if you're on a dedicated server,
-# more will usually help for _short_ waits on databases/caches.
-worker_processes 2
+# Read about unicorn workers here:
+# http://doc.gitlab.com/ee/install/requirements.html#unicorn-workers
+#
+worker_processes 3
# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
@@ -34,22 +35,10 @@ working_directory "/home/git/gitlab" # available in 0.94.0+
listen "/home/git/gitlab/tmp/sockets/gitlab.socket", :backlog => 1024
listen "127.0.0.1:8080", :tcp_nopush => true
-# nuke workers after 30 seconds instead of 60 seconds (the default)
-#
-# NOTICE: git push over http depends on this value.
-# If you want be able to push huge amount of data to git repository over http
-# you will have to increase this value too.
-#
-# Example of output if you try to push 1GB repo to GitLab over http.
-# -> git push http://gitlab.... master
-#
-# error: RPC failed; result=18, HTTP code = 200
-# fatal: The remote end hung up unexpectedly
-# fatal: The remote end hung up unexpectedly
-#
-# For more information see http://stackoverflow.com/a/21682112/752049
-#
-timeout 60
+# Kill workers after 1 hour.
+# A shorter timeout of 60 seconds is enforced by rack-timeout for web requests.
+# Git-over-HTTP only has the below timeout since large pulls/pushes can take a long time.
+timeout 60 * 60
# feel free to point this anywhere accessible on the filesystem
pid "/home/git/gitlab/tmp/pids/unicorn.pid"
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index 004d4cd64a..bba2fc4b18 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -3,9 +3,9 @@ Gitlab::Seeder.quiet do
s.id = 1
s.name = 'Administrator'
s.email = 'admin@example.com'
+ s.notification_email = 'admin@example.com'
s.username = 'root'
s.password = '5iveL!fe'
- s.password_confirmation = '5iveL!fe'
s.admin = true
s.projects_limit = 100
s.confirmed_at = DateTime.now
diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb
index c263dd232a..24952a1f66 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -1,31 +1,31 @@
Gitlab::Seeder.quiet do
(2..20).each do |i|
begin
- User.seed(:id, [{
- id: i,
+ User.create!(
username: Faker::Internet.user_name,
name: Faker::Name.name,
email: Faker::Internet.email,
- confirmed_at: DateTime.now
- }])
+ confirmed_at: DateTime.now,
+ password: '12345678'
+ )
+
print '.'
- rescue ActiveRecord::RecordNotSaved
+ rescue ActiveRecord::RecordInvalid
print 'F'
end
end
(1..5).each do |i|
begin
- User.seed(:id, [
- id: i + 10,
+ User.create!(
username: "user#{i}",
name: "User #{i}",
email: "user#{i}@example.com",
confirmed_at: DateTime.now,
password: '12345678'
- ])
+ )
print '.'
- rescue ActiveRecord::RecordNotSaved
+ rescue ActiveRecord::RecordInvalid
print 'F'
end
end
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index ff91e8430a..b3a6f39c7d 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -1,9 +1,26 @@
Gitlab::Seeder.quiet do
- contents = [
- `curl https://gist.githubusercontent.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`,
- `curl https://gist.githubusercontent.com/randx/3754594/raw/11026a295e6ef3a151c635707a3e1e8e15fc4725/gitlab_setup.sh`,
- `curl https://gist.githubusercontent.com/randx/3065552/raw/29fbd09f4605a5ea22a5a9095e35fd1938dea4d6/gistfile1.sh`,
- ]
+ content =< { where(access_level: GUEST) }
+ scope :reporters, -> { where(access_level: REPORTER) }
+ scope :developers, -> { where(access_level: DEVELOPER) }
+ scope :masters, -> { where(access_level: MASTER) }
+ scope :owners, -> { where(access_level: OWNER) }
+
+ delegate :name, :username, :email, to: :user, prefix: true
+end
+eos
(1..50).each do |i|
user = User.all.sample
@@ -12,10 +29,11 @@ Gitlab::Seeder.quiet do
id: i,
author_id: user.id,
title: Faker::Lorem.sentence(3),
- file_name: Faker::Internet.domain_word + '.sh',
- private: [true, false].sample,
- content: contents.sample,
+ file_name: Faker::Internet.domain_word + '.rb',
+ visibility_level: Gitlab::VisibilityLevel.values.sample,
+ content: content,
}])
+
print('.')
end
end
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index e0b13db020..8b560ee09e 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -1,16 +1,17 @@
-password = if ENV['GITLAB_ROOT_PASSWORD'].blank?
- "5iveL!fe"
- else
- ENV['GITLAB_ROOT_PASSWORD']
- end
+if ENV['GITLAB_ROOT_PASSWORD'].blank?
+ password = '5iveL!fe'
+ expire_time = Time.now
+else
+ password = ENV['GITLAB_ROOT_PASSWORD']
+ expire_time = nil
+end
admin = User.create(
email: "admin@example.com",
name: "Administrator",
username: 'root',
password: password,
- password_confirmation: password,
- password_expires_at: Time.now,
+ password_expires_at: expire_time,
theme_id: Gitlab::Theme::MARS
)
diff --git a/db/migrate/20140125162722_add_avatar_to_projects.rb b/db/migrate/20140125162722_add_avatar_to_projects.rb
new file mode 100644
index 0000000000..9523ac722f
--- /dev/null
+++ b/db/migrate/20140125162722_add_avatar_to_projects.rb
@@ -0,0 +1,5 @@
+class AddAvatarToProjects < ActiveRecord::Migration
+ def change
+ add_column :projects, :avatar, :string
+ end
+end
diff --git a/db/migrate/20140907220153_serialize_service_properties.rb b/db/migrate/20140907220153_serialize_service_properties.rb
index b95f5b82e0..d45a10465b 100644
--- a/db/migrate/20140907220153_serialize_service_properties.rb
+++ b/db/migrate/20140907220153_serialize_service_properties.rb
@@ -1,6 +1,9 @@
class SerializeServiceProperties < ActiveRecord::Migration
def change
- add_column :services, :properties, :text
+ unless column_exists?(:services, :properties)
+ add_column :services, :properties, :text
+ end
+
Service.reset_column_information
associations =
@@ -19,18 +22,21 @@ class SerializeServiceProperties < ActiveRecord::Migration
:api_version, :jira_issue_transition_id],
}
- Service.all.each do |service|
+ Service.find_each(batch_size: 500).each do |service|
associations[service.type.to_sym].each do |attribute|
service.send("#{attribute}=", service.attributes[attribute.to_s])
end
- service.save!
+
+ service.save(validate: false)
end
- remove_column :services, :project_url, :string
- remove_column :services, :subdomain, :string
- remove_column :services, :room, :string
- remove_column :services, :recipients, :text
- remove_column :services, :api_key, :string
- remove_column :services, :token, :string
+ if column_exists?(:services, :project_url)
+ remove_column :services, :project_url, :string
+ remove_column :services, :subdomain, :string
+ remove_column :services, :room, :string
+ remove_column :services, :recipients, :text
+ remove_column :services, :api_key, :string
+ remove_column :services, :token, :string
+ end
end
end
diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
index 4b62b223cb..5836cd6b8d 100644
--- a/db/migrate/20141006143943_move_slack_service_to_webhook.rb
+++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
@@ -10,7 +10,7 @@ class MoveSlackServiceToWebhook < ActiveRecord::Migration
slack_service.properties.delete('subdomain')
# Room is configured on the Slack side
slack_service.properties.delete('room')
- slack_service.save!
+ slack_service.save(validate: false)
end
end
end
diff --git a/db/migrate/20141121133009_add_timestamps_to_members.rb b/db/migrate/20141121133009_add_timestamps_to_members.rb
new file mode 100644
index 0000000000..ef6d4dedf3
--- /dev/null
+++ b/db/migrate/20141121133009_add_timestamps_to_members.rb
@@ -0,0 +1,15 @@
+# In 20140914145549_migrate_to_new_members_model.rb we forgot to set the
+# created_at and updated_at times for new records in the 'members' table. This
+# became a problem after commit c8e78d972a5a628870eefca0f2ccea0199c55bda which
+# was added in GitLab 7.5. With this migration we ensure that all rows in
+# 'members' have at least some created_at and updated_at timestamp.
+class AddTimestampsToMembers < ActiveRecord::Migration
+ def up
+ execute "UPDATE members SET created_at = NOW() WHERE created_at is NULL"
+ execute "UPDATE members SET updated_at = NOW() WHERE updated_at is NULL"
+ end
+
+ def down
+ # no change
+ end
+end
diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb
new file mode 100644
index 0000000000..a85b0426ce
--- /dev/null
+++ b/db/migrate/20141121161704_add_identity_table.rb
@@ -0,0 +1,46 @@
+class AddIdentityTable < ActiveRecord::Migration
+ def up
+ create_table :identities do |t|
+ t.string :extern_uid
+ t.string :provider
+ t.references :user
+ end
+
+ add_index :identities, :user_id
+
+ execute < 2
+ end
+end
diff --git a/db/migrate/20150205211843_add_timestamps_to_identities.rb b/db/migrate/20150205211843_add_timestamps_to_identities.rb
new file mode 100644
index 0000000000..77cddbfec3
--- /dev/null
+++ b/db/migrate/20150205211843_add_timestamps_to_identities.rb
@@ -0,0 +1,5 @@
+class AddTimestampsToIdentities < ActiveRecord::Migration
+ def change
+ add_timestamps(:identities)
+ end
+end
diff --git a/db/migrate/20150206181414_add_index_to_created_at.rb b/db/migrate/20150206181414_add_index_to_created_at.rb
new file mode 100644
index 0000000000..fc624fca60
--- /dev/null
+++ b/db/migrate/20150206181414_add_index_to_created_at.rb
@@ -0,0 +1,16 @@
+class AddIndexToCreatedAt < ActiveRecord::Migration
+ def change
+ add_index "users", [:created_at, :id]
+ add_index "members", [:created_at, :id]
+ add_index "projects", [:created_at, :id]
+ add_index "issues", [:created_at, :id]
+ add_index "merge_requests", [:created_at, :id]
+ add_index "milestones", [:created_at, :id]
+ add_index "namespaces", [:created_at, :id]
+ add_index "notes", [:created_at, :id]
+ add_index "identities", [:created_at, :id]
+ add_index "keys", [:created_at, :id]
+ add_index "web_hooks", [:created_at, :id]
+ add_index "snippets", [:created_at, :id]
+ end
+end
diff --git a/db/migrate/20150206222854_add_notification_email_to_user.rb b/db/migrate/20150206222854_add_notification_email_to_user.rb
new file mode 100644
index 0000000000..ab80f7e582
--- /dev/null
+++ b/db/migrate/20150206222854_add_notification_email_to_user.rb
@@ -0,0 +1,11 @@
+class AddNotificationEmailToUser < ActiveRecord::Migration
+ def up
+ add_column :users, :notification_email, :string
+
+ execute "UPDATE users SET notification_email = email"
+ end
+
+ def down
+ remove_column :users, :notification_email
+ end
+end
diff --git a/db/migrate/20150209222013_add_missing_index.rb b/db/migrate/20150209222013_add_missing_index.rb
new file mode 100644
index 0000000000..a816c2e9e8
--- /dev/null
+++ b/db/migrate/20150209222013_add_missing_index.rb
@@ -0,0 +1,5 @@
+class AddMissingIndex < ActiveRecord::Migration
+ def change
+ add_index "services", [:created_at, :id]
+ end
+end
diff --git a/db/migrate/20150211172122_add_template_to_service.rb b/db/migrate/20150211172122_add_template_to_service.rb
new file mode 100644
index 0000000000..b1bfbc45ee
--- /dev/null
+++ b/db/migrate/20150211172122_add_template_to_service.rb
@@ -0,0 +1,5 @@
+class AddTemplateToService < ActiveRecord::Migration
+ def change
+ add_column :services, :template, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20150211174341_allow_null_in_services_project_id.rb b/db/migrate/20150211174341_allow_null_in_services_project_id.rb
new file mode 100644
index 0000000000..68f0281279
--- /dev/null
+++ b/db/migrate/20150211174341_allow_null_in_services_project_id.rb
@@ -0,0 +1,5 @@
+class AllowNullInServicesProjectId < ActiveRecord::Migration
+ def change
+ change_column :services, :project_id, :integer, null: true
+ end
+end
diff --git a/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb b/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb
new file mode 100644
index 0000000000..a043917239
--- /dev/null
+++ b/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddTwitterSharingEnabledToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :twitter_sharing_enabled, :boolean, default: true
+ end
+end
diff --git a/db/migrate/20150213114800_add_hide_no_password_to_user.rb b/db/migrate/20150213114800_add_hide_no_password_to_user.rb
new file mode 100644
index 0000000000..685f084427
--- /dev/null
+++ b/db/migrate/20150213114800_add_hide_no_password_to_user.rb
@@ -0,0 +1,5 @@
+class AddHideNoPasswordToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :hide_no_password, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20150213121042_add_password_automatically_set_to_user.rb b/db/migrate/20150213121042_add_password_automatically_set_to_user.rb
new file mode 100644
index 0000000000..c3c7c1ffc7
--- /dev/null
+++ b/db/migrate/20150213121042_add_password_automatically_set_to_user.rb
@@ -0,0 +1,5 @@
+class AddPasswordAutomaticallySetToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :password_automatically_set, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb
new file mode 100644
index 0000000000..23ac1b399e
--- /dev/null
+++ b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb
@@ -0,0 +1,6 @@
+class AddBitbucketAccessTokenAndSecretToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :bitbucket_access_token, :string
+ add_column :users, :bitbucket_access_token_secret, :string
+ end
+end
diff --git a/db/migrate/20150219004514_add_events_to_services.rb b/db/migrate/20150219004514_add_events_to_services.rb
new file mode 100644
index 0000000000..cf73a0174f
--- /dev/null
+++ b/db/migrate/20150219004514_add_events_to_services.rb
@@ -0,0 +1,8 @@
+class AddEventsToServices < ActiveRecord::Migration
+ def change
+ add_column :services, :push_events, :boolean, :default => true
+ add_column :services, :issues_events, :boolean, :default => true
+ add_column :services, :merge_requests_events, :boolean, :default => true
+ add_column :services, :tag_push_events, :boolean, :default => true
+ end
+end
diff --git a/db/migrate/20150223022001_set_missing_last_activity_at.rb b/db/migrate/20150223022001_set_missing_last_activity_at.rb
new file mode 100644
index 0000000000..3f6d4d8347
--- /dev/null
+++ b/db/migrate/20150223022001_set_missing_last_activity_at.rb
@@ -0,0 +1,8 @@
+class SetMissingLastActivityAt < ActiveRecord::Migration
+ def up
+ execute "UPDATE projects SET last_activity_at = updated_at WHERE last_activity_at IS NULL"
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20150225065047_add_note_events_to_services.rb b/db/migrate/20150225065047_add_note_events_to_services.rb
new file mode 100644
index 0000000000..d54ba9e482
--- /dev/null
+++ b/db/migrate/20150225065047_add_note_events_to_services.rb
@@ -0,0 +1,5 @@
+class AddNoteEventsToServices < ActiveRecord::Migration
+ def change
+ add_column :services, :note_events, :boolean, default: true, null: false
+ end
+end
diff --git a/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb b/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb
new file mode 100644
index 0000000000..494c3033bf
--- /dev/null
+++ b/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddRestrictedVisibilityLevelsToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :restricted_visibility_levels, :text
+ end
+end
diff --git a/db/migrate/20150306023106_fix_namespace_duplication.rb b/db/migrate/20150306023106_fix_namespace_duplication.rb
new file mode 100644
index 0000000000..334e557455
--- /dev/null
+++ b/db/migrate/20150306023106_fix_namespace_duplication.rb
@@ -0,0 +1,21 @@
+class FixNamespaceDuplication < ActiveRecord::Migration
+ def up
+ #fixes path duplication
+ select_all('SELECT MAX(id) max, COUNT(id) cnt, path FROM namespaces GROUP BY path HAVING COUNT(id) > 1').each do |nms|
+ bad_nms_ids = select_all("SELECT id FROM namespaces WHERE path = '#{nms['path']}' AND id <> #{nms['max']}").map{|x| x["id"]}
+ execute("UPDATE projects SET namespace_id = #{nms["max"]} WHERE namespace_id IN(#{bad_nms_ids.join(', ')})")
+ execute("DELETE FROM namespaces WHERE id IN(#{bad_nms_ids.join(', ')})")
+ end
+
+ #fixes name duplication
+ select_all('SELECT MAX(id) max, COUNT(id) cnt, name FROM namespaces GROUP BY name HAVING COUNT(id) > 1').each do |nms|
+ bad_nms_ids = select_all("SELECT id FROM namespaces WHERE name = '#{nms['name']}' AND id <> #{nms['max']}").map{|x| x["id"]}
+ execute("UPDATE projects SET namespace_id = #{nms["max"]} WHERE namespace_id IN(#{bad_nms_ids.join(', ')})")
+ execute("DELETE FROM namespaces WHERE id IN(#{bad_nms_ids.join(', ')})")
+ end
+ end
+
+ def down
+ # not implemented
+ end
+end
diff --git a/db/migrate/20150306023112_add_unique_index_to_namespace.rb b/db/migrate/20150306023112_add_unique_index_to_namespace.rb
new file mode 100644
index 0000000000..6472138e3e
--- /dev/null
+++ b/db/migrate/20150306023112_add_unique_index_to_namespace.rb
@@ -0,0 +1,9 @@
+class AddUniqueIndexToNamespace < ActiveRecord::Migration
+ def change
+ remove_index :namespaces, column: :name if index_exists?(:namespaces, :name)
+ remove_index :namespaces, column: :path if index_exists?(:namespaces, :path)
+
+ add_index :namespaces, :name, unique: true
+ add_index :namespaces, :path, unique: true
+ end
+end
diff --git a/db/migrate/20150313012111_create_subscriptions_table.rb b/db/migrate/20150313012111_create_subscriptions_table.rb
new file mode 100644
index 0000000000..a1d4d9dedc
--- /dev/null
+++ b/db/migrate/20150313012111_create_subscriptions_table.rb
@@ -0,0 +1,16 @@
+class CreateSubscriptionsTable < ActiveRecord::Migration
+ def change
+ create_table :subscriptions do |t|
+ t.integer :user_id
+ t.references :subscribable, polymorphic: true
+ t.boolean :subscribed
+
+ t.timestamps
+ end
+
+ add_index :subscriptions,
+ [:subscribable_id, :subscribable_type, :user_id],
+ unique: true,
+ name: 'subscriptions_user_id_and_ref_fields'
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8ddebc5132..e7dccbad4f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,11 +11,25 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20141007100818) do
+ActiveRecord::Schema.define(version: 20150313012111) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
+ create_table "application_settings", force: true do |t|
+ t.integer "default_projects_limit"
+ t.boolean "signup_enabled"
+ t.boolean "signin_enabled"
+ t.boolean "gravatar_enabled"
+ t.text "sign_in_text"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "home_page_url"
+ t.integer "default_branch_protection", default: 2
+ t.boolean "twitter_sharing_enabled", default: true
+ t.text "restricted_visibility_levels"
+ end
+
create_table "broadcast_messages", force: true do |t|
t.text "message", null: false
t.datetime "starts_at"
@@ -74,6 +88,17 @@ ActiveRecord::Schema.define(version: 20141007100818) do
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
+ create_table "identities", force: true do |t|
+ t.string "extern_uid"
+ t.string "provider"
+ t.integer "user_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "identities", ["created_at", "id"], name: "index_identities_on_created_at_and_id", using: :btree
+ add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
+
create_table "issues", force: true do |t|
t.string "title"
t.integer "assignee_id"
@@ -91,6 +116,7 @@ ActiveRecord::Schema.define(version: 20141007100818) do
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
+ add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree
add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
@@ -107,6 +133,7 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.string "fingerprint"
end
+ add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree
add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
create_table "label_links", force: true do |t|
@@ -142,6 +169,7 @@ ActiveRecord::Schema.define(version: 20141007100818) do
end
add_index "members", ["access_level"], name: "index_members_on_access_level", using: :btree
+ add_index "members", ["created_at", "id"], name: "index_members_on_created_at_and_id", using: :btree
add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree
add_index "members", ["type"], name: "index_members_on_type", using: :btree
add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
@@ -173,10 +201,12 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.integer "iid"
t.text "description"
t.integer "position", default: 0
+ t.datetime "locked_at"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
+ add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree
add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
@@ -196,6 +226,7 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.integer "iid"
end
+ add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree
add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
@@ -211,9 +242,10 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.string "avatar"
end
- add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree
+ add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree
+ add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
- add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree
+ add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree
add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
create_table "notes", force: true do |t|
@@ -233,6 +265,7 @@ ActiveRecord::Schema.define(version: 20141007100818) do
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
+ add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
@@ -240,6 +273,49 @@ ActiveRecord::Schema.define(version: 20141007100818) do
add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree
add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree
+ create_table "oauth_access_grants", force: true do |t|
+ t.integer "resource_owner_id", null: false
+ t.integer "application_id", null: false
+ t.string "token", null: false
+ t.integer "expires_in", null: false
+ t.text "redirect_uri", null: false
+ t.datetime "created_at", null: false
+ t.datetime "revoked_at"
+ t.string "scopes"
+ end
+
+ add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree
+
+ create_table "oauth_access_tokens", force: true do |t|
+ t.integer "resource_owner_id"
+ t.integer "application_id"
+ t.string "token", null: false
+ t.string "refresh_token"
+ t.integer "expires_in"
+ t.datetime "revoked_at"
+ t.datetime "created_at", null: false
+ t.string "scopes"
+ end
+
+ add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree
+ add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree
+ add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree
+
+ create_table "oauth_applications", force: true do |t|
+ t.string "name", null: false
+ t.string "uid", null: false
+ t.string "secret", null: false
+ t.text "redirect_uri", null: false
+ t.string "scopes", default: "", null: false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "owner_id"
+ t.string "owner_type"
+ end
+
+ add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
+ add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
+
create_table "projects", force: true do |t|
t.string "name"
t.string "path"
@@ -259,21 +335,26 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.string "import_url"
t.integer "visibility_level", default: 0, null: false
t.boolean "archived", default: false, null: false
+ t.string "avatar"
t.string "import_status"
t.float "repository_size", default: 0.0
t.integer "star_count", default: 0, null: false
+ t.string "import_type"
+ t.string "import_source"
end
+ add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
create_table "protected_branches", force: true do |t|
- t.integer "project_id", null: false
- t.string "name", null: false
+ t.integer "project_id", null: false
+ t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.boolean "developers_can_push", default: false, null: false
end
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
@@ -281,13 +362,20 @@ ActiveRecord::Schema.define(version: 20141007100818) do
create_table "services", force: true do |t|
t.string "type"
t.string "title"
- t.integer "project_id", null: false
+ t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "active", default: false, null: false
+ t.boolean "active", default: false, null: false
t.text "properties"
+ t.boolean "template", default: false
+ t.boolean "push_events", default: true
+ t.boolean "issues_events", default: true
+ t.boolean "merge_requests_events", default: true
+ t.boolean "tag_push_events", default: true
+ t.boolean "note_events", default: true, null: false
end
+ add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
create_table "snippets", force: true do |t|
@@ -304,11 +392,23 @@ ActiveRecord::Schema.define(version: 20141007100818) do
end
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
+ add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree
add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
+ create_table "subscriptions", force: true do |t|
+ t.integer "user_id"
+ t.integer "subscribable_id"
+ t.string "subscribable_type"
+ t.boolean "subscribed"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree
+
create_table "taggings", force: true do |t|
t.integer "tag_id"
t.integer "taggable_id"
@@ -327,12 +427,12 @@ ActiveRecord::Schema.define(version: 20141007100818) do
end
create_table "users", force: true do |t|
- t.string "email", default: "", null: false
- t.string "encrypted_password", default: "", null: false
+ t.string "email", default: "", null: false
+ t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", default: 0
+ t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
@@ -340,42 +440,47 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
- t.boolean "admin", default: false, null: false
- t.integer "projects_limit", default: 10
- t.string "skype", default: "", null: false
- t.string "linkedin", default: "", null: false
- t.string "twitter", default: "", null: false
+ t.boolean "admin", default: false, null: false
+ t.integer "projects_limit", default: 10
+ t.string "skype", default: "", null: false
+ t.string "linkedin", default: "", null: false
+ t.string "twitter", default: "", null: false
t.string "authentication_token"
- t.integer "theme_id", default: 1, null: false
+ t.integer "theme_id", default: 1, null: false
t.string "bio"
- t.integer "failed_attempts", default: 0
+ t.integer "failed_attempts", default: 0
t.datetime "locked_at"
- t.string "extern_uid"
- t.string "provider"
t.string "username"
- t.boolean "can_create_group", default: true, null: false
- t.boolean "can_create_team", default: true, null: false
+ t.boolean "can_create_group", default: true, null: false
+ t.boolean "can_create_team", default: true, null: false
t.string "state"
- t.integer "color_scheme_id", default: 1, null: false
- t.integer "notification_level", default: 1, null: false
+ t.integer "color_scheme_id", default: 1, null: false
+ t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at"
t.integer "created_by_id"
+ t.datetime "last_credential_check_at"
t.string "avatar"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
- t.boolean "hide_no_ssh_key", default: false
- t.string "website_url", default: "", null: false
- t.datetime "last_credential_check_at"
+ t.boolean "hide_no_ssh_key", default: false
+ t.string "website_url", default: "", null: false
+ t.string "github_access_token"
+ t.string "gitlab_access_token"
+ t.string "notification_email"
+ t.boolean "hide_no_password", default: false
+ t.boolean "password_automatically_set", default: false
+ t.string "bitbucket_access_token"
+ t.string "bitbucket_access_token_secret"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
+ add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree
add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
- add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
@@ -404,6 +509,7 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.boolean "tag_push_events", default: false
end
+ add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
end
diff --git a/doc/README.md b/doc/README.md
index 2f90cf14a6..4e00dceac2 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -2,24 +2,30 @@
## User documentation
-- [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
-- [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
+- [API](api/README.md) Automate GitLab via a simple and powerful API.
+- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
-- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
+- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
+- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
-- [Workflow](workflow/README.md) Learn how to use Git and GitLab together.
+- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
+- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
## Administrator documentation
-- [Install](install/README.md) Requirements, directory structures and manual installation.
+- [Install](install/README.md) Requirements, directory structures and installation from source.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
-- [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier.
-- [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out.
+- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
+- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
+- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
+- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
+- [Operations](operations/README.md) Keeping GitLab up and running
+- [Log system](logs/logs.md) Log system
## Contributor documentation
diff --git a/doc/api/README.md b/doc/api/README.md
index f76a253083..dec530d0b8 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -21,13 +21,8 @@
## Clients
-- [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
-- [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com)
-- [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
-- [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
-- [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
-- [node-gitlab](https://github.com/moul/node-gitlab) - Node.js
-- [NGitLab](https://github.com/Scooletz/NGitLab) - .NET
+Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients).
+You can use [GitLab as an OAuth2 client](oauth2.md) to make API calls.
## Introduction
@@ -57,6 +52,24 @@ curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://example.com/api/v3/p
The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL.
+## Authentication with OAuth2 token
+
+Instead of the private_token you can transmit the OAuth2 access token as a header or as a parameter.
+
+### OAuth2 token (as a parameter)
+
+```
+curl https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
+```
+
+### OAuth2 token (as a header)
+
+```
+curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+```
+
+Read more about [GitLab as an OAuth2 client](oauth2.md).
+
## Status codes
The API is designed to return different status codes according to context and action. In this way if a request results in an error the caller is able to get insight into what went wrong, e.g. status code `400 Bad Request` is returned if a required attribute is missing from the request. The following list gives an overview of how the API functions generally behave.
@@ -85,7 +98,7 @@ Return values:
## Sudo
-All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by url or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
+All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by URL or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
If a non administrative `private_token` is provided then an error message will be returned with status code 403:
@@ -130,7 +143,7 @@ When listing resources you can pass the following parameters:
- `page` (default: `1`) - page number
- `per_page` (default: `20`, max: `100`) - number of items to list per page
-[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own urls.
+[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own URLs.
## id vs iid
@@ -158,7 +171,7 @@ When an attribute is missing, you will get something like:
HTTP/1.1 400 Bad Request
Content-Type: application/json
-
+
{
"message":"400 (Bad request) \"title\" not given"
}
@@ -167,7 +180,7 @@ When a validation error occurs, error messages will be different. They will hold
HTTP/1.1 400 Bad Request
Content-Type: application/json
-
+
{
"message": {
"bio": [
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 7438661554..6a9c10c852 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -15,27 +15,20 @@ Parameters:
```json
[
{
- "name": "master",
"commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
+ "author_email": "john@example.com",
+ "author_name": "John Smith",
"authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": true
}
]
@@ -56,27 +49,20 @@ Parameters:
```json
{
- "name": "master",
"commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
+ "author_email": "john@example.com",
+ "author_name": "John Smith",
"authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": true
}
```
@@ -97,27 +83,20 @@ Parameters:
```json
{
- "name": "master",
"commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
+ "author_email": "john@example.com",
+ "author_name": "John Smith",
"authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": true
}
```
@@ -138,27 +117,20 @@ Parameters:
```json
{
- "name": "master",
"commit": {
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "parents": [
- {
- "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- }
- ],
- "tree": "46e82de44b1061621357f24c05515327f2795a95",
- "message": "add projects API",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "John Smith",
- "email": "john@example.com"
- },
+ "author_email": "john@example.com",
+ "author_name": "John Smith",
"authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00"
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": false
}
```
@@ -177,21 +149,20 @@ Parameters:
```json
{
- "name": "my-new-branch",
"commit": {
- "id": "8848c0e90327a0b70f1865b843fb2fbfb9345e57",
- "message": "Merge pull request #54 from brightbox/use_fog_brightbox_module\n\nUpdate to use fog-brightbox module",
- "parent_ids": [
- "fff449e0bf453576f16c91d6544f00a2664009d8",
- "f93a93626fec20fd659f4ed3ab2e64019b6169ae"
- ],
- "authored_date": "2014-02-20T19:54:55+02:00",
- "author_name": "john smith",
"author_email": "john@example.com",
- "committed_date": "2014-02-20T19:54:55+02:00",
- "committer_name": "john smith",
- "committer_email": "john@example.com"
+ "author_name": "John Smith",
+ "authored_date": "2012-06-27T05:51:39-07:00",
+ "committed_date": "2012-06-28T03:44:20-07:00",
+ "committer_email": "john@example.com",
+ "committer_name": "John Smith",
+ "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
+ "message": "add projects API",
+ "parent_ids": [
+ "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
+ ]
},
+ "name": "master",
"protected": false
}
```
@@ -211,3 +182,11 @@ Parameters:
It return 200 if succeed, 404 if the branch to be deleted does not exist
or 400 for other reasons. In case of an error, an explaining message is provided.
+
+Success response:
+
+```json
+{
+ "branch_name": "my-removed-branch"
+}
+```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 9475ecbaa6..eb8d6a4359 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -93,3 +93,66 @@ Parameters:
}
]
```
+
+## Get the comments of a commit
+
+Get the comments of a commit in a project.
+
+```
+GET /projects/:id/repository/commits/:sha/comments
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+
+```json
+[
+ {
+ "note": "this code is really nice",
+ "author": {
+ "id": 11,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2014-03-06T08:17:35.000Z"
+ }
+ }
+]
+```
+
+## Post comment to commit
+
+Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required.
+
+```
+POST /projects/:id/repository/commits/:sha/comments
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+- `note` (required) - Text of comment
+- `path` (optional) - The file path
+- `line` (optional) - The line number
+- `line_type` (optional) - The line type (new or old)
+
+```json
+{
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "blocked": false,
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "note": "text1",
+ "path": "example.rb",
+ "line": 5,
+ "line_type": "new"
+}
+```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 6b379b02d2..b5a4b05cca 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -14,11 +14,13 @@ GET /groups
"id": 1,
"name": "Foobar Group",
"path": "foo-bar",
- "owner_id": 18
+ "description": "An interesting group"
}
]
```
+You can search for groups by name or path, see below.
+
## Details of a group
Get all details of a group.
@@ -29,7 +31,7 @@ GET /groups/:id
Parameters:
-- `id` (required) - The ID of a group
+- `id` (required) - The ID or path of a group
## New group
@@ -43,6 +45,7 @@ Parameters:
- `name` (required) - The name of the group
- `path` (required) - The path of the group
+- `description` (optional) - The group's description
## Transfer project to group
@@ -54,7 +57,7 @@ POST /groups/:id/projects/:project_id
Parameters:
-- `id` (required) - The ID of a group
+- `id` (required) - The ID or path of a group
- `project_id` (required) - The ID of a project
## Remove group
@@ -67,7 +70,26 @@ DELETE /groups/:id
Parameters:
-- `id` (required) - The ID of a user group
+- `id` (required) - The ID or path of a user group
+
+## Search for group
+
+Get all groups that match your string in their name or path.
+
+```
+GET /groups?search=foobar
+```
+
+```json
+[
+ {
+ "id": 1,
+ "name": "Foobar Group",
+ "path": "foo-bar",
+ "description": "An interesting group"
+ }
+]
+```
## Group members
@@ -124,10 +146,24 @@ POST /groups/:id/members
Parameters:
-- `id` (required) - The ID of a group
+- `id` (required) - The ID or path of a group
- `user_id` (required) - The ID of a user to add
- `access_level` (required) - Project access level
+### Edit group team member
+
+Updates a group team member to a specified access level.
+
+```
+PUT /groups/:id/members/:user_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a group
+- `user_id` (required) - The ID of a group member
+- `access_level` (required) - Project access level
+
### Remove user team member
Removes user from user team.
@@ -138,5 +174,5 @@ DELETE /groups/:id/members/:user_id
Parameters:
-- `id` (required) - The ID of a user group
+- `id` (required) - The ID or path of a user group
- `user_id` (required) - The ID of a group member
diff --git a/doc/api/issues.md b/doc/api/issues.md
index ceeb683a6b..a7dd8b74c3 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -18,6 +18,8 @@ Parameters:
- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
- `labels` (optional) - Comma-separated list of label names
+- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
```json
[
@@ -56,7 +58,7 @@ Parameters:
"title": "v1.0",
"description": "",
"due_date": "2012-07-20",
- "state": "reopenend",
+ "state": "reopened",
"updated_at": "2012-07-04T13:42:48Z",
"created_at": "2012-07-04T13:42:48Z"
},
@@ -105,6 +107,8 @@ Parameters:
- `state` (optional) - Return `all` issues or just those that are `opened` or `closed`
- `labels` (optional) - Comma-separated list of label names
- `milestone` (optional) - Milestone title
+- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
## Single issue
@@ -204,7 +208,7 @@ If an error occurs, an error number and a message explaining the reason is retur
## Delete existing issue (**Deprecated**)
-The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `closed` set to 1.
+The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `state_event` set to `close`.
```
DELETE /projects/:id/issues/:issue_id
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 14884e5391..1f3fd26a24 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -2,7 +2,9 @@
## List merge requests
-Get all merge requests for this project. The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests.
+Get all merge requests for this project.
+The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`).
+The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests.
```
GET /projects/:id/merge_requests
@@ -14,8 +16,8 @@ Parameters:
- `id` (required) - The ID of a project
- `state` (optional) - Return `all` requests or just those that are `merged`, `opened` or `closed`
-- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields
-- `sort` (optional) - Return requests sorted in `asc` or `desc` order
+- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
```json
[
@@ -94,6 +96,76 @@ Parameters:
}
```
+## Get single MR changes
+
+Shows information about the merge request including its files and changes
+
+```
+GET /projects/:id/merge_request/:merge_request_id/changes
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `merge_request_id` (required) - The ID of MR
+
+```json
+{
+ "id": 21,
+ "iid": 1,
+ "project_id": 4,
+ "title": "Blanditiis beatae suscipit hic assumenda et molestias nisi asperiores repellat et.",
+ "description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.",
+ "state": "reopened",
+ "created_at": "2015-02-02T19:49:39.159Z",
+ "updated_at": "2015-02-02T20:08:49.959Z",
+ "target_branch": "secret_token",
+ "source_branch": "version-1-9",
+ "upvotes": 0,
+ "downvotes": 0,
+ "author": {
+ "name": "Chad Hamill",
+ "username": "jarrett",
+ "id": 5,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/b95567800f828948baf5f4160ebb2473?s=40&d=identicon"
+ },
+ "assignee": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40&d=identicon"
+ },
+ "source_project_id": 4,
+ "target_project_id": 4,
+ "labels": [ ],
+ "milestone": {
+ "id": 5,
+ "iid": 1,
+ "project_id": 4,
+ "title": "v2.0",
+ "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.",
+ "state": "closed",
+ "created_at": "2015-02-02T19:49:26.013Z",
+ "updated_at": "2015-02-02T19:49:26.013Z",
+ "due_date": null
+ },
+ "files": [
+ {
+ "old_path": "VERSION",
+ "new_path": "VERSION",
+ "a_mode": "100644",
+ "b_mode": "100644",
+ "diff": "--- a/VERSION\ +++ b/VERSION\ @@ -1 +1 @@\ -1.9.7\ +1.9.8",
+ "new_file": false,
+ "renamed_file": false,
+ "deleted_file": false
+ }
+ ]
+}
+```
+
## Create MR
Creates a new merge request.
@@ -109,6 +181,7 @@ Parameters:
- `target_branch` (required) - The target branch
- `assignee_id` (optional) - Assignee user ID
- `title` (required) - Title of MR
+- `description` (optional) - Description of MR
- `target_project_id` (optional) - The target project (numeric id)
```json
@@ -160,6 +233,7 @@ Parameters:
- `target_branch` - The target branch
- `assignee_id` - Assignee user ID
- `title` - Title of MR
+- `description` - Description of MR
- `state_event` - New state (close|reopen|merge)
```json
@@ -169,6 +243,7 @@ Parameters:
"source_branch": "test1",
"project_id": 3,
"title": "test1",
+ "description": "description1",
"state": "opened",
"upvotes": 0,
"downvotes": 0,
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 2f52532750..d48b3bcce8 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -72,3 +72,16 @@ Parameters:
- `description` (optional) - The description of a milestone
- `due_date` (optional) - The due date of the milestone
- `state_event` (optional) - The state event of the milestone (close|activate)
+
+## Get all issues assigned to a single milestone
+
+Gets all issues assigned to a single project milestone.
+
+```
+GET /projects/:id/milestones/:milestone_id/issues
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `milestone_id` (required) - The ID of a project milestone
diff --git a/doc/api/notes.md b/doc/api/notes.md
index b5256ac803..c22e493562 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -78,6 +78,21 @@ Parameters:
- `issue_id` (required) - The ID of an issue
- `body` (required) - The content of a note
+### Modify existing issue note
+
+Modify existing note of an issue.
+
+```
+PUT /projects/:id/issues/:issue_id/notes/:note_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `issue_id` (required) - The ID of an issue
+- `note_id` (required) - The ID of a note
+- `body` (required) - The content of a note
+
## Snippets
### List all snippet notes
@@ -137,7 +152,22 @@ POST /projects/:id/snippets/:snippet_id/notes
Parameters:
- `id` (required) - The ID of a project
-- `snippet_id` (required) - The ID of an snippet
+- `snippet_id` (required) - The ID of a snippet
+- `body` (required) - The content of a note
+
+### Modify existing snippet note
+
+Modify existing note of a snippet.
+
+```
+PUT /projects/:id/snippets/:snippet_id/notes/:note_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `snippet_id` (required) - The ID of a snippet
+- `note_id` (required) - The ID of a note
- `body` (required) - The content of a note
## Merge Requests
@@ -199,3 +229,18 @@ Parameters:
- `id` (required) - The ID of a project
- `merge_request_id` (required) - The ID of a merge request
- `body` (required) - The content of a note
+
+### Modify existing merge request note
+
+Modify existing note of a merge request.
+
+```
+PUT /projects/:id/merge_requests/:merge_request_id/notes/:note_id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `merge_request_id` (required) - The ID of a merge request
+- `note_id` (required) - The ID of a note
+- `body` (required) - The content of a note
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
new file mode 100644
index 0000000000..d416a826f7
--- /dev/null
+++ b/doc/api/oauth2.md
@@ -0,0 +1,102 @@
+# GitLab as an OAuth2 client
+
+This document is about using other OAuth authentication service providers to sign into GitLab.
+If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [Oauth2 provider documentation](../integration/oauth_provider.md).
+
+OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password.
+
+Before using the OAuth2 you should create an application in user's account. Each application gets a unique App ID and App Secret parameters. You should not share these.
+
+This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper)
+
+## Web Application Flow
+
+This flow is using for authentication from third-party web sites and is probably used the most.
+It basically consists of an exchange of an authorization token for an access token. For more detailed info, check out the [RFC spec here](http://tools.ietf.org/html/rfc6749#section-4.1)
+
+This flow consists from 3 steps.
+
+### 1. Registering the client
+
+Create an application in user's account profile.
+
+### 2. Requesting authorization
+
+To request the authorization token, you should visit the `/oauth/authorize` endpoint. You can do that by visiting manually the URL:
+
+```
+http://localhost:3000/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code
+```
+
+Where REDIRECT_URI is the URL in your app where users will be sent after authorization.
+
+### 3. Requesting the access token
+
+To request the access token, you should use the returned code and exchange it for an access token. To do that you can use any HTTP client. In this case, I used rest-client:
+
+```
+parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI'
+RestClient.post 'http://localhost:3000/oauth/token', parameters
+
+# The response will be
+{
+ "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
+ "token_type": "bearer",
+ "expires_in": 7200,
+ "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
+}
+```
+
+You can now make requests to the API with the access token returned.
+
+### Use the access token to access the API
+
+The access token allows you to make requests to the API on a behalf of a user.
+
+```
+GET https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
+```
+
+Or you can put the token to the Authorization header:
+
+```
+curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+```
+
+## Resource Owner Password Credentials
+
+In this flow, a token is requested in exchange for the resource owner credentials (username and password).
+The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the
+client is part of the device operating system or a highly privileged application), and when other authorization grant types are not
+available (such as an authorization code).
+
+Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used
+for a single request and are exchanged for an access token. This grant type can eliminate the need for the client to store the
+resource owner credentials for future use, by exchanging the credentials with a long-lived access token or refresh token.
+You can do POST request to `/oauth/token` with parameters:
+
+```
+{
+ "grant_type" : "password",
+ "username" : "user@example.com",
+ "password" : "sekret"
+}
+```
+
+Then, you'll receive the access token back in the response:
+
+```
+{
+ "access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
+ "token_type": "bearer",
+ "expires_in": 7200
+}
+```
+
+For testing you can use the oauth2 ruby gem:
+
+```
+client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com")
+access_token = client.password.get_token('user@example.com', 'sekret')
+puts access_token.token
+```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index dfe3502b6e..7fe244477d 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -1,5 +1,23 @@
# Projects
+
+### Project visibility level
+
+Project in GitLab has be either private, internal or public.
+You can determine it by `visibility_level` field in project.
+
+Constants for project visibility levels are next:
+
+* Private. `visibility_level` is `0`.
+ Project access must be granted explicitly for each user.
+
+* Internal. `visibility_level` is `10`.
+ The project can be cloned by any logged in user.
+
+* Public. `visibility_level` is `20`.
+ The project can be cloned without any authentication.
+
+
## List projects
Get a list of projects accessible by the authenticated user.
@@ -11,6 +29,9 @@ GET /projects
Parameters:
- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
```json
[
@@ -47,7 +68,8 @@ Parameters:
"path": "diaspora",
"updated_at": "2013-09-30T13: 46: 02Z"
},
- "archived": false
+ "archived": false,
+ "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png"
},
{
"id": 6,
@@ -82,7 +104,8 @@ Parameters:
"path": "brightbox",
"updated_at": "2013-09-30T13:46:02Z"
},
- "archived": false
+ "archived": false,
+ "avatar_url": null
}
]
```
@@ -95,6 +118,13 @@ Get a list of projects which are owned by the authenticated user.
GET /projects/owned
```
+Parameters:
+
+- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
+
### List ALL projects
Get a list of all GitLab projects (admin only).
@@ -103,6 +133,13 @@ Get a list of all GitLab projects (admin only).
GET /projects/all
```
+Parameters:
+
+- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
+
### Get single project
Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user.
@@ -160,7 +197,8 @@ Parameters:
"notification_level": 3
}
},
- "archived": false
+ "archived": false,
+ "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png"
}
```
@@ -186,6 +224,7 @@ Parameters:
"target_id": 830,
"target_type": "Issue",
"author_id": 1,
+ "author_username": "john",
"data": null,
"target_title": "Public project search field"
},
@@ -196,6 +235,7 @@ Parameters:
"target_id": null,
"target_type": null,
"author_id": 1,
+ "author_username": "john",
"data": {
"before": "50d4420237a9de7be1304607147aec22e4a14af7",
"after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
@@ -231,6 +271,7 @@ Parameters:
"target_id": 840,
"target_type": "Issue",
"author_id": 1,
+ "author_username": "john",
"data": null,
"target_title": "Finish & merge Code search PR"
}
@@ -281,6 +322,31 @@ Parameters:
- `visibility_level` (optional)
- `import_url` (optional)
+### Edit project
+
+Updates an existing project
+
+```
+PUT /projects/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `name` (optional) - project name
+- `path` (optional) - repository name for project
+- `description` (optional) - short project description
+- `default_branch` (optional)
+- `issues_enabled` (optional)
+- `merge_requests_enabled` (optional)
+- `wiki_enabled` (optional)
+- `snippets_enabled` (optional)
+- `public` (optional) - if `true` same as setting visibility_level = 20
+- `visibility_level` (optional)
+
+On success, method returns 200 with the updated project. If parameters are
+invalid, 400 is returned.
+
### Fork project
Forks a project into the user namespace of the authenticated user.
@@ -510,7 +576,7 @@ Parameters:
}
],
"tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee",
- "message": "give caolan credit where it's due (up top)",
+ "message": "give Caolan credit where it's due (up top)",
"author": {
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
@@ -625,6 +691,8 @@ GET /projects/search/:query
Parameters:
-- query (required) - A string contained in the project name
-- per_page (optional) - number of projects to return per page
-- page (optional) - the page to retrieve
+- `query` (required) - A string contained in the project name
+- `per_page` (optional) - number of projects to return per page
+- `page` (optional) - the page to retrieve
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index a412f60c0d..3316745380 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -15,24 +15,21 @@ Parameters:
```json
[
{
- "name": "v1.0.0",
"commit": {
- "id": "2695effb5807a22ff3d138d593fd856244e155e7",
- "parents": [],
- "tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d",
- "message": "Initial commit",
- "author": {
- "name": "John Smith",
- "email": "john@example.com"
- },
- "committer": {
- "name": "Jack Smith",
- "email": "jack@example.com"
- },
+ "author_name": "John Smith",
+ "author_email": "john@example.com",
"authored_date": "2012-05-28T04:42:42-07:00",
- "committed_date": "2012-05-28T04:42:42-07:00"
+ "committed_date": "2012-05-28T04:42:42-07:00",
+ "committer_name": "Jack Smith",
+ "committer_email": "jack@example.com",
+ "id": "2695effb5807a22ff3d138d593fd856244e155e7",
+ "message": "Initial commit",
+ "parents_ids": [
+ "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
+ ]
},
- "protected": null
+ "name": "v1.0.0",
+ "message": null
}
]
```
@@ -53,24 +50,26 @@ Parameters:
- `message` (optional) - Creates annotated tag.
```json
-[
- {
- "name": "v1.0.0",
- "commit": {
- "id": "2695effb5807a22ff3d138d593fd856244e155e7",
- "parents": [],
- "message": "Initial commit",
- "authored_date": "2012-05-28T04:42:42-07:00",
- "author_name": "John Smith",
- "author email": "john@example.com",
- "committer_name": "Jack Smith",
- "committed_date": "2012-05-28T04:42:42-07:00",
- "committer_email": "jack@example.com"
- },
- "protected": false
- }
-]
+{
+ "commit": {
+ "author_name": "John Smith",
+ "author_email": "john@example.com",
+ "authored_date": "2012-05-28T04:42:42-07:00",
+ "committed_date": "2012-05-28T04:42:42-07:00",
+ "committer_name": "Jack Smith",
+ "committer_email": "jack@example.com",
+ "id": "2695effb5807a22ff3d138d593fd856244e155e7",
+ "message": "Initial commit",
+ "parents_ids": [
+ "2a4b78934375d7f53875269ffd4f45fd83a84ebe"
+ ]
+ },
+ "name": "v1.0.0",
+ "message": null
+}
```
+The message will be `nil` when creating a lightweight tag otherwise
+it will contain the annotation.
It returns 200 if the operation succeed. In case of an error,
405 with an explaining error message is returned.
diff --git a/doc/api/services.md b/doc/api/services.md
new file mode 100644
index 0000000000..cbf767d1b2
--- /dev/null
+++ b/doc/api/services.md
@@ -0,0 +1,46 @@
+# Services
+
+## GitLab CI
+
+### Edit GitLab CI service
+
+Set GitLab CI service for a project.
+
+```
+PUT /projects/:id/services/gitlab-ci
+```
+
+Parameters:
+
+- `token` (required) - CI project token
+- `project_url` (required) - CI project URL
+
+### Delete GitLab CI service
+
+Delete GitLab CI service settings for a project.
+
+```
+DELETE /projects/:id/services/gitlab-ci
+```
+
+## HipChat
+
+### Edit HipChat service
+
+Set HipChat service for project.
+
+```
+PUT /projects/:id/services/hipchat
+```
+Parameters:
+
+- `token` (required) - HipChat token
+- `room` (required) - HipChat room name
+
+### Delete HipChat service
+
+Delete HipChat service for a project.
+
+```
+DELETE /projects/:id/services/hipchat
+```
diff --git a/doc/api/users.md b/doc/api/users.md
index 3fdd3a75e8..a8b7685b50 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -78,7 +78,8 @@ GET /users
"is_admin": false,
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
]
```
@@ -140,7 +141,8 @@ Parameters:
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
```
@@ -168,6 +170,7 @@ Parameters:
- `bio` (optional) - User's biography
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
+- `confirm` (optional) - Require confirmation - true (default) or false
## User modification
@@ -240,7 +243,8 @@ GET /user
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
```
@@ -257,12 +261,14 @@ GET /user/keys
{
"id": 1,
"title": "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2014-08-01T14:47:39.080Z"
},
{
"id": 3,
"title": "Another Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2014-08-01T14:47:39.080Z"
}
]
```
@@ -299,7 +305,8 @@ Parameters:
{
"id": 1,
"title": "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2014-08-01T14:47:39.080Z"
}
```
@@ -316,6 +323,31 @@ Parameters:
- `title` (required) - new SSH Key's title
- `key` (required) - new SSH key
+```json
+{
+ "created_at": "2015-01-21T17:44:33.512Z",
+ "key": "ssh-dss AAAAB3NzaC1kc3MAAACBAMLrhYgI3atfrSD6KDas1b/3n6R/HP+bLaHHX6oh+L1vg31mdUqK0Ac/NjZoQunavoyzqdPYhFz9zzOezCrZKjuJDS3NRK9rspvjgM0xYR4d47oNZbdZbwkI4cTv/gcMlquRy0OvpfIvJtjtaJWMwTLtM5VhRusRuUlpH99UUVeXAAAAFQCVyX+92hBEjInEKL0v13c/egDCTQAAAIEAvFdWGq0ccOPbw4f/F8LpZqvWDydAcpXHV3thwb7WkFfppvm4SZte0zds1FJ+Hr8Xzzc5zMHe6J4Nlay/rP4ewmIW7iFKNBEYb/yWa+ceLrs+TfR672TaAgO6o7iSRofEq5YLdwgrwkMmIawa21FrZ2D9SPao/IwvENzk/xcHu7YAAACAQFXQH6HQnxOrw4dqf0NqeKy1tfIPxYYUZhPJfo9O0AmBW2S36pD2l14kS89fvz6Y1g8gN/FwFnRncMzlLY/hX70FSc/3hKBSbH6C6j8hwlgFKfizav21eS358JJz93leOakJZnGb8XlWvz1UJbwCsnR2VEY8Dz90uIk1l/UqHkA= loic@call",
+ "title": "ABC",
+ "id": 4
+}
+```
+
+Will return created key with status `201 Created` on success. If an
+error occurs a `400 Bad Request` is returned with a message explaining the error:
+
+```json
+{
+ "message": {
+ "fingerprint": [
+ "has already been taken"
+ ],
+ "key": [
+ "has already been taken"
+ ]
+ }
+}
+```
+
## Add SSH key for user
Create new key owned by specified user. Available only for admin
diff --git a/doc/customization/libravatar.md b/doc/customization/libravatar.md
new file mode 100644
index 0000000000..ee57fdc659
--- /dev/null
+++ b/doc/customization/libravatar.md
@@ -0,0 +1,69 @@
+# Use Libravatar service with GitLab
+
+GitLab by default supports [Gravatar](gravatar.com) avatar service.
+Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is
+[heavily based on gravatar](http://wiki.libravatar.org/api/).
+
+This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server.
+
+# Configuration
+
+In [gitlab.yml gravatar section](https://gitlab.com/gitlab-org/gitlab-ce/blob/672bd3902d86b78d730cea809fce312ec49d39d7/config/gitlab.yml.example#L122) set
+the configuration options as follows:
+
+## For HTTP
+
+```yml
+ gravatar:
+ enabled: true
+ # gravatar URLs: possible placeholders: %{hash} %{size} %{email}
+ plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+## For HTTPS
+
+```yml
+ gravatar:
+ enabled: true
+ # gravatar URLs: possible placeholders: %{hash} %{size} %{email}
+ ssl_url: "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+## Self-hosted
+
+If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration
+but the important part is to provide the same placeholders so GitLab can parse the URL correctly.
+
+For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is
+
+`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon`
+
+
+## Omnibus-gitlab example
+
+In `/etc/gitlab/gitlab.rb`:
+
+#### For http
+
+```ruby
+gitlab_rails['gravatar_enabled'] = true
+gitlab_rails['gravatar_plain_url'] = "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+#### For https
+
+```ruby
+gitlab_rails['gravatar_enabled'] = true
+gitlab_rails['gravatar_ssl_url'] = "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
+```
+
+
+Run `sudo gitlab-ctl reconfigure` for changes to take effect.
+
+
+## Default URL for missing images
+
+[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service.
+
+In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set.
+For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
diff --git a/doc/development/README.md b/doc/development/README.md
index 20db6662ac..d5d264be19 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -4,3 +4,5 @@
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Rake tasks](rake_tasks.md) for development
- [CI setup](ci_setup.md) for testing GitLab
+- [Sidekiq debugging](sidekiq_debugging.md)
+- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index c4813d22ea..714cc01600 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -8,6 +8,38 @@ EE releases are available not long after CE releases. To obtain the GitLab EE th
Both EE and CE require an add-on component called gitlab-shell. It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master). New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical.
+## Physical office analogy
+
+You can imagine GitLab as a physical office.
+
+**The repositories** are the goods GitLab handling.
+They can be stored in a warehouse.
+This can be either a hard disk, or something more complex, such as a NFS filesystem;
+
+**Nginx** acts like the front-desk.
+Users come to Nginx and request actions to be done by workers in the office;
+
+**The database** is a series of metal file cabinets with information on:
+ - The goods in the warehouse (metadata, issues, merge requests etc);
+ - The users coming to the front desk (permissions)
+
+**Redis** is a communication board with “cubby holes” that can contain tasks for office workers;
+
+**Sidekiq** is a worker that primarily handles sending out emails.
+It takes tasks from the Redis communication board;
+
+**A Unicorn worker** is a worker that handles quick/mundane tasks.
+They work with the communication board (Redis).
+Their job description:
+ - check permissions by checking the user session stored in a Redis “cubby hole”;
+ - make tasks for Sidekiq;
+ - fetch stuff from the warehouse or move things around in there;
+
+**Gitlab-shell** is a third kind of worker that takes orders from a fax machine (SSH) instead of the front desk (HTTP).
+Gitlab-shell communicates with Sidekiq via the “communication board” (Redis), and asks quick questions of the Unicorn workers either directly or via the front desk.
+
+**GitLab Enterprise Edition (the application)** is the collection of processes and business practices that the office is run by.
+
## System Layout
When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git.
@@ -38,7 +70,7 @@ To summarize here's the [directory structure of the `git` user home directory](.
ps aux | grep '^git'
-GitLab has several components to operate. As a system user (i.e. any user that is not the `git` user) it requires a persistent database (MySQL/PostreSQL) and redis database. It also uses Apache httpd or nginx to proxypass Unicorn. As the `git` user it starts Sidekiq and Unicorn (a simple ruby HTTP server running on port `8080` by default). Under the GitLab user there are normally 4 processes: `unicorn_rails master` (1 process), `unicorn_rails worker` (2 processes), `sidekiq` (1 process).
+GitLab has several components to operate. As a system user (i.e. any user that is not the `git` user) it requires a persistent database (MySQL/PostreSQL) and redis database. It also uses Apache httpd or Nginx to proxypass Unicorn. As the `git` user it starts Sidekiq and Unicorn (a simple ruby HTTP server running on port `8080` by default). Under the GitLab user there are normally 4 processes: `unicorn_rails master` (1 process), `unicorn_rails worker` (2 processes), `sidekiq` (1 process).
### Repository access
@@ -114,13 +146,13 @@ nginx
Apache httpd
-- [Explanation of apache logs](http://httpd.apache.org/docs/2.2/logs.html).
+- [Explanation of Apache logs](http://httpd.apache.org/docs/2.2/logs.html).
- `/var/log/apache2/` contains error and output logs (on Ubuntu).
- `/var/log/httpd/` contains error and output logs (on RHEL).
redis
-- `/var/log/redis/redis.log` there are also logrotated logs there.
+- `/var/log/redis/redis.log` there are also log-rotated logs there.
PostgreSQL
diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md
index b3e84183a4..f9b4886818 100644
--- a/doc/development/ci_setup.md
+++ b/doc/development/ci_setup.md
@@ -4,28 +4,30 @@ This document describes what services we use for testing GitLab and GitLab CI.
We currently use three CI services to test GitLab:
-1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/2/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
+1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org
3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
| Software @ configuration being tested | GitLab CI (ci.gitlab.org) | GitLab CI (GitHost.io) | Semaphore |
-|---------------------------------------|---------------------------|------------------------|-----------|
-| GitLab CE @ MySQL | ✓ | ✓ | |
-| GitLab CE @ PostgreSQL | | | ✓ |
-| GitLab EE @ MySQL | ✓ | | |
-| GitLab CI @ MySQL | ✓ | | |
-| GitLab CI @ PostgreSQL | | | ✓ |
-| GitLab CI Runner | ✓ | | ✓ |
-| GitLab Shell | ✓ | | ✓ |
-| GitLab Shell | ✓ | | ✓ |
+|---------------------------------------|---------------------------|---------------------------------------------------------------------------|-----------|
+| GitLab CE @ MySQL | ✓ | ✓ [Core team can trigger builds](https://gitlab-ce.githost.io/projects/4) | |
+| GitLab CE @ PostgreSQL | | | ✓ [Core team can trigger builds](https://semaphoreapp.com/gitlabhq/gitlabhq/branches/master) |
+| GitLab EE @ MySQL | ✓ | | |
+| GitLab CI @ MySQL | ✓ | | |
+| GitLab CI @ PostgreSQL | | | ✓ |
+| GitLab CI Runner | ✓ | | ✓ |
+| GitLab Shell | ✓ | | ✓ |
+| GitLab Shell | ✓ | | ✓ |
+
+Core team has access to trigger builds if needed for GitLab CE.
We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/build_script_gitlab_ce.md) for testing with GitLab CI.
# Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
-Language: Ruby
-Ruby verion: 2.1.2
-database.yml: pg
+- Language: Ruby
+- Ruby version: 2.1.2
+- database.yml: pg
Build commands
@@ -35,7 +37,10 @@ bundle install --deployment --path vendor/bundle (Setup)
cp config/gitlab.yml.example config/gitlab.yml (Setup)
bundle exec rake db:create (Setup)
bundle exec rake spinach (Thread #1)
-bundle exec rake spec (Thread #2)
+bundle exec rake spec (thread #2)
+bundle exec rake rubocop (thread #3)
+bundle exec rake brakeman (thread #4)
+bundle exec rake jasmine:ci (thread #5)
```
Use rubygems mirror.
diff --git a/doc/development/omnibus.md b/doc/development/omnibus.md
new file mode 100644
index 0000000000..0ba354d28a
--- /dev/null
+++ b/doc/development/omnibus.md
@@ -0,0 +1,32 @@
+# What you should know about omnibus packages
+
+Most users install GitLab using our omnibus packages. As a developer it can be
+good to know how the omnibus packages differ from what you have on your laptop
+when you are coding.
+
+## Files are owned by root by default
+
+All the files in the Rails tree (`app/`, `config/` etc.) are owned by 'root' in
+omnibus installations. This makes the installation simpler and it provides
+extra security. The omnibus reconfigure script contains commands that give
+write access to the 'git' user only where needed.
+
+For example, the 'git' user is allowed to write in the `log/` directory, in
+`public/uploads`, and they are allowed to rewrite the `db/schema.rb` file.
+
+In other cases, the reconfigure script tricks GitLab into not trying to write a
+file. For instance, GitLab will generate a `.secret` file if it cannot find one
+and write it to the Rails root. In the omnibus packages, reconfigure writes the
+`.secret` file first, so that GitLab never tries to write it.
+
+## Code, data and logs are in separate directories
+
+The omnibus design separates code (read-only, under `/opt/gitlab`) from data
+(read/write, under `/var/opt/gitlab`) and logs (read/write, under
+`/var/log/gitlab`). To make this happen the reconfigure script sets custom
+paths where it can in GitLab config files, and where there are no path
+settings, it uses symlinks.
+
+For example, `config/gitlab.yml` is treated as data so that file is a symlink.
+The same goes for `public/uploads`. The `log/` directory is replaced by omnibus
+with a symlink to `/var/log/gitlab/gitlab-rails`.
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 6d9ac161e9..53f8095cb1 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -1,6 +1,6 @@
# Rake tasks for developers
-## Setup db with developer seeds:
+## Setup db with developer seeds
Note that if your db user does not have advanced privileges you must create the db manually before running this command.
@@ -8,6 +8,10 @@ Note that if your db user does not have advanced privileges you must create the
bundle exec rake setup
```
+The `setup` task is a alias for `gitlab:setup`.
+This tasks calls `db:setup` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and fianlly it calls `db:seed_fu` to seed the database.
+Note: `db:setup` calls `db:seed` but this does nothing.
+
## Run tests
This runs all test suites present in GitLab.
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
index 23c8365c34..821027f43f 100644
--- a/doc/development/shell_commands.md
+++ b/doc/development/shell_commands.md
@@ -1,5 +1,8 @@
# Guidelines for shell commands in the GitLab codebase
+This document contains guidelines for working with processes and files in the GitLab codebase.
+These guidelines are meant to make your code more reliable _and_ secure.
+
## References
- [Google Ruby Security Reviewer's Guide](https://code.google.com/p/ruby-security/wiki/Guide)
@@ -105,7 +108,72 @@ In other repositories, such as gitlab-shell you can also use `IO.popen`.
```ruby
# Safe IO.popen example
-logs = IO.popen(%W(git log), chdir: repo_dir).read
+logs = IO.popen(%W(git log), chdir: repo_dir) { |p| p.read }
```
Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error.
+
+## Avoid user input at the start of path strings
+
+Various methods for opening and reading files in Ruby can be used to read the
+standard output of a process instead of a file. The following two commands do
+roughly the same:
+
+```
+`touch /tmp/pawned-by-backticks`
+File.read('|touch /tmp/pawned-by-file-read')
+```
+
+The key is to open a 'file' whose name starts with a `|`.
+Affected methods include Kernel#open, File::read, File::open, IO::open and IO::read.
+
+You can protect against this behavior of 'open' and 'read' by ensuring that an
+attacker cannot control the start of the filename string you are opening. For
+instance, the following is sufficient to protect against accidentally starting
+a shell command with `|`:
+
+```
+# we assume repo_path is not controlled by the attacker (user)
+path = File.join(repo_path, user_input)
+# path cannot start with '|' now.
+File.read(path)
+```
+
+If you have to use user input a relative path, prefix `./` to the path.
+
+Prefixing user-supplied paths also offers extra protection against paths
+starting with `-` (see the discussion about using `--` above).
+
+## Guard against path traversal
+
+Path traversal is a security where the program (GitLab) tries to restrict user
+access to a certain directory on disk, but the user manages to open a file
+outside that directory by taking advantage of the `../` path notation.
+
+```
+# Suppose the user gave us a path and they are trying to trick us
+user_input = '../other-repo.git/other-file'
+
+# We look up the repo path somewhere
+repo_path = 'repositories/user-repo.git'
+
+# The intention of the code below is to open a file under repo_path, but
+# because the user used '..' she can 'break out' into
+# 'repositories/other-repo.git'
+full_path = File.join(repo_path, user_input)
+File.open(full_path) do # Oops!
+```
+
+A good way to protect against this is to compare the full path with its
+'absolute path' according to Ruby's `File.absolute_path`.
+
+```
+full_path = File.join(repo_path, user_input)
+if full_path != File.absolute_path(full_path)
+ raise "Invalid path: #{full_path.inspect}"
+end
+
+File.open(full_path) do # Etc.
+```
+
+A check like this could have avoided CVE-2013-4583.
diff --git a/doc/development/sidekiq_debugging.md b/doc/development/sidekiq_debugging.md
new file mode 100644
index 0000000000..cea11e5f12
--- /dev/null
+++ b/doc/development/sidekiq_debugging.md
@@ -0,0 +1,14 @@
+# Sidekiq debugging
+
+## Log arguments to Sidekiq jobs
+
+If you want to see what arguments are being passed to Sidekiq jobs you can set
+the SIDEKIQ_LOG_ARGUMENTS environment variable.
+
+```
+SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start
+```
+
+It is not recommend to enable this setting in production because some Sidekiq
+jobs (such as sending a password reset email) take secret arguments (for
+example the password reset token).
diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md
new file mode 100644
index 0000000000..2f01defc11
--- /dev/null
+++ b/doc/development/ui_guide.md
@@ -0,0 +1,12 @@
+# UI Guide for building GitLab
+
+## Best practices for creating new pages in GitLab
+
+TODO: write some best practices when develop GitLab features.
+
+## GitLab UI development kit
+
+We created a page inside GitLab where you can check commonly used html and css elements.
+
+When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples
+you can use during GitLab development.
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
new file mode 100644
index 0000000000..f7d4f3de68
--- /dev/null
+++ b/doc/hooks/custom_hooks.md
@@ -0,0 +1,41 @@
+# Custom Git Hooks
+
+**Note: Custom git hooks must be configured on the filesystem of the GitLab
+server. Only GitLab server administrators will be able to complete these tasks.
+Please explore webhooks as an option if you do not have filesystem access.**
+
+Git natively supports hooks that are executed on different actions.
+Examples of server-side git hooks include pre-receive, post-receive, and update.
+See
+[Git SCM Server-Side Hooks](http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks)
+for more information about each hook type.
+
+As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab
+administrators can add custom git hooks to any GitLab project.
+
+## Setup
+
+Normally, git hooks are placed in the repository or project's `hooks` directory.
+GitLab creates a symlink from each project's `hooks` directory to the
+gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell
+upgrades. As such, custom hooks are implemented a little differently. Behavior
+is exactly the same once the hook is created, though. Follow these steps to
+set up a custom hook.
+
+1. Pick a project that needs a custom git hook.
+1. On the GitLab server, navigate to the project's repository directory.
+For an installation from source the path is usually
+`/home/git/repositories//.git`. For Omnibus installs the path is
+usually `/var/opt/gitlab/git-data/repositories//.git`.
+1. Create a new directory in this location called `custom_hooks`.
+1. Inside the new `custom_hooks` directory, create a file with a name matching
+the hook type. For a pre-receive hook the file name should be `pre-receive` with
+no extension.
+1. Make the hook file executable and make sure it's owned by git.
+1. Write the code to make the git hook function as expected. Hooks can be
+in any language. Ensure the 'shebang' at the top properly reflects the language
+type. For example, if the script is in Ruby the shebang will probably be
+`#!/usr/bin/env ruby`.
+
+That's it! Assuming the hook code is properly implemented the hook will fire
+as appropriate.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 0d1a8da4d1..d6208bb079 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -1,10 +1,14 @@
-# Installation
+# Installation from source
+
+## Consider the Omnibus package installation
+
+Since an installation from source is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
## Select Version to Install
-Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below).
-
-
+Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the tag (version) of GitLab you would like to install.
+In most cases this should be the highest numbered production tag (without rc in it).
+You can select the tag in the version dropdown in the top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
@@ -18,7 +22,9 @@ This is the official installation guide to set up a production server. To set up
The following steps have been known to work. Please **use caution when you deviate** from this guide. Make sure you don't violate any assumptions GitLab makes about its environment. For example many people run into permission problems because they changed the location of directories or run services as the wrong user.
-If you find a bug/error in this guide please **submit a merge request** following the [contributing guide](../../CONTRIBUTING.md).
+If you find a bug/error in this guide please **submit a merge request**
+following the
+[contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md).
## Overview
@@ -50,7 +56,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake libkrb5-dev nodejs
Make sure you have the right version of Git installed
@@ -70,8 +76,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.0.0.tar.gz | tar xz
- cd git-2.0.0/
+ curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz
+ cd git-2.1.2/
+ ./configure
make prefix=/usr/local all
# Install into /usr/local/bin
@@ -87,7 +94,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby
-The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system ruby.
+The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby.
Remove the old Ruby 1.8 if present
@@ -96,8 +103,8 @@ Remove the old Ruby 1.8 if present
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl -L --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
- cd ruby-2.1.2
+ curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.5.tar.gz | tar xz
+ cd ruby-2.1.5
./configure --disable-install-rdoc
make
sudo make install
@@ -122,7 +129,8 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Login to PostgreSQL
sudo -u postgres psql -d template1
- # Create a user for GitLab.
+ # Create a user for GitLab
+ # Do not type the 'template1=#', this is part of the prompt
template1=# CREATE USER git CREATEDB;
# Create the GitLab production database & grant all privileges on database
@@ -134,6 +142,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Try connecting to the new database with the new user
sudo -u git -H psql -d gitlabhq_production
+ # Quit the database session
+ gitlabhq_production> \q
+
## 5. Redis
sudo apt-get install redis-server
@@ -146,6 +157,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+ # Grant permission to the socket to all members of the redis group
+ echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
+
+ # Create the directory which contains the socket
+ mkdir /var/run/redis
+ chown redis:redis /var/run/redis
+ chmod 755 /var/run/redis
+ # Persist the directory which contains the socket, if applicable
+ if [ -d /etc/tmpfiles.d ]; then
+ echo 'd /var/run/redis 0755 redis redis 10d -' | sudo tee -a /etc/tmpfiles.d/redis.conf
+ fi
# Activate the changes to redis.conf
sudo service redis-server restart
@@ -161,9 +183,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-3-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-9-stable gitlab
-**Note:** You can change `7-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `7-9-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -179,7 +201,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Make sure GitLab can write to the log/ and tmp/ directories
sudo chown -R git log/
sudo chown -R git tmp/
- sudo chmod -R u+rwX log/
+ sudo chmod -R u+rwX,go-w log/
sudo chmod -R u+rwX tmp/
# Create directory for satellites
@@ -258,7 +280,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install[v2.0.1] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
+ sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.0] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
# By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows:
@@ -274,9 +296,9 @@ GitLab Shell is an SSH access and repository management software developed speci
# When done you see 'Administrator account created:'
-**Note:** You can set the Administrator password by supplying it in environmental variable `GITLAB_ROOT_PASSWORD`, eg.:
+**Note:** You can set the Administrator/root password by supplying it in environmental variable `GITLAB_ROOT_PASSWORD` as seen below. If you don't set the password (and it is set to the default one) please wait with exposing GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login you'll be forced to change the default password.
- sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=newpassword
+ sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword
### Install Init Script
@@ -363,15 +385,17 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj
### Initial Login
-Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created an admin account for you. You can use it to log in:
+Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in:
root
5iveL!fe
-**Important Note:** Please go over to your profile page and immediately change the password, so nobody can access your GitLab by using this login information later on.
+**Important Note:** On login you'll be prompted to change the password.
**Enjoy!**
+You can use `sudo service gitlab start` and `sudo service gitlab stop` to start and stop GitLab.
+
## Advanced Setup Tips
### Using HTTPS
@@ -435,4 +459,4 @@ You can configure LDAP authentication in `config/gitlab.yml`. Please restart Git
### Using Custom Omniauth Providers
-See the [omniauth integration document](doc/integration/omniauth.md)
+See the [omniauth integration document](../integration/omniauth.md)
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 3e4c6a28c0..f42af65796 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -7,7 +7,7 @@
- Ubuntu
- Debian
- CentOS
-- RedHat Enterprise Linux (please use the CentOS packages and instructions)
+- Red Hat Enterprise Linux (please use the CentOS packages and instructions)
- Scientific Linux (please use the CentOS packages and instructions)
- Oracle Linux (please use the CentOS packages and instructions)
@@ -22,9 +22,9 @@ For the installations options please see [the installation page on the GitLab we
- FreeBSD
On the above unsupported distributions is still possible to install GitLab yourself.
-Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information.
+Please see the [installation from source guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information.
-### Non Unix operating systems such as Windows
+### Non-Unix operating systems such as Windows
GitLab is developed for Unix operating systems.
GitLab does **not** run on Windows and we have no plans of supporting it in the near future.
@@ -38,6 +38,16 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
## Hardware requirements
+### Storage
+
+The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
+
+If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
+
+Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume.
+
+If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
+
### CPU
- 1 core works supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
@@ -50,11 +60,10 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
### Memory
-- 512MB is the absolute minimum but we do not recommend this amount of memory.
-You will either need to configure 512MB or 1.5GB of swap space.
-With 512MB of swap space you must configure only one unicorn worker.
-With one unicorn worker only git over ssh access will work because the git over http access requires two running workers (one worker to receive the user request and one worker for the authorization check).
-If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow http access but it will still be slow.
+You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
+With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
+
+- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advise.
- 1GB RAM + 1GB swap supports up to 100 users
- **2GB RAM** is the **recommended** memory size and supports up to 500 users
- 4GB RAM supports up to 2,000 users
@@ -65,15 +74,19 @@ If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn
Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application.
-### Storage
+## Unicorn Workers
-The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
+It's possible to increase the amount of unicorn workers and tis will usually help for to reduce the response time of the applications and increase the ability to handle parallel requests.
-If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
+For most instances we recommend using: CPU cores + 1 = unicorn workers.
+So for a machine with 2 cores, 3 unicorn workers is ideal.
-Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume.
+For all machines that have 1GB and up we recommend a minimum of three unicorn workers.
+If you have a 512MB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping.
+With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
+If you have a 512MB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping.
-If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab.
+To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings).
## Database
@@ -90,7 +103,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
## Supported web browsers
- Chrome (Latest stable version)
-- Firefox (Latest released version)
+- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
- IE 10+
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 357ed03831..286bd34a0b 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -6,14 +6,16 @@ See the documentation below for details on how to configure these services.
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [LDAP](ldap.md) Set up sign in via LDAP
-- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, and Google via OAuth.
+- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth.
- [Slack](slack.md) Integrate with the Slack chat service
+- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
+- [Gmail](gitlab_buttons_in_gmail.md) Adds GitLab actions to messages
-Jenkins support is [available in GitLab EE](http://doc.gitlab.com/ee/integration/jenkins.html).
+GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html) and [advanced Jenkins support](http://doc.gitlab.com/ee/integration/jenkins.html).
## Project services
-Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, PivotalTracker and Slack are available in the from of a Project Service.
+Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, Pivotal Tracker, and Slack are available in the form of a Project Service.
You can find these within GitLab in the Services page under Project Settings if you are at least a master on the project.
Project Services are a bit like plugins in that they allow a lot of freedom in adding functionality to GitLab, for example there is also a service that can send an email every time someone pushes new commits.
Because GitLab is open source we can ship with the code and tests for all plugins.
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
new file mode 100644
index 0000000000..cc6389f5aa
--- /dev/null
+++ b/doc/integration/bitbucket.md
@@ -0,0 +1,121 @@
+# Integrate your server with Bitbucket
+
+Import projects from Bitbucket and login to your GitLab instance with your Bitbucket account.
+
+To enable the Bitbucket OmniAuth provider you must register your application with Bitbucket.
+Bitbucket will generate an application ID and secret key for you to use.
+
+1. Sign in to Bitbucket.
+
+1. Navigate to your individual user settings or a team's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you.
+
+1. Select "OAuth" in the left menu.
+
+1. Select "Add consumer".
+
+1. Provide the required details.
+ - Name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive.
+ - Application description: Fill this in if you wish.
+ - URL: The URL to your GitLab installation. 'https://gitlab.company.com'
+1. Select "Save".
+
+1. You should now see a Key and Secret in the list of OAuth customers.
+ Keep this page open as you continue configuration.
+
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For instalations from source:
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "bitbucket",
+ "app_id" => "YOUR_KEY",
+ "app_secret" => "YOUR_APP_SECRET",
+ "url" => "https://bitbucket.org/"
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```
+ - { name: 'bitbucket', app_id: 'YOUR_KEY',
+ app_secret: 'YOUR_APP_SECRET' }
+ ```
+
+1. Change 'YOUR_APP_ID' to the key from the Bitbucket application page from step 7.
+
+1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7.
+
+1. Save the configuration file.
+
+1. Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a Bitbucket icon below the regular sign in form.
+Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application.
+If everything goes well the user will be returned to GitLab and will be signed in.
+
+## Bitbucket project import
+
+To allow projects to be imported directly into GitLab, Bitbucket requires two extra setup steps compared to GitHub and GitLab.com.
+
+Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key.
+
+### Step 1: Known hosts
+
+To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
+
+1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
+
+ ```sh
+ ssh git@bitbucket.org
+ ```
+
+1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
+
+ ```sh
+ The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
+ RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
+ Are you sure you want to continue connecting (yes/no)?
+ ```
+
+1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
+
+1. Your GitLab server is now able to connect to Bitbucket over SSH. Continue to step 2:
+
+### Step 2: Public key
+
+To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/id_rsa.pub`, which will expand to `/home/git/.ssh/id_rsa.pub` in most configurations.
+
+If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
+
+1. Create a new SSH key:
+
+ ```sh
+ sudo -u git -H ssh-keygen
+ ```
+
+ Make sure to use an **empty passphrase**.
+
+2. Restart GitLab to allow it to find the new public key.
+
+You should now see the "Import projects from Bitbucket" option on the New Project page enabled.
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 87af94512e..96755707de 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -1,13 +1,39 @@
# External issue tracker
-GitLab has a great issue tracker but you can also use an external issue tracker such as JIRA, Bugzilla or Redmine. This is something that you can turn on per GitLab project. If for example you configure JIRA it provides the following functionality:
+GitLab has a great issue tracker but you can also use an external issue tracker such as Jira, Bugzilla or Redmine. You can configure issue trackers per GitLab project. For instance, if you configure Jira it allows you to do the following:
-- the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index;
-- clicking 'New issue' on the project dashboard creates a new JIRA issue;
-- To reference JIRA issue PROJECT-1234 in comments, use syntax PROJECT-1234. Commit messages get turned into HTML links to the corresponding JIRA issue.
+- the 'Issues' link on the GitLab project pages takes you to the appropriate Jira issue index;
+- clicking 'New issue' on the project dashboard creates a new Jira issue;
+- To reference Jira issue PROJECT-1234 in comments, use syntax PROJECT-1234. Commit messages get turned into HTML links to the corresponding Jira issue.
-
+
-You can configure the integration in the gitlab.yml configuration file.
+GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html).
+
+## Configuration
+
+### Project Service
+
+You can enable an external issue tracker per project. As an example, we will configure `Redmine` for project named gitlab-ci.
+
+Fill in the required details on the page:
+
+
+
+* `description` A name for the issue tracker (to differentiate between instances, for example).
+* `project_url` The URL to the project in Redmine which is being linked to this GitLab project.
+* `issues_url` The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the url. This id is used by GitLab as a placeholder to replace the issue number.
+* `new_issue_url` This is the URL to create a new issue in Redmine for the project linked to this GitLab project.
+
+### Service Template
+
+It is necessary to configure the external issue tracker per project, because project specific details are needed for the integration with GitLab.
+The admin can add a service template that sets a default for each project. This makes it much easier to configure individual projects.
+
+In GitLab Admin section, navigate to `Service Templates` and choose the service template you want to create:
+
+
+
+After the template is created, the template details will be pre-filled on the project service page.
Support to add your commits to the Jira ticket automatically is [available in GitLab EE](http://doc.gitlab.com/ee/integration/jira.html).
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 714593d826..b64501c2aa 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -1,6 +1,9 @@
-# GitHub OAuth2 OmniAuth Provider
+# Integrate your server with GitHub
-To enable the GitHub OmniAuth provider you must register your application with GitHub. GitHub will generate a client ID and secret key for you to use.
+Import projects from GitHub and login to your GitLab instance with your GitHub account.
+
+To enable the GitHub OmniAuth provider you must register your application with GitHub.
+GitHub will generate an application ID and secret key for you to use.
1. Sign in to GitHub.
@@ -14,35 +17,63 @@ To enable the GitHub OmniAuth provider you must register your application with G
- Application name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive.
- Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com'
- Application description: Fill this in if you wish.
- - Authorization callback URL: 'https://gitlab.company.com/users/auth/github/callback'
+ - Authorization callback URL: 'https://gitlab.company.com/'
1. Select "Register application".
-1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). Keep this page open as you continue configuration. 
+1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
+ Keep this page open as you continue configuration.
+ 
1. On your GitLab server, open the configuration file.
+ For omnibus package:
+
```sh
- cd /home/git/gitlab
-
- sudo -u git -H editor config/gitlab.yml
+ sudo editor /etc/gitlab/gitlab.rb
```
-1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for more details.
+ For instalations from source:
-1. Under `providers:` uncomment (or add) lines that look like the following:
+ ```sh
+ cd /home/git/gitlab
- ```
- - { name: 'github', app_id: 'YOUR APP ID',
- app_secret: 'YOUR APP SECRET',
- args: { scope: 'user:email' } }
+ sudo -u git -H editor config/gitlab.yml
```
-1. Change 'YOUR APP ID' to the client ID from the GitHub application page from step 7.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Change 'YOUR APP SECRET' to the client secret from the GitHub application page from step 7.
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "github",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "url" => "https://github.com/",
+ "args" => { "scope" => "user:email" }
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```
+ - { name: 'github', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { scope: 'user:email' } }
+ ```
+
+1. Change 'YOUR_APP_ID' to the client ID from the GitHub application page from step 7.
+
+1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7.
1. Save the configuration file.
1. Restart GitLab for the changes to take effect.
-On the sign in page there should now be a GitHub icon below the regular sign in form. Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
+On the sign in page there should now be a GitHub icon below the regular sign in form.
+Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application.
+If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/integration/github_app.png b/doc/integration/github_app.png
index c0873b2e20..d890345ced 100644
Binary files a/doc/integration/github_app.png and b/doc/integration/github_app.png differ
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
new file mode 100644
index 0000000000..216f1f11a9
--- /dev/null
+++ b/doc/integration/gitlab.md
@@ -0,0 +1,84 @@
+# Integrate your server with GitLab.com
+
+Import projects from GitLab.com and login to your GitLab instance with your GitLab.com account.
+
+To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com.
+GitLab.com will generate an application ID and secret key for you to use.
+
+1. Sign in to GitLab.com
+
+1. Navigate to your profile settings.
+
+1. Select "Applications" in the left menu.
+
+1. Select "New application".
+
+1. Provide the required details.
+ - Name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive.
+ - Redirect URI:
+
+ ```
+ http://your-gitlab.example.com/import/gitlab/callback
+ http://your-gitlab.example.com/users/auth/gitlab/callback
+ ```
+
+ The first link is required for the importer and second for the authorization.
+
+1. Select "Submit".
+
+1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot).
+ Keep this page open as you continue configuration.
+ 
+
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For instalations from source:
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "gitlab",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "args" => { "scope" => "api" }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```
+ - { name: 'gitlab', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { scope: 'api' } }
+ ```
+
+1. Change 'YOUR_APP_ID' to the Application ID from the GitLab.com application page.
+
+1. Change 'YOUR_APP_SECRET' to the secret from the GitLab.com application page.
+
+1. Save the configuration file.
+
+1. Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a GitLab.com icon below the regular sign in form.
+Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application.
+If everything goes well the user will be returned to your GitLab instance and will be signed in.
diff --git a/doc/integration/gitlab_actions.png b/doc/integration/gitlab_actions.png
new file mode 100644
index 0000000000..b08f54d137
Binary files /dev/null and b/doc/integration/gitlab_actions.png differ
diff --git a/doc/integration/gitlab_app.png b/doc/integration/gitlab_app.png
new file mode 100644
index 0000000000..3f9391a821
Binary files /dev/null and b/doc/integration/gitlab_app.png differ
diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md
new file mode 100644
index 0000000000..a9885cef10
--- /dev/null
+++ b/doc/integration/gitlab_buttons_in_gmail.md
@@ -0,0 +1,28 @@
+# GitLab buttons in Gmail
+
+GitLab supports [Google actions in email](https://developers.google.com/gmail/markup/actions/actions-overview).
+
+If correctly setup, emails that require an action will be marked in Gmail.
+
+
+
+To get this functioning, you need to be registered with Google.
+[See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
+
+To aid the registering with google, GitLab offers a rake task that will send an email to google whitelisting email address from your GitLab server.
+
+To check what would be sent to the google email address, run the rake task:
+
+```bash
+bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production
+```
+
+**This will not send the email but give you the output of how the mail will look.**
+
+Copy the output of the rake task to [google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate".
+
+If you receive "No errors detected" message from the tester you can send the email using:
+
+```bash
+bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production SEND=true
+```
diff --git a/doc/integration/google.md b/doc/integration/google.md
index 7a78aff8ea..e1c14c7c94 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -27,27 +27,50 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
- Authorized redirect URI: 'https://gitlab.example.com/users/auth/google_oauth2/callback'
1. Under the heading "Client ID for web application" you should see a Client ID and Client secret (see screenshot). Keep this page open as you continue configuration. 
-1. On your GitLab server, open the configuration file.
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
```sh
- cd /home/git/gitlab
-
- sudo -u git -H editor config/gitlab.yml
+ sudo editor /etc/gitlab/gitlab.rb
```
-1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for more details.
+ For instalations from source:
-1. Under `providers:` uncomment (or add) lines that look like the following:
+ ```sh
+ cd /home/git/gitlab
- ```
- - { name: 'google_oauth2', app_id: 'YOUR APP ID',
- app_secret: 'YOUR APP SECRET',
- args: { access_type: 'offline', approval_prompt: '' } }
+ sudo -u git -H editor config/gitlab.yml
```
-1. Change 'YOUR APP ID' to the client ID from the GitHub application page from step 7.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Change 'YOUR APP SECRET' to the client secret from the GitHub application page from step 7.
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "google_oauth2",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "args" => { "access_type" => "offline", "approval_prompt" => '' }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```
+ - { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { access_type: 'offline', approval_prompt: '' } }
+ ```
+
+1. Change 'YOUR_APP_ID' to the client ID from the Google Developer page from step 10.
+
+1. Change 'YOUR_APP_SECRET' to the client secret from the Google Developer page from step 10.
1. Save the configuration file.
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index ee472ac3e3..125ce31b52 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -6,6 +6,98 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G
GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
+## Configuring GitLab for LDAP integration
+
+To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
+In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server.
+
+Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration.
+The old LDAP integration syntax still works in GitLab 7.4.
+If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab.
+
+```ruby
+# For omnibus packages
+gitlab_rails['ldap_enabled'] = true
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
+main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+
+ host: '_your_ldap_server'
+ port: 389
+ uid: 'sAMAccountName'
+ method: 'plain' # "tls" or "ssl" or "plain"
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
+
+ # This setting specifies if LDAP server is Active Directory LDAP server.
+ # For non AD servers it skips the AD specific queries.
+ # If your LDAP server is not AD, set this to false.
+ active_directory: true
+
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
+
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ user_filter: ''
+
+# GitLab EE only: add more LDAP servers
+# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+# so that GitLab can remember which LDAP server a user belongs to.
+# uswest2:
+# label:
+# host:
+# ....
+EOS
+```
+
+If you are getting 'Connection Refused' errors when trying to connect to the LDAP server please double-check the LDAP `port` and `method` settings used by GitLab.
+Common combinations are `method: 'plain'` and `port: 389`, OR `method: 'ssl'` and `port: 636`.
+
+If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
+
+```
+production:
+ # snip...
+ ldap:
+ enabled: false
+ servers:
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+ # snip...
+```
+
## Enabling LDAP sign-in for existing GitLab users
When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
@@ -24,15 +116,22 @@ If you want to limit all GitLab access to a subset of the LDAP users on your LDA
The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515).
```ruby
-# For omnibus-gitlab
-gitlab_rails['ldap_user_filter'] = '(employeeType=developer)'
+# For omnibus packages; new LDAP server syntax
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+main:
+ # snip...
+ user_filter: '(employeeType=developer)'
+EOS
```
```yaml
-# For installations from source
+# For installations from source; new LDAP server syntax
production:
ldap:
- user_filter: '(employeeType=developer)'
+ servers:
+ main:
+ # snip...
+ user_filter: '(employeeType=developer)'
```
Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax:
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
new file mode 100644
index 0000000000..192c321f71
--- /dev/null
+++ b/doc/integration/oauth_provider.md
@@ -0,0 +1,35 @@
+## GitLab as OAuth2 authentication service provider
+
+This document is about using GitLab as an OAuth authentication service provider to sign into other services.
+If you want to use other OAuth authentication service providers to sign into GitLab please see the [OAuth2 client documentation](../api/oauth2.md)
+
+OAuth2 provides client applications a 'secure delegated access' to server resources on behalf of a resource owner. Or you can allow users to sign in to your application with their GitLab.com account.
+In fact OAuth allows to issue access token to third-party clients by an authorization server,
+with the approval of the resource owner, or end-user.
+Mostly, OAuth2 is using for SSO (Single sign-on). But you can find a lot of different usages for this functionality.
+For example, our feature 'GitLab Importer' is using OAuth protocol to give an access to repositories without sharing user credentials to GitLab.com account.
+Also GitLab.com application can be used for authentication to your GitLab instance if needed [GitLab OmniAuth](gitlab.md).
+
+GitLab has two ways to add new OAuth2 application to an instance, you can add application as regular user and through admin area. So GitLab actually can have an instance-wide and a user-wide applications. There is no defferences between them except the different permission levels.
+
+### Adding application through profile
+Go to your profile section 'Application' and press button 'New Application'
+
+
+
+After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com.
+
+
+
+### Authorized application
+Every application you authorized will be shown in your "Authorized application" sections.
+
+
+
+At any time you can revoke access just clicking button "Revoke"
+
+### OAuth applications in admin area
+
+If you want to create application that does not belong to certain user you can create it from admin area
+
+
\ No newline at end of file
diff --git a/doc/integration/oauth_provider/admin_application.png b/doc/integration/oauth_provider/admin_application.png
new file mode 100644
index 0000000000..a5f34512aa
Binary files /dev/null and b/doc/integration/oauth_provider/admin_application.png differ
diff --git a/doc/integration/oauth_provider/application_form.png b/doc/integration/oauth_provider/application_form.png
new file mode 100644
index 0000000000..ae135db262
Binary files /dev/null and b/doc/integration/oauth_provider/application_form.png differ
diff --git a/doc/integration/oauth_provider/authorized_application.png b/doc/integration/oauth_provider/authorized_application.png
new file mode 100644
index 0000000000..d3ce05be9c
Binary files /dev/null and b/doc/integration/oauth_provider/authorized_application.png differ
diff --git a/doc/integration/oauth_provider/user_wide_applications.png b/doc/integration/oauth_provider/user_wide_applications.png
new file mode 100644
index 0000000000..719e197406
Binary files /dev/null and b/doc/integration/oauth_provider/user_wide_applications.png differ
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 00adae58df..24f7b4bb4b 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -1,18 +1,47 @@
# OmniAuth
-GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and other popular services. Configuring
+GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and other popular services.
-OmniAuth does not prevent standard GitLab authentication or LDAP (if configured) from continuing to work. Users can choose to sign in using any of the configured mechanisms.
+Configuring OmniAuth does not prevent standard GitLab authentication or LDAP (if configured) from continuing to work. Users can choose to sign in using any of the configured mechanisms.
- [Initial OmniAuth Configuration](#initial-omniauth-configuration)
- [Supported Providers](#supported-providers)
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
+- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
## Initial OmniAuth Configuration
-Before configuring individual OmniAuth providers there are a few global settings that need to be verified.
+Before configuring individual OmniAuth providers there are a few global settings that are in common for all providers that we need to consider.
-1. Open the configuration file.
+- Omniauth needs to be enabled, see details below for example.
+- `allow_single_sign_on` defaults to `false`. If `false` users must be created manually or they will not be able to
+sign in via OmniAuth.
+- `block_auto_created_users` defaults to `true`. If `true` auto created users will be blocked by default and will
+have to be unblocked by an administrator before they are able to sign in.
+- **Note:** If you set `allow_single_sign_on` to `true` and `block_auto_created_users` to `false` please be aware
+that any user on the Internet will be able to successfully sign in to your GitLab without administrative approval.
+
+If you want to change these settings:
+
+* **For omnibus package**
+
+ Open the configuration file:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ and change
+
+ ```
+ gitlab_rails['omniauth_enabled'] = true
+ gitlab_rails['omniauth_allow_single_sign_on'] = false
+ gitlab_rails['block_auto_created_users'] = true
+ ```
+
+* **For installations from source**
+
+ Open the configuration file:
```sh
cd /home/git/gitlab
@@ -20,13 +49,13 @@ Before configuring individual OmniAuth providers there are a few global settings
sudo -u git -H editor config/gitlab.yml
```
-1. Find the section dealing with OmniAuth. The section will look similar to the following.
+ and change the following section
```
- ## OmniAuth settings
+ ## OmniAuth settings
omniauth:
# Allow login via Twitter, Google, etc. using OmniAuth providers
- enabled: false
+ enabled: true
# CAUTION!
# This allows users to login without having a user account first (default: false).
@@ -34,47 +63,15 @@ Before configuring individual OmniAuth providers there are a few global settings
allow_single_sign_on: false
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
-
- ## Auth providers
- # Uncomment the following lines and fill in the data of the auth provider you want to use
- # If your favorite auth provider is not listed you can use others:
- # see https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations
- # The 'app_id' and 'app_secret' parameters are always passed as the first two
- # arguments, followed by optional 'args' which can be either a hash or an array.
- providers:
- # - { name: 'google_oauth2', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET',
- # args: { access_type: 'offline', approval_prompt: '' } }
- # - { name: 'twitter', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET'}
- # - { name: 'github', app_id: 'YOUR APP ID',
- # app_secret: 'YOUR APP SECRET',
- # args: { scope: 'user:email' } }
- # - {"name": 'shibboleth',
- # args: { shib_session_id_field: "HTTP_SHIB_SESSION_ID",
- # shib_application_id_field: "HTTP_SHIB_APPLICATION_ID",
- # uid_field: "HTTP_EPPN",
- # name_field: "HTTP_CN",
- # info_fields: {"email": "HTTP_MAIL" } } }
-
```
-1. Change `enabled` to `true`.
-
-1. Consider the next two configuration options: `allow_single_sign_on` and `block_auto_created_users`.
-
- - `allow_single_sign_on` defaults to `false`. If `false` users must be created manually or they will not be able to
- sign in via OmniAuth.
- - `block_auto_created_users` defaults to `true`. If `true` auto created users will be blocked by default and will
- have to be unblocked by an administrator before they are able to sign in.
- - **Note:** If you set `allow_single_sign_on` to `true` and `block_auto_created_users` to `false` please be aware
- that any user on the Internet will be able to successfully sign in to your GitLab without administrative approval.
-
-1. Choose one or more of the Supported Providers below to continue configuration.
+Now we can choose one or more of the Supported Providers below to continue configuration.
## Supported Providers
- [GitHub](github.md)
+- [Bitbucket](bitbucket.md)
+- [GitLab.com](gitlab.md)
- [Google](google.md)
- [Shibboleth](shibboleth.md)
- [Twitter](twitter.md)
diff --git a/doc/integration/redmine_configuration.png b/doc/integration/redmine_configuration.png
new file mode 100644
index 0000000000..6b14536322
Binary files /dev/null and b/doc/integration/redmine_configuration.png differ
diff --git a/doc/integration/redmine_service_template.png b/doc/integration/redmine_service_template.png
new file mode 100644
index 0000000000..1159eb5b96
Binary files /dev/null and b/doc/integration/redmine_service_template.png differ
diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md
index 78317a5c0f..6258e5f103 100644
--- a/doc/integration/shibboleth.md
+++ b/doc/integration/shibboleth.md
@@ -2,19 +2,19 @@
This documentation is for enabling shibboleth with gitlab-omnibus package.
-In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however I did not found way to easily configure nginx that is bundled in gitlab-omnibus package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider.
+In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however I did not found way to easily configure Nginx that is bundled in gitlab-omnibus package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider.
To enable the Shibboleth OmniAuth provider you must:
-1. Configure Apache shibboleth module. Installation and configuration of module it self is out of scope of this document.
+1. Configure Apache shibboleth module. Installation and configuration of module it self is out of scope of this document.
Check https://wiki.shibboleth.net/ for more info.
-1. You can find Apache config in gitlab-reciepes (https://github.com/gitlabhq/gitlab-recipes/blob/master/web-server/apache/gitlab-ssl.conf)
+1. You can find Apache config in gitlab-recipes (https://github.com/gitlabhq/gitlab-recipes/blob/master/web-server/apache/gitlab-ssl.conf)
Following changes are needed to enable shibboleth:
-protect omniauth-shibboleth callback url:
+protect omniauth-shibboleth callback URL:
```
AuthType shibboleth
@@ -32,25 +32,25 @@ protect omniauth-shibboleth callback url:
SetHandler shib
```
-exclude shibboleth urls from rewriting, add "RewriteCond %{REQUEST_URI} !/Shibboleth.sso" and "RewriteCond %{REQUEST_URI} !/shibboleth-sp", config should look like this:
+exclude shibboleth URLs from rewriting, add "RewriteCond %{REQUEST_URI} !/Shibboleth.sso" and "RewriteCond %{REQUEST_URI} !/shibboleth-sp", config should look like this:
```
- #apache equivalent of nginx try files
+ # Apache equivalent of Nginx try files
RewriteEngine on
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
- RewriteCond %{REQUEST_URI} !/Shibboleth.sso
- RewriteCond %{REQUEST_URI} !/shibboleth-sp
+ RewriteCond %{REQUEST_URI} !/Shibboleth.sso
+ RewriteCond %{REQUEST_URI} !/shibboleth-sp
RewriteRule .* http://127.0.0.1:8080%{REQUEST_URI} [P,QSA]
RequestHeader set X_FORWARDED_PROTO 'https'
```
-1. Edit /etc/gitlab/gitlab.rb configuration file, your shibboleth attributes should be in form of "HTTP_ATTRIBUTE" and you should addjust them to your need and environment. Add any other configuration you need.
+1. Edit /etc/gitlab/gitlab.rb configuration file, your shibboleth attributes should be in form of "HTTP_ATTRIBUTE" and you should addjust them to your need and environment. Add any other configuration you need.
-File it should look like this:
+File should look like this:
```
external_url 'https://gitlab.example.com'
gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
-# disable nginx
+# disable Nginx
nginx['enable'] = false
gitlab_rails['omniauth_allow_single_sign_on'] = true
@@ -70,7 +70,7 @@ gitlab_rails['omniauth_providers'] = [
]
```
-1. Save changes and reconfigure gitlab:
+1. Save changes and reconfigure gitlab:
```
sudo gitlab-ctl reconfigure
```
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 95cb0c6fae..2fd22c513a 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -4,15 +4,23 @@
To enable Slack integration you must create an Incoming WebHooks integration on Slack;
-1. Sign in to [Slack](https://slack.com) (https://YOURSUBDOMAIN.slack.com/services)
-1. Click on the Integrations menu at the top of the page.
-1. Add a new Integration.
+1. [Sign in to Slack](https://slack.com/signin)
+
+1. Select **Configure Integrations** from the dropdown next to your team name.
+
+1. Select the **All Services** tab
+
+1. Click **Add** next to Incoming Webhooks
+
1. Pick Incoming WebHooks
-1. Choose the channel name you want to send notifications to, in the Settings section
-1. Add Integrations.
- - Optional step; You can change bot's name and avatar by clicking "change the name of your bot", and "change the icon" after that you have to click "Save settings".
+
+1. Choose the channel name you want to send notifications to
+
+1. Click **Add Incoming WebHooks Integration**Add Integrations.
+ - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
+
+1. Copy the **Webhook URL**, we'll need this later for GitLab.
-Now, Slack is ready to get external hooks. Before you leave this page don't forget to get the Token that you'll need on GitLab. You can find it by clicking Expand button, located in the "Instructions for creating Incoming WebHooks" section. It's a random alpha-numeric text 24 characters long.
## On GitLab
@@ -26,10 +34,8 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this
1. Fill in your Slack details
- - Mark as active it
- - Type your subdomain's prefix (If your subdomain is https://somedomain.slack.com you only have to type the somedomain)
- - Type in the token you got from Slack
- - Type in the channel name you want to use (eg. #announcements)
+ - Mark it as active
+ - Paste in the webhook URL you got from Slack
Have fun :)
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index d1b52927d3..fe9091ad9a 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -13,7 +13,7 @@ To enable the Twitter OmniAuth provider you must register your application with
something else descriptive.
- Description: Create a description.
- Website: The URL to your GitLab installation. 'https://gitlab.example.com'
- - Callback URL: 'https://gitlab.example.com/users/auth/github/callback'
+ - Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
- Agree to the "Rules of the Road."

@@ -33,25 +33,46 @@ To enable the Twitter OmniAuth provider you must register your application with
1. On your GitLab server, open the configuration file.
+ For omnibus package:
+
```sh
- cd /home/git/gitlab
-
- sudo -u git -H editor config/gitlab.yml
+ sudo editor /etc/gitlab/gitlab.rb
```
-1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration)
-for more details.
+ For instalations from source:
-1. Under `providers:` uncomment (or add) lines that look like the following:
+ ```sh
+ cd /home/git/gitlab
- ```
- - { name: 'twitter', app_id: 'YOUR APP ID',
- app_secret: 'YOUR APP SECRET' }
+ sudo -u git -H editor config/gitlab.yml
```
-1. Change 'YOUR APP ID' to the API key from Twitter page in step 11.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Change 'YOUR APP SECRET' to the API secret from the Twitter page in step 11.
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "twitter",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET"
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```
+ - { name: 'twitter', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ ```
+
+1. Change 'YOUR_APP_ID' to the API key from Twitter page in step 11.
+
+1. Change 'YOUR_APP_SECRET' to the API secret from the Twitter page in step 11.
1. Save the configuration file.
diff --git a/doc/logs/logs.md b/doc/logs/logs.md
new file mode 100644
index 0000000000..ec0109a426
--- /dev/null
+++ b/doc/logs/logs.md
@@ -0,0 +1,102 @@
+## Log system
+GitLab has advanced log system so everything is logging and you can analize your instance using various system log files.
+In addition to system log files, GitLab Enterprise Edition comes with Audit Events. Find more about them [in Audit Events documentation](http://doc.gitlab.com/ee/administration/audit_events.html)
+
+System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files.
+
+#### production.log
+This file lives in `/var/log/gitlab/gitlab-rails/production.log` for omnibus package or in `/home/git/gitlab/logs/production.log` for installations from the source.
+
+This file contains information about all performed requests. You can see url and type of request, IP address and what exactly parts of code were involved to service this particular request. Also you can see all SQL request that have been performed and how much time it took.
+This task is more useful for GitLab contributors and developers. Use part of this log file when you are going to report bug.
+
+```
+Started GET "/gitlabhq/yaml_db/tree/master" for 168.111.56.1 at 2015-02-12 19:34:53 +0200
+Processing by Projects::TreeController#show as HTML
+ Parameters: {"project_id"=>"gitlabhq/yaml_db", "id"=>"master"}
+
+ ... [CUT OUT]
+
+ amespaces"."created_at" DESC, "namespaces"."id" DESC LIMIT 1[0m [["id", 26]]
+ [1m[35mCACHE (0.0ms)[0m SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members"."type" IN ('ProjectMember') AND "members"."source_id" = $1 AND "members"."source_type" = $2 AND "members"."user_id" = 1 ORDER BY "members"."created_at" DESC, "members"."id" DESC LIMIT 1 [["source_id", 18], ["source_type", "Project"]]
+ [1m[36mCACHE (0.0ms)[0m [1mSELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members".
+ [1m[36m (1.4ms)[0m [1mSELECT COUNT(*) FROM "merge_requests" WHERE "merge_requests"."target_project_id" = $1 AND ("merge_requests"."state" IN ('opened','reopened'))[0m [["target_project_id", 18]]
+ Rendered layouts/nav/_project.html.haml (28.0ms)
+ Rendered layouts/_collapse_button.html.haml (0.2ms)
+ Rendered layouts/_flash.html.haml (0.1ms)
+ Rendered layouts/_page.html.haml (32.9ms)
+Completed 200 OK in 166ms (Views: 117.4ms | ActiveRecord: 27.2ms)
+```
+In this example we can see that server processed HTTP request with url `/gitlabhq/yaml_db/tree/master` from IP 168.111.56.1 at 2015-02-12 19:34:53 +0200. Also we can see that request was processed by Projects::TreeController.
+
+#### application.log
+This file lives in `/var/log/gitlab/gitlab-rails/application.log` for omnibus package or in `/home/git/gitlab/logs/application.log` for installations from the source.
+
+This log file helps you discover events happening in your instance such as user creation, project removing and so on.
+
+```
+October 06, 2014 11:56: User "Administrator" (admin@example.com) was created
+October 06, 2014 11:56: Documentcloud created a new project "Documentcloud / Underscore"
+October 06, 2014 11:56: Gitlab Org created a new project "Gitlab Org / Gitlab Ce"
+October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was removed
+October 07, 2014 11:25: Project "project133" was removed
+```
+#### githost.log
+This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for omnibus package or in `/home/git/gitlab/logs/githost.log` for installations from the source.
+
+The GitLab has to interact with git repositories but in some rare cases something can go wrong and in this case you will know what exactly happened. This log file contains all failed requests from GitLab to git repository. In majority of cases this file will be useful for developers only.
+```
+December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq/.git --work-tree=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq merge --no-ff -mMerge branch 'feature_conflict' into 'feature' source/feature_conflict
+
+error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
+```
+
+#### satellites.log
+This file lives in `/var/log/gitlab/gitlab-rails/satellites.log` for omnibus package or in `/home/git/gitlab/logs/satellites.log` for installations from the source.
+
+In some cases GitLab should perform write actions to git repository, for example when it is needed to merge the merge request or edit a file with online editor. If something went wrong you can look into this file to find out what exactly happened.
+```
+October 07, 2014 11:36: Failed to create satellite for Chesley Weimann III / project1817
+October 07, 2014 11:36: PID: 1872: git clone /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/conrad6841/gitlabhq
+October 07, 2014 11:36: PID: 1872: -> fatal: repository '/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git' does not exist
+```
+
+#### sidekiq.log
+This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/logs/sidekiq.log` for installations from the source.
+
+GitLab uses background jobs for processing tasks which can take a long time. All information about processing these jobs are writing down to this file.
+```
+2014-06-10T07:55:20Z 2037 TID-tm504 ERROR: /opt/bitnami/apps/discourse/htdocs/vendor/bundle/ruby/1.9.1/gems/redis-3.0.7/lib/redis/client.rb:228:in `read'
+2014-06-10T18:18:26Z 14299 TID-55uqo INFO: Booting Sidekiq 3.0.0 with redis options {:url=>"redis://localhost:6379/0", :namespace=>"sidekiq"}
+```
+
+#### gitlab-shell.log
+This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for omnibus package or in `/home/git/gitlab-shell/logs/sidekiq.log` for installations from the source.
+
+gitlab-shell is using by Gitlab for executing git commands and provide ssh access to git repositories.
+
+```
+I, [2015-02-13T06:17:00.671315 #9291] INFO -- : Adding project root/example.git at .
+I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory and simlinking global hooks directory for /var/opt/gitlab/git-data/repositories/root/example.git.
+```
+
+#### unicorn_stderr.log
+This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for omnibus package or in `/home/git/gitlab/logs/unicorn_stderr.log` for installations from the source.
+
+Unicorn is a high-performance forking Web server which is used for serving GitLab application. You can look at this log, for example, if your application does not respond. This log cantains all information about state of unicorn processes at any given time.
+
+```
+I, [2015-02-13T06:14:46.680381 #9047] INFO -- : Refreshing Gem list
+I, [2015-02-13T06:14:56.931002 #9047] INFO -- : listening on addr=127.0.0.1:8080 fd=12
+I, [2015-02-13T06:14:56.931381 #9047] INFO -- : listening on addr=/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket fd=13
+I, [2015-02-13T06:14:56.936638 #9047] INFO -- : master process ready
+I, [2015-02-13T06:14:56.946504 #9092] INFO -- : worker=0 spawned pid=9092
+I, [2015-02-13T06:14:56.946943 #9092] INFO -- : worker=0 ready
+I, [2015-02-13T06:14:56.947892 #9094] INFO -- : worker=1 spawned pid=9094
+I, [2015-02-13T06:14:56.948181 #9094] INFO -- : worker=1 ready
+W, [2015-02-13T07:16:01.312916 #9094] WARN -- : #: worker (pid: 9094) exceeds memory limit (320626688 bytes > 247066940 bytes)
+W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 9094) alive: 3621 sec (trial 1)
+I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped # worker=1
+I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379
+I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready
+```
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 0f63eca1f6..ddf1bbc6ee 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -6,7 +6,7 @@
* [Newlines](#newlines)
* [Multiple underscores in words](#multiple-underscores-in-words)
-* [URL autolinking](#url-autolinking)
+* [URL auto-linking](#url-autolinking)
* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
* [Emoji](#emoji)
* [Special GitLab references](#special-gitlab-references)
@@ -40,7 +40,7 @@ You can use GFM in
- milestones
- wiki pages
-You can also use other rich text files in GitLab. You might have to install a depency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
+You can also use other rich text files in GitLab. You might have to install a dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
## Newlines
@@ -68,7 +68,7 @@ It is not reasonable to italicize just _part_ of a word, especially when you're
perform_complicated_task
do_this_and_do_that_and_another_thing
-## URL autolinking
+## URL auto-linking
GFM will autolink standard URLs you copy and paste into your text. So if you want to link to a URL (instead of a textural link), you can simply put the URL in verbatim and it will be turned into a link to that URL.
@@ -140,25 +140,25 @@ But let's throw in a tag .
## Emoji
- Sometimes you want to be a :ninja: and add some :glowing_star: to your :speech_balloon:. Well we have a gift for you:
+ Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
- :high_voltage_sign: You can use emoji anywhere GFM is supported. :victory_hand:
+ :zap: You can use emoji anywhere GFM is supported. :v:
- You can use it to point out a :bug: or warn about :speak_no_evil_monkey: patches. And if someone improves your really :snail: code, send them some :cake:. People will :heart: you for that.
+ You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
- If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
+ If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
- Consult the [Emoji Cheat Sheet](https://www.dropbox.com/s/b9xaqb977s6d8w1/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup:
+ Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
-Sometimes you want to be a :ninja: and add some :glowing_star: to your :speech_balloon:. Well we have a gift for you:
+Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
-:high_voltage_sign: You can use emoji anywhere GFM is supported. :victory_hand:
+:zap: You can use emoji anywhere GFM is supported. :v:
-You can use it to point out a :bug: or warn about :speak_no_evil_monkey: patches. And if someone improves your really :snail: code, send them some :cake:. People will :heart: you for that.
+You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
-If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
+If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
-Consult the [Emoji Cheat Sheet](https://www.dropbox.com/s/b9xaqb977s6d8w1/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup:
+Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
## Special GitLab References
@@ -170,7 +170,7 @@ GFM will turn that reference into a link so you can navigate between them easily
GFM will recognize the following:
-- @foo : for team members
+- @foo : for specific team members or groups
- @all : for the whole team
- #123 : for issues
- !123 : for merge requests
@@ -250,17 +250,17 @@ The IDs are generated from the content of the header according to the following
For example:
```
-###### ..Ab_c-d. e [anchor](url) ..
+###### ..Ab_c-d. e [anchor](URL) ..
```
which renders as:
-###### ..Ab_c-d. e [anchor](url) ..
+###### ..Ab_c-d. e [anchor](URL) ..
will first be converted by step 1) into a string like:
```
-..Ab_c-d. e <a href="url">anchor</a> <img src="url" alt="alt text"/>..
+..Ab_c-d. e <a href="URL">anchor</a> <img src="URL" alt="alt text"/>..
```
After removing the tags in step 2) we get:
@@ -277,8 +277,8 @@ ab_c-d-e-anchor
Note in particular how:
-- for markdown anchors `[text](url)`, only the `text` is used
-- markdown images `` are completely ignored
+- for markdown anchors `[text](URL)`, only the `text` is used
+- markdown images `` are completely ignored
## Emphasis
@@ -420,6 +420,8 @@ Quote break.
You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
+Note that inline HTML is disabled in the default Gitlab configuration, although it is [possible](https://github.com/gitlabhq/gitlabhq/pull/8007/commits) for the system administrator to enable it.
+
```no-highlight
Definition list
@@ -542,6 +544,10 @@ This line is separated from the one above by two newlines, so it will be a *sepa
This line is also a separate paragraph, but...
This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+
+This line is also a separate paragraph, and...
+This line is on its own line, because the previous line ends with two
+spaces.
```
Here's a line for us to start with.
@@ -551,6 +557,10 @@ This line is separated from the one above by two newlines, so it will be a *sepa
This line is also begins a separate paragraph, but...
This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+This line is also a separate paragraph, and...
+This line is on its own line, because the previous line ends with two
+spaces.
+
## Tables
Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them.
@@ -569,6 +579,10 @@ Code above produces next output:
| cell 1 | cell 2 |
| cell 3 | cell 4 |
+**Note**
+
+The row of dashes between the table header and body must have at least three dashes in each column.
+
## References
- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
diff --git a/doc/operations/README.md b/doc/operations/README.md
new file mode 100644
index 0000000000..f1456c6c8e
--- /dev/null
+++ b/doc/operations/README.md
@@ -0,0 +1,4 @@
+# GitLab operations
+
+- [Sidekiq MemoryKiller](sidekiq_memory_killer.md)
+- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md)
diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md
new file mode 100644
index 0000000000..93521e976d
--- /dev/null
+++ b/doc/operations/cleaning_up_redis_sessions.md
@@ -0,0 +1,52 @@
+# Cleaning up stale Redis sessions
+
+Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis.
+Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If
+you have been running a large GitLab server (thousands of users) since before
+GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis
+database after you upgrade to GitLab 7.3. You can also perform a cleanup while
+still running GitLab 7.2 or older, but in that case new stale sessions will
+start building up again after you clean up.
+
+In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte
+hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with
+GitLab 7.3.0, the keys are
+prefixed with 'session:gitlab:', so they would look like
+'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to
+remove the keys in the old format.
+
+First we define a shell function with the proper Redis connection details.
+
+```
+rcli() {
+ # This example works for Omnibus installations of GitLab 7.3 or newer. For an
+ # installation from source you will have to change the socket path and the
+ # path to redis-cli.
+ sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@"
+}
+
+# test the new shell function; the response should be PONG
+rcli ping
+```
+
+Now we do a search to see if there are any session keys in the old format for
+us to clean up.
+
+```
+# returns the number of old-format session keys in Redis
+rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l
+```
+
+If the number is larger than zero, you can proceed to expire the keys from
+Redis. If the number is zero there is nothing to clean up.
+
+```
+# Tell Redis to expire each matched key after 600 seconds.
+rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli
+# This will print '(integer) 1' for each key that gets expired.
+```
+
+Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis
+background save interval) your Redis database will be compacted. If you are
+still using GitLab 7.2, users who are not clicking around in GitLab during the
+10 minute expiry window will be signed out of GitLab.
diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md
new file mode 100644
index 0000000000..867b01b0d5
--- /dev/null
+++ b/doc/operations/sidekiq_memory_killer.md
@@ -0,0 +1,38 @@
+# Sidekiq MemoryKiller
+
+The GitLab Rails application code suffers from memory leaks. For web requests
+this problem is made manageable using
+[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which
+restarts Unicorn worker processes in between requests when needed. The Sidekiq
+MemoryKiller applies the same approach to the Sidekiq processes used by GitLab
+to process background jobs.
+
+Unlike unicorn-worker-killer, which is enabled by default for all GitLab
+installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default
+_only_ for Omnibus packages. The reason for this is that the MemoryKiller
+relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab
+installations from source do not all use Runit or an equivalent.
+
+With the default settings, the MemoryKiller will cause a Sidekiq restart no
+more often than once every 15 minutes, with the restart causing about one
+minute of delay for incoming background jobs.
+
+## Configuring the MemoryKiller
+
+The MemoryKiller is controlled using environment variables.
+
+- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is
+ greater than 0, then after each Sidekiq job, the MemoryKiller will check the
+ RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq
+ process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a
+ delayed shutdown is triggered. The default value for Omnibus packages is set
+ [in the omnibus-gitlab
+ repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
+- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When
+ a shutdown is triggered, the Sidekiq process will keep working normally for
+ another 15 minutes.
+- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace
+ time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs.
+ Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells
+ Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must
+ restart Sidekiq.
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index d561868c8b..c9928e11b2 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -8,7 +8,6 @@ If a user is a GitLab administrator they receive all permissions.
## Project
-
| Action | Guest | Reporter | Developer | Master | Owner |
|---------------------------------------|---------|------------|-------------|----------|--------|
| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
@@ -19,6 +18,7 @@ If a user is a GitLab administrator they receive all permissions.
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
@@ -28,6 +28,7 @@ If a user is a GitLab administrator they receive all permissions.
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
| Enable/disable branch protection | | | | ✓ | ✓ |
+| Turn on/off prot. branch push for devs| | | | ✓ | ✓ |
| Rewrite/remove git tags | | | | ✓ | ✓ |
| Edit project | | | | ✓ | ✓ |
| Add deploy keys to project | | | | ✓ | ✓ |
@@ -35,6 +36,8 @@ If a user is a GitLab administrator they receive all permissions.
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
+| Force push to protected branches | | | | | |
+| Remove protected branches | | | | | |
## Group
@@ -46,4 +49,4 @@ If a user is a GitLab administrator they receive all permissions.
| Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ |
-Any user can remove himself from a group, unless he is the last Owner of the group.
+Any user can remove themselves from a group, unless they are the last Owner of the group.
diff --git a/doc/project_services/bamboo.md b/doc/project_services/bamboo.md
new file mode 100644
index 0000000000..51668128c6
--- /dev/null
+++ b/doc/project_services/bamboo.md
@@ -0,0 +1,60 @@
+# Atlassian Bamboo CI Service
+
+GitLab provides integration with Atlassian Bamboo for continuous integration.
+When configured, pushes to a project will trigger a build in Bamboo automatically.
+Merge requests will also display CI status showing whether the build is pending,
+failed, or completed successfully. It also provides a link to the Bamboo build
+page for more information.
+
+Bamboo doesn't quite provide the same features as a traditional build system when
+it comes to accepting webhooks and commit data. There are a few things that
+need to be configured in a Bamboo build plan before GitLab can integrate.
+
+## Setup
+
+### Complete these steps in Bamboo:
+
+1. Navigate to a Bamboo build plan and choose 'Configure plan' from the 'Actions'
+dropdown.
+1. Select the 'Triggers' tab.
+1. Click 'Add trigger'.
+1. Enter a description such as 'GitLab trigger'
+1. Choose 'Repository triggers the build when changes are committed'
+1. Check one or more repositories checkboxes
+1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a
+whitelist of IP addresses that are allowed to trigger Bamboo builds.
+1. Save the trigger.
+1. In the left pane, select a build stage. If you have multiple build stages
+you want to select the last stage that contains the git checkout task.
+1. Select the 'Miscellaneous' tab.
+1. Under 'Pattern Match Labelling' put '${bamboo.repository.revision.number}'
+in the 'Labels' box.
+1. Save
+
+Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo
+service in GitLab
+
+### Complete these steps in GitLab:
+
+1. Navigate to the project you want to configure to trigger builds.
+1. Select 'Settings' in the top navigation.
+1. Select 'Services' in the left navigation.
+1. Click 'Atlassian Bamboo CI'
+1. Select the 'Active' checkbox.
+1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com'
+1. Enter the build key from your Bamboo build plan. Build keys are a short,
+all capital letter, identifier that is unique. It will be something like PR-BLD
+1. If necessary, enter username and password for a Bamboo user that has
+access to trigger the build plan. Leave these fields blank if you do not require
+authentication.
+1. Save or optionally click 'Test Settings'. Please note that 'Test Settings'
+will actually trigger a build in Bamboo.
+
+## Troubleshooting
+
+If builds are not triggered, these are a couple of things to keep in mind.
+
+1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger
+IP addresses'.
+1. Remember that GitLab only triggers builds on push events. A commit via the
+web interface will not trigger CI currently.
diff --git a/doc/project_services/hipchat.md b/doc/project_services/hipchat.md
new file mode 100644
index 0000000000..021a93a288
--- /dev/null
+++ b/doc/project_services/hipchat.md
@@ -0,0 +1,54 @@
+# Atlassian HipChat
+
+GitLab provides a way to send HipChat notifications upon a number of events,
+such as when a user pushes code, creates a branch or tag, adds a comment, and
+creates a merge request.
+
+## Setup
+
+GitLab requires the use of a HipChat v2 API token to work. v1 tokens are
+not supported at this time. Note the differences between v1 and v2 tokens:
+
+HipChat v1 API (legacy) supports "API Auth Tokens" in the Group API menu. A v1
+token is allowed to send messages to *any* room.
+
+HipChat v2 API has tokens that are can be created using the Integrations tab
+in the Group or Room admin page. By design, these are lightweight tokens that
+allow GitLab to send messages only to *one* room.
+
+### Complete these steps in HipChat:
+
+1. Go to: https://admin.hipchat.com/admin
+1. Click on "Group Admin" -> "Integrations".
+1. Find "Build Your Own!" and click "Create".
+1. Select the desired room, name the integration "GitLab", and click "Create".
+1. In the "Send messages to this room by posting this URL" column, you should
+see a URL in the format:
+
+```
+ https://api.hipchat.com/v2/room//notification?auth_token=
+```
+
+HipChat is now ready to accept messages from GitLab. Next, set up the HipChat
+service in GitLab.
+
+### Complete these steps in GitLab:
+
+1. Navigate to the project you want to configure for notifications.
+1. Select "Settings" in the top navigation.
+1. Select "Services" in the left navigation.
+1. Click "HipChat".
+1. Select the "Active" checkbox.
+1. Insert the `token` field from the URL into the `Token` field on the Web page.
+1. Insert the `room` field from the URL into the `Room` field on the Web page.
+1. Save or optionally click "Test Settings".
+
+## Troubleshooting
+
+If you do not see notifications, make sure you are using a HipChat v2 API
+token, not a v1 token.
+
+Note that the v2 token is tied to a specific room. If you want to be able to
+specify arbitrary rooms, you can create an API token for a specific user in
+HipChat under "Account settings" and "API access". Use the `XXX` value under
+`auth_token=XXX`.
diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md
new file mode 100644
index 0000000000..780a45bca2
--- /dev/null
+++ b/doc/project_services/irker.md
@@ -0,0 +1,46 @@
+# Irker IRC Gateway
+
+GitLab provides a way to push update messages to an Irker server. When
+configured, pushes to a project will trigger the service to send data directly
+to the Irker server.
+
+See the project homepage for further info: http://www.catb.org/esr/irker/
+
+## Needed setup
+
+You will first need an Irker daemon. You can download the Irker code from its
+gitorious repository on https://gitorious.org/irker: `git clone
+git@gitorious.org:irker/irker.git`. Once you have downloaded the code, you can
+run the python script named `irkerd`. This script is the gateway script, it acts
+both as an IRC client, for sending messages to an IRC server obviously, and as a
+TCP server, for receiving messages from the GitLab service.
+
+If the Irker server runs on the same machine, you are done. If not, you will
+need to follow the firsts steps of the next section.
+
+## Optional setup
+
+In the `app/models/project_services/irker_service.rb` file, you can modify some
+options in the `initialize_settings` method:
+- **server_ip** (defaults to `localhost`): the server IP address where the
+`irkerd` daemon runs;
+- **server_port** (defaults to `6659`): the server port of the `irkerd` daemon;
+- **max_channels** (defaults to `3`): the maximum number of recipients the
+client is authorized to join, per project;
+- **default_irc_uri** (no default) : if this option is set, it has to be in the
+format `irc[s]://domain.name` and will be prepend to each and every channel
+provided by the user which is not a full URI.
+
+If the Irker server and the GitLab application do not run on the same host, you
+will **need** to setup at least the **server_ip** option.
+
+## Note on Irker recipients
+
+Irker accepts channel names of the form `chan` and `#chan`, both for the
+`#chan` channel. If you want to send messages in query, you will need to add
+`,isnick` avec the channel name, in this form: `Aorimn,isnick`. In this latter
+case, `Aorimn` is treated as a nick and no more as a channel name.
+
+Irker can also join password-protected channels. Users need to append
+`?key=thesecretpassword` to the chan name.
+
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
new file mode 100644
index 0000000000..03937d2072
--- /dev/null
+++ b/doc/project_services/project_services.md
@@ -0,0 +1,20 @@
+# Project Services
+
+__Project integrations with external services for continuous integration and more.__
+
+## Services
+
+- Assembla
+- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continuous integration.
+- Build box
+- Campfire
+- Emails on push
+- Flowdock
+- Gemnasium
+- GitLab CI
+- [HipChat](hipchat.md) An Atlassian product for private group chat and instant messaging.
+- [Irker](irker.md) An IRC gateway to receive messages on repository updates.
+- Pivotal Tracker
+- Pushover
+- Slack
+- TeamCity
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 4712c38702..7c5a6c0463 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -41,4 +41,4 @@ When visiting the public page of an user, you will only see listed projects whic
## Restricting the use of public or internal projects
-In [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/dbd88d453b8e6c78a423fa7e692004b1db6ea069/config/gitlab.yml.example#L64) you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident.
+In [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/dbd88d453b8e6c78a423fa7e692004b1db6ea069/config/gitlab.yml.example#L64) you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident. The restricted visibility settings do not apply to admin users.
diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md
index 9e2f697bca..770b7a70fe 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -1,5 +1,8 @@
+# Rake tasks
+
- [Backup restore](backup_restore.md)
- [Cleanup](cleanup.md)
+- [Features](features.md)
- [Maintenance](maintenance.md) and self-checks
- [User management](user_management.md)
- [Web hooks](web_hooks.md)
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index b4581e2a07..99cdfff0ac 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -13,8 +13,8 @@ You can only restore a backup to exactly the same version of GitLab that you cre
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create
-# if you've installed GitLab from source or using the cookbook
-bundle exec rake gitlab:backup:create RAILS_ENV=production
+# if you've installed GitLab from source
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
Example output:
@@ -137,7 +137,7 @@ with the name of your bucket:
Please be informed that a backup does not store your configuration files.
If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
If you have a cookbook installation there should be a copy of your configuration in Chef.
-If you have a manual installation please consider backing up your gitlab.yml file and any SSL keys and certificates.
+If you have an installation from source, please consider backing up your `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
## Restore a previously created backup
@@ -147,7 +147,7 @@ You can only restore a backup to exactly the same version of GitLab that you cre
# Omnibus package installation
sudo gitlab-rake gitlab:backup:restore
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
@@ -192,7 +192,7 @@ Deleting tmp directories...[DONE]
For Omnibus package installations, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup .
-For installation from source or cookbook:
+For installation from source:
```
cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups
@@ -203,5 +203,31 @@ Add the following lines at the bottom:
```
# Create a full backup of the GitLab repositories and SQL database every day at 4am
-0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production
+0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production CRON=1
```
+
+The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
+This is recommended to reduce cron spam.
+
+## Alternative backup strategies
+
+If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow.
+In this case you can consider using filesystem snapshots as part of your backup strategy.
+
+Example: Amazon EBS
+
+> A GitLab server using omnibus-gitlab hosted on Amazon AWS.
+> An EBS drive containing an ext4 filesystem is mounted at `/var/opt/gitlab`.
+> In this case you could make an application backup by taking an EBS snapshot.
+> The backup includes all repositories, uploads and Postgres data.
+
+Example: LVM snapshots + rsync
+
+> A GitLab server using omnibus-gitlab, with an LVM logical volume mounted at `/var/opt/gitlab`.
+> Replicating the `/var/opt/gitlab` directory using rsync would not be reliable because too many files would change while rsync is running.
+> Instead of rsync-ing `/var/opt/gitlab`, we create a temporary LVM snapshot, which we mount as a read-only filesystem at `/mnt/gitlab_backup`.
+> Now we can have a longer running rsync job which will create a consistent replica on the remote server.
+> The replica includes all repositories, uploads and Postgres data.
+
+If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server.
+It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use.
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index 9e48f56c95..96d67f7b5d 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -8,7 +8,7 @@ Remove namespaces(dirs) from `/home/git/repositories` if they don't exist in Git
# omnibus-gitlab
sudo gitlab-rake gitlab:cleanup:dirs
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:cleanup:dirs RAILS_ENV=production
```
@@ -18,6 +18,6 @@ Remove repositories (global only for now) from `/home/git/repositories` if they
# omnibus-gitlab
sudo gitlab-rake gitlab:cleanup:repos
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:cleanup:repos RAILS_ENV=production
```
diff --git a/doc/raketasks/features.md b/doc/raketasks/features.md
index 99b3d5525b..f9a4619354 100644
--- a/doc/raketasks/features.md
+++ b/doc/raketasks/features.md
@@ -6,7 +6,7 @@ This command will enable the namespaces feature introduced in v4.0. It will move
Note:
-- Because the **repository location will change**, you will need to **update all your git url's** to point to the new location.
+- Because the **repository location will change**, you will need to **update all your git URLs** to point to the new location.
- Username can be changed at [Profile / Account](/profile/account)
**Example:**
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index 39b1a52a44..8a38937062 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -1,28 +1,62 @@
-# Import
+# Import bare repositories into your GitLab instance
-### Import bare repositories into GitLab project instance
+## Notes
-Notes:
+- The owner of the project will be the first admin
+- The groups will be created as needed
+- The owner of the group will be the first admin
+- Existing projects will be skipped
-* project owner will be a first admin
-* groups will be created as needed
-* group owner will be the first admin
-* existing projects will be skipped
+## How to use
-How to use:
+### Create a new folder inside the git repositories path. This will be the name of the new group.
-1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path)
-2. run the command below
+- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed
+it in the `/etc/gitlab/gitlab.rb` file.
+- For installations from source, it is usually located at: `/home/git/repositories` or you can see where
+your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry.
+
+New folder needs to have git user ownership and read/write/execute access for git user and its group:
```
-# omnibus-gitlab
-sudo gitlab-rake gitlab:import:repos
-
-# installation from source or cookbook
-bundle exec rake gitlab:import:repos RAILS_ENV=production
+sudo -u git mkdir /var/opt/gitlab/git-data/repositories/new_group
```
-Example output:
+If you are using an installation from source, replace `/var/opt/gitlab/git-data`
+with `/home/git`.
+
+### Copy your bare repositories inside this newly created folder:
+
+```
+sudo cp -r /old/git/foo.git /var/opt/gitlab/git-data/repositories/new_group/
+
+# Do this once when you are done copying git repositories
+sudo chown -R git:git /var/opt/gitlab/git-data/repositories/new_group/
+```
+
+`foo.git` needs to be owned by the git user and git users group.
+
+If you are using an installation from source, replace `/var/opt/gitlab/git-data`
+with `/home/git`.
+
+### Run the command below depending on your type of installation:
+
+#### Omnibus Installation
+
+```
+$ sudo gitlab-rake gitlab:import:repos
+```
+
+#### Installation from source
+
+Before running this command you need to change the directory to where your GitLab installation is located:
+
+```
+$ cd /home/git/gitlab
+$ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production
+```
+
+#### Example output
```
Processing abcd.git
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index f6bd756579..41a994f3f6 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -8,7 +8,7 @@ This command gathers information about your GitLab installation and the System i
# omnibus-gitlab
sudo gitlab-rake gitlab:env:info
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:env:info RAILS_ENV=production
```
@@ -16,30 +16,31 @@ Example output:
```
System information
-System: Debian 6.0.7
-Current User: git
-Using RVM: no
-Ruby Version: 2.0.0-p481
-Gem Version: 1.8.23
-Bundler Version:1.3.5
-Rake Version: 10.0.4
+System: Debian 7.8
+Current User: git
+Using RVM: no
+Ruby Version: 2.1.5p273
+Gem Version: 2.4.3
+Bundler Version: 1.7.6
+Rake Version: 10.3.2
+Sidekiq Version: 2.17.8
GitLab information
-Version: 5.1.0.beta2
-Revision: 4da8b37
-Directory: /home/git/gitlab
-DB Adapter: mysql2
-URL: http://example.com
-HTTP Clone URL: http://example.com/some-project.git
-SSH Clone URL: git@example.com:some-project.git
-Using LDAP: no
-Using Omniauth: no
+Version: 7.7.1
+Revision: 41ab9e1
+Directory: /home/git/gitlab
+DB Adapter: postgresql
+URL: https://gitlab.example.com
+HTTP Clone URL: https://gitlab.example.com/some-project.git
+SSH Clone URL: git@gitlab.example.com:some-project.git
+Using LDAP: no
+Using Omniauth: no
GitLab Shell
-Version: 1.2.0
-Repositories: /home/git/repositories/
-Hooks: /home/git/gitlab-shell/hooks/
-Git: /usr/bin/git
+Version: 2.4.1
+Repositories: /home/git/repositories/
+Hooks: /home/git/gitlab-shell/hooks/
+Git: /usr/bin/git
```
## Check GitLab configuration
@@ -59,7 +60,7 @@ You may also have a look at our [Trouble Shooting Guide](https://github.com/gitl
# omnibus-gitlab
sudo gitlab-rake gitlab:check
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:check RAILS_ENV=production
```
@@ -122,3 +123,56 @@ sudo -u git -H mkdir -p /home/git/gitlab-satellites
sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
```
+
+## Rebuild authorized_keys file
+
+In some case it is necessary to rebuild the `authorized_keys` file.
+
+For Omnibus-packages:
+```
+sudo gitlab-rake gitlab:shell:setup
+```
+
+For installations from source:
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
+```
+
+```
+This will rebuild an authorized_keys file.
+You will lose any data stored in authorized_keys file.
+Do you want to continue (yes/no)? yes
+```
+
+## Clear redis cache
+
+If for some reason the dashboard shows wrong information you might want to
+clear Redis' cache.
+
+For Omnibus-packages:
+```
+sudo gitlab-rake cache:clear
+```
+
+For installations from source:
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+```
+
+## Precompile the assets
+
+Sometimes during version upgrades you might end up with some wrong CSS or
+missing some icons. In that case, try to precompile the assets again.
+
+For Omnibus-packages:
+```
+sudo gitlab-rake assets:precompile
+```
+
+For installations from source:
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
+```
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index 3c67753ad2..80b01ca404 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -6,7 +6,7 @@
# omnibus-gitlab
sudo gitlab-rake gitlab:import:user_to_projects[username@domain.tld]
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:import:user_to_projects[username@domain.tld] RAILS_ENV=production
```
@@ -20,7 +20,7 @@ Notes:
# omnibus-gitlab
sudo gitlab-rake gitlab:import:all_users_to_all_projects
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:import:all_users_to_all_projects RAILS_ENV=production
```
@@ -30,7 +30,7 @@ bundle exec rake gitlab:import:all_users_to_all_projects RAILS_ENV=production
# omnibus-gitlab
sudo gitlab-rake gitlab:import:user_to_groups[username@domain.tld]
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:import:user_to_groups[username@domain.tld] RAILS_ENV=production
```
@@ -44,6 +44,6 @@ Notes:
# omnibus-gitlab
sudo gitlab-rake gitlab:import:all_users_to_all_groups
-# installation from source or cookbook
+# installation from source
bundle exec rake gitlab:import:all_users_to_all_groups RAILS_ENV=production
```
diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md
index e1a58835d8..5a8b94af9b 100644
--- a/doc/raketasks/web_hooks.md
+++ b/doc/raketasks/web_hooks.md
@@ -4,42 +4,42 @@
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook"
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" RAILS_ENV=production
## Add a web hook for projects in a given **NAMESPACE**:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
## Remove a web hook from **ALL** projects using:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook"
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" RAILS_ENV=production
## Remove a web hook from projects in a given **NAMESPACE**:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
## List **ALL** web hooks:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:list
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:list RAILS_ENV=production
## List the web hooks from projects in a given **NAMESPACE**:
# omnibus-gitlab
sudo gitlab-rake gitlab:web_hook:list NAMESPACE=/
- # source installations or cookbook
+ # source installations
bundle exec rake gitlab:web_hook:list NAMESPACE=/ RAILS_ENV=production
> Note: `/` is the global namespace.
diff --git a/doc/release/howto_rc1.md b/doc/release/howto_rc1.md
new file mode 100644
index 0000000000..07c703142d
--- /dev/null
+++ b/doc/release/howto_rc1.md
@@ -0,0 +1,55 @@
+# How to create RC1
+
+The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub.
+
+### 1. Update the installation guide
+
+1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay)
+1. Check the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782)
+1. Check the [Git version](/lib/tasks/gitlab/check.rake#L794)
+1. There might be other changes. Ask around.
+
+### 2. Create update guides
+
+[Follow this guide](howto_update_guides.md) to create update guides.
+
+### 3. Code quality indicators
+
+Make sure the code quality indicators are green / good.
+
+- [](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
+
+- [](https://semaphoreapp.com/gitlabhq/gitlabhq) (master branch)
+
+- [](https://codeclimate.com/github/gitlabhq/gitlabhq)
+
+- [](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available)
+
+- [](https://coveralls.io/r/gitlabhq/gitlabhq)
+
+### 4. Run release tool
+
+**Make sure EE `master` has latest changes from CE `master`**
+
+Get release tools
+
+```
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
+```
+
+Release candidate creates stable branch from master.
+So we need to sync master branch between all CE, EE and CI remotes.
+
+```
+bundle exec rake sync
+```
+
+Create release candidate and stable branch:
+
+```
+bundle exec rake release["x.x.0.rc1"]
+```
+
+Now developers can use master for merging new features.
+So you should use stable branch for future code changes related to release.
diff --git a/doc/release/howto_update_guides.md b/doc/release/howto_update_guides.md
new file mode 100644
index 0000000000..23d0959c33
--- /dev/null
+++ b/doc/release/howto_update_guides.md
@@ -0,0 +1,55 @@
+# Create update guides
+
+1. Create: CE update guide from previous version. Like `7.3-to-7.4.md`
+1. Create: CE to EE update guide in EE repository for latest version.
+1. Update: `6.x-or-7.x-to-7.x.md` to latest version.
+1. Create: CI update guide from previous version
+
+It's best to copy paste the previous guide and make changes where necessary.
+The typical steps are listed below with any points you should specifically look at.
+
+#### 0. Any major changes?
+
+List any major changes here, so the user is aware of them before starting to upgrade. For instance:
+
+- Database updates
+- Web server changes
+- File structure changes
+
+#### 1. Stop server
+
+#### 2. Make backup
+
+#### 3. Do users need to update dependencies like `git`?
+
+- Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release.
+
+- Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release.
+
+#### 4. Get latest code
+
+#### 5. Does GitLab shell need to be updated?
+
+#### 6. Install libs, migrations, etc.
+
+#### 7. Any config files updated since last release?
+
+Check if any of these changed since last release:
+
+- [lib/support/nginx/gitlab](/lib/support/nginx/gitlab)
+- [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl)
+-
+- [config/gitlab.yml.example](/config/gitlab.yml.example)
+- [config/unicorn.rb.example](/config/unicorn.rb.example)
+- [config/database.yml.mysql](/config/database.yml.mysql)
+- [config/database.yml.postgresql](/config/database.yml.postgresql)
+- [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example)
+- [config/resque.yml.example](/config/resque.yml.example)
+
+#### 8. Need to update init script?
+
+Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab)
+
+#### 9. Start application
+
+#### 10. Check application status
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index c46a3ed9c9..cfe01896d8 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -1,200 +1,126 @@
# Monthly Release
-NOTE: This is a guide for GitLab developers.
+NOTE: This is a guide used by the GitLab B.V. developers.
-# **15th - Code Freeze & Release Manager**
+It starts 7 working days before the release.
+The release manager doesn't have to perform all the work but must ensure someone is assigned.
+The current release manager must schedule the appointment of the next release manager.
+The new release manager should create overall issue to track the progress.
-### **1. Stop merging in code, except for important bugfixes**
+## Release Manager
-### **2. Release Manager**
+A release manager is selected that coordinates all releases the coming month, including the patch releases for previous releases.
+The release manager has to make sure all the steps below are done and delegated where necessary.
+This person should also make sure this document is kept up to date and issues are created and updated.
-A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated.
+## Take vacations into account
-### **3. Create an overall issue**
-Name it "Release x.x.x" for easier searching.
+The time is measured in weekdays to compensate for weekends.
+Do everything on time to prevent problems due to rush jobs or too little testing time.
+Make sure that you take into account any vacations of maintainers.
+If the release is falling behind immediately warn the team.
+
+## Create an overall issue and follow it
+
+Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching.
+Replace the dates with actual dates based on the number of workdays before the release.
+All steps from issue template are explained below
```
-15th:
+Xth: (7 working days before the 22nd)
-* Update the changelog (#LINK)
-* Triage the omnibus-gitlab milestone
+- [ ] Code freeze
+- [ ] Update the CE changelog (#LINK)
+- [ ] Update the EE changelog (#LINK)
+- [ ] Update the CI changelog (#LINK)
+- [ ] Triage the omnibus-gitlab milestone
-16th:
+Xth: (6 working days before the 22nd)
-* Merge CE in to EE (#LINK)
-* Close the omnibus-gitlab milestone
+- [ ] Merge CE master in to EE master via merge request (#LINK)
+- [ ] Determine QA person and notify this person
+- [ ] Check the tasks in [how to rc1 guide](howto_rc1.md) and delegate tasks if necessary
+- [ ] Create CE, EE, CI RC1 versions (#LINK)
-17th:
+Xth: (5 working days before the 22nd)
-* Create x.x.0.rc1 (#LINK)
-* Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package)
+- [ ] Do QA and fix anything coming out of it (#LINK)
+- [ ] Close the omnibus-gitlab milestone
+- [ ] Prepare the blog post (#LINK)
-18th:
+Xth: (4 working days before the 22nd)
-* Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
-* Regression issue and tweet about rc1 (#LINK)
-* Start blog post (#LINK)
+- [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
+- [ ] Update ci.gitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
+- [ ] Create regression issues (CE, CI) (#LINK)
+- [ ] Tweet about rc1 (#LINK)
-21th:
+Xth: (3 working days before the 22nd)
-* Do QA and fix anything coming out of it (#LINK)
+- [ ] Merge CE stable branch into EE stable branch
+
+Xth: (2 working days before the 22nd)
+
+- [ ] Check that everyone is mentioned on the blog post (the reviewer should have done this one working day ago)
+- [ ] Check that MVP is added to the mvp page (source/mvp/index.html in www-gitlab-com)
+
+Xth: (1 working day before the 22nd)
+
+- [ ] Create CE, EE, CI stable versions (#LINK)
+- [ ] Create Omnibus tags and build packages
+- [ ] Update GitLab.com with the stable version (#LINK)
+- [ ] Update ci.gitLab.com with the stable version (#LINK)
22nd:
-* Release CE and EE (#LINK)
-
-23rd:
-
-* Prepare package for GitLab.com release (#LINK)
-
-24th:
-
-* Deploy to GitLab.com (#LINK)
-```
-
-### **4. Update Changelog**
-
-Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing.
-
-### **5. Take weekend and vacations into account**
-
-Ensure that there is enough time to incorporate the findings of the release candidate, etc.
-
-# **16th - Merge the CE into EE**
-
-Do this via a merge request.
-
-# **17th - Create RC1**
-
-The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub.
-
-### **1. Update the installation guide**
-
-1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay)
-1. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782)
-1. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794)
-1. There might be other changes. Ask around.
-
-### **2. Create an update guides**
-
-1. Create: CE update guide from previous version. Like `from-6-8-to-6.9`
-1. Create: CE to EE update guide in EE repository for latest version.
-1. Update: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/6.0-to-6.x.md to latest version.
-
-It's best to copy paste the previous guide and make changes where necessary.
-The typical steps are listed below with any points you should specifically look at.
-
-#### 0. Any major changes?
-
-List any major changes here, so the user is aware of them before starting to upgrade. For instance:
-
-- Database updates
-- Web server changes
-- File structure changes
-
-#### 1. Make backup
-
-#### 2. Stop server
-
-#### 3. Do users need to update dependencies like `git`?
-
-- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release.
-
-- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release.
-
-#### 4. Get latest code
-
-#### 5. Does GitLab shell need to be updated?
-
-#### 6. Install libs, migrations, etc.
-
-#### 7. Any config files updated since last release?
-
-Check if any of these changed since last release:
-
--
--
--
--
--
--
--
--
--
-
-#### 8. Need to update init script?
-
-Check if the `init.d/gitlab` script changed since last release:
-
-#### 9. Start application
-
-#### 10. Check application status
-
-### **3. Code quality indicators**
-
-Make sure the code quality indicators are green / good.
-
-- [](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
-
-- [](https://semaphoreapp.com/gitlabhq/gitlabhq) (master branch)
-
-- [](https://codeclimate.com/github/gitlabhq/gitlabhq)
-
-- [](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available)
-
-- [](https://coveralls.io/r/gitlabhq/gitlabhq)
-
-### **4. Set VERSION**
-
-Change version in VERSION to `x.x.0.rc1`.
-
-### **5. Tag**
-
-Create an annotated tag that points to the version change commit:
+- [ ] Release CE, EE and CI (#LINK)
-```
-git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1'
```
-### **6. Create stable branches**
+- - -
-For GitLab EE, append `-ee` to the branch.
+## Code Freeze
-`x-x-stable-ee`
+Stop merging code in master, except for important bug fixes
-```
-git checkout master
-git pull
-git checkout -b x-x-stable
-git push x-x-stable
-```
+## Update changelog
-Now developers can use master for merging new features.
-So you should use stable branch for future code chages related to release.
+Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is
+asked if there is anything missing.
+There are three changelogs that need to be updated: CE, EE and CI.
-# **18th - Release RC1**
+## Create RC1 (CE, EE, CI)
-### **1. Update GitLab.com**
+[Follow this How-to guide](howto_rc1.md) to create RC1.
-Merge the RC1 EE code into GitLab.com.
-Once the build is green, create a package.
+## Prepare CHANGELOG for next release
+
+Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version, usually X.X.X.pre.
+
+## QA
+
+Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X QA" in order to keep track of the progress.
+
+Use the omnibus packages created for RC1 of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md).
+
+**NOTE** Upgrader can only be tested when tags are pushed to all repositories. Do not forget to confirm it is working before releasing. Note that in the issue.
+
+#### Fix anything coming out of the QA
+
+Create an issue with description of a problem, if it is quick fix fix it yourself otherwise contact the team for advice.
+
+**NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted,
+create an issue about it in order to discuss the next steps after the release.
+
+## Update GitLab.com with RC1
+
+Use the omnibus EE packages created for RC1.
If there are big database migrations consider testing them with the production db on a VM.
Try to deploy in the morning.
It is important to do this as soon as possible, so we can catch any errors before we release the full version.
-### **2. Prepare the blog post**
-
-- Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
-- Check the changelog of CE and EE for important changes.
-- Create a WIP MR for the blog post
-- Ask Dmitriy to add screenshots to the WIP MR.
-- Decide with team who will be the MVP user.
-- Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
-- Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor)
-- After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments
-
-### **3. Create a regressions issue**
+## Create a regressions issue
On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text:
@@ -203,122 +129,84 @@ Please do not raise issues directly in this issue but link to issues that might
The decision to create a patch release or not is with the release manager who is assigned to this issue.
The release manager will comment here about the plans for patch releases.
-Assign the issue to the release manager and /cc all the core-team members active on the issue tracker. If there are any known bugs in the release add them immediately.
+Assign the issue to the release manager and at mention all members of gitlab core team. If there are any known bugs in the release add them immediately.
-### **4. Tweet**
+## Tweet about RC1
Tweet about the RC release:
> GitLab x.x.0.rc1 is out. This release candidate is only suitable for testing. Please link regressions issues from LINK_TO_REGRESSION_ISSUE
-# **21st - Preparation**
+## Prepare the blog post
-### **1. Pre QA merge**
+1. Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
+1. Make sure the blog post contains information about the GitLab CI release.
+1. Check the changelog of CE and EE for important changes.
+1. Also check the CI changelog
+1. Add a proposed tweet text to the blog post WIP MR description.
+1. Create a WIP MR for the blog post
+1. Ask Dmitriy (or a team member with OS X) to add screenshots to the WIP MR.
+1. Decide with core team who will be the MVP user.
+1. Create WIP MR for adding MVP to MVP page on website
+1. Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
+1. Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master)
+1. Assign to one reviewer who will fix spelling issues by editing the branch (either with a git client or by using the online editor)
+1. Comment to the reviewer: '@person Please mention the whole team as soon as you are done (3 workdays before release at the latest)'
-Merge CE into EE before doing the QA.
+## Create CE, EE, CI stable versions
-### **2. QA**
-
-Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X QA" in order to keep track of the progress.
-
-Use the omnibus packages of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md).
-
-**NOTE** Upgrader can only be tested when tags are pushed to all repositories. Do not forget to confirm it is working before releasing. Note that in the issue.
-
-### **3. Fix anything coming out of the QA**
-
-Create an issue with description of a problem, if it is quick fix fix it yourself otherwise contact the team for advice.
-
-**NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted,
-create an issue about it in order to discuss the next steps after the release.
-
-# **22nd - Release CE and EE**
-
-For GitLab EE, append `-ee` to the branches and tags.
-
-`x-x-stable-ee`
-
-`v.x.x.0-ee`
-
-Note: Merge CE into EE if needed.
-
-### **1. Set VERSION to x.x.x and push**
-
-- Change the GITLAB_SHELL_VERSION file in `master` of the CE repository if the version changed.
-- Change the GITLAB_SHELL_VERSION file in `master` of the EE repository if the version changed.
-- Change the VERSION file in `master` branch of the CE repository and commit and push to origin.
-- Change the VERSION file in `master` branch of the EE repository and commit and push to origin.
-
-### **2. Update installation.md**
-
-Update [installation.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) to the newest version in master.
-
-### **3. Push latest changes from x-x-stable branch to dev.gitlab.org**
+Get release tools
```
-git checkout -b x-x-stable
-git push origin x-x-stable
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
```
-### **4. Build the Omnibus packages**
+Bump version, create release tag and push to remotes:
+
+```
+bundle exec rake release["x.x.0"]
+```
+
+This will create correct version and tag and push to all CE, EE and CI remotes.
+
+Update [installation.md](/doc/install/installation.md) to the newest version in master.
+
+
+## Create Omnibus tags and build packages
Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md).
This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase.
-### **5. Create annotated tag vx.x.x**
+## Update GitLab.com with the stable version
-In `x-x-stable` branch check for the SHA-1 of the commit with VERSION file changed. Tag that commit,
+- Deploy the package (should not need downtime because of the small difference with RC1)
+- Deploy the package for ci.gitlab.com
-```
-git tag -a vx.x.0 -m 'Version x.x.0' xxxxx
-```
+## Release CE, EE and CI
-where `xxxxx` is SHA-1.
-
-### **6. Push the tag and x-x-stable branch to the remotes**
-
-For GitLab CE, push to dev, GitLab.com and GitHub.
-
-For GitLab EE, push to the subscribers repo.
-
-Make sure the branch is marked 'protected' on each of the remotes you pushed to.
-
-```
-git push x-x-stable(-ee)
-git push vx.x.0
-```
-
-### **7. Publish packages for new release**
+__1. Publish packages for new release__
Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository.
-### **8. Publish blog for new release**
+__2. Publish blog for new release__
+Doublecheck the everyone has been mentioned in the blog post.
Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository.
-### **9. Tweet to blog**
+__3. Tweet to blog__
Send out a tweet to share the good news with the world.
List the most important features and link to the blog post.
-Proposed tweet for CE "GitLab X.X is released! It brings *** "
+Proposed tweet "Release of GitLab X.X & CI Y.Y! FEATURE, FEATURE and FEATURE <link-to-blog-post> #gitlab"
-### **10. Send out the newsletter**
+Consider creating a post on Hacker News.
-Send out an email to the 'GitLab Newsletter' mailing list on MailChimp.
-Replicate the former release newsletter and modify it accordingly.
-**Do not forget to edit `Subject line` and regenerate `Plain-Text Email` from HTML source**
+## Release new AMIs
-Include a link to the blog post and keep it short.
+[Follow this guide](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md)
-Proposed email text:
-"We have released a new version of GitLab. See our blog post( ) for more information."
+## Create a WIP blogpost for the next release
-
-# **23rd - Optional Patch Release**
-
-# **24th - Update GitLab.com**
-
-Merge the stable release into GitLab.com. Once the build is green deploy the next morning.
-
-# **25th - Release GitLab CI**
+Create a WIP blogpost using [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md).
diff --git a/doc/release/patch.md b/doc/release/patch.md
index bcc14568fc..68156ae9c0 100644
--- a/doc/release/patch.md
+++ b/doc/release/patch.md
@@ -10,22 +10,49 @@ Otherwise include it in the monthly release and note there was a regression fix
## Release Procedure
+### Preparation
+
1. Verify that the issue can be reproduced
1. Note in the 'GitLab X.X regressions' that you will create a patch
1. Create an issue on private GitLab development server
1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier
1. Fix the issue on a feature branch, do this on the private GitLab development server
+1. If it is a security issue, then assign it to the release manager and apply a 'security' label
1. Consider creating and testing workarounds
1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch
-1. In a separate commit in the stable branch update the CHANGELOG
-1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
-1. In a separate commit in the stable branch update the VERSION
-1. Create an annotated tag vX.X.X for CE and another patch release for EE `git tag -a vx.x.x -m 'Version x.x.x'`
1. Make sure that the build has passed and all tests are passing
-1. Push the code and the tags to all the CE and EE repositories
-1. Apply the patch to GitLab Cloud and the private GitLab development server
+1. In a separate commit in the master branch update the CHANGELOG
+1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
+1. Merge CE stable branch into EE stable branch
+
+
+### Bump version
+
+Get release tools
+
+```
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
+```
+
+Bump version in stable branch, create release tag and push to remotes:
+
+```
+bundle exec rake release["x.x.x"]
+```
+
+Or if you need to release only EE:
+
+```
+CE=false be rake release['x.x.x']
+```
+
+### Release
+
1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md)
-1. Cherry-pick the changelog update back into master
-1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog
+1. Apply the patch to GitLab.com and the private GitLab development server
+1. Apply the patch to ci.gitLab.com and the private GitLab CI development server
+1. Create and publish a blog post, see [patch release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/patch_release_blog_template.md)
+1. Send tweets about the release from `@gitlab`, tweet should include the most important feature that the release is addressing and link to the blog post
1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only)
-1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the patch is EE only)
+1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md)
diff --git a/doc/release/security.md b/doc/release/security.md
index 79d23c02ea..60bcfbb6da 100644
--- a/doc/release/security.md
+++ b/doc/release/security.md
@@ -14,13 +14,17 @@ Please report suspected security vulnerabilities in private to *\s*\).\{2\}/\\1"\.\*"/g' /home/git/.gitolite.rc
# gitlite v3
diff --git a/doc/update/2.9-to-3.0.md b/doc/update/2.9-to-3.0.md
index 8af86b0dc9..f4a997a8c5 100644
--- a/doc/update/2.9-to-3.0.md
+++ b/doc/update/2.9-to-3.0.md
@@ -1,4 +1,5 @@
# From 2.9 to 3.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/2.9-to-3.0.md) for the most up to date instructions.*
## 1. Stop server & resque
diff --git a/doc/update/3.0-to-3.1.md b/doc/update/3.0-to-3.1.md
index 3206df3499..a30485c42f 100644
--- a/doc/update/3.0-to-3.1.md
+++ b/doc/update/3.0-to-3.1.md
@@ -1,4 +1,5 @@
# From 3.0 to 3.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/3.0-to-3.1.md) for the most up to date instructions.*
**IMPORTANT!**
diff --git a/doc/update/3.1-to-4.0.md b/doc/update/3.1-to-4.0.md
index 165f4e6a30..f1ef4df474 100644
--- a/doc/update/3.1-to-4.0.md
+++ b/doc/update/3.1-to-4.0.md
@@ -1,4 +1,5 @@
# From 3.1 to 4.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/3.1-to-4.0.md) for the most up to date instructions.*
## Important changes
diff --git a/doc/update/4.0-to-4.1.md b/doc/update/4.0-to-4.1.md
index 4149ed6b08..d89d523591 100644
--- a/doc/update/4.0-to-4.1.md
+++ b/doc/update/4.0-to-4.1.md
@@ -1,4 +1,5 @@
# From 4.0 to 4.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.0-to-4.1.md) for the most up to date instructions.*
## Important changes
diff --git a/doc/update/4.1-to-4.2.md b/doc/update/4.1-to-4.2.md
index 5ee8e8781e..6fe4412ff9 100644
--- a/doc/update/4.1-to-4.2.md
+++ b/doc/update/4.1-to-4.2.md
@@ -1,4 +1,5 @@
# From 4.1 to 4.2
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.1-to-4.2.md) for the most up to date instructions.*
## 1. Stop server & Resque
diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md
index 897cd0b91f..f9faf65f95 100644
--- a/doc/update/4.2-to-5.0.md
+++ b/doc/update/4.2-to-5.0.md
@@ -1,4 +1,5 @@
# From 4.2 to 5.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.2-to-5.0.md) for the most up to date instructions.*
## Warning
@@ -41,8 +42,8 @@ git checkout v1.1.0
# copy config
cp config.yml.example config.yml
-# change url to GitLab instance
-# ! make sure url end with '/' like 'https://gitlab.example/'
+# change URL to GitLab instance
+# ! make sure the URL ends with '/' like 'https://gitlab.example/'
vim config.yml
# rewrite hooks
@@ -111,7 +112,7 @@ sudo chmod -R u+rwX /home/git/gitlab/tmp/pids
```
-## 6. Update init.d script and nginx config
+## 6. Update init.d script and Nginx config
```bash
# init.d
@@ -123,7 +124,7 @@ sudo chmod +x /etc/init.d/gitlab
sudo -u git -H cp /home/git/gitlab/config/unicorn.rb /home/git/gitlab/config/unicorn.rb.old
sudo -u git -H cp /home/git/gitlab/config/unicorn.rb.example /home/git/gitlab/config/unicorn.rb
-#nginx
+# Nginx
# Replace path from '/home/gitlab/' to '/home/git/'
sudo vim /etc/nginx/sites-enabled/gitlab
sudo service nginx restart
@@ -137,7 +138,7 @@ sudo service gitlab start
# check if unicorn and sidekiq started
# If not try to logout, also check replaced path from '/home/gitlab/' to '/home/git/'
-# in nginx, unicorn, init.d etc
+# in Nginx, unicorn, init.d etc
ps aux | grep unicorn
ps aux | grep sidekiq
@@ -195,6 +196,12 @@ sudo rm -R tmp
sudo -u git -H mkdir tmp
sudo chmod -R u+rwX tmp/
+# create directory for pids, make sure GitLab can write to it
+sudo -u git -H mkdir tmp/pids/
+sudo chmod -R u+rwX tmp/pids/
+
+# if you are already running a newer version of GitLab check that installation guide for other tmp folders you need to create
+
# reboot system
sudo reboot
diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md
index 0e597abb1a..9fbd1f8851 100644
--- a/doc/update/5.0-to-5.1.md
+++ b/doc/update/5.0-to-5.1.md
@@ -1,4 +1,5 @@
# From 5.0 to 5.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.0-to-5.1.md) for the most up to date instructions.*
## Warning
diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md
index 6ef559ac9f..cf9c4e4f77 100644
--- a/doc/update/5.1-to-5.2.md
+++ b/doc/update/5.1-to-5.2.md
@@ -1,4 +1,5 @@
# From 5.1 to 5.2
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-5.2.md) for the most up to date instructions.*
## Warning
diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md
index 8ec56b266c..97a98ede07 100644
--- a/doc/update/5.1-to-5.4.md
+++ b/doc/update/5.1-to-5.4.md
@@ -1,4 +1,5 @@
# From 5.1 to 5.4
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-5.4.md) for the most up to date instructions.*
Also works starting from 5.2.
diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md
index a76b371e6d..a3fdd92bd2 100644
--- a/doc/update/5.1-to-6.0.md
+++ b/doc/update/5.1-to-6.0.md
@@ -1,4 +1,5 @@
# From 5.1 to 6.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-6.0.md) for the most up to date instructions.*
## Warning
@@ -40,7 +41,7 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
The migrations in this update are very sensitive to incomplete or inconsistent data. If you have a long-running GitLab installation and some of the previous upgrades did not work out 100% correct this may bite you now. The following can help you have a more smooth upgrade.
-### Find projets with invalid project names
+### Find projects with invalid project names
#### MySQL
Login to MySQL:
diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md
index 61ddf13564..27613aeda0 100644
--- a/doc/update/5.2-to-5.3.md
+++ b/doc/update/5.2-to-5.3.md
@@ -1,4 +1,5 @@
# From 5.2 to 5.3
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.2-to-5.3.md) for the most up to date instructions.*
## Warning
diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md
index 8a0d43e3e6..577b9a585f 100644
--- a/doc/update/5.3-to-5.4.md
+++ b/doc/update/5.3-to-5.4.md
@@ -1,4 +1,5 @@
# From 5.3 to 5.4
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.3-to-5.4.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md
index 7bf7bce6aa..d9c6d9bfb9 100644
--- a/doc/update/5.4-to-6.0.md
+++ b/doc/update/5.4-to-6.0.md
@@ -1,16 +1,20 @@
# From 5.4 to 6.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.4-to-6.0.md) for the most up to date instructions.*
## Warning
GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489.
+**You need to follow this guide first, before updating past 6.0, as it contains critical migration steps that are only present
+in the `6-0-stable` branch**
+
## Deprecations
### Global projects
The root (global) namespace for projects is deprecated.
-So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote url. Please make sure you disable sending email when you do a test of the upgrade.
+So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote URL. Please make sure you disable sending email when you do a test of the upgrade.
### Teams
diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md
index 9d67a3bcb9..c5eba1c01c 100644
--- a/doc/update/6.0-to-6.1.md
+++ b/doc/update/6.0-to-6.1.md
@@ -1,4 +1,5 @@
# From 6.0 to 6.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.0-to-6.1.md) for the most up to date instructions.*
## Warning
diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md
index efa6e43124..a534528108 100644
--- a/doc/update/6.1-to-6.2.md
+++ b/doc/update/6.1-to-6.2.md
@@ -1,4 +1,5 @@
# From 6.1 to 6.2
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.1-to-6.2.md) for the most up to date instructions.*
**You should update to 6.1 before installing 6.2 so all the necessary conversions are run.**
@@ -35,7 +36,7 @@ sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulner
## 4. Install additional packages
```bash
-# Add support for lograte for better log file handling
+# Add support for logrotate for better log file handling
sudo apt-get install logrotate
```
diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md
index e9b3bdd2f5..b08ebde080 100644
--- a/doc/update/6.2-to-6.3.md
+++ b/doc/update/6.2-to-6.3.md
@@ -1,4 +1,5 @@
# From 6.2 to 6.3
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.2-to-6.3.md) for the most up to date instructions.*
**Requires version: 6.1 or 6.2.**
diff --git a/doc/update/6.3-to-6.4.md b/doc/update/6.3-to-6.4.md
index 96c2895981..951d92dfeb 100644
--- a/doc/update/6.3-to-6.4.md
+++ b/doc/update/6.3-to-6.4.md
@@ -1,4 +1,5 @@
# From 6.3 to 6.4
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.3-to-6.4.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/6.4-to-6.5.md b/doc/update/6.4-to-6.5.md
index 1624296fc3..0dae9a9fe5 100644
--- a/doc/update/6.4-to-6.5.md
+++ b/doc/update/6.4-to-6.5.md
@@ -1,4 +1,5 @@
# From 6.4 to 6.5
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.4-to-6.5.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/6.5-to-6.6.md b/doc/update/6.5-to-6.6.md
index 544eee17fe..c24e83eb00 100644
--- a/doc/update/6.5-to-6.6.md
+++ b/doc/update/6.5-to-6.6.md
@@ -1,4 +1,5 @@
# From 6.5 to 6.6
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.5-to-6.6.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/6.6-to-6.7.md b/doc/update/6.6-to-6.7.md
index 77ac4d0bfa..5622a7001e 100644
--- a/doc/update/6.6-to-6.7.md
+++ b/doc/update/6.6-to-6.7.md
@@ -1,4 +1,5 @@
# From 6.6 to 6.7
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.6-to-6.7.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/6.7-to-6.8.md b/doc/update/6.7-to-6.8.md
index 16f3439c99..4fb90639f1 100644
--- a/doc/update/6.7-to-6.8.md
+++ b/doc/update/6.7-to-6.8.md
@@ -1,4 +1,5 @@
# From 6.7 to 6.8
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.7-to-6.8.md) for the most up to date instructions.*
## 0. Backup
diff --git a/doc/update/6.8-to-6.9.md b/doc/update/6.8-to-6.9.md
index 9efb384ff5..b9b8b63f65 100644
--- a/doc/update/6.8-to-6.9.md
+++ b/doc/update/6.8-to-6.9.md
@@ -1,4 +1,5 @@
# From 6.8 to 6.9
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.8-to-6.9.md) for the most up to date instructions.*
### 0. Backup
diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md
index 1f3421a799..236430b595 100644
--- a/doc/update/6.9-to-7.0.md
+++ b/doc/update/6.9-to-7.0.md
@@ -1,4 +1,5 @@
# From 6.9 to 7.0
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.9-to-7.0.md) for the most up to date instructions.*
### 0. Backup
diff --git a/doc/update/6.x-or-7.x-to-7.3.md b/doc/update/6.x-or-7.x-to-7.9.md
similarity index 54%
rename from doc/update/6.x-or-7.x-to-7.3.md
rename to doc/update/6.x-or-7.x-to-7.9.md
index 171fcb4033..bd6eb6b211 100644
--- a/doc/update/6.x-or-7.x-to-7.3.md
+++ b/doc/update/6.x-or-7.x-to-7.9.md
@@ -1,6 +1,7 @@
-# From 6.x or 7.x to 7.3
+# From 6.x or 7.x to 7.9
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.9.md) for the most up to date instructions.*
-This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.3.
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.9.
## Global issue numbers
@@ -13,7 +14,11 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are
no longer allowed however so those will be removed from your tags during the
database migrations for GitLab 7.2.
-## 0. Backup
+## 0. Stop server
+
+ sudo service gitlab stop
+
+## 1. Backup
It's useful to make a backup just in case things go south:
(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
@@ -23,10 +28,6 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
-## 1. Stop server
-
- sudo service gitlab stop
-
## 2. Update Ruby
If you are still using Ruby 1.9.3 or below, you will need to update Ruby.
@@ -34,7 +35,7 @@ You can check which version you are running with `ruby -v`.
If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons.
-If you are running Ruby 2.1.1 consider upgrading to 2.1.2, because of the high memory usage of Ruby 2.1.1.
+If you are running Ruby 2.1.1 consider upgrading to 2.1.5, because of the high memory usage of Ruby 2.1.1.
Install, update dependencies:
@@ -46,8 +47,8 @@ Download and compile Ruby:
```bash
mkdir /tmp/ruby && cd /tmp/ruby
-curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
-cd ruby-2.1.2
+curl --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.5.tar.gz | tar xz
+cd ruby-2.1.5
./configure --disable-install-rdoc
make
sudo make install
@@ -64,13 +65,13 @@ sudo gem install bundler --no-ri --no-rdoc
```bash
cd /home/git/gitlab
sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
-sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
-sudo -u git -H git checkout 7-3-stable
+sudo -u git -H git checkout 7-9-stable
```
OR
@@ -78,18 +79,23 @@ OR
For GitLab Enterprise Edition:
```bash
-sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
-sudo -u git -H git checkout 7-3-stable-ee
+sudo -u git -H git checkout 7-9-stable-ee
```
## 4. Install additional packages
```bash
-# Add support for lograte for better log file handling
+# Add support for logrotate for better log file handling
sudo apt-get install logrotate
# Install pkg-config and cmake, which is needed for the latest versions of rugged
sudo apt-get install pkg-config cmake
+
+# Install Kerberos header files, which are needed for GitLab EE Kerberos support
+sudo apt-get install libkrb5-dev
+
+# Install nodejs, javascript runtime required for assets
+sudo apt-get install nodejs
```
## 5. Configure Redis to use sockets
@@ -100,6 +106,8 @@ sudo apt-get install pkg-config cmake
sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+ # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
+ sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
# Activate the changes to redis.conf
sudo service redis-server restart
# Add git to the redis group
@@ -118,7 +126,7 @@ sudo apt-get install pkg-config cmake
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.0.1
+sudo -u git -H git checkout v2.6.0
```
## 7. Install libs, migrations, etc.
@@ -153,14 +161,12 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
TIP: to see what changed in `gitlab.yml.example` in this release use next command:
```
-git diff 6-0-stable:config/gitlab.yml.example 7-3-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-9-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/unicorn.rb.example but with your settings.
-* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.0/config.yml.example but with your settings.
-* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-9-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-9-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.0/config.yml.example but with your settings.
* Copy rack attack middleware config
```bash
@@ -173,6 +179,12 @@ sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers
sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
```
+### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-9-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-9-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+
## 9. Start application
sudo service gitlab start
@@ -195,13 +207,84 @@ If all items are green, then congratulations upgrade complete!
When using Google omniauth login, changes of the Google account required.
Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/).
-More details can be found at the [integration documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/google.md).
+More details can be found at the [integration documentation](../../../master/doc/integration/google.md).
-## Things went south? Revert to previous version (6.0)
+## 12. Optional optimizations for GitLab setups with MySQL databases
+
+Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
+
+```
+# Stop GitLab
+sudo service gitlab stop
+
+# Secure your MySQL installation (added in GitLab 6.2)
+sudo mysql_secure_installation
+
+# Login to MySQL
+mysql -u root -p
+
+# do not type the 'mysql>', this is part of the prompt
+
+# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all shown SQL statements
+
+# Convert all tables to correct character set
+SET foreign_key_checks = 0;
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all shown SQL statements
+
+# turn foreign key checks back on
+SET foreign_key_checks = 1;
+
+# Find MySQL users
+mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
+
+# If git user exists and gitlab user does not exist
+# you are done with the database cleanup tasks
+mysql> \q
+
+# If both users exist skip to Delete gitlab user
+
+# Create new user for GitLab (changed in GitLab 6.4)
+# change $password in the command below to a real password you pick
+mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+
+# Grant the git user necessary permissions on the database
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
+
+# Delete the old gitlab user
+mysql> DELETE FROM mysql.user WHERE user='gitlab';
+
+# Quit the database session
+mysql> \q
+
+# Try connecting to the new database with the new user
+sudo -u git -H mysql -u git -p -D gitlabhq_production
+
+# Type the password you replaced $password with earlier
+
+# You should now see a 'mysql>' prompt
+
+# Quit the database session
+mysql> \q
+
+# Update database configuration details
+# See config/database.yml.mysql for latest recommended configuration details
+# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
+# Set production -> pool: 10 (updated in GitLab 5.3)
+# Set production -> username: git
+# Set production -> password: the password your replaced $password with earlier
+sudo -u git -H editor /home/git/gitlab/config/database.yml
+```
+
+## Things went south? Revert to previous version (7.0)
### 1. Revert the code to the previous version
-Follow the [upgrade guide from 5.4 to 6.0](5.4-to-6.0.md), except for the database migration (the backup is already migrated to the previous version).
+Follow the [upgrade guide from 6.9 to 7.0](6.9-to-7.0.md), except for the database migration (the backup is already migrated to the previous version).
### 2. Restore from the backup:
diff --git a/doc/update/7.0-to-7.1.md b/doc/update/7.0-to-7.1.md
index 82bb570873..a4e9be9946 100644
--- a/doc/update/7.0-to-7.1.md
+++ b/doc/update/7.0-to-7.1.md
@@ -1,4 +1,5 @@
# From 7.0 to 7.1
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.0-to-7.1.md) for the most up to date instructions.*
### 0. Backup
diff --git a/doc/update/7.1-to-7.2.md b/doc/update/7.1-to-7.2.md
index 699111f014..88cb63d7d4 100644
--- a/doc/update/7.1-to-7.2.md
+++ b/doc/update/7.1-to-7.2.md
@@ -1,4 +1,5 @@
# From 7.1 to 7.2
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.1-to-7.2.md) for the most up to date instructions.*
## Editable labels
diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md
index 329b763322..18f77d6396 100644
--- a/doc/update/7.2-to-7.3.md
+++ b/doc/update/7.2-to-7.3.md
@@ -1,4 +1,5 @@
# From 7.2 to 7.3
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.2-to-7.3.md) for the most up to date instructions.*
### 0. Backup
@@ -18,12 +19,12 @@ sudo service gitlab stop
```bash
cd /home/git/gitlab
sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
-sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable
```
@@ -32,7 +33,6 @@ OR
For GitLab Enterprise Edition:
```bash
-sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
sudo -u git -H git checkout 7-3-stable-ee
```
@@ -75,7 +75,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
# Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
- sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
+ sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
# Activate the changes to redis.conf
sudo service redis-server restart
# Add git to the redis group
diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md
new file mode 100644
index 0000000000..53e739c06f
--- /dev/null
+++ b/doc/update/7.3-to-7.4.md
@@ -0,0 +1,197 @@
+# From 7.3 to 7.4
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.3-to-7.4.md) for the most up to date instructions.*
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-4-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-4-stable-ee
+```
+
+### 3. Install libs, migrations, etc.
+
+```bash
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 4. Update config files
+
+#### New configuration options for gitlab.yml
+
+There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
+
+```
+git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gitlab.yml.example
+```
+
+#### Change timeout for unicorn
+
+```
+# set timeout to 60
+sudo -u git -H editor config/unicorn.rb
+```
+
+#### Change Nginx HTTPS settings
+
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting
+
+#### MySQL Databases: Update database.yml config file
+
+* Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql](/config/database.yml.mysql)
+
+```
+sudo -u git -H editor config/database.yml
+```
+
+### 5. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+
+### 7. Optional optimizations for GitLab setups with MySQL databases
+
+Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
+
+```
+# Stop GitLab
+sudo service gitlab stop
+
+# Secure your MySQL installation (added in GitLab 6.2)
+sudo mysql_secure_installation
+
+# Login to MySQL
+mysql -u root -p
+
+# do not type the 'mysql>', this is part of the prompt
+
+# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all shown SQL statements
+
+# Convert all tables to correct character set
+SET foreign_key_checks = 0;
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all shown SQL statements
+
+# turn foreign key checks back on
+SET foreign_key_checks = 1;
+
+# Find MySQL users
+mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
+
+# If git user exists and gitlab user does not exist
+# you are done with the database cleanup tasks
+mysql> \q
+
+# If both users exist skip to Delete gitlab user
+
+# Create new user for GitLab (changed in GitLab 6.4)
+# change $password in the command below to a real password you pick
+mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+
+# Grant the git user necessary permissions on the database
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
+
+# Delete the old gitlab user
+mysql> DELETE FROM mysql.user WHERE user='gitlab';
+
+# Quit the database session
+mysql> \q
+
+# Try connecting to the new database with the new user
+sudo -u git -H mysql -u git -p -D gitlabhq_production
+
+# Type the password you replaced $password with earlier
+
+# You should now see a 'mysql>' prompt
+
+# Quit the database session
+mysql> \q
+
+# Update database configuration details
+# See config/database.yml.mysql for latest recommended configuration details
+# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
+# Set production -> pool: 10 (updated in GitLab 5.3)
+# Set production -> username: git
+# Set production -> password: the password your replaced $password with earlier
+sudo -u git -H editor /home/git/gitlab/config/database.yml
+
+# Start GitLab
+sudo service gitlab start
+sudo service nginx restart
+
+# Run thorough check
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+
+## Things went south? Revert to previous version (7.3)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.2 to 7.3](7.2-to-7.3.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+
+
+
diff --git a/doc/update/7.4-to-7.5.md b/doc/update/7.4-to-7.5.md
new file mode 100644
index 0000000000..673eab3c56
--- /dev/null
+++ b/doc/update/7.4-to-7.5.md
@@ -0,0 +1,108 @@
+# From 7.4 to 7.5
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-5-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-5-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.2.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for gitlab.yml
+
+There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
+
+```
+git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (7.4)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.3 to 7.4](7.3-to-7.4.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.5-to-7.6.md b/doc/update/7.5-to-7.6.md
new file mode 100644
index 0000000000..35cd437fdc
--- /dev/null
+++ b/doc/update/7.5-to-7.6.md
@@ -0,0 +1,114 @@
+# From 7.5 to 7.6
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-6-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-6-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.4.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+sudo apt-get install libkrb5-dev
+
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (7.5)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.4 to 7.5](7.4-to-7.5.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.6-to-7.7.md b/doc/update/7.6-to-7.7.md
new file mode 100644
index 0000000000..5924371315
--- /dev/null
+++ b/doc/update/7.6-to-7.7.md
@@ -0,0 +1,119 @@
+# From 7.6 to 7.7
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-7-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-7-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.4.2
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+sudo apt-get install libkrb5-dev
+
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-6-stable:config/gitlab.yml.example origin/7-7-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+### 8. GitHub settings (if applicable)
+
+If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
+only contains a root URL (ex. `https://gitlab.example.com/`)
+
+## Things went south? Revert to previous version (7.6)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.5 to 7.6](7.5-to-7.6.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.7-to-7.8.md b/doc/update/7.7-to-7.8.md
new file mode 100644
index 0000000000..46ca163c1b
--- /dev/null
+++ b/doc/update/7.7-to-7.8.md
@@ -0,0 +1,120 @@
+# From 7.7 to 7.8
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-8-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-8-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.5.4
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+sudo apt-get install libkrb5-dev
+
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-7-stable:config/gitlab.yml.example origin/7-8-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings.
+* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+### 8. GitHub settings (if applicable)
+
+If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
+only contains a root URL (ex. `https://gitlab.example.com/`)
+
+## Things went south? Revert to previous version (7.7)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.6 to 7.7](7.6-to-7.7.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.8-to-7.9.md b/doc/update/7.8-to-7.9.md
new file mode 100644
index 0000000000..28fd433e1c
--- /dev/null
+++ b/doc/update/7.8-to-7.9.md
@@ -0,0 +1,120 @@
+# From 7.8 to 7.9
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-9-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-9-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.6.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+sudo apt-get install nodejs
+
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-8-stable:config/gitlab.yml.example origin/7-9-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings.
+* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+### 8. GitHub settings (if applicable)
+
+If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it
+only contains a root URL (ex. `https://gitlab.example.com/`)
+
+## Things went south? Revert to previous version (7.8)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.7 to 7.8](7.7-to-7.8.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/README.md b/doc/update/README.md
index 30e9137d7b..0472537eeb 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -4,13 +4,13 @@ Depending on the installation method and your GitLab version, there are multiple
- [Omnibus update guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md) contains the steps needed to update a GitLab [package](https://about.gitlab.com/downloads/).
-## Manual Installation
+## Installation from source
-- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab manually.
+- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab from source.
- [The CE to EE update guides](https://gitlab.com/subscribers/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status.
-- [Upgrader](upgrader.md) is an automatic ruby script that performs the update for manual installations.
+- [Upgrader](upgrader.md) is an automatic ruby script that performs the update for installations from source.
- [Patch versions](patch_versions.md) guide includes the steps needed for a patch version, eg. 6.2.0 to 6.2.1.
## Miscellaneous
-- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostrgreSQL.
+- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostgreSQL.
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 695c083d36..50941db25f 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -1,4 +1,5 @@
# Migrating GitLab from MySQL to Postgres
+*Make sure you view this [guide from the `master` branch](../../../master/doc/update/mysql_to_postgresql.md) for the most up to date instructions.*
If you are replacing MySQL with Postgres while keeping GitLab on the same server all you need to do is to export from MySQL, import into Postgres and rebuild the indexes as described below. If you are also moving GitLab to another server, or if you are switching to omnibus-gitlab, you may want to use a GitLab backup file. The second part of this documents explains the procedure to do this.
@@ -11,9 +12,9 @@ sudo service gitlab stop
# Update /home/git/gitlab/config/database.yml
-git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
+git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd mysql-postgresql-converter
-mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production
+mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production -p
python db_converter.py databasename.mysql databasename.psql
# Import the database dump as the application database user
@@ -38,7 +39,7 @@ On non-omnibus installations (distributed using Git) we retrieve the index decla
```
# Clone the database converter on your Postgres-backed GitLab server
cd /tmp
-git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
+git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd /home/git/gitlab
@@ -59,7 +60,7 @@ On omnibus-gitlab we need to get the index declarations from a file called `sche
```
# Clone the database converter on your Postgres-backed GitLab server
cd /tmp
-/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
+/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd /tmp/mysql-postgresql-converter
# Download schema.rb.bundled if necessary
@@ -75,7 +76,7 @@ test -e /opt/gitlab/embedded/service/gitlab-rails/db/schema.rb.bundled || sudo /
## Converting a GitLab backup file from MySQL to Postgres
**Note:** Please make sure to have Python 2.7.x (or higher) installed.
-GitLab backup files (_gitlab_backup.tar) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server.
+GitLab backup files (`_gitlab_backup.tar`) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server.
```
# Stop GitLab
@@ -94,10 +95,10 @@ sudo -u git -H mv tmp/backups/TIMESTAMP_gitlab_backup.tar tmp/backups/postgresql
# Create a separate database dump with PostgreSQL compatibility
cd tmp/backups/postgresql
-sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production
+sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p
# Clone the database converter
-sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git
+sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
# Convert gitlabhq_production.mysql
sudo -u git -H mkdir db
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index c4a77d1280..ad30249255 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -1,4 +1,5 @@
# Universal update guide for patch versions
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/patch_versions.md) for the most up to date instructions.*
For example from 6.2.0 to 6.2.1, also see the [semantic versioning specification](http://semver.org/).
@@ -26,16 +27,14 @@ sudo -u git -H git checkout LATEST_TAG
Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`.
-### 3. Update gitlab-shell if it is not the latest version
+### 3. Update gitlab-shell to the corresponding version
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout LATEST_TAG
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
```
-Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`.
-
### 4. Install libs, migrations, etc.
```bash
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index cf59b0e461..d8476fb345 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -1,4 +1,5 @@
# GitLab Upgrader
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/upgrader.md) for the most up to date instructions.*
GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version.
@@ -10,6 +11,8 @@ If you have local changes to your GitLab repository the script will stash them a
**GitLab Upgrader is available only for GitLab version 6.4.2 or higher.**
+**This script does NOT update gitlab-shell, it needs manual update. See step 5 below.**
+
## 0. Backup
cd /home/git/gitlab
@@ -21,7 +24,7 @@ If you have local changes to your GitLab repository the script will stash them a
## 2. Run GitLab upgrade tool
-Note: GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
+Note: GitLab 7.9 adds nodejs as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
# Starting with GitLab version 7.0 upgrader script has been moved to bin directory
cd /home/git/gitlab
@@ -43,28 +46,31 @@ Check if GitLab and its dependencies are configured correctly:
If all items are green, then congratulations upgrade is complete!
-## 5. Upgrade GitLab Shell (if needed)
+## 5. Upgrade GitLab Shell
-If the `gitlab:check` task reports an outdated version of `gitlab-shell` you should upgrade it.
-
-Upgrade it by running the commands below after replacing 2.0.1 with the correct version number:
+GitLab Shell might be outdated, running the commands below ensures you're using a compatible version:
```
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.0.1
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
```
## One line upgrade command
You've read through the entire guide and probably already did all the steps one by one.
-Here is a one line command with step 1 to 4 for the next time you upgrade:
+Here is a one line command with step 1 to 5 for the next time you upgrade:
```bash
-cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
+cd /home/git/gitlab; \
+ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
sudo service gitlab stop; \
if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \
+ cd /home/git/gitlab-shell; \
+ sudo -u git -H git fetch; \
+ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \
+ cd /home/git/gitlab; \
sudo service gitlab start; \
sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 31791da807..851f50f5e9 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -16,24 +16,29 @@ Triggered when you push to the repository except when pushing tags.
```json
{
+ "object_kind": "push",
"before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
"after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"ref": "refs/heads/master",
"user_id": 4,
"user_name": "John Smith",
+ "user_email": "john@example.com",
"project_id": 15,
"repository": {
"name": "Diaspora",
- "url": "git@example.com:diaspora.git",
+ "url": "git@example.com:mike/diasporadiaspora.git",
"description": "",
- "homepage": "http://example.com/diaspora"
+ "homepage": "http://example.com/mike/diaspora",
+ "git_http_url":"http://example.com/mike/diaspora.git",
+ "git_ssh_url":"git@example.com:mike/diaspora.git",
+ "visibility_level":0
},
"commits": [
{
"id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"message": "Update Catalan translation to e38cb41.",
"timestamp": "2011-12-12T14:27:31+02:00",
- "url": "http://example.com/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
+ "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
"author": {
"name": "Jordi Mallach",
"email": "jordi@softcatala.org"
@@ -43,7 +48,7 @@ Triggered when you push to the repository except when pushing tags.
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"message": "fixed readme",
"timestamp": "2012-01-03T23:36:29+02:00",
- "url": "http://example.com/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"author": {
"name": "GitLab dev user",
"email": "gitlabdev@dv6700.(none)"
@@ -54,6 +59,35 @@ Triggered when you push to the repository except when pushing tags.
}
```
+## Tag events
+
+Triggered when you create (or delete) tags to the repository.
+
+**Request body:**
+
+```json
+{
+ "object_kind": "tag_push",
+ "ref": "refs/tags/v1.0.0",
+ "before": "0000000000000000000000000000000000000000",
+ "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
+ "user_id": 1,
+ "user_name": "John Smith",
+ "project_id": 1,
+ "repository": {
+ "name": "jsmith",
+ "url": "ssh://git@example.com/jsmith/example.git",
+ "description": "",
+ "homepage": "http://example.com/jsmith/example",
+ "git_http_url":"http://example.com/jsmith/example.git",
+ "git_ssh_url":"git@example.com:jsmith/example.git",
+ "visibility_level":0
+ },
+ "commits": [],
+ "total_commits_count": 0
+}
+```
+
## Issues events
Triggered when a new issue is created or an existing issue was updated/closed/reopened.
@@ -63,6 +97,11 @@ Triggered when a new issue is created or an existing issue was updated/closed/re
```json
{
"object_kind": "issue",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
"object_attributes": {
"id": 301,
"title": "New API: create/update/delete file",
@@ -92,6 +131,11 @@ Triggered when a new merge request is created or an existing merge request was u
```json
{
"object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
"object_attributes": {
"id": 99,
"target_branch": "master",
@@ -133,7 +177,9 @@ Triggered when a new merge request is created or an existing merge request was u
"name": "GitLab dev user",
"email": "gitlabdev@dv6700.(none)"
}
- }
+ },
+ "url": "http://example.com/diaspora/merge_requests/1",
+ "action": "open"
}
}
```
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 323ee48f3b..6e70235f5b 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -1,6 +1,14 @@
-- [Workflow](workflow.md)
+# Workflow
+
+- [Feature branch workflow](workflow.md)
- [Project Features](project_features.md)
- [Authorization for merge requests](authorization_for_merge_requests.md)
- [Groups](groups.md)
- [Labels](labels.md)
- [GitLab Flow](gitlab_flow.md)
+- [Notifications](notifications.md)
+- [Migrating from SVN to GitLab](migrating_from_svn.md)
+- [Project importing from GitHub to GitLab](import_projects_from_github.md)
+- [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md)
+- [Protected branches](protected_branches.md)
+- [Web Editor](web_editor.md)
diff --git a/doc/workflow/github_importer/importer.png b/doc/workflow/github_importer/importer.png
new file mode 100644
index 0000000000..5763671757
Binary files /dev/null and b/doc/workflow/github_importer/importer.png differ
diff --git a/doc/workflow/github_importer/new_project_page.png b/doc/workflow/github_importer/new_project_page.png
new file mode 100644
index 0000000000..002f22d81d
Binary files /dev/null and b/doc/workflow/github_importer/new_project_page.png differ
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 70edea9c8d..0e87dc7421 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -1,6 +1,6 @@

-# Introduction
+## Introduction
Version management with git makes branching and merging much easier than older versioning systems such as SVN.
This allows a wide variety of branching strategies and workflows.
@@ -26,12 +26,12 @@ After getting used to these three steps the branching model becomes the challeng
Since many organizations new to git have no conventions how to work with it, it can quickly become a mess.
The biggest problem they run into is that many long running branches that each contain part of the changes are around.
People have a hard time figuring out which branch they should develop on or deploy to production.
-Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](https://guides.github.com/introduction/flow/index.html)
+Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html)
We think there is still room for improvement and will detail a set of practices we call GitLab flow.
-# Git flow and its problems
+## Git flow and its problems
-[
+[
Git flow was one of the first proposals to use git branches and it has gotten a lot of attention.
It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes.
@@ -43,14 +43,14 @@ Since most tools automatically make the master branch the default one and displa
The second problem of git flow is the complexity introduced by the hotfix and release branches.
These branches can be a good idea for some organizations but are overkill for the vast majority of them.
Nowadays most organizations practice continuous delivery which means that your default branch can be deployed.
-This means that hotfixed and release branches can be prevented including all the ceremony they introduce.
+This means that hotfix and release branches can be prevented including all the ceremony they introduce.
An example of this ceremony is the merging back of release branches.
Though specialized tools do exist to solve this, they require documentation and add complexity.
Frequently developers make a mistake and for example changes are only merged into master and not into the develop branch.
The root cause of these errors is that git flow is too complex for most of the use cases.
And doing releases doesn't automatically mean also doing hotfixes.
-# GitHub flow as a simpler alternative
+## GitHub flow as a simpler alternative

@@ -62,13 +62,13 @@ Merging everything into the master branch and deploying often means you minimize
But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues.
With GitLab flow we offer additional guidance for these questions.
-# Production branch with GitLab flow
+## Production branch with GitLab flow

GitHub flow does assume you are able to deploy to production every time you merge a feature branch.
This is possible for SaaS applications but are many cases where this is not possible.
-One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass AppStore validation.
+One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation.
Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times.
In these cases you can make a production branch that reflects the deployed code.
You can deploy a new version by merging in master to the production branch.
@@ -78,7 +78,7 @@ This time is pretty accurate if you automatically deploy your production branch.
If you need a more exact time you can have your deployment script create a tag on each deployment.
This flow prevents the overhead of releasing, tagging and merging that is common to git flow.
-# Environment branches with GitLab flow
+## Environment branches with GitLab flow

@@ -93,14 +93,14 @@ If master is good to go (it should be if you a practicing [continuous delivery](
If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches.
An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](http://teatro.io/).
-# Release branches with GitLab flow
+## Release branches with GitLab flow
-
+
Only in case you need to release software to the outside world you need to work with release branches.
In this case, each branch contains a minor version (2-3-stable, 2-4-stable, etc.).
The stable branch uses master as a starting point and is created as late as possible.
-By branching as late as possible you minimize the time you have to apply bugfixes to multiple branches.
+By branching as late as possible you minimize the time you have to apply bug fixes to multiple branches.
After a release branch is announced, only serious bug fixes are included in the release branch.
If possible these bug fixes are first merged into master and then cherry-picked into the release branch.
This way you can't forget to cherry-pick them into master and encounter the same bug on subsequent releases.
@@ -109,7 +109,7 @@ Every time a bug-fix is included in a release branch the patch version is raised
Some projects also have a stable branch that points to the same commit as the latest released branch.
In this flow it is not common to have a production branch (or git flow master branch).
-# Merge/pull requests with GitLab flow
+## Merge/pull requests with GitLab flow

@@ -134,7 +134,7 @@ If the assigned person does not feel comfortable they can close the merge reques
In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md).
So if you want to merge it into a protected branch you assign it to someone with master authorizations.
-# Issues with GitLab flow
+## Issues with GitLab flow

@@ -168,7 +168,7 @@ In this case it is no problem to reuse the same branch name since it was deleted
At any time there is at most one branch for every issue.
It is possible that one feature branch solves more than one issue.
-# Linking and closing issues from merge requests
+## Linking and closing issues from merge requests

@@ -177,11 +177,11 @@ In GitLab this creates a comment in the issue that the merge requests mentions t
And the merge request shows the linked issues.
These issues are closed once code is merged into the default branch.
-If you only want to make the reference without closing the issue you can also just mention it: "Ducktyping is preferred. #12".
+If you only want to make the reference without closing the issue you can also just mention it: "Duck typing is preferred. #12".
If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue.
-# Squashing commits with rebase
+## Squashing commits with rebase

@@ -189,7 +189,7 @@ With git you can use an interactive rebase (rebase -i) to squash multiple commit
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them.
-When you rebase you change the identifier (SHA1) of the commit and this is confusing.
+When you rebase you change the identifier (SHA-1) of the commit and this is confusing.
If you do that the same change will be known under multiple identifiers and this can cause much confusion.
If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit.
@@ -207,7 +207,7 @@ If you revert a merge and you change your mind, revert the revert instead of mer
Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option.
Git management software will always create a merge commit when you accept a merge request.
-# Do not order commits with rebase
+## Do not order commits with rebase

@@ -231,8 +231,8 @@ The last reason for creating merge commits is having long lived branches that yo
Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI).
At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit.
That's continuous building, and a Good Thing, but there's no integration, so it's not CI.".
-The solution to prevent many merge commits is to keep your feature branches shortlived, the vast majority should take less than one day of work.
-If your feature branches commenly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html).
+The solution to prevent many merge commits is to keep your feature branches short-lived, the vast majority should take less than one day of work.
+If your feature branches commonly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html).
As for the long running branches that take more than one day there are two strategies.
In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time.
In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release.
@@ -244,7 +244,7 @@ Developing software happen in small messy steps and it is OK to have your histor
You can use tools to view the network graphs of commits and understand the messy history that created your code.
If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers.
-# Voting on merge requests
+## Voting on merge requests

@@ -252,7 +252,7 @@ It is common to voice approval or disapproval by using +1 or -1 emoticons.
In GitLab the +1 and -1 are aggregated and shown at the top of the merge request.
As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet.
-# Pushing and removing branches
+## Pushing and removing branches

@@ -266,7 +266,7 @@ This ensures that the branch overview in the repository management software show
This also ensures that when someone reopens the issue a new branch with the same name can be used without problem.
When you reopen an issue you need to create a new merge request.
-# Committing often and with the right message
+## Committing often and with the right message

@@ -282,7 +282,7 @@ Some words that are bad commit messages because they don't contain munch informa
The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number.
To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
-# Testing before merging
+## Testing before merging

@@ -299,7 +299,7 @@ If there are no merge conflicts and the feature branches are short lived the ris
If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests.
If you have long lived feature branches that last for more than a few days you should make your issues smaller.
-# Merging in other code
+## Merging in other code

diff --git a/doc/workflow/gitlab_importer/importer.png b/doc/workflow/gitlab_importer/importer.png
new file mode 100644
index 0000000000..d2a286d8ca
Binary files /dev/null and b/doc/workflow/gitlab_importer/importer.png differ
diff --git a/doc/workflow/gitlab_importer/new_project_page.png b/doc/workflow/gitlab_importer/new_project_page.png
new file mode 100644
index 0000000000..5e239208e1
Binary files /dev/null and b/doc/workflow/gitlab_importer/new_project_page.png differ
diff --git a/doc/workflow/import_projects_from_github.md b/doc/workflow/import_projects_from_github.md
new file mode 100644
index 0000000000..8644b4ffc7
--- /dev/null
+++ b/doc/workflow/import_projects_from_github.md
@@ -0,0 +1,13 @@
+# Project importing from GitHub to GitLab
+
+You can import your existing GitHub projects to GitLab. But keep in mind that it is possible only if
+GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
+To get to the importer page you need to go to "New project" page.
+
+
+
+Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
+
+
+
+To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data.
\ No newline at end of file
diff --git a/doc/workflow/import_projects_from_gitlab_com.md b/doc/workflow/import_projects_from_gitlab_com.md
new file mode 100644
index 0000000000..f4c4e955d4
--- /dev/null
+++ b/doc/workflow/import_projects_from_gitlab_com.md
@@ -0,0 +1,18 @@
+# Project importing from GitLab.com to your private GitLab instance
+
+You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
+GitLab support is enabled on your GitLab instance.
+You can read more about Gitlab support [here](http://doc.gitlab.com/ce/integration/gitlab.html)
+To get to the importer page you need to go to "New project" page.
+
+
+
+Click on the "Import projects from Gitlab.com" link and you will be redirected to GitLab.com
+for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
+
+
+
+
+
+To import a project, you can simple click "Import". The importer will import your repository and issues.
+Once the importer is done, a new GitLab project will be created with your imported data.
\ No newline at end of file
diff --git a/doc/workflow/migrating_from_svn.md b/doc/workflow/migrating_from_svn.md
new file mode 100644
index 0000000000..485db4834e
--- /dev/null
+++ b/doc/workflow/migrating_from_svn.md
@@ -0,0 +1,17 @@
+# Migrating from SVN to GitLab
+
+SVN stands for Subversion and is a version control system (VCS).
+Git is a distributed version control system.
+
+There are some major differences between the two, for more information consult your favorite search engine.
+
+Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at
+[git documentation pages](http://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion).
+
+Apart from the [official git documentation](http://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also
+user created step by step guide for migrating from SVN to GitLab.
+
+[Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca).
+
+## Contribute to this guide
+We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems.
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
new file mode 100644
index 0000000000..17215de677
--- /dev/null
+++ b/doc/workflow/notifications.md
@@ -0,0 +1,71 @@
+# GitLab Notifications
+
+GitLab has notifications system in place to notify a user of events important for the workflow.
+
+## Notification settings
+
+Under user profile page you can find the notification settings.
+
+
+
+Notification settings are divided into three groups:
+
+* Global Settings
+* Group Settings
+* Project Settings
+
+Each of these settings have levels of notification:
+
+* Disabled - turns off notifications
+* Participating - receive notifications from related resources
+* Watch - receive notifications from projects or groups user is a member of
+* Global - notifications as set at the global settings
+
+#### Global Settings
+
+Global Settings are at the bottom of the hierarchy.
+Any setting set here will be overridden by a setting at the group or a project level.
+
+Group or Project settings can use `global` notification setting which will then use
+anything that is set at Global Settings.
+
+#### Group Settings
+
+Group Settings are taking precedence over Global Settings but are on a level below Project Settings.
+This means that you can set a different level of notifications per group while still being able
+to have a finer level setting per project.
+Organization like this is suitable for users that belong to different groups but don't have the
+same need for being notified for every group they are member of.
+
+#### Project Settings
+
+Project Settings are at the top level and any setting placed at this level will take precedence of any
+other setting.
+This is suitable for users that have different needs for notifications per project basis.
+
+## Notification events
+
+Below is the table of events users can be notified of:
+
+| Event | Sent to | Settings level |
+|------------------------------|-------------------------------------------------------------------|------------------------------|
+| New SSH key added | User | Security email, always sent. |
+| New email added | User | Security email, always sent. |
+| New user created | User | Sent on user creation, except for omniauth (LDAP)|
+| New issue created | Issue assignee [1], project members [2] | [1] not disabled, [2] higher than participating |
+| User added to project | User | Sent when user is added to project |
+| Project access level changed | User | Sent when user project access level is changed |
+| User added to group | User | Sent when user is added to group |
+| Project moved | Project members [1] | [1] not disabled |
+| Group access level changed | User | Sent when user group access level is changed |
+| Close issue | Issue author [1], issue assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+| Reassign issue | New issue assignee [1], old issue assignee [2] | [1] [2] not disabled |
+| Reopen issue | Project members [1] | [1] higher than participating |
+| New merge request | MR assignee [1] | [1] not disabled |
+| Reassign merge request | New MR assignee [1], old MR assignee [2] | [1] [2] not disabled |
+| Close merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+| Reopen merge request | Project members [1] | [1] higher than participating |
+| Merge merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+| New comment | Mentioned users [1], users participating [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
+
+
diff --git a/doc/workflow/notifications/settings.png b/doc/workflow/notifications/settings.png
new file mode 100644
index 0000000000..e5b50ee249
Binary files /dev/null and b/doc/workflow/notifications/settings.png differ
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
new file mode 100644
index 0000000000..805f7f8d35
--- /dev/null
+++ b/doc/workflow/protected_branches.md
@@ -0,0 +1,33 @@
+# Protected branches
+
+Permission in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
+
+To prevent people from messing with history or pushing code without review, we've created protected branches.
+
+A protected branch does three simple things:
+
+* it prevents pushes from everybody except users with Master permission
+* it prevents anyone from force pushing to the branch
+* it prevents anyone from deleting the branch
+
+You can make any branch a protected branch. GitLab makes the master branch a protected branch by default.
+
+To protect a branch, user needs to have at least a Master permission level, see [permissions document](permissions/permissions.md).
+
+
+
+Navigate to project settings page and select `protected branches`. From the `Branch` dropdown menu select the branch you want to protect.
+
+Some workflows, like [GitLab workflow](gitlab_flow.md), require all users with write access to submit a Merge request in order to get the code into a protected branch.
+
+Since Masters and Owners can already push to protected branches, that means Developers cannot push to protected branch and need to submit a Merge request.
+
+However, there are workflows where that is not needed and only protecting from force pushes and branch removal is useful.
+
+For those workflows, you can allow everyone with write access to push to a protected branch by selecting `Developers can push` check box.
+
+On already protected branches you can also allow developers to push to the repository by selecting the `Developers can push` check box.
+
+
+
+
diff --git a/doc/workflow/protected_branches/protected_branches1.png b/doc/workflow/protected_branches/protected_branches1.png
new file mode 100644
index 0000000000..5c2a3de5f7
Binary files /dev/null and b/doc/workflow/protected_branches/protected_branches1.png differ
diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png
new file mode 100644
index 0000000000..2dca354136
Binary files /dev/null and b/doc/workflow/protected_branches/protected_branches2.png differ
diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md
new file mode 100644
index 0000000000..7fc8f96b9e
--- /dev/null
+++ b/doc/workflow/web_editor.md
@@ -0,0 +1,26 @@
+# GitLab Web Editor
+
+In GitLab you can create new files and edit existing files using our web editor.
+This is especially useful if you don't have access to a command line or you just want to do a quick fix.
+You can easily access the web editor, depending on the context.
+Let's start from newly created project.
+
+Click on `Add a file`
+to create the first file and open it in the web editor.
+
+
+
+Fill in a file name, some content, a commit message, branch name and press the commit button.
+The file will be saved to the repository.
+
+
+
+You can edit any text file in a repository by pressing the edit button, when
+viewing the file.
+
+
+
+Editing a file is almost the same as creating a new file,
+with as addition the ability to preview your changes in a separate tab. Also you can save your change to another branch by filling out field `branch`
+
+
diff --git a/doc/workflow/web_editor/edit_file.png b/doc/workflow/web_editor/edit_file.png
new file mode 100644
index 0000000000..f480c69ac3
Binary files /dev/null and b/doc/workflow/web_editor/edit_file.png differ
diff --git a/doc/workflow/web_editor/empty_project.png b/doc/workflow/web_editor/empty_project.png
new file mode 100644
index 0000000000..6a049f6bea
Binary files /dev/null and b/doc/workflow/web_editor/empty_project.png differ
diff --git a/doc/workflow/web_editor/new_file.png b/doc/workflow/web_editor/new_file.png
new file mode 100644
index 0000000000..55ebd9e025
Binary files /dev/null and b/doc/workflow/web_editor/new_file.png differ
diff --git a/doc/workflow/web_editor/show_file.png b/doc/workflow/web_editor/show_file.png
new file mode 100644
index 0000000000..9cafcb5510
Binary files /dev/null and b/doc/workflow/web_editor/show_file.png differ
diff --git a/doc/workflow/workflow.md b/doc/workflow/workflow.md
index ab29cfb670..f70e41df84 100644
--- a/doc/workflow/workflow.md
+++ b/doc/workflow/workflow.md
@@ -1,4 +1,4 @@
-# Workflow
+# Feature branch workflow
1. Clone project:
diff --git a/docker/.dockerignore b/docker/.dockerignore
new file mode 100644
index 0000000000..dd449725e1
--- /dev/null
+++ b/docker/.dockerignore
@@ -0,0 +1 @@
+*.md
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 0000000000..4eb280f955
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,37 @@
+FROM ubuntu:14.04
+
+# Install required packages
+RUN apt-get update -q \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -qy --no-install-recommends \
+ ca-certificates \
+ openssh-server \
+ wget
+
+# Download & Install GitLab
+# If the Omnibus package version below is outdated please contribute a merge request to update it.
+# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
+RUN TMP_FILE=$(mktemp); \
+ wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.8.3-omnibus-1_amd64.deb \
+ && dpkg -i $TMP_FILE \
+ && rm -f $TMP_FILE
+
+# Manage SSHD through runit
+RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
+ && mkfifo /opt/gitlab/sv/sshd/supervise/ok \
+ && printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \
+ && chmod a+x /opt/gitlab/sv/sshd/run \
+ && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
+ && mkdir -p /var/run/sshd
+
+# Expose web & ssh
+EXPOSE 80 22
+
+# Declare volumes
+VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"]
+
+# Copy assets
+COPY assets/gitlab.rb /etc/gitlab/
+COPY assets/wrapper /usr/local/bin/
+
+# Wrapper to handle signal, trigger runit and reconfigure GitLab
+CMD ["/usr/local/bin/wrapper"]
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000000..58982a238a
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,68 @@
+What is GitLab?
+===============
+
+GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster. A subscription gives you access to our support team and to GitLab Enterprise Edition that contains extra features aimed at larger organizations.
+
+
+
+
+
+
+How to use this image
+======================
+
+At this moment GitLab doesn't have official Docker images.
+Build your own based on the Omnibus packages with the following command (it assumes you're in the GitLab repo root directory):
+
+```bash
+sudo docker build --tag gitlab_image docker/
+```
+
+We assume using a data volume container, this will simplify migrations and backups.
+This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it.
+
+The directories on data container are:
+
+- `/var/opt/gitlab` for application data
+- `/var/log/gitlab` for logs
+- `/etc/gitlab` for configuration
+
+Create the data container with:
+
+```bash
+sudo docker run --name gitlab_data gitlab_image /bin/true
+```
+
+After creating this run GitLab:
+
+```bash
+sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
+```
+
+It might take a while before the docker container is responding to queries. You can follow the configuration process with `docker logs -f gitlab_app`.
+
+You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker).
+You can login with username `root` and password `5iveL!fe`.
+Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`.
+
+
+How to configure GitLab
+========================
+
+This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
+
+To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor:
+
+```bash
+docker run -ti -e TERM=linux --rm --volumes-from gitlab_data ubuntu
+vi /etc/gitlab/gitlab.rb
+```
+
+**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab.
+
+You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
+
+
+Troubleshooting
+=========================
+Please see the [troubleshooting](troubleshooting.md) file in this directory.
diff --git a/docker/assets/gitlab.rb b/docker/assets/gitlab.rb
new file mode 100644
index 0000000000..7fddf309c0
--- /dev/null
+++ b/docker/assets/gitlab.rb
@@ -0,0 +1,37 @@
+# External URL should be your Docker instance.
+# By default, this example is the "standard" boot2docker IP.
+# Always use port 80 here to force the internal nginx to bind port 80,
+# even if you intend to use another port in Docker.
+external_url "http://192.168.59.103/"
+
+# Prevent Postgres from trying to allocate 25% of total memory
+postgresql['shared_buffers'] = '1MB'
+
+# Configure GitLab to redirect PostgreSQL logs to the data volume
+postgresql['log_directory'] = '/var/log/gitlab/postgresql'
+
+# Some configuration of GitLab
+# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
+gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
+gitlab_rails['gitlab_support_email'] = 'support@example.com'
+gitlab_rails['time_zone'] = 'Europe/Paris'
+
+# SMTP settings
+# You must use an external server, the Docker container does not install an SMTP server
+gitlab_rails['smtp_enable'] = true
+gitlab_rails['smtp_address'] = "smtp.example.com"
+gitlab_rails['smtp_port'] = 587
+gitlab_rails['smtp_user_name'] = "user"
+gitlab_rails['smtp_password'] = "password"
+gitlab_rails['smtp_domain'] = "example.com"
+gitlab_rails['smtp_authentication'] = "plain"
+gitlab_rails['smtp_enable_starttls_auto'] = true
+
+# Enable LDAP authentication
+# gitlab_rails['ldap_enabled'] = true
+# gitlab_rails['ldap_host'] = 'ldap.example.com'
+# gitlab_rails['ldap_port'] = 389
+# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
+# gitlab_rails['ldap_allow_username_or_email_login'] = false
+# gitlab_rails['ldap_uid'] = 'uid'
+# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
diff --git a/docker/assets/wrapper b/docker/assets/wrapper
new file mode 100755
index 0000000000..9e6e7a0590
--- /dev/null
+++ b/docker/assets/wrapper
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+function sigterm_handler() {
+ echo "SIGTERM signal received, try to gracefully shutdown all services..."
+ gitlab-ctl stop
+}
+
+trap "sigterm_handler; exit" TERM
+
+function entrypoint() {
+ # Default is to run runit and reconfigure GitLab
+ gitlab-ctl reconfigure &
+ /opt/gitlab/embedded/bin/runsvdir-start &
+ wait
+}
+
+entrypoint
diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md
new file mode 100644
index 0000000000..b1b70de599
--- /dev/null
+++ b/docker/troubleshooting.md
@@ -0,0 +1,63 @@
+# Troubleshooting
+
+This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245
+But it might contain useful commands for other cases as well.
+
+The configuration to add the postgres log in vim is:
+postgresql['log_directory'] = '/var/log/gitlab/postgresql'
+
+# Commands
+
+```bash
+sudo docker build --tag gitlab_image docker/
+
+sudo docker rm -f gitlab_app
+sudo docker rm -f gitlab_data
+
+sudo docker run --name gitlab_data gitlab_image /bin/true
+
+sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb
+
+sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb
+```
+
+# Interactively
+
+```bash
+# First start a GitLab container without starting GitLab
+# This is almost the same as starting the GitLab container except:
+# - we run interactively (-t -i)
+# - we define TERM=linux because it allows to use arrow keys in vi (!!!)
+# - we choose another startup command (bash)
+sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash
+
+# Configure GitLab to redirect PostgreSQL logs
+echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
+
+# Prevent Postgres from allocating 25% of total memory
+echo "postgresql['shared_buffers'] = '1MB'" >> /etc/gitlab/gitlab.rb
+
+# You can now start GitLab manually from Bash (in the background)
+# Maybe the command below is still missing something to run in the background
+gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start &
+
+# Inspect PostgreSQL config
+cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
+
+# And tail the logs (PostgreSQL log may not exist immediately)
+tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current
+
+# And get the memory
+cat /proc/meminfo
+head /proc/sys/kernel/shmmax /proc/sys/kernel/shmall
+free -m
+
+```
diff --git a/features/admin/applications.feature b/features/admin/applications.feature
new file mode 100644
index 0000000000..2a00e1666c
--- /dev/null
+++ b/features/admin/applications.feature
@@ -0,0 +1,18 @@
+@admin
+Feature: Admin Applications
+ Background:
+ Given I sign in as an admin
+ And I visit applications page
+
+ Scenario: I can manage application
+ Then I click on new application button
+ And I should see application form
+ Then I fill application form out and submit
+ And I see application
+ Then I click edit
+ And I see edit application form
+ Then I change name of application and submit
+ And I see that application was changed
+ Then I visit applications page
+ And I click to remove application
+ Then I see that application is removed
\ No newline at end of file
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index 1a465c1be5..aa365a6ea1 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -20,3 +20,10 @@ Feature: Admin Groups
When I visit admin group page
When I select user "John Doe" from user list as "Reporter"
Then I should see "John Doe" in team list in every project as "Reporter"
+
+ @javascript
+ Scenario: Remove user from group
+ Given we have user "John Doe" in group
+ When I visit admin group page
+ And I remove user "John Doe" from group
+ Then I should not see "John Doe" in team list
diff --git a/features/admin/settings.feature b/features/admin/settings.feature
new file mode 100644
index 0000000000..8fdf0575c2
--- /dev/null
+++ b/features/admin/settings.feature
@@ -0,0 +1,9 @@
+@admin
+Feature: Admin Settings
+ Background:
+ Given I sign in as an admin
+ And I visit admin settings page
+
+ Scenario: Change application settings
+ When I modify settings and save form
+ Then I should see application settings saved
diff --git a/features/admin/users.feature b/features/admin/users.feature
index 278f6a43e9..1a8720dd77 100644
--- a/features/admin/users.feature
+++ b/features/admin/users.feature
@@ -35,3 +35,13 @@ Feature: Admin Users
And I see the secondary email
When I click remove secondary email
Then I should not see secondary email anymore
+
+ Scenario: Show user keys
+ Given user "Pete" with ssh keys
+ And I visit admin users page
+ And click on user "Pete"
+ Then I should see key list
+ And I click on the key title
+ Then I should see key details
+ And I click on remove key
+ Then I should see the key removed
diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature
index 3af93bc373..69b3a77644 100644
--- a/features/dashboard/archived_projects.feature
+++ b/features/dashboard/archived_projects.feature
@@ -10,8 +10,3 @@ Feature: Dashboard Archived Projects
Scenario: I should see non-archived projects on dashboard
Then I should see "Shop" project link
And I should not see "Forum" project link
-
- Scenario: I should see all projects on projects page
- And I visit dashboard projects page
- Then I should see "Shop" project link
- And I should see "Forum" project link
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
index bebaa78e46..1959d32708 100644
--- a/features/dashboard/dashboard.feature
+++ b/features/dashboard/dashboard.feature
@@ -27,11 +27,11 @@ Feature: Dashboard
Scenario: I should see User joined Project event
Given user with name "John Doe" joined project "Shop"
When I visit dashboard page
- Then I should see "John Doe joined project at Shop" event
+ Then I should see "John Doe joined project Shop" event
@javascript
Scenario: I should see User left Project event
Given user with name "John Doe" joined project "Shop"
And user with name "John Doe" left project "Shop"
When I visit dashboard page
- Then I should see "John Doe left project at Shop" event
+ Then I should see "John Doe left project Shop" event
diff --git a/features/profile/group.feature b/features/dashboard/group.feature
similarity index 73%
rename from features/profile/group.feature
rename to features/dashboard/group.feature
index e2fbfde77b..cf4b8d7283 100644
--- a/features/profile/group.feature
+++ b/features/dashboard/group.feature
@@ -1,5 +1,5 @@
-@profile
-Feature: Profile Group
+@dashboard
+Feature: Dashboard Group
Background:
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
@@ -10,18 +10,18 @@ Feature: Profile Group
@javascript
Scenario: Owner should be able to leave from group if he is not the last owner
Given "Mary Jane" is owner of group "Owned"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
When I click on the "Leave" button for group "Owned"
- And I visit profile groups page
+ And I visit dashboard groups page
Then I should not see group "Owned" in group list
Then I should see group "Guest" in group list
@javascript
Scenario: Owner should not be able to leave from group if he is the last owner
Given "Mary Jane" is guest of group "Owned"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
Then I should not see the "Leave" button for group "Owned"
@@ -29,20 +29,28 @@ Feature: Profile Group
@javascript
Scenario: Guest should be able to leave from group
Given "Mary Jane" is guest of group "Guest"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
When I click on the "Leave" button for group "Guest"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should not see group "Guest" in group list
@javascript
Scenario: Guest should be able to leave from group even if he is the only user in the group
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
When I click on the "Leave" button for group "Guest"
- When I visit profile groups page
+ When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should not see group "Guest" in group list
+
+ Scenario: Create a group from dasboard
+ And I visit dashboard groups page
+ And I click new group link
+ And submit form with new group "Samurai" info
+ Then I should be redirected to group "Samurai" page
+ And I should see newly created group "Samurai"
+
diff --git a/features/dashboard/projects.feature b/features/dashboard/projects.feature
deleted file mode 100644
index bb4e84f015..0000000000
--- a/features/dashboard/projects.feature
+++ /dev/null
@@ -1,9 +0,0 @@
-@dashboard
-Feature: Dashboard Projects
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And I visit dashboard projects page
-
- Scenario: I should see projects list
- Then I should see projects list
diff --git a/features/dashboard/starred_projects.feature b/features/dashboard/starred_projects.feature
new file mode 100644
index 0000000000..9dfd2fbab9
--- /dev/null
+++ b/features/dashboard/starred_projects.feature
@@ -0,0 +1,12 @@
+@dashboard
+Feature: Dashboard Starred Projects
+ Background:
+ Given I sign in as a user
+ And public project "Community"
+ And I starred project "Community"
+ And I own project "Shop"
+ And I visit dashboard starred projects page
+
+ Scenario: I should see projects list
+ Then I should see project "Community"
+ And I should not see project "Shop"
diff --git a/features/explore/groups.feature b/features/explore/groups.feature
index b50a3e766c..c11634bd74 100644
--- a/features/explore/groups.feature
+++ b/features/explore/groups.feature
@@ -28,7 +28,6 @@ Feature: Explore Groups
Given group "TestGroup" has internal project "Internal"
When I sign in as a user
And I visit group "TestGroup" issues page
- And I change filter to Everyone's
Then I should see project "Internal" items
And I should not see project "Enterprise" items
@@ -36,7 +35,6 @@ Feature: Explore Groups
Given group "TestGroup" has internal project "Internal"
When I sign in as a user
And I visit group "TestGroup" merge requests page
- And I change filter to Everyone's
Then I should see project "Internal" items
And I should not see project "Enterprise" items
@@ -94,7 +92,6 @@ Feature: Explore Groups
Given group "TestGroup" has public project "Community"
When I sign in as a user
And I visit group "TestGroup" issues page
- And I change filter to Everyone's
Then I should see project "Community" items
And I should see project "Internal" items
And I should not see project "Enterprise" items
@@ -104,7 +101,6 @@ Feature: Explore Groups
Given group "TestGroup" has public project "Community"
When I sign in as a user
And I visit group "TestGroup" merge requests page
- And I change filter to Everyone's
Then I should see project "Community" items
And I should see project "Internal" items
And I should not see project "Enterprise" items
diff --git a/features/groups.feature b/features/groups.feature
index b5ff03db84..05546e0d6e 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -10,14 +10,6 @@ Feature: Groups
Then I should see group "Owned" projects list
And I should see projects activity feed
- Scenario: Create a group from dasboard
- When I visit group "Owned" page
- And I visit dashboard page
- And I click new group link
- And submit form with new group "Samurai" info
- Then I should be redirected to group "Samurai" page
- And I should see newly created group "Samurai"
-
Scenario: I should see group "Owned" issues list
Given project from group "Owned" has issues assigned to me
When I visit group "Owned" issues page
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index d2125e013b..d586167cdf 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -71,6 +71,20 @@ Feature: Profile
And I click on my profile picture
Then I should see my user page
+ Scenario: I can manage application
+ Given I visit profile applications page
+ Then I click on new application button
+ And I should see application form
+ Then I fill application form out and submit
+ And I see application
+ Then I click edit
+ And I see edit application form
+ Then I change name of application and submit
+ And I see that application was changed
+ Then I visit profile applications page
+ And I click to remove application
+ Then I see that application is removed
+
@javascript
Scenario: I change my application theme
Given I visit profile design page
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 8d3e0bd967..05faad4e64 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -106,24 +106,19 @@ Feature: Project Active Tab
And no other sub tabs should be active
And the active main tab should be Commits
- # Sub Tabs: Issues
-
Scenario: On Project Issues/Browse
Given I visit my project's issues page
- Then the active sub tab should be Browse Issues
- And no other sub tabs should be active
- And the active main tab should be Issues
+ Then the active main tab should be Issues
+ And no other main tabs should be active
Scenario: On Project Issues/Milestones
Given I visit my project's issues page
And I click the "Milestones" tab
- Then the active sub tab should be Milestones
- And no other sub tabs should be active
- And the active main tab should be Issues
+ Then the active main tab should be Milestones
+ And no other main tabs should be active
Scenario: On Project Issues/Labels
Given I visit my project's issues page
And I click the "Labels" tab
- Then the active sub tab should be Labels
- And no other sub tabs should be active
- And the active main tab should be Issues
+ Then the active main tab should be Labels
+ And no other main tabs should be active
diff --git a/features/project/archived.feature b/features/project/archived.feature
index 9aac29384b..ad466f4f30 100644
--- a/features/project/archived.feature
+++ b/features/project/archived.feature
@@ -14,15 +14,6 @@ Feature: Project Archived
And I visit project "Forum" page
Then I should see "Archived"
- Scenario: I should not see archived on projects page with no archived projects
- And I visit dashboard projects page
- Then I should not see "Archived"
-
- Scenario: I should see archived on projects page with archived projects
- And project "Forum" is archived
- And I visit dashboard projects page
- Then I should see "Archived"
-
Scenario: I archive project
When project "Shop" has push event
And I visit project "Shop" page
diff --git a/features/project/commits/comments.feature b/features/project/commits/comments.feature
index e176752cfb..c41075d7ad 100644
--- a/features/project/commits/comments.feature
+++ b/features/project/commits/comments.feature
@@ -13,15 +13,10 @@ Feature: Project Commits Comments
Scenario: I can't cancel the main form
Then I should not see the cancel comment button
- @javascript
- Scenario: I can't preview without text
- Given I haven't written any comment text
- Then I should not see the comment preview button
-
@javascript
Scenario: I can preview with text
- Given I write a comment like "Nice"
- Then I should see the comment preview button
+ Given I write a comment like ":+1: Nice"
+ Then The comment preview tab should be display rendered Markdown
@javascript
Scenario: I preview a comment
@@ -32,7 +27,7 @@ Feature: Project Commits Comments
@javascript
Scenario: I can edit after preview
Given I preview a comment text like "Bug fixed :smile:"
- Then I should see the comment edit button
+ Then I should see the comment write tab
@javascript
Scenario: I have a reset form after posting from preview
@@ -46,3 +41,9 @@ Feature: Project Commits Comments
Given I leave a comment like "XML attached"
And I delete a comment
Then I should not see a comment saying "XML attached"
+
+ @javascript
+ Scenario: I can edit a comment with +1
+ Given I leave a comment like "XML attached"
+ And I edit the last comment with a +1
+ Then I should see +1 in the description
diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature
index a145ec84b7..56b9a13678 100644
--- a/features/project/commits/diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
@@ -54,17 +54,11 @@ Feature: Project Commits Diff Comments
Given I leave a diff comment like "Typo, please fix"
Then I should see a discussion reply button
- @javascript
- Scenario: I can't preview without text
- Given I open a diff comment form
- And I haven't written any diff comment text
- Then I should not see the diff comment preview button
-
@javascript
Scenario: I can preview with text
Given I open a diff comment form
And I write a diff comment like ":-1: I don't like this"
- Then I should see the diff comment preview button
+ Then The diff comment preview tab should display rendered Markdown
@javascript
Scenario: I preview a diff comment
@@ -75,7 +69,7 @@ Feature: Project Commits Diff Comments
@javascript
Scenario: I can edit after preview
Given I preview a diff comment text like "Should fix it :smile:"
- Then I should see the diff comment edit button
+ Then I should see the diff comment write tab
@javascript
Scenario: The form gets removed after posting
diff --git a/features/project/edit_issuetracker.feature b/features/project/edit_issuetracker.feature
deleted file mode 100644
index cc0de07ca6..0000000000
--- a/features/project/edit_issuetracker.feature
+++ /dev/null
@@ -1,18 +0,0 @@
-Feature: Project Issue Tracker
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And project "Shop" has issues enabled
- And I visit project "Shop" page
-
- Scenario: I set the issue tracker to "GitLab"
- When I visit edit project "Shop" page
- And change the issue tracker to "GitLab"
- And I save project
- Then I the project should have "GitLab" as issue tracker
-
- Scenario: I set the issue tracker to "Redmine"
- When I visit edit project "Shop" page
- And change the issue tracker to "Redmine"
- And I save project
- Then I the project should have "Redmine" as issue tracker
diff --git a/features/project/fork.feature b/features/project/fork.feature
index d3d1180db0..22f68e5b34 100644
--- a/features/project/fork.feature
+++ b/features/project/fork.feature
@@ -6,9 +6,11 @@ Feature: Project Fork
Scenario: User fork a project
Given I click link "Fork"
+ When I fork to my namespace
Then I should see the forked project page
Scenario: User already has forked the project
Given I already have a project named "Shop" in my namespace
And I click link "Fork"
+ When I fork to my namespace
Then I should see a "Name has already been taken" warning
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 4db8551559..b9031f6f32 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -139,6 +139,15 @@ Feature: Project Issues
And I leave a comment with task markdown
Then I should not see task checkboxes in the comment
+ @javascript
+ Scenario: Issue notes should be editable with +1
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ And I leave a comment with a header containing "Comment with a header"
+ Then The comment with the header should not have an ID
+ And I edit the last comment with a +1
+ Then I should see +1 in the description
+
# Task status in issues list
Scenario: Issues list should display task status
@@ -159,3 +168,45 @@ Feature: Project Issues
Given project "Shop" has "Tasks-closed" closed issue with task markdown
When I visit issue page "Tasks-closed"
Then Task checkboxes should be disabled
+
+ # Issue description preview
+
+ @javascript
+ Scenario: I can't preview without text
+ Given I click link "New Issue"
+ And I haven't written any description text
+ Then The Markdown preview tab should say there is nothing to do
+
+ @javascript
+ Scenario: I can preview with text
+ Given I click link "New Issue"
+ And I write a description like ":+1: Nice"
+ Then The Markdown preview tab should display rendered Markdown
+
+ @javascript
+ Scenario: I preview an issue description
+ Given I click link "New Issue"
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown preview
+ And I should not see the Markdown text field
+
+ @javascript
+ Scenario: I can edit after preview
+ Given I click link "New Issue"
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown write tab
+
+ @javascript
+ Scenario: I can preview when editing an existing issue
+ Given I click link "Release 0.4"
+ And I click link "Edit" for the issue
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown write tab
+
+ @javascript
+ Scenario: I can unsubscribe from issue
+ Given project "Shop" has "Tasks-open" open issue with task markdown
+ When I visit issue page "Tasks-open"
+ Then I should see that I am subscribed
+ When I click button "Unsubscribe"
+ Then I should see that I am unsubscribed
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index d20358a7dc..91dc576f8b 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -187,3 +187,48 @@ Feature: Project Merge Requests
And I visit merge request page "MR-task-open"
And I click link "Close"
Then Task checkboxes should be disabled
+
+ # Description preview
+
+ @javascript
+ Scenario: I can't preview without text
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I haven't written any description text
+ Then The Markdown preview tab should say there is nothing to do
+
+ @javascript
+ Scenario: I can preview with text
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I write a description like ":+1: Nice"
+ Then The Markdown preview tab should display rendered Markdown
+
+ @javascript
+ Scenario: I preview a merge request description
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown preview
+ And I should not see the Markdown text field
+
+ @javascript
+ Scenario: I can edit after preview
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ And I preview a description text like "Bug fixed :smile:"
+ Then I should see the Markdown write tab
+
+ @javascript
+ Scenario: I search merge request
+ Given I click link "All"
+ When I fill in merge request search with "Fe"
+ Then I should see "Feature NS-03" in merge requests
+ And I should not see "Bug NS-04" in merge requests
+
+ @javascript
+ Scenario: I can unsubscribe from merge request
+ Given I visit merge request page "Bug NS-04"
+ Then I should see that I am subscribed
+ When I click button "Unsubscribe"
+ Then I should see that I am unsubscribed
diff --git a/features/project/project.feature b/features/project/project.feature
index 7bb24e013a..3e1fd54bee 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -5,6 +5,19 @@ Feature: Project
And project "Shop" has push event
And I visit project "Shop" page
+ Scenario: I edit the project avatar
+ Given I visit edit project "Shop" page
+ When I change the project avatar
+ And I should see new project avatar
+ And I should see the "Remove avatar" button
+
+ Scenario: I remove the project avatar
+ Given I visit edit project "Shop" page
+ And I have an project avatar
+ When I remove my project avatar
+ Then I should see the default project avatar
+ And I should not see the "Remove avatar" button
+
@javascript
Scenario: I should see project activity
When I visit project "Shop" page
diff --git a/features/project/service.feature b/features/project/service.feature
index af88eaefa8..fdff640ec8 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -19,6 +19,12 @@ Feature: Project Services
And I fill hipchat settings
Then I should see hipchat service settings saved
+ Scenario: Activate hipchat service with custom server
+ When I visit project "Shop" services page
+ And I click hipchat service link
+ And I fill hipchat settings with custom server
+ Then I should see hipchat service settings with custom server saved
+
Scenario: Activate pivotaltracker service
When I visit project "Shop" services page
And I click pivotaltracker service link
@@ -54,3 +60,27 @@ Feature: Project Services
And I click email on push service link
And I fill email on push settings
Then I should see email on push service settings saved
+
+ Scenario: Activate Irker (IRC Gateway) service
+ When I visit project "Shop" services page
+ And I click Irker service link
+ And I fill Irker settings
+ Then I should see Irker service settings saved
+
+ Scenario: Activate Atlassian Bamboo CI service
+ When I visit project "Shop" services page
+ And I click Atlassian Bamboo CI service link
+ And I fill Atlassian Bamboo CI settings
+ Then I should see Atlassian Bamboo CI service settings saved
+
+ Scenario: Activate jetBrains TeamCity CI service
+ When I visit project "Shop" services page
+ And I click jetBrains TeamCity CI service link
+ And I fill jetBrains TeamCity CI settings
+ Then I should see jetBrains TeamCity CI service settings saved
+
+ Scenario: Activate Asana service
+ When I visit project "Shop" services page
+ And I click Asana service link
+ And I fill Asana settings
+ Then I should see Asana service settings saved
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 83f23f66e3..90b966dd64 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -30,26 +30,91 @@ Feature: Project Source Browse Files
And I edit code
And I fill the new file name
And I fill the commit message
- And I click on "Commit changes"
+ And I click on "Commit Changes"
Then I am redirected to the new file
And I should see its new content
+ @javascript
+ Scenario: I can create and commit file and specify new branch
+ Given I click on "new file" link in repo
+ And I edit code
+ And I fill the new file name
+ And I fill the commit message
+ And I fill the new branch name
+ And I click on "Commit Changes"
+ Then I am redirected to the new file on new branch
+ And I should see its new content
+
+ @javascript @tricky
+ Scenario: I can create file in empty repo
+ Given I own an empty project
+ And I visit my empty project page
+ And I create bare repo
+ When I click on "add a file" link
+ And I edit code
+ And I fill the new file name
+ And I fill the commit message
+ And I click on "Commit Changes"
+ Then I am redirected to the new file
+ And I should see its new content
+
+ @javascript
+ Scenario: If I enter an illegal file name I see an error message
+ Given I click on "new file" link in repo
+ And I fill the new file name with an illegal name
+ And I edit code
+ And I fill the commit message
+ And I click on "Commit changes"
+ Then I am on the new file page
+ And I see a commit error message
+
@javascript
Scenario: I can edit file
Given I click on ".gitignore" file in repo
And I click button "Edit"
Then I can edit code
+ Scenario: If the file is binary the edit link is hidden
+ Given I visit a binary file in the repo
+ Then I cannot see the edit button
+
+ Scenario: If I don't have edit permission the edit link is disabled
+ Given public project "Community"
+ And I visit project "Community" source page
+ And I click on ".gitignore" file in repo
+ Then The edit button is disabled
+
@javascript
Scenario: I can edit and commit file
Given I click on ".gitignore" file in repo
And I click button "Edit"
And I edit code
And I fill the commit message
- And I click on "Commit changes"
+ And I click on "Commit Changes"
Then I am redirected to the ".gitignore"
And I should see its new content
+ @javascript
+ Scenario: I can edit and commit file to new branch
+ Given I click on ".gitignore" file in repo
+ And I click button "Edit"
+ And I edit code
+ And I fill the commit message
+ And I fill the new branch name
+ And I click on "Commit Changes"
+ Then I am redirected to the ".gitignore" on new branch
+ And I should see its new content
+
+ @javascript @wip
+ Scenario: If I don't change the content of the file I see an error message
+ Given I click on ".gitignore" file in repo
+ And I click button "edit"
+ And I fill the commit message
+ And I click on "Commit changes"
+ # Test fails because carriage returns are added to the file.
+ Then I am on the ".gitignore" edit file page
+ And I see a commit error message
+
@javascript
Scenario: I can see editing preview
Given I click on ".gitignore" file in repo
diff --git a/features/project/team_management.feature b/features/project/team_management.feature
index 86ea6cd6e9..22393622bb 100644
--- a/features/project/team_management.feature
+++ b/features/project/team_management.feature
@@ -13,7 +13,7 @@ Feature: Project Team Management
@javascript
Scenario: Add user to project
- Given I click link "New Team Member"
+ Given I click link "Add members"
And I select "Mike" as "Reporter"
Then I should see "Mike" in team list as "Reporter"
diff --git a/features/search.feature b/features/search.feature
index 54708c1757..def21e0092 100644
--- a/features/search.feature
+++ b/features/search.feature
@@ -13,15 +13,15 @@ Feature: Search
And project has issues
When I search for "Foo"
And I click "Issues" link
- Then I should see "Foo" link
- And I should not see "Bar" link
+ Then I should see "Foo" link in the search results
+ And I should not see "Bar" link in the search results
Scenario: I should see merge requests I am looking for
And project has merge requests
When I search for "Foo"
When I click "Merge requests" link
- Then I should see "Foo" link
- And I should not see "Bar" link
+ Then I should see "Foo" link in the search results
+ And I should not see "Bar" link in the search results
Scenario: I should see project code I am looking for
When I click project "Shop" link
@@ -33,14 +33,14 @@ Feature: Search
When I click project "Shop" link
And I search for "Foo"
And I click "Issues" link
- Then I should see "Foo" link
- And I should not see "Bar" link
+ Then I should see "Foo" link in the search results
+ And I should not see "Bar" link in the search results
Scenario: I should see project merge requests
And project has merge requests
When I click project "Shop" link
And I search for "Foo"
And I click "Merge requests" link
- Then I should see "Foo" link
- And I should not see "Bar" link
+ Then I should see "Foo" link in the search results
+ And I should not see "Bar" link in the search results
diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature
new file mode 100644
index 0000000000..c2afb63b6d
--- /dev/null
+++ b/features/snippets/public_snippets.feature
@@ -0,0 +1,10 @@
+Feature: Public snippets
+ Scenario: Unauthenticated user should see public snippets
+ Given There is public "Personal snippet one" snippet
+ And I visit snippet page "Personal snippet one"
+ Then I should see snippet "Personal snippet one"
+
+ Scenario: Unauthenticated user should see raw public snippets
+ Given There is public "Personal snippet one" snippet
+ And I visit snippet raw page "Personal snippet one"
+ Then I should see raw snippet "Personal snippet one"
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
index 4c4e3ee2cf..6e8019c326 100644
--- a/features/snippets/snippets.feature
+++ b/features/snippets/snippets.feature
@@ -25,4 +25,4 @@ Feature: Snippets
Scenario: I destroy "Personal snippet one"
Given I visit snippet page "Personal snippet one"
And I click link "Destroy"
- Then I should not see "Personal snippet one" in snippets
+ Then I should not see "Personal snippet one" in snippets
\ No newline at end of file
diff --git a/features/steps/admin/applications.rb b/features/steps/admin/applications.rb
new file mode 100644
index 0000000000..d59088fa3c
--- /dev/null
+++ b/features/steps/admin/applications.rb
@@ -0,0 +1,55 @@
+class Spinach::Features::AdminApplications < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+
+ step 'I click on new application button' do
+ click_on 'New Application'
+ end
+
+ step 'I should see application form' do
+ page.should have_content "New application"
+ end
+
+ step 'I fill application form out and submit' do
+ fill_in :doorkeeper_application_name, with: 'test'
+ fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
+ click_on "Submit"
+ end
+
+ step 'I see application' do
+ page.should have_content "Application: test"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click edit' do
+ click_on "Edit"
+ end
+
+ step 'I see edit application form' do
+ page.should have_content "Edit application"
+ end
+
+ step 'I change name of application and submit' do
+ page.should have_content "Edit application"
+ fill_in :doorkeeper_application_name, with: 'test_changed'
+ click_on "Submit"
+ end
+
+ step 'I see that application was changed' do
+ page.should have_content "test_changed"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click to remove application' do
+ within '.oauth-applications' do
+ click_on "Destroy"
+ end
+ end
+
+ step "I see that application is removed" do
+ page.find(".oauth-applications").should_not have_content "test_changed"
+ end
+end
diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb
index 4f0ba05606..721460b937 100644
--- a/features/steps/admin/groups.rb
+++ b/features/steps/admin/groups.rb
@@ -22,7 +22,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
step 'submit form with new group info' do
- fill_in 'group_name', with: 'gitlab'
+ fill_in 'group_path', with: 'gitlab'
fill_in 'group_description', with: 'Group description'
click_button "Create group"
end
@@ -33,16 +33,15 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
step 'I should be redirected to group page' do
- current_path.should == admin_group_path(Group.last)
+ current_path.should == admin_group_path(Group.find_by(path: 'gitlab'))
end
When 'I select user "John Doe" from user list as "Reporter"' do
- user = User.find_by(name: "John Doe")
- select2(user.id, from: "#user_ids", multiple: true)
- within "#new_team_member" do
+ select2(user_john.id, from: "#user_ids", multiple: true)
+ within "#new_project_member" do
select "Reporter", from: "access_level"
end
- click_button "Add users into group"
+ click_button "Add users to group"
end
step 'I should see "John Doe" in team list in every project as "Reporter"' do
@@ -58,9 +57,29 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
end
+ step 'we have user "John Doe" in group' do
+ current_group.add_user(user_john, Gitlab::Access::REPORTER)
+ end
+
+ step 'I remove user "John Doe" from group' do
+ within "#user_#{user_john.id}" do
+ click_link 'Remove user from group'
+ end
+ end
+
+ step 'I should not see "John Doe" in team list' do
+ within ".group-users-list" do
+ page.should_not have_content "John Doe"
+ end
+ end
+
protected
def current_group
@group ||= Group.first
end
+
+ def user_john
+ @user_john ||= User.find_by(name: "John Doe")
+ end
end
diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb
index 2fd6385fe7..9be4d39d2d 100644
--- a/features/steps/admin/projects.rb
+++ b/features/steps/admin/projects.rb
@@ -15,17 +15,17 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
step 'I should see project details' do
project = Project.first
- current_path.should == admin_project_path(project)
+ current_path.should == admin_namespace_project_path(project.namespace, project)
page.should have_content(project.name_with_namespace)
page.should have_content(project.creator.name)
end
step 'I visit admin project page' do
- visit admin_project_path(project)
+ visit admin_namespace_project_path(project.namespace, project)
end
step 'I transfer project to group \'Web\'' do
- find(:xpath, "//input[@id='namespace_id']").set group.id
+ find(:xpath, "//input[@id='new_namespace_id']").set group.id
click_button 'Transfer'
end
diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb
new file mode 100644
index 0000000000..c2d0d2a3fa
--- /dev/null
+++ b/features/steps/admin/settings.rb
@@ -0,0 +1,18 @@
+class Spinach::Features::AdminSettings < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedAdmin
+ include Gitlab::CurrentSettings
+
+ step 'I modify settings and save form' do
+ uncheck 'Gravatar enabled'
+ fill_in 'Home page url', with: 'https://about.gitlab.com/'
+ click_button 'Save'
+ end
+
+ step 'I should see application settings saved' do
+ current_application_settings.gravatar_enabled.should be_false
+ current_application_settings.home_page_url.should == 'https://about.gitlab.com/'
+ page.should have_content 'Application settings saved successfully'
+ end
+end
diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb
index 546c1bf2a1..e138309724 100644
--- a/features/steps/admin/users.rb
+++ b/features/steps/admin/users.rb
@@ -82,4 +82,36 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
page.should have_content 'Account'
page.should have_content 'Personal projects limit'
end
+
+ step 'user "Pete" with ssh keys' do
+ user = create(:user, name: 'Pete')
+ create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
+ create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
+ end
+
+ step 'click on user "Pete"' do
+ click_link 'Pete'
+ end
+
+ step 'I should see key list' do
+ page.should have_content 'ssh-rsa Key2'
+ page.should have_content 'ssh-rsa Key1'
+ end
+
+ step 'I click on the key title' do
+ click_link 'ssh-rsa Key2'
+ end
+
+ step 'I should see key details' do
+ page.should have_content 'ssh-rsa Key2'
+ page.should have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2'
+ end
+
+ step 'I click on remove key' do
+ click_link 'Remove'
+ end
+
+ step 'I should see the key removed' do
+ page.should_not have_content 'ssh-rsa Key2'
+ end
end
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 1826ead1d5..8508b2a809 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -21,7 +21,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'I see prefilled new Merge Request page' do
- current_path.should == new_project_merge_request_path(@project)
+ current_path.should == new_namespace_project_merge_request_path(@project.namespace, @project)
find("#merge_request_target_project_id").value.should == @project.id.to_s
find("#merge_request_source_branch").value.should == "fix"
find("#merge_request_target_branch").value.should == "master"
@@ -37,8 +37,8 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
)
end
- step 'I should see "John Doe joined project at Shop" event' do
- page.should have_content "John Doe joined project at #{project.name_with_namespace}"
+ step 'I should see "John Doe joined project Shop" event' do
+ page.should have_content "John Doe joined project #{project.name_with_namespace}"
end
step 'user with name "John Doe" left project "Shop"' do
@@ -50,8 +50,8 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
)
end
- step 'I should see "John Doe left project at Shop" event' do
- page.should have_content "John Doe left project at #{project.name_with_namespace}"
+ step 'I should see "John Doe left project Shop" event' do
+ page.should have_content "John Doe left project #{project.name_with_namespace}"
end
step 'I have group with projects' do
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
index 332bfa95d9..3da3d62d0c 100644
--- a/features/steps/dashboard/event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
@@ -29,7 +29,7 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
step 'this project has push event' do
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/new_design",
user_id: @user.id,
diff --git a/features/steps/profile/group.rb b/features/steps/dashboard/group.rb
similarity index 67%
rename from features/steps/profile/group.rb
rename to features/steps/dashboard/group.rb
index 0a10e04e21..8384df2fb5 100644
--- a/features/steps/profile/group.rb
+++ b/features/steps/dashboard/group.rb
@@ -1,4 +1,4 @@
-class Spinach::Features::ProfileGroup < Spinach::FeatureSteps
+class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
include SharedAuthentication
include SharedGroup
include SharedPaths
@@ -41,4 +41,23 @@ class Spinach::Features::ProfileGroup < Spinach::FeatureSteps
step 'I should not see group "Guest" in group list' do
page.should_not have_content("Guest")
end
+
+ step 'I click new group link' do
+ click_link "New Group"
+ end
+
+ step 'submit form with new group "Samurai" info' do
+ fill_in 'group_path', with: 'Samurai'
+ fill_in 'group_description', with: 'Tokugawa Shogunate'
+ click_button "Create group"
+ end
+
+ step 'I should be redirected to group "Samurai" page' do
+ current_path.should == group_path(Group.find_by(name: 'Samurai'))
+ end
+
+ step 'I should see newly created group "Samurai"' do
+ page.should have_content "Samurai"
+ page.should have_content "Tokugawa Shogunate"
+ end
end
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 6b5f88e589..b77113e397 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -10,6 +10,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I should see issues authored by me' do
should_see(authored_issue)
+ should_see(authored_issue_on_public_project)
should_not_see(assigned_issue)
should_not_see(other_issue)
end
@@ -22,6 +23,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I have authored issues' do
authored_issue
+ authored_issue_on_public_project
end
step 'I have assigned issues' do
@@ -33,14 +35,20 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
end
step 'I click "Authored by me" link' do
- within ".scope-filter" do
- click_link 'Created by me'
+ within ".assignee-filter" do
+ click_link "Any"
+ end
+ within ".author-filter" do
+ click_link current_user.name
end
end
step 'I click "All" link' do
- within ".scope-filter" do
- click_link "Everyone's"
+ within ".author-filter" do
+ click_link "Any"
+ end
+ within ".assignee-filter" do
+ click_link "Any"
end
end
@@ -64,6 +72,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
@other_issue ||= create :issue, project: project
end
+ def authored_issue_on_public_project
+ @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project
+ end
+
def project
@project ||= begin
project =create :project
@@ -71,4 +83,8 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
project
end
end
+
+ def public_project
+ @public_project ||= create :project, :public
+ end
end
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 95c378fa20..6261c89924 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -4,13 +4,17 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I should see merge requests assigned to me' do
should_see(assigned_merge_request)
+ should_see(assigned_merge_request_from_fork)
should_not_see(authored_merge_request)
+ should_not_see(authored_merge_request_from_fork)
should_not_see(other_merge_request)
end
step 'I should see merge requests authored by me' do
should_see(authored_merge_request)
+ should_see(authored_merge_request_from_fork)
should_not_see(assigned_merge_request)
+ should_not_see(assigned_merge_request_from_fork)
should_not_see(other_merge_request)
end
@@ -22,10 +26,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I have authored merge requests' do
authored_merge_request
+ authored_merge_request_from_fork
end
step 'I have assigned merge requests' do
assigned_merge_request
+ assigned_merge_request_from_fork
end
step 'I have other merge requests' do
@@ -33,14 +39,20 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
end
step 'I click "Authored by me" link' do
- within ".scope-filter" do
- click_link 'Created by me'
+ within ".assignee-filter" do
+ click_link "Any"
+ end
+ within ".author-filter" do
+ click_link current_user.name
end
end
step 'I click "All" link' do
- within ".scope-filter" do
- click_link "Everyone's"
+ within ".author-filter" do
+ click_link "Any"
+ end
+ within ".assignee-filter" do
+ click_link "Any"
end
end
@@ -53,15 +65,41 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
end
def assigned_merge_request
- @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project
+ @assigned_merge_request ||= create :merge_request,
+ assignee: current_user,
+ target_project: project,
+ source_project: project
end
def authored_merge_request
- @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project
+ @authored_merge_request ||= create :merge_request,
+ source_branch: 'simple_merge_request',
+ author: current_user,
+ target_project: project,
+ source_project: project
end
def other_merge_request
- @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project
+ @other_merge_request ||= create :merge_request,
+ source_branch: '2_3_notes_fix',
+ target_project: project,
+ source_project: project
+ end
+
+ def authored_merge_request_from_fork
+ @authored_merge_request_from_fork ||= create :merge_request,
+ source_branch: 'basic_page',
+ author: current_user,
+ target_project: public_project,
+ source_project: forked_project
+ end
+
+ def assigned_merge_request_from_fork
+ @assigned_merge_request_from_fork ||= create :merge_request,
+ source_branch: 'basic_page_fix',
+ assignee: current_user,
+ target_project: public_project,
+ source_project: forked_project
end
def project
@@ -71,4 +109,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
project
end
end
+
+ def public_project
+ @public_project ||= create :project, :public
+ end
+
+ def forked_project
+ @forked_project ||= Projects::ForkService.new(public_project, current_user).execute
+ end
end
diff --git a/features/steps/dashboard/projects.rb b/features/steps/dashboard/projects.rb
deleted file mode 100644
index 2a34816306..0000000000
--- a/features/steps/dashboard/projects.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class Spinach::Features::DashboardProjects < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- step 'I should see projects list' do
- @user.authorized_projects.all.each do |project|
- page.should have_link project.name_with_namespace
- end
- end
-end
diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb
new file mode 100644
index 0000000000..b9ad2f13e2
--- /dev/null
+++ b/features/steps/dashboard/starred_projects.rb
@@ -0,0 +1,15 @@
+class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+
+ step 'I starred project "Community"' do
+ current_user.toggle_star(Project.find_by(name: 'Community'))
+ end
+
+ step 'I should not see project "Shop"' do
+ within 'aside' do
+ page.should_not have_content('Shop')
+ end
+ end
+end
diff --git a/features/steps/explore/groups.rb b/features/steps/explore/groups.rb
index ccbf6cda07..0c2127d4c4 100644
--- a/features/steps/explore/groups.rb
+++ b/features/steps/explore/groups.rb
@@ -35,7 +35,7 @@ class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
end
step 'I visit group "TestGroup" members page' do
- visit members_group_path(Group.find_by(name: "TestGroup"))
+ visit group_group_members_path(Group.find_by(name: "TestGroup"))
end
step 'I should not see project "Enterprise" items' do
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 8172f7922c..26b71406bd 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -65,7 +65,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
title: "New feature",
project: public_project
)
- visit project_issues_path(public_project)
+ visit namespace_project_issues_path(public_project.namespace, public_project)
end
@@ -84,7 +84,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
title: "New internal feature",
project: internal_project
)
- visit project_issues_path(internal_project)
+ visit namespace_project_issues_path(internal_project.namespace, internal_project)
end
@@ -95,7 +95,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I visit "Community" merge requests page' do
- visit project_merge_requests_path(public_project)
+ visit namespace_project_merge_requests_path(public_project.namespace, public_project)
end
step 'project "Community" has "Bug fix" open merge request' do
@@ -112,7 +112,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I visit "Internal" merge requests page' do
- visit project_merge_requests_path(internal_project)
+ visit namespace_project_merge_requests_path(internal_project.namespace, internal_project)
end
step 'project "Internal" has "Feature implemented" open merge request' do
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 616a297db9..91921f5e21 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -29,12 +29,12 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I select user "Mary Jane" from list with role "Reporter"' do
user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
- click_link 'Add members'
+ click_button 'Add members'
within ".users-group-form" do
select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "access_level"
end
- click_button "Add users into group"
+ click_button "Add users to group"
end
step 'I should see user "John Doe" in team list' do
@@ -72,34 +72,15 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
author: current_user
end
- When 'I click new group link' do
- click_link "New group"
- end
-
- step 'submit form with new group "Samurai" info' do
- fill_in 'group_name', with: 'Samurai'
- fill_in 'group_description', with: 'Tokugawa Shogunate'
- click_button "Create group"
- end
-
- step 'I should be redirected to group "Samurai" page' do
- current_path.should == group_path(Group.last)
- end
-
- step 'I should see newly created group "Samurai"' do
- page.should have_content "Samurai"
- page.should have_content "Tokugawa Shogunate"
- page.should have_content "Currently you are only seeing events from the"
- end
-
step 'I change group "Owned" name to "new-name"' do
fill_in 'group_name', with: 'new-name'
+ fill_in 'group_path', with: 'new-name'
click_button "Save group"
end
step 'I should see new group "Owned" name' do
within ".navbar-gitlab" do
- page.should have_content "group: new-name"
+ page.should have_content "new-name"
end
end
@@ -110,7 +91,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see new group "Owned" avatar' do
- Group.find_by(name: "Owned").avatar.should be_instance_of AttachmentUploader
+ Group.find_by(name: "Owned").avatar.should be_instance_of AvatarUploader
Group.find_by(name: "Owned").avatar.url.should == "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/gitlab_logo.png"
end
@@ -188,15 +169,14 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should see group milestone with descriptions and expiry date' do
- page.should have_content('Lorem Ipsum is simply dummy text of the printing and typesetting industry')
page.should have_content('expires at Aug 20, 2114')
end
step 'I should see group milestone with all issues and MRs assigned to that milestone' do
page.should have_content('Milestone GL-113')
page.should have_content('Progress: 0 closed – 4 open')
- page.should have_link(@issue1.title, href: project_issue_path(@project1, @issue1))
- page.should have_link(@mr3.title, href: project_merge_request_path(@project3, @mr3))
+ page.should have_link(@issue1.title, href: namespace_project_issue_path(@project1.namespace, @project1, @issue1))
+ page.should have_link(@mr3.title, href: namespace_project_merge_request_path(@project3.namespace, @project3, @mr3))
end
protected
diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb
index df96dddd06..13e93618eb 100644
--- a/features/steps/profile/notifications.rb
+++ b/features/steps/profile/notifications.rb
@@ -7,6 +7,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps
end
step 'I should see global notifications settings' do
- page.should have_content "Notifications settings"
+ page.should have_content "Notifications Settings"
end
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index adfaefb164..bfbfe7af19 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -3,7 +3,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
include SharedPaths
step 'I should see my profile info' do
- page.should have_content "Profile settings"
+ page.should have_content "Profile Settings"
end
step 'I change my profile info' do
@@ -29,7 +29,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I should see new avatar' do
- @user.avatar.should be_instance_of AttachmentUploader
+ @user.avatar.should be_instance_of AvatarUploader
@user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png"
end
@@ -136,7 +136,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step "I am not an ldap user" do
- current_user.update_attributes(extern_uid: nil, provider: '')
+ current_user.identities.delete
current_user.ldap_user?.should be_false
end
@@ -187,4 +187,54 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I should see groups I belong to' do
page.should have_css('.profile-groups-avatars', visible: true)
end
+
+ step 'I click on new application button' do
+ click_on 'New Application'
+ end
+
+ step 'I should see application form' do
+ page.should have_content "New application"
+ end
+
+ step 'I fill application form out and submit' do
+ fill_in :doorkeeper_application_name, with: 'test'
+ fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
+ click_on "Submit"
+ end
+
+ step 'I see application' do
+ page.should have_content "Application: test"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click edit' do
+ click_on "Edit"
+ end
+
+ step 'I see edit application form' do
+ page.should have_content "Edit application"
+ end
+
+ step 'I change name of application and submit' do
+ page.should have_content "Edit application"
+ fill_in :doorkeeper_application_name, with: 'test_changed'
+ click_on "Submit"
+ end
+
+ step 'I see that application was changed' do
+ page.should have_content "test_changed"
+ page.should have_content "Application Id"
+ page.should have_content "Secret"
+ end
+
+ step 'I click to remove application' do
+ within '.oauth-applications' do
+ click_on "Destroy"
+ end
+ end
+
+ step "I see that application is removed" do
+ page.find(".oauth-applications").should_not have_content "test_changed"
+ end
end
diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb
index d1e87d4070..ea912e5b4d 100644
--- a/features/steps/profile/ssh_keys.rb
+++ b/features/steps/profile/ssh_keys.rb
@@ -37,9 +37,7 @@ class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps
end
step 'I should not see "Work" ssh key' do
- within "#keys-table" do
- page.should_not have_content "Work"
- end
+ page.should_not have_content "Work"
end
step 'I have ssh key "ssh-rsa Work"' do
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index 83796b0ba8..dd3215adb1 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -89,15 +89,15 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Labels')
end
- step 'the active sub tab should be Browse Issues' do
- ensure_active_sub_tab('Browse Issues')
+ step 'the active sub tab should be Issues' do
+ ensure_active_sub_tab('Issues')
end
- step 'the active sub tab should be Milestones' do
- ensure_active_sub_tab('Milestones')
+ step 'the active main tab should be Milestones' do
+ ensure_active_main_tab('Milestones')
end
- step 'the active sub tab should be Labels' do
- ensure_active_sub_tab('Labels')
+ step 'the active main tab should be Labels' do
+ ensure_active_main_tab('Labels')
end
end
diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb
index afbf4d5950..37ad0c7765 100644
--- a/features/steps/project/archived.rb
+++ b/features/steps/project/archived.rb
@@ -15,7 +15,7 @@ class Spinach::Features::ProjectArchived < Spinach::FeatureSteps
When 'I visit project "Forum" page' do
project = Project.find_by(name: "Forum")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I should not see "Archived"' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index c054e0e828..b2dccf868b 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -8,7 +8,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
commit = @project.repository.commit
page.should have_content(@project.name)
page.should have_content(commit.message[0..20])
- page.should have_content(commit.id.to_s[0..5])
+ page.should have_content(commit.short_id)
end
step 'I click atom feed link' do
@@ -24,7 +24,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I click on commit link' do
- visit project_commit_path(@project, sample_commit.id)
+ visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
end
step 'I see commit info' do
@@ -58,7 +58,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'I visit big commit page' do
Commit::DIFF_SAFE_FILES = 20
- visit project_commit_path(@project, sample_big_commit.id)
+ visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
end
step 'I see big commit warning' do
@@ -68,7 +68,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I visit a commit with an image that changed' do
- visit project_commit_path(@project, sample_image_commit.id)
+ visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id)
end
step 'The diff links to both the previous and current image' do
@@ -78,14 +78,14 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I click side-by-side diff button' do
- click_link "Side-by-side Diff"
+ click_link "Side-by-side"
end
step 'I see side-by-side diff button' do
- page.should have_content "Side-by-side Diff"
+ page.should have_content "Side-by-side"
end
step 'I see inline diff button' do
- page.should have_content "Inline Diff"
+ page.should have_content "Inline"
end
end
diff --git a/features/steps/project/commits/user_lookup.rb b/features/steps/project/commits/user_lookup.rb
index 0622fef43b..63ff84c82e 100644
--- a/features/steps/project/commits/user_lookup.rb
+++ b/features/steps/project/commits/user_lookup.rb
@@ -4,11 +4,11 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
include SharedPaths
step 'I click on commit link' do
- visit project_commit_path(@project, sample_commit.id)
+ visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
end
step 'I click on another commit link' do
- visit project_commit_path(@project, sample_commit.parent_id)
+ visit namespace_project_commit_path(@project.namespace, @project, sample_commit.parent_id)
end
step 'I have user with primary email' do
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index e1062a6ce3..6b85cf74f5 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -3,13 +3,13 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
include SharedPaths
step 'fill project form with valid data' do
- fill_in 'project_name', with: 'Empty'
+ fill_in 'project_path', with: 'Empty'
click_button "Create project"
end
step 'I should see project page' do
page.should have_content "Empty"
- current_path.should == project_path(Project.last)
+ current_path.should == namespace_project_path(Project.last.namespace, Project.last)
end
step 'I should see empty project instuctions' do
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index 914da31322..4bf5cb5fa4 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -24,7 +24,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should be on deploy keys page' do
- current_path.should == project_deploy_keys_path(@project)
+ current_path.should == namespace_project_deploy_keys_path(@project.namespace, @project)
end
step 'I should see newly created deploy key' do
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index da50ba9ced..8e58597db2 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -25,4 +25,10 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I should see a "Name has already been taken" warning' do
page.should have_content "Name has already been taken"
end
+
+ step 'I fork to my namespace' do
+ within '.fork-namespaces' do
+ click_link current_user.name
+ end
+ end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index ccef84cdcc..63ad90e124 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -23,7 +23,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I should see merge request "Merge Request On Forked Project"' do
@project.merge_requests.size.should >= 1
@merge_request = @project.merge_requests.last
- current_path.should == project_merge_request_path(@project, @merge_request)
+ current_path.should == namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
@merge_request.title.should == "Merge Request On Forked Project"
@merge_request.source_project.should == @forked_project
@merge_request.source_branch.should == "fix"
@@ -64,14 +64,14 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I see prefilled new Merge Request page for the forked project' do
- current_path.should == new_project_merge_request_path(@forked_project)
+ current_path.should == new_namespace_project_merge_request_path(@forked_project.namespace, @forked_project)
find("#merge_request_source_project_id").value.should == @forked_project.id.to_s
find("#merge_request_target_project_id").value.should == @project.id.to_s
find("#merge_request_source_branch").value.should have_content "new_design"
find("#merge_request_target_branch").value.should have_content "master"
find("#merge_request_title").value.should == "New Design"
- verify_commit_link(".mr_target_commit",@project)
- verify_commit_link(".mr_source_commit",@forked_project)
+ verify_commit_link(".mr_target_commit", @project)
+ verify_commit_link(".mr_source_commit", @forked_project)
end
step 'I update the merge request title' do
@@ -86,7 +86,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
page.should have_content "An Edited Forked Merge Request"
@project.merge_requests.size.should >= 1
@merge_request = @project.merge_requests.last
- current_path.should == project_merge_request_path(@project, @merge_request)
+ current_path.should == namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
@merge_request.source_project.should == @forked_project
@merge_request.source_branch.should == "fix"
@merge_request.target_branch.should == "master"
@@ -106,7 +106,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I see the edit page prefilled for "Merge Request On Forked Project"' do
- current_path.should == edit_project_merge_request_path(@project, @merge_request)
+ current_path.should == edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
page.should have_content "Edit merge request ##{@merge_request.id}"
find("#merge_request_title").value.should == "Merge Request On Forked Project"
end
@@ -114,7 +114,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
select "Select branch", from: "merge_request_target_branch"
find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s
- find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s
+ find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s
find(:select, "merge_request_source_branch", {}).value.should == ""
find(:select, "merge_request_target_branch", {}).value.should == ""
click_button "Compare branches"
@@ -125,7 +125,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'the target repository should be the original repository' do
- page.should have_select("merge_request_target_project_id", selected: project.path_with_namespace)
+ page.should have_select("merge_request_target_project_id", selected: @project.path_with_namespace)
end
# Verify a link is generated against the correct project
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index ba460ac809..a2807c340f 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -8,16 +8,16 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
When 'I visit project "Shop" graph page' do
project = Project.find_by(name: "Shop")
- visit project_graph_path(project, "master")
+ visit namespace_project_graph_path(project.namespace, project, "master")
end
step 'I visit project "Shop" commits graph page' do
project = Project.find_by(name: "Shop")
- visit commits_project_graph_path(project, "master")
+ visit commits_namespace_project_graph_path(project.namespace, project, "master")
end
step 'page should have commits graphs' do
- page.should have_content "Commits statistic for master"
+ page.should have_content "Commit statistics for master"
page.should have_content "Commits per day of month"
end
end
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index f4b8d372be..4b13520259 100644
--- a/features/steps/project/hooks.rb
+++ b/features/steps/project/hooks.rb
@@ -29,7 +29,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
end
step 'I should see newly created hook' do
- current_path.should == project_hooks_path(current_project)
+ current_path.should == namespace_project_hooks_path(current_project.namespace, current_project)
page.should have_content(@url)
end
@@ -44,7 +44,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
end
step 'hook should be triggered' do
- current_path.should == project_hooks_path(current_project)
+ current_path.should == namespace_project_hooks_path(current_project.namespace, current_project)
page.should have_selector '.flash-notice',
text: 'Hook successfully executed.'
end
diff --git a/features/steps/project/issue_tracker.rb b/features/steps/project/issue_tracker.rb
deleted file mode 100644
index e170029270..0000000000
--- a/features/steps/project/issue_tracker.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-class Spinach::Features::ProjectIssueTracker < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- step 'project "Shop" has issues enabled' do
- @project = Project.find_by(name: "Shop")
- @project ||= create(:project, name: "Shop", namespace: @user.namespace)
- @project.issues_enabled = true
- end
-
- step 'change the issue tracker to "GitLab"' do
- select 'GitLab', from: 'project_issues_tracker'
- end
-
- step 'I the project should have "GitLab" as issue tracker' do
- find_field('project_issues_tracker').value.should == 'gitlab'
- end
-
- step 'change the issue tracker to "Redmine"' do
- select 'Redmine', from: 'project_issues_tracker'
- end
-
- step 'I the project should have "Redmine" as issue tracker' do
- find_field('project_issues_tracker').value.should == 'redmine'
- end
-
- step 'I save project' do
- click_button 'Save changes'
- end
-end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 640603562d..e8ca3f7c17 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -1,5 +1,6 @@
class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
include SharedAuthentication
+ include SharedIssuable
include SharedProject
include SharedNote
include SharedPaths
@@ -17,10 +18,23 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
page.should_not have_content "Tweet control"
end
+ step 'I should see that I am subscribed' do
+ find(".subscribe-button span").text.should == "Unsubscribe"
+ end
+
+ step 'I should see that I am unsubscribed' do
+ sleep 0.2
+ find(".subscribe-button span").text.should == "Subscribe"
+ end
+
step 'I click link "Closed"' do
click_link "Closed"
end
+ step 'I click button "Unsubscribe"' do
+ click_on "Unsubscribe"
+ end
+
step 'I should see "Release 0.3" in issues' do
page.should have_content "Release 0.3"
end
@@ -167,7 +181,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
When 'I visit empty project page' do
project = Project.find_by(name: 'Empty Project')
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I see empty project details with ssh clone info' do
@@ -179,7 +193,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
When "I visit empty project's issues page" do
project = Project.find_by(name: 'Empty Project')
- visit project_issues_path(project)
+ visit namespace_project_issues_path(project.namespace, project)
end
step 'I leave a comment with code block' do
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index 3e3e90824b..6ce34c500c 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
include SharedPaths
step 'I visit \'bug\' label edit page' do
- visit edit_project_label_path(project, bug_label)
+ visit edit_namespace_project_label_path(project.namespace, project, bug_label)
end
step 'I remove label \'bug\'' do
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index 89d7af3c9e..cce87a6d98 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -8,7 +8,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
milestone = @project.milestones.find_by(title: "v2.2")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
- page.should have_content("Browse Issues")
+ page.should have_content("Issues")
end
step 'I click link "v2.2"' do
@@ -28,7 +28,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
milestone = @project.milestones.find_by(title: "v2.3")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
- page.should have_content("Browse Issues")
+ page.should have_content("Issues")
end
step 'project "Shop" has milestone "v2.2"' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index c009568977..6e2f60972b 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -1,5 +1,6 @@
class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
include SharedAuthentication
+ include SharedIssuable
include SharedProject
include SharedNote
include SharedPaths
@@ -55,10 +56,21 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
page.should_not have_content "Bug NS-04"
end
+ step 'I should see that I am subscribed' do
+ find(".subscribe-button span").text.should == "Unsubscribe"
+ end
+
+ step 'I should see that I am unsubscribed' do
+ sleep 0.2
+ find(".subscribe-button span").text.should == "Subscribe"
+ end
+
+ step 'I click button "Unsubscribe"' do
+ click_on "Unsubscribe"
+ end
+
step 'I click link "Close"' do
- within '.page-title' do
- click_link "Close"
- end
+ first(:css, '.close-mr-link').click
end
step 'I submit new merge request "Wiki Feature"' do
@@ -102,16 +114,20 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I switch to the diff tab' do
- visit diffs_project_merge_request_path(project, merge_request)
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
end
step 'I switch to the merge request\'s comments tab' do
- visit project_merge_request_path(project, merge_request)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
step 'I click on the commit in the merge request' do
- within '.mr-commits' do
- click_link sample_commit.id[0..8]
+ within '.merge-request-tabs' do
+ click_link 'Commits'
+ end
+
+ within '.commits' do
+ click_link Commit.truncate_sha(sample_commit.id)
end
end
@@ -153,7 +169,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'merge request is mergeable' do
- page.should have_content 'You can accept this request automatically'
+ page.should have_button 'Accept Merge Request'
end
step 'I modify merge commit message' do
@@ -170,7 +186,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
merge!: true,
)
- click_button "Accept Merge Request"
+ within '.can_be_merged' do
+ click_button "Accept Merge Request"
+ end
end
step 'I should see merged request' do
@@ -180,26 +198,24 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click link "Reopen"' do
- within '.page-title' do
- click_link "Reopen"
- end
+ first(:css, '.reopen-mr-link').click
end
step 'I should see reopened merge request "Bug NS-04"' do
- within '.state-label' do
+ within '.issue-box' do
page.should have_content "Open"
end
end
step 'I click link "Hide inline discussion" of the second file' do
within '.files [id^=diff]:nth-child(2)' do
- click_link "Diff comments"
+ click_link 'Show/Hide comments'
end
end
step 'I click link "Show inline discussion" of the second file' do
within '.files [id^=diff]:nth-child(2)' do
- click_link "Diff comments"
+ click_link 'Show/Hide comments'
end
end
@@ -210,7 +226,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see a comment like "Line is wrong" in the second file' do
- within '.files [id^=diff]:nth-child(2) .note-text' do
+ within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
page.should have_visible_content "Line is wrong"
end
end
@@ -222,7 +238,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see a comment like "Line is wrong here" in the second file' do
- within '.files [id^=diff]:nth-child(2) .note-text' do
+ within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do
page.should have_visible_content "Line is wrong here"
end
end
@@ -235,7 +251,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_button "Add Comment"
end
- within ".files [id^=diff]:nth-child(1) .note-text" do
+ within ".files [id^=diff]:nth-child(1) .note-body > .note-text" do
page.should have_content "Line is correct"
end
end
@@ -250,7 +266,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should still see a comment like "Line is correct" in the first file' do
- within '.files [id^=diff]:nth-child(1) .note-text' do
+ within '.files [id^=diff]:nth-child(1) .note-body > .note-text' do
page.should have_visible_content "Line is correct"
end
end
@@ -264,15 +280,19 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I click Side-by-side Diff tab' do
- click_link 'Side-by-side Diff'
+ find('a', text: 'Side-by-side').trigger('click')
end
step 'I should see comments on the side-by-side diff page' do
- within '.files [id^=diff]:nth-child(1) .note-text' do
+ within '.files [id^=diff]:nth-child(1) .parallel .note-body > .note-text' do
page.should have_visible_content "Line is correct"
end
end
+ step 'I fill in merge request search with "Fe"' do
+ fill_in 'issue_search', with: "Fe"
+ end
+
def merge_request
@merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
end
diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb
index 14fdc72b8b..a15688ace6 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -12,7 +12,7 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
Network::Graph.stub(max_count: 10)
project = Project.find_by(name: "Shop")
- visit project_network_path(project, "master")
+ visit namespace_project_network_path(project.namespace, project, "master")
end
step 'page should select "master" in select box' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index f7fff8e64f..d39c8e7d2d 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -4,7 +4,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
include SharedPaths
step 'change project settings' do
- fill_in 'project_name', with: 'NewName'
+ fill_in 'project_name_edit', with: 'NewName'
uncheck 'project_issues_enabled'
end
@@ -17,17 +17,58 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'change project path settings' do
- fill_in "project_path", with: "new-path"
- click_button "Rename"
+ fill_in 'project_path', with: 'new-path'
+ click_button 'Rename'
end
step 'I should see project with new path settings' do
- project.path.should == "new-path"
+ project.path.should == 'new-path'
+ end
+
+ step 'I change the project avatar' do
+ attach_file(
+ :project_avatar,
+ File.join(Rails.root, 'public', 'gitlab_logo.png')
+ )
+ click_button 'Save changes'
+ @project.reload
+ end
+
+ step 'I should see new project avatar' do
+ @project.avatar.should be_instance_of AvatarUploader
+ url = @project.avatar.url
+ url.should == "/uploads/project/avatar/#{ @project.id }/gitlab_logo.png"
+ end
+
+ step 'I should see the "Remove avatar" button' do
+ page.should have_link('Remove avatar')
+ end
+
+ step 'I have an project avatar' do
+ attach_file(
+ :project_avatar,
+ File.join(Rails.root, 'public', 'gitlab_logo.png')
+ )
+ click_button 'Save changes'
+ @project.reload
+ end
+
+ step 'I remove my project avatar' do
+ click_link 'Remove avatar'
+ @project.reload
+ end
+
+ step 'I should see the default project avatar' do
+ @project.avatar?.should be_false
+ end
+
+ step 'I should not see the "Remove avatar" button' do
+ page.should_not have_link('Remove avatar')
end
step 'I should see project "Shop" version' do
within '.project-side' do
- page.should have_content "Version: 6.7.0.pre"
+ page.should have_content 'Version: 6.7.0.pre'
end
end
@@ -45,12 +86,12 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project "Forum" README' do
- page.should have_link "README.md"
- page.should have_content "Sample repo for testing gitlab features"
+ page.should have_link 'README.md'
+ page.should have_content 'Sample repo for testing gitlab features'
end
step 'I should see project "Shop" README' do
- page.should have_link "README.md"
- page.should have_content "testme"
+ page.should have_link 'README.md'
+ page.should have_content 'testme'
end
end
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
index e54637120c..57c6e39c80 100644
--- a/features/steps/project/redirects.rb
+++ b/features/steps/project/redirects.rb
@@ -13,11 +13,11 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I visit project "Community" page' do
project = Project.find_by(name: 'Community')
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I should see project "Community" home page' do
- Gitlab.config.gitlab.stub(:host).and_return("www.example.com")
+ Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com")
within '.navbar-gitlab .title' do
page.should have_content 'Community'
end
@@ -25,12 +25,12 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I visit project "Enterprise" page' do
project = Project.find_by(name: 'Enterprise')
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I visit project "CommunityDoesNotExist" page' do
project = Project.find_by(name: 'Community')
- visit project_path(project) + 'DoesNotExist'
+ visit namespace_project_path(project.namespace, project) + 'DoesNotExist'
end
step 'I click on "Sign In"' do
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 5bd60f99c8..4b3d79324a 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -4,16 +4,20 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
include SharedPaths
step 'I visit project "Shop" services page' do
- visit project_services_path(@project)
+ visit namespace_project_services_path(@project.namespace, @project)
end
step 'I should see list of available services' do
page.should have_content 'Project services'
page.should have_content 'Campfire'
- page.should have_content 'Hipchat'
+ page.should have_content 'HipChat'
page.should have_content 'GitLab CI'
page.should have_content 'Assembla'
page.should have_content 'Pushover'
+ page.should have_content 'Atlassian Bamboo'
+ page.should have_content 'JetBrains TeamCity'
+ page.should have_content 'Asana'
+ page.should have_content 'Irker (IRC gateway)'
end
step 'I click gitlab-ci service link' do
@@ -32,7 +36,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I click hipchat service link' do
- click_link 'Hipchat'
+ click_link 'HipChat'
end
step 'I fill hipchat settings' do
@@ -46,6 +50,17 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Room').value.should == 'gitlab'
end
+ step 'I fill hipchat settings with custom server' do
+ check 'Active'
+ fill_in 'Room', with: 'gitlab_custom'
+ fill_in 'Token', with: 'secretCustom'
+ fill_in 'Server', with: 'https://chat.example.com'
+ click_button 'Save'
+ end
+
+ step 'I should see hipchat service settings with custom server saved' do
+ find_field('Server').value.should == 'https://chat.example.com'
+ end
step 'I click pivotaltracker service link' do
click_link 'PivotalTracker'
@@ -89,6 +104,22 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Token').value.should == 'verySecret'
end
+ step 'I click Asana service link' do
+ click_link 'Asana'
+ end
+
+ step 'I fill Asana settings' do
+ check 'Active'
+ fill_in 'Api key', with: 'verySecret'
+ fill_in 'Restrict to branch', with: 'master'
+ click_button 'Save'
+ end
+
+ step 'I should see Asana service settings saved' do
+ find_field('Api key').value.should == 'verySecret'
+ find_field('Restrict to branch').value.should == 'master'
+ end
+
step 'I click email on push service link' do
click_link 'Emails on push'
end
@@ -102,18 +133,34 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Recipients').value.should == 'qa@company.name'
end
+ step 'I click Irker service link' do
+ click_link 'Irker (IRC gateway)'
+ end
+
+ step 'I fill Irker settings' do
+ check 'Active'
+ fill_in 'Recipients', with: 'irc://chat.freenode.net/#commits'
+ check 'Colorize messages'
+ click_button 'Save'
+ end
+
+ step 'I should see Irker service settings saved' do
+ find_field('Recipients').value.should == 'irc://chat.freenode.net/#commits'
+ find_field('Colorize messages').value.should == '1'
+ end
+
step 'I click Slack service link' do
click_link 'Slack'
end
step 'I fill Slack settings' do
check 'Active'
- fill_in 'Webhook', with: 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI'
+ fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
click_button 'Save'
end
step 'I should see Slack service settings saved' do
- find_field('Webhook').value.should == 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI'
+ find_field('Webhook').value.should == 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
end
step 'I click Pushover service link' do
@@ -137,4 +184,42 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Priority').find('option[selected]').value.should == '1'
find_field('Sound').find('option[selected]').value.should == 'bike'
end
+
+ step 'I click Atlassian Bamboo CI service link' do
+ click_link 'Atlassian Bamboo CI'
+ end
+
+ step 'I fill Atlassian Bamboo CI settings' do
+ check 'Active'
+ fill_in 'Bamboo url', with: 'http://bamboo.example.com'
+ fill_in 'Build key', with: 'KEY'
+ fill_in 'Username', with: 'user'
+ fill_in 'Password', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see Atlassian Bamboo CI service settings saved' do
+ find_field('Bamboo url').value.should == 'http://bamboo.example.com'
+ find_field('Build key').value.should == 'KEY'
+ find_field('Username').value.should == 'user'
+ end
+
+ step 'I click JetBrains TeamCity CI service link' do
+ click_link 'JetBrains TeamCity CI'
+ end
+
+ step 'I fill JetBrains TeamCity CI settings' do
+ check 'Active'
+ fill_in 'Teamcity url', with: 'http://teamcity.example.com'
+ fill_in 'Build type', with: 'GitlabTest_Build'
+ fill_in 'Username', with: 'user'
+ fill_in 'Password', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see JetBrains TeamCity CI service settings saved' do
+ find_field('Teamcity url').value.should == 'http://teamcity.example.com'
+ find_field('Build type').value.should == 'GitlabTest_Build'
+ find_field('Username').value.should == 'user'
+ end
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index 4a39bfdbb7..343aeb53b1 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -86,7 +86,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I visit snippet page "Snippet one"' do
- visit project_snippet_path(project, project_snippet)
+ visit namespace_project_snippet_path(project.namespace, project, project_snippet)
end
def project_snippet
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 0642302e79..557555aee5 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -11,7 +11,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I should see files from repository for "6d39438"' do
- current_path.should == project_tree_path(@project, "6d39438")
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "6d39438")
page.should have_content ".gitignore"
page.should have_content "LICENSE"
end
@@ -48,9 +48,17 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
click_link 'Edit'
end
+ step 'I cannot see the edit button' do
+ page.should_not have_link 'edit'
+ end
+
+ step 'The edit button is disabled' do
+ page.should have_css '.disabled', text: 'Edit'
+ end
+
step 'I can edit code' do
set_new_content
- evaluate_script('editor.getValue()').should == new_gitignore_content
+ evaluate_script('blob.editor.getValue()').should == new_gitignore_content
end
step 'I edit code' do
@@ -61,20 +69,28 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
fill_in :file_name, with: new_file_name
end
+ step 'I fill the new branch name' do
+ fill_in :new_branch, with: 'new_branch_name'
+ end
+
+ step 'I fill the new file name with an illegal name' do
+ fill_in :file_name, with: '.git'
+ end
+
step 'I fill the commit message' do
fill_in :commit_message, with: 'Not yet a commit message.'
end
step 'I click link "Diff"' do
- click_link 'Diff'
+ click_link 'Preview changes'
end
- step 'I click on "Commit changes"' do
- click_button 'Commit changes'
+ step 'I click on "Commit Changes"' do
+ click_button 'Commit Changes'
end
step 'I click on "Remove"' do
- click_link 'Remove'
+ click_button 'Remove'
end
step 'I click on "Remove file"' do
@@ -91,7 +107,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I can see new file page' do
page.should have_content "New file"
- page.should have_content "File name"
page.should have_content "Commit message"
end
@@ -130,31 +145,58 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I am redirected to the files URL' do
- current_path.should == project_tree_path(@project, 'master')
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, 'master')
end
step 'I am redirected to the ".gitignore"' do
- expect(current_path).to eq(project_blob_path(@project, 'master/.gitignore'))
+ expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore'))
+ end
+
+ step 'I am redirected to the ".gitignore" on new branch' do
+ expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/.gitignore'))
end
step 'I am redirected to the permalink URL' do
- expect(current_path).to eq(project_blob_path(
- @project, @project.repository.commit.sha + '/.gitignore'))
+ expect(current_path).to(
+ eq(namespace_project_blob_path(@project.namespace, @project,
+ @project.repository.commit.sha +
+ '/.gitignore'))
+ )
end
step 'I am redirected to the new file' do
- expect(current_path).to eq(project_blob_path(
- @project, 'master/' + new_file_name))
+ expect(current_path).to eq(namespace_project_blob_path(
+ @project.namespace, @project, 'master/' + new_file_name))
+ end
+
+ step 'I am redirected to the new file on new branch' do
+ expect(current_path).to eq(namespace_project_blob_path(
+ @project.namespace, @project, 'new_branch_name/' + new_file_name))
end
step "I don't see the permalink link" do
expect(page).not_to have_link('permalink')
end
+ step 'I see a commit error message' do
+ expect(page).to have_content('Your changes could not be committed')
+ end
+
+ step 'I create bare repo' do
+ click_link 'Create empty bare repository'
+ end
+
+ step 'I click on "add a file" link' do
+ click_link 'add a file'
+
+ # Remove pre-receive hook so we can push without auth
+ FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
+ end
+
private
def set_new_content
- execute_script("editor.setValue('#{new_gitignore_content}')")
+ execute_script("blob.editor.setValue('#{new_gitignore_content}')")
end
# Content of the gitignore file on the seed repository.
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index 53578ee597..7961fdedad 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -13,7 +13,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see files from repository in markdown' do
- current_path.should == project_tree_path(@project, "markdown")
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown")
page.should have_content "README.md"
page.should have_content "CHANGELOG"
end
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct document rendered' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
page.should have_content "All API requests require authentication"
end
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct directory rendered' do
- current_path.should == project_tree_path(@project, "markdown/doc/raketasks")
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
page.should have_content "backup_restore.md"
page.should have_content "maintenance.md"
end
@@ -52,7 +52,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct doc/api directory rendered' do
- current_path.should == project_tree_path(@project, "markdown/doc/api")
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
page.should have_content "README.md"
page.should have_content "users.md"
end
@@ -62,7 +62,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct maintenance file rendered' do
- current_path.should == project_blob_path(@project, "markdown/doc/raketasks/maintenance.md")
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md")
page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
end
@@ -93,7 +93,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see correct file rendered' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
page.should have_content "Contents"
page.should have_link "Users"
page.should have_link "Rake tasks"
@@ -104,7 +104,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see the correct document file' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/users.md")
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
page.should have_content "Get a list of users."
end
@@ -115,100 +115,100 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
# Markdown branch
When 'I visit markdown branch' do
- visit project_tree_path(@project, "markdown")
+ visit namespace_project_tree_path(@project.namespace, @project, "markdown")
end
When 'I visit markdown branch "README.md" blob' do
- visit project_blob_path(@project, "markdown/README.md")
+ visit namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
end
When 'I visit markdown branch "d" tree' do
- visit project_tree_path(@project, "markdown/d")
+ visit namespace_project_tree_path(@project.namespace, @project, "markdown/d")
end
When 'I visit markdown branch "d/README.md" blob' do
- visit project_blob_path(@project, "markdown/d/README.md")
+ visit namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
end
step 'I should see files from repository in markdown branch' do
- current_path.should == project_tree_path(@project, "markdown")
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown")
page.should have_content "README.md"
page.should have_content "CHANGELOG"
end
step 'I see correct file rendered in markdown branch' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
page.should have_content "Contents"
page.should have_link "Users"
page.should have_link "Rake tasks"
end
step 'I should see correct document rendered for markdown branch' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/README.md")
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
page.should have_content "All API requests require authentication"
end
step 'I should see correct directory rendered for markdown branch' do
- current_path.should == project_tree_path(@project, "markdown/doc/raketasks")
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
page.should have_content "backup_restore.md"
page.should have_content "maintenance.md"
end
step 'I should see the users document file in markdown branch' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/users.md")
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
page.should have_content "Get a list of users."
end
# Expected link contents
step 'The link with text "empty" should have url "tree/markdown"' do
- find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown")
+ find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown")
end
step 'The link with text "empty" should have url "blob/markdown/README.md"' do
- find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md")
+ find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
end
step 'The link with text "empty" should have url "tree/markdown/d"' do
- find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d")
+ find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown/d")
end
step 'The link with text "empty" should have '\
'url "blob/markdown/d/README.md"' do
- find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md")
+ find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
end
step 'The link with text "ID" should have url "tree/markdownID"' do
- find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
+ find('a', text: /^#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
end
step 'The link with text "/ID" should have url "tree/markdownID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
+ find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
end
step 'The link with text "README.mdID" '\
'should have url "blob/markdown/README.mdID"' do
- find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ find('a', text: /^README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
end
step 'The link with text "d/README.mdID" should have '\
'url "blob/markdown/d/README.mdID"' do
- find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
+ find('a', text: /^d\/README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "d/markdown/README.md") + '#id'
end
step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
- find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ find('a', text: /^#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
end
step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
+ find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
end
# Wiki
step 'I go to wiki page' do
click_link "Wiki"
- current_path.should == project_wiki_path(@project, "home")
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home")
end
step 'I add various links to the wiki page' do
@@ -218,7 +218,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'Wiki page should have added links' do
- current_path.should == project_wiki_path(@project, "home")
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home")
page.should have_content "test GitLab API doc Rake tasks"
end
@@ -237,13 +237,13 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see new wiki page named test' do
- current_path.should == project_wiki_path(@project, "test")
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "test")
page.should have_content "Editing"
end
When 'I go back to wiki page home' do
- visit project_wiki_path(@project, "home")
- current_path.should == project_wiki_path(@project, "home")
+ visit namespace_project_wiki_path(@project.namespace, @project, "home")
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home")
end
step 'I click on GitLab API doc link' do
@@ -251,7 +251,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see Gitlab API document' do
- current_path.should == project_wiki_path(@project, "api")
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "api")
page.should have_content "Editing"
end
@@ -260,13 +260,13 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see Rake tasks directory' do
- current_path.should == project_wiki_path(@project, "raketasks")
+ current_path.should == namespace_project_wiki_path(@project.namespace, @project, "raketasks")
page.should have_content "Editing"
end
step 'I go directory which contains README file' do
- visit project_tree_path(@project, "markdown/doc/api")
- current_path.should == project_tree_path(@project, "markdown/doc/api")
+ visit namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
end
step 'I click on a relative link in README' do
@@ -274,7 +274,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see the correct markdown' do
- current_path.should == project_blob_path(@project, "markdown/doc/api/users.md")
+ current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
page.should have_content "List users"
end
diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb
index 7907f2a6fe..0eefe2b568 100644
--- a/features/steps/project/team_management.rb
+++ b/features/steps/project/team_management.rb
@@ -15,18 +15,18 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
page.should have_content(user.username)
end
- step 'I click link "New Team Member"' do
- click_link "New project member"
+ step 'I click link "Add members"' do
+ find(:css, 'button.btn-new').click
end
step 'I select "Mike" as "Reporter"' do
user = User.find_by(name: "Mike")
- select2(user.id, from: "#user_ids", multiple: true)
- within "#new_project_member" do
+ within ".users-project-form" do
+ select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "access_level"
end
- click_button "Add users"
+ click_button "Add users to project"
end
step 'I should see "Mike" in team list as "Reporter"' do
@@ -42,9 +42,13 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
end
step 'I change "Sam" role to "Reporter"' do
- user = User.find_by(name: "Sam")
- within "#user_#{user.id}" do
+ project = Project.find_by(name: "Shop")
+ user = User.find_by(name: 'Sam')
+ project_member = project.project_members.find_by(user_id: user.id)
+ within "#project_member_#{project_member.id}" do
+ click_button "Edit access level"
select "Reporter", from: "project_member_access_level"
+ click_button "Save"
end
end
@@ -100,7 +104,10 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
end
step 'I click cancel link for "Sam"' do
- within "#user_#{User.find_by(name: 'Sam').id}" do
+ project = Project.find_by(name: "Shop")
+ user = User.find_by(name: 'Sam')
+ project_member = project.project_members.find_by(user_id: user.id)
+ within "#project_member_#{project_member.id}" do
click_link('Remove user from team')
end
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index aa00818c60..cd7d5eac24 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -11,7 +11,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should be redirected back to the Edit Home Wiki page' do
- current_path.should == project_wiki_path(project, :home)
+ current_path.should == namespace_project_wiki_path(project.namespace, project, :home)
end
step 'I create the Wiki Home page' do
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I browse to that Wiki page' do
- visit project_wiki_path(project, @page)
+ visit namespace_project_wiki_path(project.namespace, project, @page)
end
step 'I click on the Edit button' do
@@ -50,7 +50,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should be redirected back to that Wiki page' do
- current_path.should == project_wiki_path(project, @page)
+ current_path.should == namespace_project_wiki_path(project.namespace, project, @page)
end
step 'That page has two revisions' do
@@ -90,7 +90,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I browse to wiki page with images' do
- visit project_wiki_path(project, @wiki_page)
+ visit namespace_project_wiki_path(project.namespace, project, @wiki_page)
end
step 'I click on existing image link' do
diff --git a/features/steps/search.rb b/features/steps/search.rb
index f3d8bd80f1..6f0e038c4d 100644
--- a/features/steps/search.rb
+++ b/features/steps/search.rb
@@ -59,11 +59,11 @@ class Spinach::Features::Search < Spinach::FeatureSteps
create(:merge_request, :simple, title: "Bar", source_project: project, target_project: project)
end
- step 'I should see "Foo" link' do
- page.should have_link "Foo"
+ step 'I should see "Foo" link in the search results' do
+ find(:css, '.search-results').should have_link 'Foo'
end
- step 'I should not see "Bar" link' do
- page.should_not have_link "Bar"
+ step 'I should not see "Bar" link in the search results' do
+ find(:css, '.search-results').should_not have_link 'Bar'
end
end
diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb
index f41b59a6f2..9beb688bd1 100644
--- a/features/steps/shared/active_tab.rb
+++ b/features/steps/shared/active_tab.rb
@@ -2,7 +2,7 @@ module SharedActiveTab
include Spinach::DSL
def ensure_active_main_tab(content)
- find('.main-nav li.active').should have_content(content)
+ find('.nav-sidebar > li.active').should have_content(content)
end
def ensure_active_sub_tab(content)
@@ -10,11 +10,11 @@ module SharedActiveTab
end
def ensure_active_sub_nav(content)
- find('div.content ul.nav-stacked-menu li.active').should have_content(content)
+ find('.sidebar-subnav > li.active').should have_content(content)
end
step 'no other main tabs should be active' do
- page.should have_selector('.main-nav li.active', count: 1)
+ page.should have_selector('.nav-sidebar > li.active', count: 1)
end
step 'no other sub tabs should be active' do
@@ -22,11 +22,11 @@ module SharedActiveTab
end
step 'no other sub navs should be active' do
- page.should have_selector('div.content ul.nav-stacked-menu li.active', count: 1)
+ page.should have_selector('.sidebar-subnav > li.active', count: 1)
end
step 'the active main tab should be Home' do
- ensure_active_main_tab('Activity')
+ ensure_active_main_tab('Your Projects')
end
step 'the active main tab should be Projects' do
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 10f3ed90b5..510e0f0f93 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -32,7 +32,7 @@ module SharedDiffNote
click_diff_line(sample_commit.line_code)
within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Should fix it :smile:"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
end
@@ -41,7 +41,7 @@ module SharedDiffNote
within("#{diff_file_selector} form[rel$='#{sample_commit.del_line_code}']") do
fill_in "note[note]", with: "DRY this up"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
end
@@ -71,15 +71,16 @@ module SharedDiffNote
end
end
- step 'I should not see the diff comment preview button' do
+ step 'The diff comment preview tab should say there is nothing to do' do
within(diff_file_selector) do
- page.should have_css(".js-note-preview-button", visible: false)
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
end
step 'I should not see the diff comment text field' do
within(diff_file_selector) do
- page.should have_css(".js-note-text", visible: false)
+ expect(find('.js-note-text')).not_to be_visible
end
end
@@ -114,7 +115,7 @@ module SharedDiffNote
end
step 'I should see add a diff comment button' do
- page.should have_css(".js-add-diff-note-button", visible: false)
+ page.should have_css('.js-add-diff-note-button', visible: true)
end
step 'I should see an empty diff comment form' do
@@ -131,27 +132,28 @@ module SharedDiffNote
step 'I should see the diff comment preview' do
within("#{diff_file_selector} form") do
- page.should have_css(".js-note-preview", visible: false)
+ expect(page).to have_css('.js-md-preview', visible: true)
end
end
- step 'I should see the diff comment edit button' do
+ step 'I should see the diff comment write tab' do
within(diff_file_selector) do
- page.should have_css(".js-note-write-button", visible: true)
+ expect(page).to have_css('.js-md-write-button', visible: true)
end
end
- step 'I should see the diff comment preview button' do
+ step 'The diff comment preview tab should display rendered Markdown' do
within(diff_file_selector) do
- page.should have_css(".js-note-preview-button", visible: true)
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end
end
step 'I should see two separate previews' do
within(diff_file_selector) do
- page.should have_css(".js-note-preview", visible: true, count: 2)
- page.should have_content("Should fix it")
- page.should have_content("DRY this up")
+ expect(page).to have_css('.js-md-preview', visible: true, count: 2)
+ expect(page).to have_content('Should fix it')
+ expect(page).to have_content('DRY this up')
end
end
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
new file mode 100644
index 0000000000..41db2612f2
--- /dev/null
+++ b/features/steps/shared/issuable.rb
@@ -0,0 +1,15 @@
+module SharedIssuable
+ include Spinach::DSL
+
+ def edit_issuable
+ find(:css, '.issuable-edit').click
+ end
+
+ step 'I click link "Edit" for the merge request' do
+ edit_issuable
+ end
+
+ step 'I click link "Edit" for the issue' do
+ edit_issuable
+ end
+end
diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb
index 8bf138065b..e71700880c 100644
--- a/features/steps/shared/markdown.rb
+++ b/features/steps/shared/markdown.rb
@@ -54,4 +54,49 @@ EOT
'div.description li.task-list-item input[type="checkbox"]:disabled'
)
end
+
+ step 'I should not see the Markdown preview' do
+ expect(find('.gfm-form .js-md-preview')).not_to be_visible
+ end
+
+ step 'The Markdown preview tab should say there is nothing to do' do
+ within('.gfm-form') do
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_content('Nothing to preview.')
+ end
+ end
+
+ step 'I should not see the Markdown text field' do
+ expect(find('.gfm-form textarea')).not_to be_visible
+ end
+
+ step 'I should see the Markdown write tab' do
+ expect(find('.gfm-form')).to have_css('.js-md-write-button', visible: true)
+ end
+
+ step 'I should see the Markdown preview' do
+ expect(find('.gfm-form')).to have_css('.js-md-preview', visible: true)
+ end
+
+ step 'The Markdown preview tab should display rendered Markdown' do
+ within('.gfm-form') do
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
+ end
+ end
+
+ step 'I write a description like ":+1: Nice"' do
+ find('.gfm-form').fill_in 'Description', with: ':+1: Nice'
+ end
+
+ step 'I preview a description text like "Bug fixed :smile:"' do
+ within('.gfm-form') do
+ fill_in 'Description', with: 'Bug fixed :smile:'
+ find('.js-md-preview-button').click
+ end
+ end
+
+ step 'I haven\'t written any description text' do
+ find('.gfm-form').fill_in 'Description', with: ''
+ end
end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 2b2cb47a71..583746d447 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -23,7 +23,7 @@ module SharedNote
step 'I preview a comment text like "Bug fixed :smile:"' do
within(".js-main-target-form") do
fill_in "note[note]", with: "Bug fixed :smile:"
- find(".js-note-preview-button").trigger("click")
+ find('.js-md-preview-button').click
end
end
@@ -33,9 +33,9 @@ module SharedNote
end
end
- step 'I write a comment like "Nice"' do
+ step 'I write a comment like ":+1: Nice"' do
within(".js-main-target-form") do
- fill_in "note[note]", with: "Nice"
+ fill_in 'note[note]', with: ':+1: Nice'
end
end
@@ -51,19 +51,20 @@ module SharedNote
step 'I should not see the comment preview' do
within(".js-main-target-form") do
- page.should have_css(".js-note-preview", visible: false)
+ expect(find('.js-md-preview')).not_to be_visible
end
end
- step 'I should not see the comment preview button' do
+ step 'The comment preview tab should say there is nothing to do' do
within(".js-main-target-form") do
- page.should have_css(".js-note-preview-button", visible: false)
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_content('Nothing to preview.')
end
end
step 'I should not see the comment text field' do
within(".js-main-target-form") do
- page.should have_css(".js-note-text", visible: false)
+ expect(find('.js-note-text')).not_to be_visible
end
end
@@ -79,21 +80,22 @@ module SharedNote
end
end
- step 'I should see the comment edit button' do
+ step 'I should see the comment write tab' do
within(".js-main-target-form") do
- page.should have_css(".js-note-write-button", visible: true)
+ expect(page).to have_css('.js-md-write-button', visible: true)
+ end
+ end
+
+ step 'The comment preview tab should be display rendered Markdown' do
+ within(".js-main-target-form") do
+ find('.js-md-preview-button').click
+ expect(find('.js-md-preview')).to have_css('img.emoji', visible: true)
end
end
step 'I should see the comment preview' do
within(".js-main-target-form") do
- page.should have_css(".js-note-preview", visible: true)
- end
- end
-
- step 'I should see the comment preview button' do
- within(".js-main-target-form") do
- page.should have_css(".js-note-preview-button", visible: true)
+ expect(page).to have_css('.js-md-preview', visible: true)
end
end
@@ -114,7 +116,7 @@ module SharedNote
end
step 'The comment with the header should not have an ID' do
- within(".note-text") do
+ within(".note-body > .note-text") do
page.should have_content("Comment with a header")
page.should_not have_css("#comment-with-a-header")
end
@@ -133,4 +135,21 @@ module SharedNote
'li.note div.timeline-content input[type="checkbox"]'
)
end
+
+ step 'I edit the last comment with a +1' do
+ find(".note").hover
+ find('.js-note-edit').click
+
+ within(".current-note-edit-form") do
+ fill_in 'note[note]', with: '+1 Awesome!'
+ click_button 'Save Comment'
+ sleep 0.05
+ end
+ end
+
+ step 'I should see +1 in the description' do
+ within(".note") do
+ page.should have_content("+1 Awesome!")
+ end
+ end
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 1f238f8bef..e3cf1b92cd 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -1,6 +1,7 @@
module SharedPaths
include Spinach::DSL
include RepoHelpers
+ include DashboardHelper
step 'I visit new project page' do
visit new_project_path
@@ -31,7 +32,7 @@ module SharedPaths
end
step 'I visit group "Owned" members page' do
- visit members_group_path(Group.find_by(name:"Owned"))
+ visit group_group_members_path(Group.find_by(name:"Owned"))
end
step 'I visit group "Owned" settings page' do
@@ -51,7 +52,7 @@ module SharedPaths
end
step 'I visit group "Guest" members page' do
- visit members_group_path(Group.find_by(name:"Guest"))
+ visit group_group_members_path(Group.find_by(name:"Guest"))
end
step 'I visit group "Guest" settings page' do
@@ -71,11 +72,11 @@ module SharedPaths
end
step 'I visit dashboard issues page' do
- visit issues_dashboard_path
+ visit assigned_issues_dashboard_path
end
step 'I visit dashboard merge requests page' do
- visit merge_requests_dashboard_path
+ visit assigned_mrs_dashboard_path
end
step 'I visit dashboard search page' do
@@ -86,6 +87,18 @@ module SharedPaths
visit help_path
end
+ step 'I visit dashboard groups page' do
+ visit dashboard_groups_path
+ end
+
+ step 'I should be redirected to the dashboard groups page' do
+ current_path.should == dashboard_groups_path
+ end
+
+ step 'I visit dashboard starred projects page' do
+ visit starred_dashboard_projects_path
+ end
+
# ----------------------------------------
# Profile
# ----------------------------------------
@@ -94,6 +107,10 @@ module SharedPaths
visit profile_path
end
+ step 'I visit profile applications page' do
+ visit applications_profile_path
+ end
+
step 'I visit profile password page' do
visit edit_profile_password_path
end
@@ -114,14 +131,6 @@ module SharedPaths
visit history_profile_path
end
- step 'I visit profile groups page' do
- visit profile_groups_path
- end
-
- step 'I should be redirected to the profile groups page' do
- current_path.should == profile_groups_path
- end
-
# ----------------------------------------
# Admin
# ----------------------------------------
@@ -131,7 +140,7 @@ module SharedPaths
end
step 'I visit admin projects page' do
- visit admin_projects_path
+ visit admin_namespaces_projects_path
end
step 'I visit admin users page' do
@@ -162,59 +171,72 @@ module SharedPaths
visit admin_teams_path
end
+ step 'I visit admin settings page' do
+ visit admin_application_settings_path
+ end
+
+ step 'I visit applications page' do
+ visit admin_applications_path
+ end
+
# ----------------------------------------
# Generic Project
# ----------------------------------------
step "I visit my project's home page" do
- visit project_path(@project)
+ visit namespace_project_path(@project.namespace, @project)
end
step "I visit my project's settings page" do
- visit edit_project_path(@project)
+ visit edit_namespace_project_path(@project.namespace, @project)
end
step "I visit my project's files page" do
- visit project_tree_path(@project, root_ref)
+ visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+ end
+
+ step 'I visit a binary file in the repo' do
+ visit namespace_project_blob_path(@project.namespace, @project, File.join(
+ root_ref, 'files/images/logo-black.png'))
end
step "I visit my project's commits page" do
- visit project_commits_path(@project, root_ref, {limit: 5})
+ visit namespace_project_commits_path(@project.namespace, @project, root_ref, {limit: 5})
end
step "I visit my project's commits page for a specific path" do
- visit project_commits_path(@project, root_ref + "/app/models/project.rb", {limit: 5})
+ visit namespace_project_commits_path(@project.namespace, @project, root_ref + "/app/models/project.rb", {limit: 5})
end
step 'I visit my project\'s commits stats page' do
- visit stats_project_repository_path(@project)
+ visit stats_namespace_project_repository_path(@project.namespace, @project)
end
step "I visit my project's network page" do
# Stub Graph max_size to speed up test (10 commits vs. 650)
Network::Graph.stub(max_count: 10)
- visit project_network_path(@project, root_ref)
+ visit namespace_project_network_path(@project.namespace, @project, root_ref)
end
step "I visit my project's issues page" do
- visit project_issues_path(@project)
+ visit namespace_project_issues_path(@project.namespace, @project)
end
step "I visit my project's merge requests page" do
- visit project_merge_requests_path(@project)
+ visit namespace_project_merge_requests_path(@project.namespace, @project)
end
step "I visit my project's wiki page" do
- visit project_wiki_path(@project, :home)
+ visit namespace_project_wiki_path(@project.namespace, @project, :home)
end
step 'I visit project hooks page' do
- visit project_hooks_path(@project)
+ visit namespace_project_hooks_path(@project.namespace, @project)
end
step 'I visit project deploy keys page' do
- visit project_deploy_keys_path(@project)
+ visit namespace_project_deploy_keys_path(@project.namespace, @project)
end
# ----------------------------------------
@@ -222,144 +244,153 @@ module SharedPaths
# ----------------------------------------
step 'I visit project "Shop" page' do
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I visit project "Forked Shop" merge requests page' do
- visit project_merge_requests_path(@forked_project)
+ visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project)
end
step 'I visit edit project "Shop" page' do
- visit edit_project_path(project)
+ visit edit_namespace_project_path(project.namespace, project)
end
step 'I visit project branches page' do
- visit project_branches_path(@project)
+ visit namespace_project_branches_path(@project.namespace, @project)
end
step 'I visit project protected branches page' do
- visit project_protected_branches_path(@project)
+ visit namespace_project_protected_branches_path(@project.namespace, @project)
end
step 'I visit compare refs page' do
- visit project_compare_index_path(@project)
+ visit namespace_project_compare_index_path(@project.namespace, @project)
end
step 'I visit project commits page' do
- visit project_commits_path(@project, root_ref, {limit: 5})
+ visit namespace_project_commits_path(@project.namespace, @project, root_ref, {limit: 5})
end
step 'I visit project commits page for stable branch' do
- visit project_commits_path(@project, 'stable', {limit: 5})
+ visit namespace_project_commits_path(@project.namespace, @project, 'stable', {limit: 5})
end
step 'I visit project source page' do
- visit project_tree_path(@project, root_ref)
+ visit namespace_project_tree_path(@project.namespace, @project, root_ref)
end
step 'I visit blob file from repo' do
- visit project_blob_path(@project, File.join(sample_commit.id, sample_blob.path))
+ visit namespace_project_blob_path(@project.namespace, @project, File.join(sample_commit.id, sample_blob.path))
end
step 'I visit ".gitignore" file in repo' do
- visit project_blob_path(@project, File.join(root_ref, '.gitignore'))
+ visit namespace_project_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore'))
+ end
+
+ step 'I am on the new file page' do
+ current_path.should eq(namespace_project_create_blob_path(@project.namespace, @project, root_ref))
+ end
+
+ step 'I am on the ".gitignore" edit file page' do
+ current_path.should eq(namespace_project_edit_blob_path(
+ @project.namespace, @project, File.join(root_ref, '.gitignore')))
end
step 'I visit project source page for "6d39438"' do
- visit project_tree_path(@project, "6d39438")
+ visit namespace_project_tree_path(@project.namespace, @project, "6d39438")
end
step 'I visit project source page for' \
' "6d394385cf567f80a8fd85055db1ab4c5295806f"' do
- visit project_tree_path(@project,
+ visit namespace_project_tree_path(@project.namespace, @project,
'6d394385cf567f80a8fd85055db1ab4c5295806f')
end
step 'I visit project tags page' do
- visit project_tags_path(@project)
+ visit namespace_project_tags_path(@project.namespace, @project)
end
step 'I visit project commit page' do
- visit project_commit_path(@project, sample_commit.id)
+ visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
end
step 'I visit project "Shop" issues page' do
- visit project_issues_path(project)
+ visit namespace_project_issues_path(project.namespace, project)
end
step 'I visit issue page "Release 0.4"' do
issue = Issue.find_by(title: "Release 0.4")
- visit project_issue_path(issue.project, issue)
+ visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
end
step 'I visit issue page "Tasks-open"' do
issue = Issue.find_by(title: 'Tasks-open')
- visit project_issue_path(issue.project, issue)
+ visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
end
step 'I visit issue page "Tasks-closed"' do
issue = Issue.find_by(title: 'Tasks-closed')
- visit project_issue_path(issue.project, issue)
+ visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
end
step 'I visit project "Shop" labels page' do
project = Project.find_by(name: 'Shop')
- visit project_labels_path(project)
+ visit namespace_project_labels_path(project.namespace, project)
end
step 'I visit project "Forum" labels page' do
project = Project.find_by(name: 'Forum')
- visit project_labels_path(project)
+ visit namespace_project_labels_path(project.namespace, project)
end
step 'I visit project "Shop" new label page' do
project = Project.find_by(name: 'Shop')
- visit new_project_label_path(project)
+ visit new_namespace_project_label_path(project.namespace, project)
end
step 'I visit project "Forum" new label page' do
project = Project.find_by(name: 'Forum')
- visit new_project_label_path(project)
+ visit new_namespace_project_label_path(project.namespace, project)
end
step 'I visit merge request page "Bug NS-04"' do
mr = MergeRequest.find_by(title: "Bug NS-04")
- visit project_merge_request_path(mr.target_project, mr)
+ visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
step 'I visit merge request page "Bug NS-05"' do
mr = MergeRequest.find_by(title: "Bug NS-05")
- visit project_merge_request_path(mr.target_project, mr)
+ visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
step 'I visit merge request page "MR-task-open"' do
mr = MergeRequest.find_by(title: 'MR-task-open')
- visit project_merge_request_path(mr.target_project, mr)
+ visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
step 'I visit merge request page "MR-task-closed"' do
mr = MergeRequest.find_by(title: 'MR-task-closed')
- visit project_merge_request_path(mr.target_project, mr)
+ visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
step 'I visit project "Shop" merge requests page' do
- visit project_merge_requests_path(project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
end
step 'I visit forked project "Shop" merge requests page' do
- visit project_merge_requests_path(project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
end
step 'I visit project "Shop" milestones page' do
- visit project_milestones_path(project)
+ visit namespace_project_milestones_path(project.namespace, project)
end
step 'I visit project "Shop" team page' do
- visit project_team_index_path(project)
+ visit namespace_project_project_members_path(project.namespace, project)
end
step 'I visit project wiki page' do
- visit project_wiki_path(@project, :home)
+ visit namespace_project_wiki_path(@project.namespace, @project, :home)
end
# ----------------------------------------
@@ -368,17 +399,22 @@ module SharedPaths
step 'I visit project "Community" page' do
project = Project.find_by(name: "Community")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ step 'I visit project "Community" source page' do
+ project = Project.find_by(name: 'Community')
+ visit namespace_project_tree_path(project.namespace, project, root_ref)
end
step 'I visit project "Internal" page' do
project = Project.find_by(name: "Internal")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
step 'I visit project "Enterprise" page' do
project = Project.find_by(name: "Enterprise")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
# ----------------------------------------
@@ -387,7 +423,7 @@ module SharedPaths
step "I visit empty project page" do
project = Project.find_by(name: "Empty Public Project")
- visit project_path(project)
+ visit namespace_project_path(project.namespace, project)
end
# ----------------------------------------
@@ -415,7 +451,7 @@ module SharedPaths
# ----------------------------------------
step 'I visit project "Shop" snippets page' do
- visit project_snippets_path(project)
+ visit namespace_project_snippets_path(project.namespace, project)
end
step 'I visit snippets page' do
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 4b833850a1..41f71ae29c 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -28,11 +28,16 @@ module SharedProject
@project.team << [@user, :master]
end
+ step 'I visit my empty project page' do
+ project = Project.find_by(name: 'Empty Project')
+ visit namespace_project_path(project.namespace, project)
+ end
+
step 'project "Shop" has push event' do
@project = Project.find_by(name: "Shop")
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "6d394385cf567f80a8fd85055db1ab4c5295806f",
ref: "refs/heads/fix",
user_id: @user.id,
@@ -60,7 +65,7 @@ module SharedProject
end
step 'I should see project settings' do
- current_path.should == edit_project_path(@project)
+ current_path.should == edit_namespace_project_path(@project.namespace, @project)
page.should have_content("Project name")
page.should have_content("Features:")
end
@@ -131,7 +136,7 @@ module SharedProject
end
step 'public empty project "Empty Public Project"' do
- create :empty_project, :public, name: "Empty Public Project"
+ create :project_empty_repo, :public, name: "Empty Public Project"
end
step 'project "Community" has comments' do
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 6aa4f1b20d..c5aed19331 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -41,6 +41,8 @@ module SharedProjectTab
end
step 'the active main tab should be Settings' do
- ensure_active_main_tab('Settings')
+ within '.nav-sidebar' do
+ page.should have_content('Back to project')
+ end
end
end
diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb
index 432f32defc..bb596c1620 100644
--- a/features/steps/shared/snippet.rb
+++ b/features/steps/shared/snippet.rb
@@ -51,4 +51,13 @@ module SharedSnippet
visibility_level: Snippet::PUBLIC,
author: current_user)
end
+
+ step 'There is public "Personal snippet one" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet one",
+ content: "Test content",
+ file_name: "snippet.rb",
+ visibility_level: Snippet::PUBLIC,
+ author: create(:user))
+ end
end
diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb
new file mode 100644
index 0000000000..67669dc0a6
--- /dev/null
+++ b/features/steps/snippets/public_snippets.rb
@@ -0,0 +1,25 @@
+class Spinach::Features::PublicSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedSnippet
+
+ step 'I should see snippet "Personal snippet one"' do
+ page.should have_no_xpath("//i[@class='public-snippet']")
+ end
+
+ step 'I should see raw snippet "Personal snippet one"' do
+ page.should have_text(snippet.content)
+ end
+
+ step 'I visit snippet page "Personal snippet one"' do
+ visit snippet_path(snippet)
+ end
+
+ step 'I visit snippet raw page "Personal snippet one"' do
+ visit raw_snippet_path(snippet)
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
+ end
+end
diff --git a/features/support/env.rb b/features/support/env.rb
index 6766077784..be17065ccf 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -47,8 +47,8 @@ Spinach.hooks.after_scenario do
end
Spinach.hooks.before_run do
+ include RSpec::Mocks::ExampleMethods
TestEnv.init(mailer: false)
- RSpec::Mocks::setup self
include FactoryGirl::Syntax::Methods
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 2c7cd9038c..60858a3940 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -2,10 +2,11 @@ Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
module API
class API < Grape::API
+ include APIGuard
version 'v3', using: :path
rescue_from ActiveRecord::RecordNotFound do
- rack_response({'message' => '404 Not found'}.to_json, 404)
+ rack_response({ 'message' => '404 Not found' }.to_json, 404)
end
rescue_from :all do |exception|
@@ -18,7 +19,7 @@ module API
message << " " << trace.join("\n ")
API.logger.add Logger::FATAL, message
- rack_response({'message' => '500 Internal Server Error'}, 500)
+ rack_response({ 'message' => '500 Internal Server Error' }, 500)
end
format :json
@@ -27,6 +28,7 @@ module API
helpers APIHelpers
mount Groups
+ mount GroupMembers
mount Users
mount Projects
mount Repositories
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
new file mode 100644
index 0000000000..b9994fcefd
--- /dev/null
+++ b/lib/api/api_guard.rb
@@ -0,0 +1,172 @@
+# Guard API with OAuth 2.0 Access Token
+
+require 'rack/oauth2'
+
+module APIGuard
+ extend ActiveSupport::Concern
+
+ included do |base|
+ # OAuth2 Resource Server Authentication
+ use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
+ # The authenticator only fetches the raw token string
+
+ # Must yield access token to store it in the env
+ request.access_token
+ end
+
+ helpers HelperMethods
+
+ install_error_responders(base)
+ end
+
+ # Helper Methods for Grape Endpoint
+ module HelperMethods
+ # Invokes the doorkeeper guard.
+ #
+ # If token is presented and valid, then it sets @current_user.
+ #
+ # If the token does not have sufficient scopes to cover the requred scopes,
+ # then it raises InsufficientScopeError.
+ #
+ # If the token is expired, then it raises ExpiredError.
+ #
+ # If the token is revoked, then it raises RevokedError.
+ #
+ # If the token is not found (nil), then it raises TokenNotFoundError.
+ #
+ # Arguments:
+ #
+ # scopes: (optional) scopes required for this guard.
+ # Defaults to empty array.
+ #
+ def doorkeeper_guard!(scopes: [])
+ if (access_token = find_access_token).nil?
+ raise TokenNotFoundError
+
+ else
+ case validate_access_token(access_token, scopes)
+ when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
+ when Oauth2::AccessTokenValidationService::EXPIRED
+ raise ExpiredError
+ when Oauth2::AccessTokenValidationService::REVOKED
+ raise RevokedError
+ when Oauth2::AccessTokenValidationService::VALID
+ @current_user = User.find(access_token.resource_owner_id)
+ end
+ end
+ end
+
+ def doorkeeper_guard(scopes: [])
+ if access_token = find_access_token
+ case validate_access_token(access_token, scopes)
+ when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
+
+ when Oauth2::AccessTokenValidationService::EXPIRED
+ raise ExpiredError
+
+ when Oauth2::AccessTokenValidationService::REVOKED
+ raise RevokedError
+
+ when Oauth2::AccessTokenValidationService::VALID
+ @current_user = User.find(access_token.resource_owner_id)
+ end
+ end
+ end
+
+ def current_user
+ @current_user
+ end
+
+ private
+ def find_access_token
+ @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods)
+ end
+
+ def doorkeeper_request
+ @doorkeeper_request ||= ActionDispatch::Request.new(env)
+ end
+
+ def validate_access_token(access_token, scopes)
+ Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes)
+ end
+ end
+
+ module ClassMethods
+ # Installs the doorkeeper guard on the whole Grape API endpoint.
+ #
+ # Arguments:
+ #
+ # scopes: (optional) scopes required for this guard.
+ # Defaults to empty array.
+ #
+ def guard_all!(scopes: [])
+ before do
+ guard! scopes: scopes
+ end
+ end
+
+ private
+ def install_error_responders(base)
+ error_classes = [ MissingTokenError, TokenNotFoundError,
+ ExpiredError, RevokedError, InsufficientScopeError]
+
+ base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
+ end
+
+ def oauth2_bearer_token_error_handler
+ Proc.new do |e|
+ response =
+ case e
+ when MissingTokenError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
+
+ when TokenNotFoundError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Bad Access Token.")
+
+ when ExpiredError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Token is expired. You can either do re-authorization or token refresh.")
+
+ when RevokedError
+ Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
+ :invalid_token,
+ "Token was revoked. You have to re-authorize from the user.")
+
+ when InsufficientScopeError
+ # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
+ # does not include WWW-Authenticate header, which breaks the standard.
+ Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
+ :insufficient_scope,
+ Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope],
+ { scope: e.scopes })
+ end
+
+ response.finish
+ end
+ end
+ end
+
+ #
+ # Exceptions
+ #
+
+ class MissingTokenError < StandardError; end
+
+ class TokenNotFoundError < StandardError; end
+
+ class ExpiredError < StandardError; end
+
+ class RevokedError < StandardError; end
+
+ class InsufficientScopeError < StandardError
+ attr_reader :scopes
+ def initialize(scopes)
+ @scopes = scopes
+ end
+ end
+end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 14f8b20f6b..b52d786e02 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -14,7 +14,8 @@ module API
# Example Request:
# GET /projects/:id/repository/branches
get ":id/repository/branches" do
- present user_project.repository.branches.sort_by(&:name), with: Entities::RepoObject, project: user_project
+ branches = user_project.repository.branches.sort_by(&:name)
+ present branches, with: Entities::RepoObject, project: user_project
end
# Get a single branch
@@ -26,7 +27,7 @@ module API
# GET /projects/:id/repository/branches/:branch
get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do
@branch = user_project.repository.branches.find { |item| item.name == params[:branch] }
- not_found!("Branch does not exist") if @branch.nil?
+ not_found!("Branch") unless @branch
present @branch, with: Entities::RepoObject, project: user_project
end
@@ -43,7 +44,7 @@ module API
authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch])
- not_found! unless @branch
+ not_found!("Branch") unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
user_project.protected_branches.create(name: @branch.name) unless protected_branch
@@ -63,7 +64,7 @@ module API
authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch])
- not_found! unless @branch
+ not_found!("Branch does not exist") unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch
@@ -82,6 +83,7 @@ module API
authorize_push_project
result = CreateBranchService.new(user_project, current_user).
execute(params[:branch_name], params[:ref])
+
if result[:status] == :success
present result[:branch],
with: Entities::RepoObject,
@@ -104,7 +106,9 @@ module API
execute(params[:branch])
if result[:status] == :success
- true
+ {
+ branch_name: params[:branch]
+ }
else
render_api_error!(result[:message], result[:return_code])
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 4a67313430..0de4e720ff 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -50,6 +50,67 @@ module API
not_found! "Commit" unless commit
commit.diffs
end
+
+ # Get a commit's comments
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash
+ # Examples:
+ # GET /projects/:id/repository/commits/:sha/comments
+ get ':id/repository/commits/:sha/comments' do
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! 'Commit' unless commit
+ notes = Note.where(commit_id: commit.id)
+ present paginate(notes), with: Entities::CommitNote
+ end
+
+ # Post comment to commit
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash
+ # note (required) - Text of comment
+ # path (optional) - The file path
+ # line (optional) - The line number
+ # line_type (optional) - The type of line (new or old)
+ # Examples:
+ # POST /projects/:id/repository/commits/:sha/comments
+ post ':id/repository/commits/:sha/comments' do
+ required_attributes! [:note]
+
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! 'Commit' unless commit
+ opts = {
+ note: params[:note],
+ noteable_type: 'Commit',
+ commit_id: commit.id
+ }
+
+ if params[:path] && params[:line] && params[:line_type]
+ commit.diffs.each do |diff|
+ next unless diff.new_path == params[:path]
+ lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a)
+
+ lines.each do |line|
+ next unless line.new_pos == params[:line].to_i && line.type == params[:line_type]
+ break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
+ end
+
+ break if opts[:line_code]
+ end
+ end
+
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+
+ if note.save
+ present note, with: Entities::CommitNote
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
+ end
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 80e9470195..489be21078 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -14,9 +14,14 @@ module API
expose :bio, :skype, :linkedin, :twitter, :website_url
end
+ class Identity < Grape::Entity
+ expose :provider, :extern_uid
+ end
+
class UserFull < User
expose :email
- expose :theme_id, :color_scheme_id, :extern_uid, :provider
+ expose :theme_id, :color_scheme_id, :projects_limit
+ expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
end
@@ -50,7 +55,8 @@ module API
expose :path, :path_with_namespace
expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at
expose :namespace
- expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
+ expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
+ expose :avatar_url
end
class ProjectMember < UserBasic
@@ -60,7 +66,7 @@ module API
end
class Group < Grape::Entity
- expose :id, :name, :path, :owner_id
+ expose :id, :name, :path, :description
end
class GroupDetail < Group
@@ -73,6 +79,25 @@ module API
end
end
+ class RepoTag < Grape::Entity
+ expose :name
+ expose :message do |repo_obj, _options|
+ if repo_obj.respond_to?(:message)
+ repo_obj.message
+ else
+ nil
+ end
+ end
+
+ expose :commit do |repo_obj, options|
+ if repo_obj.respond_to?(:commit)
+ repo_obj.commit
+ elsif options[:project]
+ options[:project].repository.commit(repo_obj.target)
+ end
+ end
+ end
+
class RepoObject < Grape::Entity
expose :name
@@ -118,11 +143,16 @@ module API
class ProjectEntity < Grape::Entity
expose :id, :iid
- expose (:project_id) { |entity| entity.project.id }
+ expose(:project_id) { |entity| entity.project.id }
expose :title, :description
expose :state, :created_at, :updated_at
end
+ class RepoDiff < Grape::Entity
+ expose :old_path, :new_path, :a_mode, :b_mode, :diff
+ expose :new_file, :renamed_file, :deleted_file
+ end
+
class Milestone < ProjectEntity
expose :due_date
end
@@ -142,6 +172,12 @@ module API
expose :milestone, using: Entities::Milestone
end
+ class MergeRequestChanges < MergeRequest
+ expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _|
+ compare.diffs
+ end
+ end
+
class SSHKey < Grape::Entity
expose :id, :title, :key, :created_at
end
@@ -159,11 +195,25 @@ module API
expose :author, using: Entities::UserBasic
end
+ class CommitNote < Grape::Entity
+ expose :note
+ expose(:path) { |note| note.diff_file_name }
+ expose(:line) { |note| note.diff_new_line }
+ expose(:line_type) { |note| note.diff_line_type }
+ expose :author, using: Entities::UserBasic
+ end
+
class Event < Grape::Entity
expose :title, :project_id, :action_name
expose :target_id, :target_type, :author_id
expose :data, :target_title
expose :created_at
+
+ expose :author_username do |event, options|
+ if event.author
+ event.author.username
+ end
+ end
end
class Namespace < Grape::Entity
@@ -198,11 +248,6 @@ module API
expose :name, :color
end
- class RepoDiff < Grape::Entity
- expose :old_path, :new_path, :a_mode, :b_mode, :diff
- expose :new_file, :renamed_file, :deleted_file
- end
-
class Compare < Grape::Entity
expose :commit, using: Entities::RepoCommit do |compare, options|
Commit.decorate(compare.commits).last
@@ -226,5 +271,9 @@ module API
class Contributor < Grape::Entity
expose :name, :email, :commits, :additions, :deletions
end
+
+ class BroadcastMessage < Grape::Entity
+ expose :message, :starts_at, :ends_at, :color, :font
+ end
end
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index e63e635a4d..3176ef0e25 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -35,7 +35,7 @@ module API
file_path = attrs.delete(:file_path)
commit = user_project.repository.commit(ref)
- not_found! "Commit" unless commit
+ not_found! 'Commit' unless commit
blob = user_project.repository.blob_at(commit.sha, file_path)
@@ -53,7 +53,7 @@ module API
commit_id: commit.id,
}
else
- render_api_error!('File not found', 404)
+ not_found! 'File'
end
end
@@ -85,7 +85,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
@@ -117,7 +117,8 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ http_status = result[:http_status] || 400
+ render_api_error!(result[:message], http_status)
end
end
@@ -149,7 +150,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
end
diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb
new file mode 100644
index 0000000000..ed54c7f6ff
--- /dev/null
+++ b/lib/api/group_members.rb
@@ -0,0 +1,88 @@
+module API
+ class GroupMembers < Grape::API
+ before { authenticate! }
+
+ resource :groups do
+ # Get a list of group members viewable by the authenticated user.
+ #
+ # Example Request:
+ # GET /groups/:id/members
+ get ":id/members" do
+ group = find_group(params[:id])
+ members = group.group_members
+ users = (paginate members).collect(&:user)
+ present users, with: Entities::GroupMember, group: group
+ end
+
+ # Add a user to the list of group members
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /groups/:id/members
+ post ":id/members" do
+ group = find_group(params[:id])
+ authorize! :manage_group, group
+ required_attributes! [:user_id, :access_level]
+
+ unless validate_access_level?(params[:access_level])
+ render_api_error!("Wrong access level", 422)
+ end
+
+ if group.group_members.find_by(user_id: params[:user_id])
+ render_api_error!("Already exists", 409)
+ end
+
+ group.add_users([params[:user_id]], params[:access_level])
+ member = group.group_members.find_by(user_id: params[:user_id])
+ present member.user, with: Entities::GroupMember, group: group
+ end
+
+ # Update group member
+ #
+ # Parameters:
+ # id (required) - The ID of a group
+ # user_id (required) - The ID of a group member
+ # access_level (required) - Project access level
+ # Example Request:
+ # PUT /groups/:id/members/:user_id
+ put ':id/members/:user_id' do
+ group = find_group(params[:id])
+ authorize! :manage_group, group
+ required_attributes! [:access_level]
+
+ group_member = group.group_members.find_by(user_id: params[:user_id])
+ not_found!('User can not be found') if group_member.nil?
+
+ if group_member.update_attributes(access_level: params[:access_level])
+ @member = group_member.user
+ present @member, with: Entities::GroupMember, group: group
+ else
+ handle_member_errors group_member.errors
+ end
+ end
+
+ # Remove member.
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ #
+ # Example Request:
+ # DELETE /groups/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ group = find_group(params[:id])
+ authorize! :manage_group, group
+ member = group.group_members.find_by(user_id: params[:user_id])
+
+ if member.nil?
+ render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
+ else
+ member.destroy
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 4841e04689..a92abd4b69 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -4,32 +4,19 @@ module API
before { authenticate! }
resource :groups do
- helpers do
- def find_group(id)
- group = Group.find(id)
-
- if can?(current_user, :read_group, group)
- group
- else
- render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403)
- end
- end
-
- def validate_access_level?(level)
- Gitlab::Access.options_with_owner.values.include? level.to_i
- end
- end
-
# Get a groups list
#
# Example Request:
# GET /groups
get do
- if current_user.admin
- @groups = paginate Group
- else
- @groups = paginate current_user.groups
- end
+ @groups = if current_user.admin
+ Group.all
+ else
+ current_user.groups
+ end
+
+ @groups = @groups.search(params[:search]) if params[:search].present?
+ @groups = paginate @groups
present @groups, with: Entities::Group
end
@@ -44,14 +31,14 @@ module API
authenticated_as_admin!
required_attributes! [:name, :path]
- attrs = attributes_for_keys [:name, :path]
+ attrs = attributes_for_keys [:name, :path, :description]
@group = Group.new(attrs)
- @group.owner = current_user
if @group.save
+ @group.add_owner(current_user)
present @group, with: Entities::Group
else
- not_found!
+ render_api_error!("Failed to save group #{@group.errors.messages}", 400)
end
end
@@ -94,58 +81,7 @@ module API
if result
present group
else
- not_found!
- end
- end
-
- # Get a list of group members viewable by the authenticated user.
- #
- # Example Request:
- # GET /groups/:id/members
- get ":id/members" do
- group = find_group(params[:id])
- members = group.group_members
- users = (paginate members).collect(&:user)
- present users, with: Entities::GroupMember, group: group
- end
-
- # Add a user to the list of group members
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- # access_level (required) - Project access level
- # Example Request:
- # POST /groups/:id/members
- post ":id/members" do
- required_attributes! [:user_id, :access_level]
- unless validate_access_level?(params[:access_level])
- render_api_error!("Wrong access level", 422)
- end
- group = find_group(params[:id])
- if group.group_members.find_by(user_id: params[:user_id])
- render_api_error!("Already exists", 409)
- end
- group.add_users([params[:user_id]], params[:access_level])
- member = group.group_members.find_by(user_id: params[:user_id])
- present member.user, with: Entities::GroupMember, group: group
- end
-
- # Remove member.
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- #
- # Example Request:
- # DELETE /groups/:id/members/:user_id
- delete ":id/members/:user_id" do
- group = find_group(params[:id])
- member = group.group_members.find_by(user_id: params[:user_id])
- if member.nil?
- render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
- else
- member.destroy
+ render_api_error!("Failed to transfer project #{project.errors.messages}", 400)
end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 3262884f6d..a6e77002a0 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -11,7 +11,7 @@ module API
def current_user
private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
- @current_user ||= User.find_by(authentication_token: private_token)
+ @current_user ||= (User.find_by(authentication_token: private_token) || doorkeeper_guard)
unless @current_user && Gitlab::UserAccess.allowed?(@current_user)
return nil
@@ -42,7 +42,7 @@ module API
def user_project
@project ||= find_project(params[:id])
- @project || not_found!
+ @project || not_found!("Project")
end
def find_project(id)
@@ -55,6 +55,21 @@ module API
end
end
+ def find_group(id)
+ begin
+ group = Group.find(id)
+ rescue ActiveRecord::RecordNotFound
+ group = Group.find_by!(path: id)
+ end
+
+ if can?(current_user, :read_group, group)
+ group
+ else
+ forbidden!("#{current_user.username} lacks sufficient "\
+ "access to #{group.name}")
+ end
+ end
+
def paginate(relation)
per_page = params[:per_page].to_i
paginated = relation.page(params[:page]).per(per_page)
@@ -67,6 +82,13 @@ module API
unauthorized! unless current_user
end
+ def authenticate_by_gitlab_shell_token!
+ input = params['secret_token'].try(:chomp)
+ unless Devise.secure_compare(secret_token, input)
+ unauthorized!
+ end
+ end
+
def authenticated_as_admin!
forbidden! unless current_user.is_admin?
end
@@ -131,10 +153,32 @@ module API
errors
end
+ def validate_access_level?(level)
+ Gitlab::Access.options_with_owner.values.include? level.to_i
+ end
+
+ def issuable_order_by
+ if params["order_by"] == 'updated_at'
+ 'updated_at'
+ else
+ 'created_at'
+ end
+ end
+
+ def issuable_sort
+ if params["sort"] == 'asc'
+ :asc
+ else
+ :desc
+ end
+ end
+
# error helpers
- def forbidden!
- render_api_error!('403 Forbidden', 403)
+ def forbidden!(reason = nil)
+ message = ['403 Forbidden']
+ message << " - #{reason}" if reason
+ render_api_error!(message.join(' '), 403)
end
def bad_request!(attribute)
@@ -163,13 +207,13 @@ module API
end
def render_validation_error!(model)
- unless model.valid?
+ if model.errors.any?
render_api_error!(model.errors.messages || '400 Bad Request', 400)
end
end
def render_api_error!(message, status)
- error!({'message' => message}, status)
+ error!({ 'message' => message }, status)
end
private
@@ -193,5 +237,14 @@ module API
abilities
end
end
+
+ def secret_token
+ File.read(Rails.root.join('.gitlab_shell_secret')).chomp
+ end
+
+ def handle_member_errors(errors)
+ error!(errors[:access_level], 422) if errors[:access_level].any?
+ not_found!(errors)
+ end
end
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 9ac659f50f..753d0fcbd9 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,6 +1,8 @@
module API
# Internal access API
class Internal < Grape::API
+ before { authenticate_by_gitlab_shell_token! }
+
namespace 'internal' do
# Check if git command is allowed to project
#
@@ -14,6 +16,17 @@ module API
#
post "/allowed" do
status 200
+
+ actor = if params[:key_id]
+ Key.find_by(id: params[:key_id])
+ elsif params[:user_id]
+ User.find_by(id: params[:user_id])
+ end
+
+ unless actor
+ return Gitlab::GitAccessStatus.new(false, 'No such user or key')
+ end
+
project_path = params[:project]
# Check for *.wiki repositories.
@@ -21,30 +34,29 @@ module API
# project. This applies the correct project permissions to
# the wiki repository as well.
access =
- if project_path =~ /\.wiki\Z/
- project_path.sub!(/\.wiki\Z/, '')
+ if project_path.end_with?('.wiki')
+ project_path.chomp!('.wiki')
Gitlab::GitAccessWiki.new
else
Gitlab::GitAccess.new
end
project = Project.find_with_namespace(project_path)
- return false unless project
- actor = if params[:key_id]
- Key.find(params[:key_id])
- elsif params[:user_id]
- User.find(params[:user_id])
- end
+ if project
+ status = access.check(
+ actor,
+ params[:action],
+ project,
+ params[:changes]
+ )
+ end
- return false unless actor
-
- access.allowed?(
- actor,
- params[:action],
- project,
- params[:changes]
- )
+ if project && status && status.allowed?
+ status
+ else
+ Gitlab::GitAccessStatus.new(false, 'No such project')
+ end
end
#
@@ -62,6 +74,14 @@ module API
gitlab_rev: Gitlab::REVISION,
}
end
+
+ get "/broadcast_message" do
+ if message = BroadcastMessage.current
+ present message, with: Entities::BroadcastMessage
+ else
+ {}
+ end
+ end
end
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index d2828b24c3..ff062be604 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -27,7 +27,9 @@ module API
# Parameters:
# state (optional) - Return "opened" or "closed" issues
# labels (optional) - Comma-separated list of label names
-
+ # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+ # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+ #
# Example Requests:
# GET /issues
# GET /issues?state=opened
@@ -39,8 +41,7 @@ module API
issues = current_user.issues
issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
- issues = issues.order('issues.id DESC')
-
+ issues.reorder(issuable_order_by => issuable_sort)
present paginate(issues), with: Entities::Issue
end
end
@@ -53,6 +54,8 @@ module API
# state (optional) - Return "opened" or "closed" issues
# labels (optional) - Comma-separated list of label names
# milestone (optional) - Milestone title
+ # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+ # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
#
# Example Requests:
# GET /projects/:id/issues
@@ -67,11 +70,12 @@ module API
issues = user_project.issues
issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
+
unless params[:milestone].nil?
issues = filter_issues_milestone(issues, params[:milestone])
end
- issues = issues.order('issues.id DESC')
+ issues.reorder(issuable_order_by => issuable_sort)
present paginate(issues), with: Entities::Issue
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index a365f1db00..25b7857f4b 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -25,6 +25,8 @@ module API
# Parameters:
# id (required) - The ID of a project
# state (optional) - Return requests "merged", "opened" or "closed"
+ # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+ # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
#
# Example:
# GET /projects/:id/merge_requests
@@ -37,25 +39,18 @@ module API
#
get ":id/merge_requests" do
authorize! :read_merge_request, user_project
+ merge_requests = user_project.merge_requests
- mrs = case params["state"]
- when "opened" then user_project.merge_requests.opened
- when "closed" then user_project.merge_requests.closed
- when "merged" then user_project.merge_requests.merged
- else user_project.merge_requests
- end
+ merge_requests =
+ case params["state"]
+ when "opened" then merge_requests.opened
+ when "closed" then merge_requests.closed
+ when "merged" then merge_requests.merged
+ else merge_requests
+ end
- sort = case params["sort"]
- when 'desc' then 'DESC'
- else 'ASC'
- end
-
- mrs = case params["order_by"]
- when 'updated_at' then mrs.order("updated_at #{sort}")
- else mrs.order("created_at #{sort}")
- end
-
- present paginate(mrs), with: Entities::MergeRequest
+ merge_requests.reorder(issuable_order_by => issuable_sort)
+ present paginate(merge_requests), with: Entities::MergeRequest
end
# Show MR
@@ -75,6 +70,22 @@ module API
present merge_request, with: Entities::MergeRequest
end
+ # Show MR changes
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # merge_request_id (required) - The ID of MR
+ #
+ # Example:
+ # GET /projects/:id/merge_request/:merge_request_id/changes
+ #
+ get ':id/merge_request/:merge_request_id/changes' do
+ merge_request = user_project.merge_requests.
+ find(params[:merge_request_id])
+ authorize! :read_merge_request, merge_request
+ present merge_request, with: Entities::MergeRequestChanges
+ end
+
# Create MR
#
# Parameters:
@@ -167,13 +178,9 @@ module API
put ":id/merge_request/:merge_request_id/merge" do
merge_request = user_project.merge_requests.find(params[:merge_request_id])
- action = if user_project.protected_branch?(merge_request.target_branch)
- :push_code_to_protected_branches
- else
- :push_code
- end
+ allowed = ::Gitlab::GitAccess.can_push_to_branch?(current_user, user_project, merge_request.target_branch)
- if can?(current_user, action, user_project)
+ if allowed
if merge_request.unchecked?
merge_request.check_if_can_be_merged
end
@@ -233,7 +240,7 @@ module API
if note.save
present note, with: Entities::MRNote
else
- render_validation_error!(note)
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
end
end
end
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index a4fdb752d6..c5cd73943f 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -48,7 +48,7 @@ module API
if milestone.valid?
present milestone, with: Entities::Milestone
else
- not_found!
+ render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
end
end
@@ -72,9 +72,24 @@ module API
if milestone.valid?
present milestone, with: Entities::Milestone
else
- not_found!
+ render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
end
end
+
+ # Get all issues for a single project milestone
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # milestone_id (required) - The ID of a project milestone
+ # Example Request:
+ # GET /projects/:id/milestones/:milestone_id/issues
+ get ":id/milestones/:milestone_id/issues" do
+ authorize! :read_milestone, user_project
+
+ @milestone = user_project.milestones.find(params[:milestone_id])
+ present paginate(@milestone.issues), with: Entities::Issue
+ end
+
end
end
end
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index f9f2ed90cc..b90ed6af5f 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -1,10 +1,10 @@
module API
# namespaces API
class Namespaces < Grape::API
- before {
+ before do
authenticate!
authenticated_as_admin!
- }
+ end
resource :namespaces do
# Get a namespaces list
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 0ef9a3c4be..3726be7c53 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -61,9 +61,42 @@ module API
if @note.valid?
present @note, with: Entities::Note
else
- not_found!
+ not_found!("Note #{@note.errors.messages}")
end
end
+
+ # Modify existing +noteable+ note
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # noteable_id (required) - The ID of an issue or snippet
+ # node_id (required) - The ID of a note
+ # body (required) - New content of a note
+ # Example Request:
+ # PUT /projects/:id/issues/:noteable_id/notes/:note_id
+ # PUT /projects/:id/snippets/:noteable_id/notes/:node_id
+ put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do
+ required_attributes! [:body]
+
+ authorize! :admin_note, user_project.notes.find(params[:note_id])
+
+ opts = {
+ note: params[:body],
+ note_id: params[:note_id],
+ noteable_type: noteables_str.classify,
+ noteable_id: params[noteable_id_str]
+ }
+
+ @note = ::Notes::UpdateService.new(user_project, current_user,
+ opts).execute
+
+ if @note.valid?
+ present @note, with: Entities::Note
+ else
+ render_api_error!("Failed to save note #{note.errors.messages}", 400)
+ end
+ end
+
end
end
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 7d056b9bf5..be9850367b 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -53,7 +53,7 @@ module API
if @hook.errors[:url].present?
error!("Invalid url given", 422)
end
- not_found!
+ not_found!("Project hook #{@hook.errors.messages}")
end
end
@@ -82,7 +82,7 @@ module API
if @hook.errors[:url].present?
error!("Invalid url given", 422)
end
- not_found!
+ not_found!("Project hook #{@hook.errors.messages}")
end
end
diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb
index 1595ed0bc3..c756bb479f 100644
--- a/lib/api/project_members.rb
+++ b/lib/api/project_members.rb
@@ -4,14 +4,6 @@ module API
before { authenticate! }
resource :projects do
- helpers do
- def handle_project_member_errors(errors)
- if errors[:access_level].any?
- error!(errors[:access_level], 422)
- end
- not_found!
- end
- end
# Get a project team members
#
@@ -54,19 +46,19 @@ module API
required_attributes! [:user_id, :access_level]
# either the user is already a team member or a new one
- team_member = user_project.team_member_by_id(params[:user_id])
- if team_member.nil?
- team_member = user_project.project_members.new(
+ project_member = user_project.project_member_by_id(params[:user_id])
+ if project_member.nil?
+ project_member = user_project.project_members.new(
user_id: params[:user_id],
access_level: params[:access_level]
)
end
- if team_member.save
- @member = team_member.user
+ if project_member.save
+ @member = project_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- handle_project_member_errors team_member.errors
+ handle_member_errors project_member.errors
end
end
@@ -82,14 +74,14 @@ module API
authorize! :admin_project, user_project
required_attributes! [:access_level]
- team_member = user_project.project_members.find_by(user_id: params[:user_id])
- not_found!("User can not be found") if team_member.nil?
+ project_member = user_project.project_members.find_by(user_id: params[:user_id])
+ not_found!("User can not be found") if project_member.nil?
- if team_member.update_attributes(access_level: params[:access_level])
- @member = team_member.user
+ if project_member.update_attributes(access_level: params[:access_level])
+ @member = project_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- handle_project_member_errors team_member.errors
+ handle_member_errors project_member.errors
end
end
@@ -102,11 +94,11 @@ module API
# DELETE /projects/:id/members/:user_id
delete ":id/members/:user_id" do
authorize! :admin_project, user_project
- team_member = user_project.project_members.find_by(user_id: params[:user_id])
- unless team_member.nil?
- team_member.destroy
+ project_member = user_project.project_members.find_by(user_id: params[:user_id])
+ unless project_member.nil?
+ project_member.destroy
else
- {message: "Access revoked", id: params[:user_id].to_i}
+ { message: "Access revoked", id: params[:user_id].to_i }
end
end
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 0c2d282f78..54f2555903 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -42,21 +42,22 @@ module API
# title (required) - The title of a snippet
# file_name (required) - The name of a snippet file
# code (required) - The content of a snippet
+ # visibility_level (required) - The snippet's visibility
# Example Request:
# POST /projects/:id/snippets
post ":id/snippets" do
authorize! :write_project_snippet, user_project
- required_attributes! [:title, :file_name, :code]
+ required_attributes! [:title, :file_name, :code, :visibility_level]
- attrs = attributes_for_keys [:title, :file_name]
+ attrs = attributes_for_keys [:title, :file_name, :visibility_level]
attrs[:content] = params[:code] if params[:code].present?
- @snippet = user_project.snippets.new attrs
- @snippet.author = current_user
+ @snippet = CreateSnippetService.new(user_project, current_user,
+ attrs).execute
- if @snippet.save
- present @snippet, with: Entities::ProjectSnippet
- else
+ if @snippet.errors.any?
render_validation_error!(@snippet)
+ else
+ present @snippet, with: Entities::ProjectSnippet
end
end
@@ -68,19 +69,22 @@ module API
# title (optional) - The title of a snippet
# file_name (optional) - The name of a snippet file
# code (optional) - The content of a snippet
+ # visibility_level (optional) - The snippet's visibility
# Example Request:
# PUT /projects/:id/snippets/:snippet_id
put ":id/snippets/:snippet_id" do
@snippet = user_project.snippets.find(params[:snippet_id])
authorize! :modify_project_snippet, @snippet
- attrs = attributes_for_keys [:title, :file_name]
+ attrs = attributes_for_keys [:title, :file_name, :visibility_level]
attrs[:content] = params[:code] if params[:code].present?
- if @snippet.update_attributes attrs
- present @snippet, with: Entities::ProjectSnippet
- else
+ UpdateSnippetService.new(user_project, current_user, @snippet,
+ attrs).execute
+ if @snippet.errors.any?
render_validation_error!(@snippet)
+ else
+ present @snippet, with: Entities::ProjectSnippet
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 7f7d2f8e9a..83f65eec6c 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -11,23 +11,46 @@ module API
attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true
attrs
end
+
+ def filter_projects(projects)
+ # If the archived parameter is passed, limit results accordingly
+ if params[:archived].present?
+ projects = projects.where(archived: parse_boolean(params[:archived]))
+ end
+
+ if params[:search].present?
+ projects = projects.search(params[:search])
+ end
+
+ projects.reorder(project_order_by => project_sort)
+ end
+
+ def project_order_by
+ order_fields = %w(id name path created_at updated_at last_activity_at)
+
+ if order_fields.include?(params['order_by'])
+ params['order_by']
+ else
+ 'created_at'
+ end
+ end
+
+ def project_sort
+ if params["sort"] == 'asc'
+ :asc
+ else
+ :desc
+ end
+ end
end
# Get a projects list for authenticated user
#
- # Parameters:
- # archived (optional) - if passed, limit by archived status
- #
# Example Request:
# GET /projects
get do
@projects = current_user.authorized_projects
-
- # If the archived parameter is passed, limit results accordingly
- if params[:archived].present?
- @projects = @projects.where(archived: parse_boolean(params[:archived]))
- end
-
+ @projects = filter_projects(@projects)
@projects = paginate @projects
present @projects, with: Entities::Project
end
@@ -37,7 +60,9 @@ module API
# Example Request:
# GET /projects/owned
get '/owned' do
- @projects = paginate current_user.owned_projects
+ @projects = current_user.owned_projects
+ @projects = filter_projects(@projects)
+ @projects = paginate @projects
present @projects, with: Entities::Project
end
@@ -47,7 +72,9 @@ module API
# GET /projects/all
get '/all' do
authenticated_as_admin!
- @projects = paginate Project
+ @projects = Project.all
+ @projects = filter_projects(@projects)
+ @projects = paginate @projects
present @projects, with: Entities::Project
end
@@ -66,7 +93,7 @@ module API
# Parameters:
# id (required) - The ID of a project
# Example Request:
- # GET /projects/:id
+ # GET /projects/:id/events
get ":id/events" do
limit = (params[:per_page] || 20).to_i
offset = (params[:page] || 0).to_i * limit
@@ -170,6 +197,49 @@ module API
end
end
+ # Update an existing project
+ #
+ # Parameters:
+ # id (required) - the id of a project
+ # name (optional) - name of a project
+ # path (optional) - path of a project
+ # description (optional) - short project description
+ # issues_enabled (optional)
+ # merge_requests_enabled (optional)
+ # wiki_enabled (optional)
+ # snippets_enabled (optional)
+ # public (optional) - if true same as setting visibility_level = 20
+ # visibility_level (optional) - visibility level of a project
+ # Example Request
+ # PUT /projects/:id
+ put ':id' do
+ attrs = attributes_for_keys [:name,
+ :path,
+ :description,
+ :default_branch,
+ :issues_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :snippets_enabled,
+ :public,
+ :visibility_level]
+ attrs = map_public_to_visibility_level(attrs)
+ authorize_admin_project
+ authorize! :rename_project, user_project if attrs[:name].present?
+ if attrs[:visibility_level].present?
+ authorize! :change_visibility_level, user_project
+ end
+
+ ::Projects::UpdateService.new(user_project,
+ current_user, attrs).execute
+
+ if user_project.errors.any?
+ render_validation_error!(user_project)
+ else
+ present user_project, with: Entities::Project
+ end
+ end
+
# Remove project
#
# Parameters:
@@ -178,7 +248,7 @@ module API
# DELETE /projects/:id
delete ":id" do
authorize! :remove_project, user_project
- user_project.destroy
+ ::Projects::DestroyService.new(user_project, current_user, {}).execute
end
# Mark this project as forked from another
@@ -198,7 +268,7 @@ module API
render_api_error!("Project already forked", 409)
end
else
- not_found!
+ not_found!("Source Project")
end
end
@@ -227,6 +297,16 @@ module API
ids = current_user.authorized_projects.map(&:id)
visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ]
projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%")
+ sort = params[:sort] == 'desc' ? 'desc' : 'asc'
+
+ projects = case params["order_by"]
+ when 'id' then projects.order("id #{sort}")
+ when 'name' then projects.order("name #{sort}")
+ when 'created_at' then projects.order("created_at #{sort}")
+ when 'last_activity_at' then projects.order("last_activity_at #{sort}")
+ else projects
+ end
+
present paginate(projects), with: Entities::Project
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 626d99c264..b259914a01 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -23,7 +23,8 @@ module API
# Example Request:
# GET /projects/:id/repository/tags
get ":id/repository/tags" do
- present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project
+ present user_project.repo.tags.sort_by(&:name).reverse,
+ with: Entities::RepoTag, project: user_project
end
# Create tag
@@ -43,7 +44,7 @@ module API
if result[:status] == :success
present result[:tag],
- with: Entities::RepoObject,
+ with: Entities::RepoTag,
project: user_project
else
render_api_error!(result[:message], 400)
@@ -57,11 +58,13 @@ module API
# ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
# Example Request:
# GET /projects/:id/repository/tree
- get ":id/repository/tree" do
+ get ':id/repository/tree' do
ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
path = params[:path] || nil
commit = user_project.repository.commit(ref)
+ not_found!('Tree') unless commit
+
tree = user_project.repository.tree(commit.id, path)
present tree.sorted_entries, with: Entities::RepoTreeObject
@@ -99,14 +102,18 @@ module API
# sha (required) - The blob's sha
# Example Request:
# GET /projects/:id/repository/raw_blobs/:sha
- get ":id/repository/raw_blobs/:sha" do
+ get ':id/repository/raw_blobs/:sha' do
ref = params[:sha]
repo = user_project.repository
- blob = Gitlab::Git::Blob.raw(repo, ref)
+ begin
+ blob = Gitlab::Git::Blob.raw(repo, ref)
+ rescue
+ not_found! 'Blob'
+ end
- not_found! "Blob" unless blob
+ not_found! 'Blob' unless blob
env['api.format'] = :txt
@@ -121,18 +128,28 @@ module API
# sha (optional) - the commit sha to download defaults to the tip of the default branch
# Example Request:
# GET /projects/:id/repository/archive
- get ":id/repository/archive", requirements: { format: Gitlab::Regex.archive_formats_regex } do
+ get ':id/repository/archive',
+ requirements: { format: Gitlab::Regex.archive_formats_regex } do
authorize! :download_code, user_project
- file_path = ArchiveRepositoryService.new.execute(user_project, params[:sha], params[:format])
+
+ begin
+ file_path = ArchiveRepositoryService.new.execute(
+ user_project,
+ params[:sha],
+ params[:format])
+ rescue
+ not_found!('File')
+ end
if file_path && File.exists?(file_path)
data = File.open(file_path, 'rb').read
- header["Content-Disposition"] = "attachment; filename=\"#{File.basename(file_path)}\""
+ basename = File.basename(file_path)
+ header['Content-Disposition'] = "attachment; filename=\"#{basename}\""
content_type MIME::Types.type_for(file_path).first.content_type
env['api.format'] = :binary
present data
else
- not_found!
+ not_found!('File')
end
end
@@ -160,7 +177,12 @@ module API
get ':id/repository/contributors' do
authorize! :download_code, user_project
- present user_project.repository.contributors, with: Entities::Contributor
+ begin
+ present user_project.repository.contributors,
+ with: Entities::Contributor
+ rescue
+ not_found!
+ end
end
end
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index bde502e32e..3ad59cf3ad 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -28,7 +28,7 @@ module API
# Delete GitLab CI service settings
#
# Example Request:
- # DELETE /projects/:id/keys/:id
+ # DELETE /projects/:id/services/gitlab-ci
delete ":id/services/gitlab-ci" do
if user_project.gitlab_ci_service
user_project.gitlab_ci_service.update_attributes(
@@ -38,7 +38,41 @@ module API
)
end
end
+
+ # Set Hipchat service for project
+ #
+ # Parameters:
+ # token (required) - Hipchat token
+ # room (required) - Hipchat room name
+ #
+ # Example Request:
+ # PUT /projects/:id/services/hipchat
+ put ':id/services/hipchat' do
+ required_attributes! [:token, :room]
+ attrs = attributes_for_keys [:token, :room]
+ user_project.build_missing_services
+
+ if user_project.hipchat_service.update_attributes(
+ attrs.merge(active: true))
+ true
+ else
+ not_found!
+ end
+ end
+
+ # Delete Hipchat service settings
+ #
+ # Example Request:
+ # DELETE /projects/:id/services/hipchat
+ delete ':id/services/hipchat' do
+ if user_project.hipchat_service
+ user_project.hipchat_service.update_attributes(
+ active: false,
+ token: nil,
+ room: nil
+ )
+ end
+ end
end
end
end
-
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 3e239c5afe..518964db50 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -1,10 +1,10 @@
module API
# Hooks API
class SystemHooks < Grape::API
- before {
+ before do
authenticate!
authenticated_as_admin!
- }
+ end
resource :hooks do
# Get the list of system hooks
diff --git a/lib/api/users.rb b/lib/api/users.rb
index d07815a8a9..7c8b3250cd 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -54,15 +54,24 @@ module API
# bio - Bio
# admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false
+ # confirm - Require user confirmation - true (default) or false
# Example Request:
# POST /users
post do
authenticated_as_admin!
required_attributes! [:email, :password, :name, :username]
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin, :confirm]
user = User.build_user(attrs)
admin = attrs.delete(:admin)
user.admin = admin unless admin.nil?
+ confirm = !(attrs.delete(:confirm) =~ (/(false|f|no|0)$/i))
+ user.skip_confirmation! unless confirm
+
+ identity_attrs = attributes_for_keys [:provider, :extern_uid]
+ if identity_attrs.any?
+ user.identities.build(identity_attrs)
+ end
+
if user.save
present user, with: Entities::UserFull
else
@@ -89,8 +98,6 @@ module API
# twitter - Twitter account
# website_url - Website url
# projects_limit - Limit projects each user can create
- # extern_uid - External authentication provider UID
- # provider - External provider
# bio - Bio
# admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false
@@ -99,7 +106,7 @@ module API
put ":id" do
authenticated_as_admin!
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.find(params[:id])
not_found!('User') unless user
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index d12d30a911..9ab6aca276 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -13,10 +13,10 @@ module Backup
def dump
success = case config["adapter"]
when /^mysql/ then
- print "Dumping MySQL database #{config['database']} ... "
+ $progress.print "Dumping MySQL database #{config['database']} ... "
system('mysqldump', *mysql_args, config['database'], out: db_file_name)
when "postgresql" then
- print "Dumping PostgreSQL database #{config['database']} ... "
+ $progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
system('pg_dump', config['database'], out: db_file_name)
end
@@ -27,13 +27,14 @@ module Backup
def restore
success = case config["adapter"]
when /^mysql/ then
- print "Restoring MySQL database #{config['database']} ... "
+ $progress.print "Restoring MySQL database #{config['database']} ... "
system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then
- print "Restoring PostgreSQL database #{config['database']} ... "
+ $progress.print "Restoring PostgreSQL database #{config['database']} ... "
# Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
# statements like MySQL.
Rake::Task["gitlab:db:drop_all_tables"].invoke
+ Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke
pg_env
system('psql', config['database'], '-f', db_file_name)
end
@@ -68,9 +69,9 @@ module Backup
def report_success(success)
if success
- puts '[DONE]'.green
+ $progress.puts '[DONE]'.green
else
- puts '[FAILED]'.red
+ $progress.puts '[FAILED]'.red
end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 03fe0f0b02..ab8db4e983 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -18,11 +18,11 @@ module Backup
end
# create archive
- print "Creating backup archive: #{tar_file} ... "
+ $progress.print "Creating backup archive: #{tar_file} ... "
if Kernel.system('tar', '-cf', tar_file, *BACKUP_CONTENTS)
- puts "done".green
+ $progress.puts "done".green
else
- puts "failed".red
+ puts "creating archive #{tar_file} failed".red
abort 'Backup failed'
end
@@ -31,37 +31,37 @@ module Backup
def upload(tar_file)
remote_directory = Gitlab.config.backup.upload.remote_directory
- print "Uploading backup archive to remote storage #{remote_directory} ... "
+ $progress.print "Uploading backup archive to remote storage #{remote_directory} ... "
connection_settings = Gitlab.config.backup.upload.connection
if connection_settings.blank?
- puts "skipped".yellow
+ $progress.puts "skipped".yellow
return
end
connection = ::Fog::Storage.new(connection_settings)
directory = connection.directories.get(remote_directory)
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false)
- puts "done".green
+ $progress.puts "done".green
else
- puts "failed".red
+ puts "uploading backup to #{remote_directory} failed".red
abort 'Backup failed'
end
end
def cleanup
- print "Deleting tmp directories ... "
+ $progress.print "Deleting tmp directories ... "
if Kernel.system('rm', '-rf', *BACKUP_CONTENTS)
- puts "done".green
+ $progress.puts "done".green
else
- puts "failed".red
+ puts "deleting tmp directory failed".red
abort 'Backup failed'
end
end
def remove_old
# delete backups
- print "Deleting old backups ... "
+ $progress.print "Deleting old backups ... "
keep_time = Gitlab.config.backup.keep_time.to_i
path = Gitlab.config.backup.path
@@ -76,9 +76,9 @@ module Backup
end
end
end
- puts "done. (#{removed} removed)".green
+ $progress.puts "done. (#{removed} removed)".green
else
- puts "skipping".yellow
+ $progress.puts "skipping".yellow
end
end
@@ -101,12 +101,12 @@ module Backup
exit 1
end
- print "Unpacking backup ... "
+ $progress.print "Unpacking backup ... "
unless Kernel.system(*%W(tar -xf #{tar_file}))
- puts "failed".red
+ puts "unpacking backup failed".red
exit 1
else
- puts "done".green
+ $progress.puts "done".green
end
settings = YAML.load_file("backup_information.yml")
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 4e99d4bbe5..e18bc80443 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -8,19 +8,21 @@ module Backup
prepare
Project.find_each(batch_size: 1000) do |project|
- print " * #{project.path_with_namespace} ... "
+ $progress.print " * #{project.path_with_namespace} ... "
# Create namespace dir if missing
FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
if project.empty_repo?
- puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".cyan
else
- output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all))
+ cmd = %W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all)
+ output, status = Gitlab::Popen.popen(cmd)
if status.zero?
- puts "[DONE]".green
+ $progress.puts "[DONE]".green
else
puts "[FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
puts output
abort 'Backup failed'
end
@@ -29,15 +31,17 @@ module Backup
wiki = ProjectWiki.new(project)
if File.exists?(path_to_repo(wiki))
- print " * #{wiki.path_with_namespace} ... "
- if wiki.empty?
- puts " [SKIPPED]".cyan
+ $progress.print " * #{wiki.path_with_namespace} ... "
+ if wiki.repository.empty?
+ $progress.puts " [SKIPPED]".cyan
else
- output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all))
+ cmd = %W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)
+ output, status = Gitlab::Popen.popen(cmd)
if status.zero?
- puts " [DONE]".green
+ $progress.puts " [DONE]".green
else
puts " [FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
abort 'Backup failed'
end
end
@@ -55,35 +59,52 @@ module Backup
FileUtils.mkdir_p(repos_path)
Project.find_each(batch_size: 1000) do |project|
- print "#{project.path_with_namespace} ... "
+ $progress.print " * #{project.path_with_namespace} ... "
project.namespace.ensure_dir_exist if project.namespace
- if system(*%W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)}), silent)
- puts "[DONE]".green
+ if File.exists?(path_to_bundle(project))
+ cmd = %W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)})
+ else
+ cmd = %W(git init --bare #{path_to_repo(project)})
+ end
+
+ if system(*cmd, silent)
+ $progress.puts "[DONE]".green
else
puts "[FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
abort 'Restore failed'
end
wiki = ProjectWiki.new(project)
if File.exists?(path_to_bundle(wiki))
- print " * #{wiki.path_with_namespace} ... "
- if system(*%W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}), silent)
- puts " [DONE]".green
+ $progress.print " * #{wiki.path_with_namespace} ... "
+
+ # If a wiki bundle exists, first remove the empty repo
+ # that was initialized with ProjectWiki.new() and then
+ # try to restore with 'git clone --bare'.
+ FileUtils.rm_rf(path_to_repo(wiki))
+ cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)})
+
+ if system(*cmd, silent)
+ $progress.puts " [DONE]".green
else
puts " [FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
abort 'Restore failed'
end
end
end
- print 'Put GitLab hooks in repositories dirs'.yellow
- if system("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks")
- puts " [DONE]".green
+ $progress.print 'Put GitLab hooks in repositories dirs'.yellow
+ cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks"
+ if system(cmd)
+ $progress.puts " [DONE]".green
else
puts " [FAILED]".red
+ puts "failed: #{cmd}"
end
end
@@ -91,7 +112,7 @@ module Backup
protected
def path_to_repo(project)
- File.join(repos_path, project.path_with_namespace + '.git')
+ project.repository.path_to_repo
end
def path_to_bundle(project)
diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb
new file mode 100644
index 0000000000..1b80be112a
--- /dev/null
+++ b/lib/disable_email_interceptor.rb
@@ -0,0 +1,8 @@
+# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
+class DisableEmailInterceptor
+
+ def self.delivering_email(message)
+ message.perform_deliveries = false
+ Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}"
+ end
+end
diff --git a/lib/email_validator.rb b/lib/email_validator.rb
index 0a67ebcd79..f509f0a584 100644
--- a/lib/email_validator.rb
+++ b/lib/email_validator.rb
@@ -1,5 +1,5 @@
# Based on https://github.com/balexand/email_validator
-#
+#
# Extended to use only strict mode with following allowed characters:
# ' - apostrophe
#
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index e51cb30bdd..6e4ed01e07 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -1,17 +1,9 @@
# Module providing methods for dealing with separating a tree-ish string and a
# file path string when combined in a request parameter
module ExtractsPath
- extend ActiveSupport::Concern
-
# Raised when given an invalid file path
class InvalidPathError < StandardError; end
- included do
- if respond_to?(:before_filter)
- before_filter :assign_ref_vars
- end
- end
-
# Given a string containing both a Git tree-ish, such as a branch or tag, and
# a filesystem path joined by forward slashes, attempts to separate the two.
#
@@ -110,7 +102,8 @@ module ExtractsPath
raise InvalidPathError unless @commit
@hex_path = Digest::SHA1.hexdigest(@path)
- @logs_path = logs_file_project_ref_path(@project, @ref, @path)
+ @logs_path = logs_file_namespace_project_ref_path(@project.namespace,
+ @project, @ref, @path)
rescue RuntimeError, NoMethodError, InvalidPathError
not_found!
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 411b2b9a3c..424541b4a0 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -11,6 +11,11 @@ module Gitlab
MASTER = 40
OWNER = 50
+ # Branch protection settings
+ PROTECTION_NONE = 0
+ PROTECTION_DEV_CAN_PUSH = 1
+ PROTECTION_FULL = 2
+
class << self
def values
options.values
@@ -43,6 +48,18 @@ module Gitlab
master: MASTER,
}
end
+
+ def protection_options
+ {
+ "Not protected, developers and masters can (force) push and delete the branch" => PROTECTION_NONE,
+ "Partially protected, developers can also push but prevent all force pushes and deletion" => PROTECTION_DEV_CAN_PUSH,
+ "Fully protected, only masters can push and prevent all force pushes and deletion" => PROTECTION_FULL,
+ }
+ end
+
+ def protection_values
+ protection_options.values
+ end
end
def human_access
diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb
index 8e4717b46e..dddcb2538f 100644
--- a/lib/gitlab/app_logger.rb
+++ b/lib/gitlab/app_logger.rb
@@ -1,7 +1,7 @@
module Gitlab
class AppLogger < Gitlab::Logger
- def self.file_name
- 'application.log'
+ def self.file_name_noext
+ 'application'
end
def format_message(severity, timestamp, progname, msg)
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 955abc1bed..30509528b8 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,24 +1,18 @@
module Gitlab
class Auth
def find(login, password)
- user = User.find_by(email: login) || User.find_by(username: login)
+ user = User.by_login(login)
+ # If no user is found, or it's an LDAP server, try LDAP.
+ # LDAP users are only authenticated via LDAP
if user.nil? || user.ldap_user?
# Second chance - try LDAP authentication
- return nil unless ldap_conf.enabled
+ return nil unless Gitlab::LDAP::Config.enabled?
- Gitlab::LDAP::User.authenticate(login, password)
+ Gitlab::LDAP::Authentication.login(login, password)
else
user if user.valid_password?(password)
end
end
-
- def log
- Gitlab::AppLogger
- end
-
- def ldap_conf
- @ldap_conf ||= Gitlab.config.ldap
- end
end
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index c2f3b851c0..ee877e099b 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -10,8 +10,9 @@ module Grack
@request = Rack::Request.new(env)
@auth = Request.new(env)
- # Need this patch due to the rails mount
+ @gitlab_ci = false
+ # Need this patch due to the rails mount
# Need this if under RELATIVE_URL_ROOT
unless Gitlab.config.gitlab.relative_url_root.empty?
# If website is mounted using relative_url_root need to remove it first
@@ -22,8 +23,12 @@ module Grack
@env['SCRIPT_NAME'] = ""
- if project
- auth!
+ auth!
+
+ if project && authorized_request?
+ @app.call(env)
+ elsif @user.nil? && !@gitlab_ci
+ unauthorized
else
render_not_found
end
@@ -32,35 +37,30 @@ module Grack
private
def auth!
- if @auth.provided?
- return bad_request unless @auth.basic?
+ return unless @auth.provided?
- # Authentication with username and password
- login, password = @auth.credentials
+ return bad_request unless @auth.basic?
- # Allow authentication for GitLab CI service
- # if valid token passed
- if gitlab_ci_request?(login, password)
- return @app.call(env)
- end
+ # Authentication with username and password
+ login, password = @auth.credentials
- @user = authenticate_user(login, password)
-
- if @user
- Gitlab::ShellEnv.set_env(@user)
- @env['REMOTE_USER'] = @auth.username
- end
+ # Allow authentication for GitLab CI service
+ # if valid token passed
+ if gitlab_ci_request?(login, password)
+ @gitlab_ci = true
+ return
end
- if authorized_request?
- @app.call(env)
- else
- unauthorized
+ @user = authenticate_user(login, password)
+
+ if @user
+ Gitlab::ShellEnv.set_env(@user)
+ @env['REMOTE_USER'] = @auth.username
end
end
def gitlab_ci_request?(login, password)
- if login == "gitlab-ci-token" && project.gitlab_ci?
+ if login == "gitlab-ci-token" && project && project.gitlab_ci?
token = project.gitlab_ci_service.token
if token.present? && token == password && git_cmd == 'git-upload-pack'
@@ -71,16 +71,48 @@ module Grack
false
end
+ def oauth_access_token_check(login, password)
+ if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present?
+ token = Doorkeeper::AccessToken.by_token(password)
+ token && token.accessible? && User.find_by(id: token.resource_owner_id)
+ end
+ end
+
def authenticate_user(login, password)
- auth = Gitlab::Auth.new
- auth.find(login, password)
+ user = Gitlab::Auth.new.find(login, password)
+
+ unless user
+ user = oauth_access_token_check(login, password)
+ end
+
+ return user if user.present?
+
+ # At this point, we know the credentials were wrong. We let Rack::Attack
+ # know there was a failed authentication attempt from this IP. This
+ # information is stored in the Rails cache (Redis) and will be used by
+ # the Rack::Attack middleware to decide whether to block requests from
+ # this IP.
+ config = Gitlab.config.rack_attack.git_basic_auth
+ Rack::Attack::Allow2Ban.filter(@request.ip, config) do
+ # Unless the IP is whitelisted, return true so that Allow2Ban
+ # increments the counter (stored in Rails.cache) for the IP
+ if config.ip_whitelist.include?(@request.ip)
+ false
+ else
+ true
+ end
+ end
+
+ nil # No user was found
end
def authorized_request?
+ return true if @gitlab_ci
+
case git_cmd
when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
if user
- Gitlab::GitAccess.new.download_allowed?(user, project)
+ Gitlab::GitAccess.new.download_access_check(user, project).allowed?
elsif project.public?
# Allow clone/fetch for public projects
true
@@ -90,7 +122,7 @@ module Grack
when *Gitlab::GitAccess::PUSH_COMMANDS
if user
# Skip user authorization on upload request.
- # It will be serverd by update hook in repository
+ # It will be done by the pre-receive hook in the repository.
true
else
false
@@ -111,7 +143,9 @@ module Grack
end
def project
- @project ||= project_by_path(@request.path_info)
+ return @project if defined?(@project)
+
+ @project = project_by_path(@request.path_info)
end
def project_by_path(path)
@@ -119,12 +153,13 @@ module Grack
path_with_namespace = m.last
path_with_namespace.gsub!(/\.wiki$/, '')
+ path_with_namespace[0] = '' if path_with_namespace.start_with?('/')
Project.find_with_namespace(path_with_namespace)
end
end
def render_not_found
- [404, {"Content-Type" => "text/plain"}, ["Not Found"]]
+ [404, { "Content-Type" => "text/plain" }, ["Not Found"]]
end
end
end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index f95bbde5b3..aabc7f1e69 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -8,6 +8,13 @@ module Gitlab
end
end
+ class << self
+ def version_required
+ @version_required ||= File.read(Rails.root.
+ join('GITLAB_SHELL_VERSION')).strip
+ end
+ end
+
# Init new repository
#
# name - project path with namespace
@@ -16,7 +23,8 @@ module Gitlab
# add_repository("gitlab/gitlab-ci")
#
def add_repository(name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path,
+ 'add-project', "#{name}.git"])
end
# Import repository
@@ -27,7 +35,8 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(name, url)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url, '240'
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project',
+ "#{name}.git", url, '240'])
end
# Move repository
@@ -39,7 +48,8 @@ module Gitlab
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
#
def mv_repository(path, new_path)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
+ "#{path}.git", "#{new_path}.git"])
end
# Update HEAD for repository
@@ -51,7 +61,8 @@ module Gitlab
# update_repository_head("gitlab/gitlab-ci", "3-1-stable")
#
def update_repository_head(path, branch)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head',
+ "#{path}.git", branch])
end
# Fork repository to new namespace
@@ -63,7 +74,8 @@ module Gitlab
# fork_repository("gitlab/gitlab-ci", "randx")
#
def fork_repository(path, fork_namespace)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
+ "#{path}.git", fork_namespace])
end
# Remove repository from file system
@@ -74,7 +86,8 @@ module Gitlab
# remove_repository("gitlab/gitlab-ci")
#
def remove_repository(name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path,
+ 'rm-project', "#{name}.git"])
end
# Add repository branch from passed ref
@@ -87,7 +100,8 @@ module Gitlab
# add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
#
def add_branch(path, branch_name, ref)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch',
+ "#{path}.git", branch_name, ref])
end
# Remove repository branch
@@ -99,7 +113,8 @@ module Gitlab
# rm_branch("gitlab/gitlab-ci", "4-0-stable")
#
def rm_branch(path, branch_name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch',
+ "#{path}.git", branch_name])
end
# Add repository tag from passed ref
@@ -117,7 +132,7 @@ module Gitlab
cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git
#{tag_name} #{ref})
cmd << message unless message.nil? || message.empty?
- system *cmd
+ Gitlab::Utils.system_silent(cmd)
end
# Remove repository tag
@@ -129,7 +144,8 @@ module Gitlab
# rm_tag("gitlab/gitlab-ci", "v4.0")
#
def rm_tag(path, tag_name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag',
+ "#{path}.git", tag_name])
end
# Add new key to gitlab-shell
@@ -138,7 +154,8 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
- system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path,
+ 'add-key', key_id, key_content])
end
# Batch-add keys to authorized_keys
@@ -157,7 +174,8 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...")
#
def remove_key(key_id, key_content)
- system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path,
+ 'rm-key', key_id, key_content])
end
# Remove all ssh keys from gitlab shell
@@ -166,7 +184,7 @@ module Gitlab
# remove_all_keys
#
def remove_all_keys
- system "#{gitlab_shell_path}/bin/gitlab-keys", "clear"
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear'])
end
# Add empty directory for storing repositories
@@ -249,5 +267,13 @@ module Gitlab
def exists?(dir_name)
File.exists?(full_path(dir_name))
end
+
+ def gitlab_shell_projects_path
+ File.join(gitlab_shell_path, 'bin', 'gitlab-projects')
+ end
+
+ def gitlab_shell_keys_path
+ File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
+ end
end
end
diff --git a/lib/gitlab/backend/shell_adapter.rb b/lib/gitlab/backend/shell_adapter.rb
index f247f4593d..fbe2a7a0d7 100644
--- a/lib/gitlab/backend/shell_adapter.rb
+++ b/lib/gitlab/backend/shell_adapter.rb
@@ -9,4 +9,3 @@ module Gitlab
end
end
end
-
diff --git a/lib/gitlab/bitbucket_import.rb b/lib/gitlab/bitbucket_import.rb
new file mode 100644
index 0000000000..7298152e7e
--- /dev/null
+++ b/lib/gitlab/bitbucket_import.rb
@@ -0,0 +1,6 @@
+module Gitlab
+ module BitbucketImport
+ mattr_accessor :public_key
+ @public_key = nil
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb
new file mode 100644
index 0000000000..1e4906c9e3
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/client.rb
@@ -0,0 +1,99 @@
+module Gitlab
+ module BitbucketImport
+ class Client
+ attr_reader :consumer, :api
+
+ def initialize(access_token = nil, access_token_secret = nil)
+ @consumer = ::OAuth::Consumer.new(
+ config.app_id,
+ config.app_secret,
+ bitbucket_options
+ )
+
+ if access_token && access_token_secret
+ @api = ::OAuth::AccessToken.new(@consumer, access_token, access_token_secret)
+ end
+ end
+
+ def request_token(redirect_uri)
+ request_token = consumer.get_request_token(oauth_callback: redirect_uri)
+
+ {
+ oauth_token: request_token.token,
+ oauth_token_secret: request_token.secret,
+ oauth_callback_confirmed: request_token.callback_confirmed?.to_s
+ }
+ end
+
+ def authorize_url(request_token, redirect_uri)
+ request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash)
+
+ if request_token.callback_confirmed?
+ request_token.authorize_url
+ else
+ request_token.authorize_url(oauth_callback: redirect_uri)
+ end
+ end
+
+ def get_token(request_token, oauth_verifier, redirect_uri)
+ request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash)
+
+ if request_token.callback_confirmed?
+ request_token.get_access_token(oauth_verifier: oauth_verifier)
+ else
+ request_token.get_access_token(oauth_callback: redirect_uri)
+ end
+ end
+
+ def user
+ JSON.parse(api.get("/api/1.0/user").body)
+ end
+
+ def issues(project_identifier)
+ JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues").body)
+ end
+
+ def issue_comments(project_identifier, issue_id)
+ JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues/#{issue_id}/comments").body)
+ end
+
+ def project(project_identifier)
+ JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}").body)
+ end
+
+ def find_deploy_key(project_identifier, key)
+ JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find do |deploy_key|
+ deploy_key["key"].chomp == key.chomp
+ end
+ end
+
+ def add_deploy_key(project_identifier, key)
+ deploy_key = find_deploy_key(project_identifier, key)
+ return if deploy_key
+
+ JSON.parse(api.post("/api/1.0/repositories/#{project_identifier}/deploy-keys", key: key, label: "GitLab import key").body)
+ end
+
+ def delete_deploy_key(project_identifier, key)
+ deploy_key = find_deploy_key(project_identifier, key)
+ return unless deploy_key
+
+ api.delete("/api/1.0/repositories/#{project_identifier}/deploy-keys/#{deploy_key["pk"]}").code == "204"
+ end
+
+ def projects
+ JSON.parse(api.get("/api/1.0/user/repositories").body).select { |repo| repo["scm"] == "git" }
+ end
+
+ private
+
+ def config
+ Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"}
+ end
+
+ def bitbucket_options
+ OmniAuth::Strategies::Bitbucket.default_options[:client_options].dup
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
new file mode 100644
index 0000000000..42c93707ca
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -0,0 +1,52 @@
+module Gitlab
+ module BitbucketImport
+ class Importer
+ attr_reader :project, :client
+
+ def initialize(project)
+ @project = project
+ @client = Client.new(project.creator.bitbucket_access_token, project.creator.bitbucket_access_token_secret)
+ @formatter = Gitlab::ImportFormatter.new
+ end
+
+ def execute
+ project_identifier = project.import_source
+
+ return true unless client.project(project_identifier)["has_issues"]
+
+ #Issues && Comments
+ issues = client.issues(project_identifier)
+
+ issues["issues"].each do |issue|
+ body = @formatter.author_line(issue["reported_by"]["username"], issue["content"])
+
+ comments = client.issue_comments(project_identifier, issue["local_id"])
+
+ if comments.any?
+ body += @formatter.comments_header
+ end
+
+ comments.each do |comment|
+ body += @formatter.comment(comment["author_info"]["username"], comment["utc_created_on"], comment["content"])
+ end
+
+ project.issues.create!(
+ description: body,
+ title: issue["title"],
+ state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened',
+ author_id: gl_user_id(project, issue["reported_by"]["username"])
+ )
+ end
+
+ true
+ end
+
+ private
+
+ def gl_user_id(project, bitbucket_id)
+ user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s)
+ (user && user.id) || project.creator_id
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/key_adder.rb b/lib/gitlab/bitbucket_import/key_adder.rb
new file mode 100644
index 0000000000..9931aa7e02
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/key_adder.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module BitbucketImport
+ class KeyAdder
+ attr_reader :repo, :current_user, :client
+
+ def initialize(repo, current_user)
+ @repo, @current_user = repo, current_user
+ @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret)
+ end
+
+ def execute
+ return false unless BitbucketImport.public_key.present?
+
+ project_identifier = "#{repo["owner"]}/#{repo["slug"]}"
+ client.add_deploy_key(project_identifier, BitbucketImport.public_key)
+
+ true
+ rescue
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/key_deleter.rb b/lib/gitlab/bitbucket_import/key_deleter.rb
new file mode 100644
index 0000000000..1a24a86fc3
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/key_deleter.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module BitbucketImport
+ class KeyDeleter
+ attr_reader :project, :current_user, :client
+
+ def initialize(project)
+ @project = project
+ @current_user = project.creator
+ @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret)
+ end
+
+ def execute
+ return false unless BitbucketImport.public_key.present?
+
+ client.delete_deploy_key(project.import_source, BitbucketImport.public_key)
+
+ true
+ rescue
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb
new file mode 100644
index 0000000000..db33af2c2d
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/project_creator.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module BitbucketImport
+ class ProjectCreator
+ attr_reader :repo, :namespace, :current_user
+
+ def initialize(repo, namespace, current_user)
+ @repo = repo
+ @namespace = namespace
+ @current_user = current_user
+ end
+
+ def execute
+ @project = Project.new(
+ name: repo["name"],
+ path: repo["slug"],
+ description: repo["description"],
+ namespace: namespace,
+ creator: current_user,
+ visibility_level: repo["is_private"] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC,
+ import_type: "bitbucket",
+ import_source: "#{repo["owner"]}/#{repo["slug"]}",
+ import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git"
+ )
+
+ if @project.save!
+ @project.reload
+
+ if @project.import_failed?
+ @project.import_retry
+ else
+ @project.import_start
+ end
+ end
+
+ @project
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index 401e6e047b..a9fd59f03d 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -3,14 +3,19 @@ module Gitlab
ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
def self.closed_by_message_in_project(message, project)
- md = ISSUE_CLOSING_REGEX.match(message)
- if md
- extractor = Gitlab::ReferenceExtractor.new
- extractor.analyze(md[0], project)
- extractor.issues_for(project)
- else
- []
+ issues = []
+
+ unless message.nil?
+ md = message.scan(ISSUE_CLOSING_REGEX)
+
+ md.each do |ref|
+ extractor = Gitlab::ReferenceExtractor.new
+ extractor.analyze(ref[0], project)
+ issues += extractor.issues_for(project)
+ end
end
+
+ issues.uniq
end
end
end
diff --git a/lib/gitlab/commits_calendar.rb b/lib/gitlab/commits_calendar.rb
new file mode 100644
index 0000000000..2f30d238e6
--- /dev/null
+++ b/lib/gitlab/commits_calendar.rb
@@ -0,0 +1,33 @@
+module Gitlab
+ class CommitsCalendar
+ attr_reader :timestamps
+
+ def initialize(projects, user)
+ @timestamps = {}
+ date_timestamps = []
+
+ projects.reject(&:forked?).each do |project|
+ date_timestamps << ProjectContributions.new(project, user).commits_log
+ end
+
+ # Sumarrize commits from all projects per days
+ date_timestamps = date_timestamps.inject do |collection, date|
+ collection.merge(date) { |k, old_v, new_v| old_v + new_v }
+ end
+
+ date_timestamps ||= []
+ date_timestamps.each do |date, commits|
+ timestamp = Date.parse(date).to_time.to_i.to_s rescue nil
+ @timestamps[timestamp] = commits if timestamp
+ end
+ end
+
+ def starting_year
+ (Time.now - 1.year).strftime("%Y")
+ end
+
+ def starting_month
+ Date.today.strftime("%m").to_i
+ end
+ end
+end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
new file mode 100644
index 0000000000..0ebebfa09c
--- /dev/null
+++ b/lib/gitlab/current_settings.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module CurrentSettings
+ def current_application_settings
+ key = :current_application_settings
+
+ RequestStore.store[key] ||= begin
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('application_settings')
+ ApplicationSetting.current || ApplicationSetting.create_from_defaults
+ else
+ fake_application_settings
+ end
+ end
+ end
+
+ def fake_application_settings
+ OpenStruct.new(
+ default_projects_limit: Settings.gitlab['default_projects_limit'],
+ default_branch_protection: Settings.gitlab['default_branch_protection'],
+ signup_enabled: Settings.gitlab['signup_enabled'],
+ signin_enabled: Settings.gitlab['signin_enabled'],
+ gravatar_enabled: Settings.gravatar['enabled'],
+ sign_in_text: Settings.extra['sign_in_text'],
+ restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels']
+ )
+ end
+ end
+end
diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
index f7c1f20d76..c1d9520ddf 100644
--- a/lib/gitlab/diff/parser.rb
+++ b/lib/gitlab/diff/parser.rb
@@ -4,7 +4,7 @@ module Gitlab
include Enumerable
def parse(lines)
- @lines = lines,
+ @lines = lines
lines_obj = []
line_obj_index = 0
line_old = 1
@@ -27,7 +27,7 @@ module Gitlab
line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
- next if line_old == 1 && line_new == 1 #top of file
+ next if line_old <= 1 && line_new <= 1 #top of file
lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new)
line_obj_index += 1
next
@@ -74,7 +74,7 @@ module Gitlab
def html_escape(str)
replacements = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
- str.gsub(/[&"'><]/, replacements)
+ str.gsub(/[&"'><]/, replacements)
end
end
end
diff --git a/lib/gitlab/force_push_check.rb b/lib/gitlab/force_push_check.rb
new file mode 100644
index 0000000000..eae9773a06
--- /dev/null
+++ b/lib/gitlab/force_push_check.rb
@@ -0,0 +1,14 @@
+module Gitlab
+ class ForcePushCheck
+ def self.force_push?(project, oldrev, newrev)
+ return false if project.empty_repo?
+
+ if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA
+ missed_refs, _ = Gitlab::Popen.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev}))
+ missed_refs.split("\n").size > 0
+ else
+ false
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
new file mode 100644
index 0000000000..0c350d7c67
--- /dev/null
+++ b/lib/gitlab/git.rb
@@ -0,0 +1,25 @@
+module Gitlab
+ module Git
+ BLANK_SHA = '0' * 40
+ TAG_REF_PREFIX = "refs/tags/"
+ BRANCH_REF_PREFIX = "refs/heads/"
+
+ class << self
+ def ref_name(ref)
+ ref.gsub(/\Arefs\/(tags|heads)\//, '')
+ end
+
+ def tag_ref?(ref)
+ ref.start_with?(TAG_REF_PREFIX)
+ end
+
+ def branch_ref?(ref)
+ ref.start_with?(BRANCH_REF_PREFIX)
+ end
+
+ def blank_ref?(ref)
+ ref == BLANK_SHA
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index b768a99a0e..cb69e4b13d 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -5,104 +5,138 @@ module Gitlab
attr_reader :params, :project, :git_cmd, :user
- def allowed?(actor, cmd, project, changes = nil)
+ def self.can_push_to_branch?(user, project, ref)
+ return false unless user
+
+ if project.protected_branch?(ref) &&
+ !(project.developers_can_push_to_protected_branch?(ref) && project.team.developer?(user))
+ user.can?(:push_code_to_protected_branches, project)
+ else
+ user.can?(:push_code, project)
+ end
+ end
+
+ def check(actor, cmd, project, changes = nil)
case cmd
when *DOWNLOAD_COMMANDS
- if actor.is_a? User
- download_allowed?(actor, project)
- elsif actor.is_a? DeployKey
- actor.projects.include?(project)
- elsif actor.is_a? Key
- download_allowed?(actor.user, project)
- else
- raise 'Wrong actor'
- end
+ download_access_check(actor, project)
when *PUSH_COMMANDS
if actor.is_a? User
- push_allowed?(actor, project, changes)
+ push_access_check(actor, project, changes)
elsif actor.is_a? DeployKey
- # Deploy key not allowed to push
- return false
+ return build_status_object(false, "Deploy key not allowed to push")
elsif actor.is_a? Key
- push_allowed?(actor.user, project, changes)
+ push_access_check(actor.user, project, changes)
else
raise 'Wrong actor'
end
else
- false
+ return build_status_object(false, "Wrong command")
end
end
- def download_allowed?(user, project)
- if user && user_allowed?(user)
- user.can?(:download_code, project)
+ def download_access_check(actor, project)
+ if actor.is_a?(User)
+ user_download_access_check(actor, project)
+ elsif actor.is_a?(DeployKey)
+ if actor.projects.include?(project)
+ build_status_object(true)
+ else
+ build_status_object(false, "Deploy key not allowed to access this project")
+ end
+ elsif actor.is_a? Key
+ user_download_access_check(actor.user, project)
else
- false
+ raise 'Wrong actor'
end
end
- def push_allowed?(user, project, changes)
- return false unless user && user_allowed?(user)
- return true if changes.blank?
+ def user_download_access_check(user, project)
+ if user && user_allowed?(user) && user.can?(:download_code, project)
+ build_status_object(true)
+ else
+ build_status_object(false, "You don't have access")
+ end
+ end
+
+ def push_access_check(user, project, changes)
+ unless user && user_allowed?(user)
+ return build_status_object(false, "You don't have access")
+ end
+
+ if changes.blank?
+ return build_status_object(true)
+ end
+
+ unless project.repository.exists?
+ return build_status_object(false, "Repository does not exist")
+ end
changes = changes.lines if changes.kind_of?(String)
# Iterate over all changes to find if user allowed all of them to be applied
- changes.each do |change|
- unless change_allowed?(user, project, change)
+ changes.map(&:strip).reject(&:blank?).each do |change|
+ status = change_access_check(user, project, change)
+ unless status.allowed?
# If user does not have access to make at least one change - cancel all push
- return false
+ return status
end
end
- # If user has access to make all changes
- true
+ return build_status_object(true)
end
- def change_allowed?(user, project, change)
+ def change_access_check(user, project, change)
oldrev, newrev, ref = change.split(' ')
action = if project.protected_branch?(branch_name(ref))
- # we dont allow force push to protected branch
- if forced_push?(project, oldrev, newrev)
- :force_push_code_to_protected_branches
- # and we dont allow remove of protected branch
- elsif newrev =~ /0000000/
- :remove_protected_branches
- else
- :push_code_to_protected_branches
- end
- elsif project.repository && project.repository.tag_names.include?(tag_name(ref))
+ protected_branch_action(project, oldrev, newrev, branch_name(ref))
+ elsif protected_tag?(project, tag_name(ref))
# Prevent any changes to existing git tag unless user has permissions
:admin_project
else
:push_code
end
- user.can?(action, project)
- end
-
- def forced_push?(project, oldrev, newrev)
- return false if project.empty_repo?
-
- if oldrev !~ /00000000/ && newrev !~ /00000000/
- missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read
- missed_refs.split("\n").size > 0
+ if user.can?(action, project)
+ build_status_object(true)
else
- false
+ build_status_object(false, "You don't have permission")
end
end
+ def forced_push?(project, oldrev, newrev)
+ Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
+ end
+
private
+ def protected_branch_action(project, oldrev, newrev, branch_name)
+ # we dont allow force push to protected branch
+ if forced_push?(project, oldrev, newrev)
+ :force_push_code_to_protected_branches
+ elsif Gitlab::Git.blank_ref?(newrev)
+ # and we dont allow remove of protected branch
+ :remove_protected_branches
+ elsif project.developers_can_push_to_protected_branch?(branch_name)
+ :push_code
+ else
+ :push_code_to_protected_branches
+ end
+ end
+
+ def protected_tag?(project, tag_name)
+ project.repository.tag_names.include?(tag_name)
+ end
+
def user_allowed?(user)
Gitlab::UserAccess.allowed?(user)
end
def branch_name(ref)
ref = ref.to_s
- if ref.start_with?('refs/heads')
- ref.sub(%r{\Arefs/heads/}, '')
+ if Gitlab::Git.branch_ref?(ref)
+ Gitlab::Git.ref_name(ref)
else
nil
end
@@ -110,11 +144,17 @@ module Gitlab
def tag_name(ref)
ref = ref.to_s
- if ref.start_with?('refs/tags')
- ref.sub(%r{\Arefs/tags/}, '')
+ if Gitlab::Git.tag_ref?(ref)
+ Gitlab::Git.ref_name(ref)
else
nil
end
end
+
+ protected
+
+ def build_status_object(status, message = '')
+ GitAccessStatus.new(status, message)
+ end
end
end
diff --git a/lib/gitlab/git_access_status.rb b/lib/gitlab/git_access_status.rb
new file mode 100644
index 0000000000..5a806ff6e0
--- /dev/null
+++ b/lib/gitlab/git_access_status.rb
@@ -0,0 +1,15 @@
+module Gitlab
+ class GitAccessStatus
+ attr_accessor :status, :message
+ alias_method :allowed?, :status
+
+ def initialize(status, message = '')
+ @status = status
+ @message = message
+ end
+
+ def to_json
+ { status: @status, message: @message }.to_json
+ end
+ end
+end
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 9f0eb3be20..a2177c8d54 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -1,7 +1,11 @@
module Gitlab
class GitAccessWiki < GitAccess
- def change_allowed?(user, project, change)
- user.can?(:write_wiki, project)
+ def change_access_check(user, project, change)
+ if user.can?(:write_wiki, project)
+ build_status_object(true)
+ else
+ build_status_object(false, "You don't have access")
+ end
end
end
end
diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb
index fbfed205a0..9e02ccc0f4 100644
--- a/lib/gitlab/git_logger.rb
+++ b/lib/gitlab/git_logger.rb
@@ -1,7 +1,7 @@
module Gitlab
class GitLogger < Gitlab::Logger
- def self.file_name
- 'githost.log'
+ def self.file_name_noext
+ 'githost'
end
def format_message(severity, timestamp, progname, msg)
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
index 13cb08948b..39d17def93 100644
--- a/lib/gitlab/git_ref_validator.rb
+++ b/lib/gitlab/git_ref_validator.rb
@@ -5,7 +5,8 @@ module Gitlab
#
# Returns true for a valid reference name, false otherwise
def validate(ref_name)
- system *%W(git check-ref-format refs/#{ref_name})
+ Gitlab::Utils.system_silent(
+ %W(git check-ref-format refs/#{ref_name}))
end
end
end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
new file mode 100644
index 0000000000..7fe076b333
--- /dev/null
+++ b/lib/gitlab/github_import/client.rb
@@ -0,0 +1,53 @@
+module Gitlab
+ module GithubImport
+ class Client
+ attr_reader :client, :api
+
+ def initialize(access_token)
+ @client = ::OAuth2::Client.new(
+ config.app_id,
+ config.app_secret,
+ github_options
+ )
+
+ if access_token
+ ::Octokit.auto_paginate = true
+ @api = ::Octokit::Client.new(access_token: access_token)
+ end
+ end
+
+ def authorize_url(redirect_uri)
+ client.auth_code.authorize_url({
+ redirect_uri: redirect_uri,
+ scope: "repo, user, user:email"
+ })
+ end
+
+ def get_token(code)
+ client.auth_code.get_token(code).token
+ end
+
+ def method_missing(method, *args, &block)
+ if api.respond_to?(method)
+ api.send(method, *args, &block)
+ else
+ super(method, *args, &block)
+ end
+ end
+
+ def respond_to?(method)
+ api.respond_to?(method) || super
+ end
+
+ private
+
+ def config
+ Gitlab.config.omniauth.providers.find{|provider| provider.name == "github"}
+ end
+
+ def github_options
+ OmniAuth::Strategies::GitHub.default_options[:client_options].dup
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
new file mode 100644
index 0000000000..23832b3233
--- /dev/null
+++ b/lib/gitlab/github_import/importer.rb
@@ -0,0 +1,46 @@
+module Gitlab
+ module GithubImport
+ class Importer
+ attr_reader :project, :client
+
+ def initialize(project)
+ @project = project
+ @client = Client.new(project.creator.github_access_token)
+ @formatter = Gitlab::ImportFormatter.new
+ end
+
+ def execute
+ #Issues && Comments
+ client.list_issues(project.import_source, state: :all).each do |issue|
+ if issue.pull_request.nil?
+
+ body = @formatter.author_line(issue.user.login, issue.body)
+
+ if issue.comments > 0
+ body += @formatter.comments_header
+
+ client.issue_comments(project.import_source, issue.number).each do |c|
+ body += @formatter.comment(c.user.login, c.created_at, c.body)
+ end
+ end
+
+ project.issues.create!(
+ description: body,
+ title: issue.title,
+ state: issue.state == 'closed' ? 'closed' : 'opened',
+ author_id: gl_user_id(project, issue.user.id)
+ )
+ end
+ end
+ end
+
+ private
+
+ def gl_user_id(project, github_id)
+ user = User.joins(:identities).
+ find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s)
+ (user && user.id) || project.creator_id
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb
new file mode 100644
index 0000000000..9439ca6cbf
--- /dev/null
+++ b/lib/gitlab/github_import/project_creator.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module GithubImport
+ class ProjectCreator
+ attr_reader :repo, :namespace, :current_user
+
+ def initialize(repo, namespace, current_user)
+ @repo = repo
+ @namespace = namespace
+ @current_user = current_user
+ end
+
+ def execute
+ @project = Project.new(
+ name: repo.name,
+ path: repo.name,
+ description: repo.description,
+ namespace: namespace,
+ creator: current_user,
+ visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC,
+ import_type: "github",
+ import_source: repo.full_name,
+ import_url: repo.clone_url.sub("https://", "https://#{current_user.github_access_token}@")
+ )
+
+ if @project.save!
+ @project.reload
+
+ if @project.import_failed?
+ @project.import_retry
+ else
+ @project.import_start
+ end
+ end
+
+ @project
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb
new file mode 100644
index 0000000000..2236439c6c
--- /dev/null
+++ b/lib/gitlab/gitlab_import/client.rb
@@ -0,0 +1,78 @@
+module Gitlab
+ module GitlabImport
+ class Client
+ attr_reader :client, :api
+
+ PER_PAGE = 100
+
+ def initialize(access_token)
+ @client = ::OAuth2::Client.new(
+ config.app_id,
+ config.app_secret,
+ gitlab_options
+ )
+
+ if access_token
+ @api = OAuth2::AccessToken.from_hash(@client, access_token: access_token)
+ end
+ end
+
+ def authorize_url(redirect_uri)
+ client.auth_code.authorize_url({
+ redirect_uri: redirect_uri,
+ scope: "api"
+ })
+ end
+
+ def get_token(code, redirect_uri)
+ client.auth_code.get_token(code, redirect_uri: redirect_uri).token
+ end
+
+ def issues(project_identifier)
+ lazy_page_iterator(PER_PAGE) do |page|
+ api.get("/api/v3/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed
+ end
+ end
+
+ def issue_comments(project_identifier, issue_id)
+ lazy_page_iterator(PER_PAGE) do |page|
+ api.get("/api/v3/projects/#{project_identifier}/issues/#{issue_id}/notes?per_page=#{PER_PAGE}&page=#{page}").parsed
+ end
+ end
+
+ def project(id)
+ api.get("/api/v3/projects/#{id}").parsed
+ end
+
+ def projects
+ lazy_page_iterator(PER_PAGE) do |page|
+ api.get("/api/v3/projects?per_page=#{PER_PAGE}&page=#{page}").parsed
+ end
+ end
+
+ private
+
+ def lazy_page_iterator(per_page)
+ Enumerator.new do |y|
+ page = 1
+ loop do
+ items = yield(page)
+ items.each do |item|
+ y << item
+ end
+ break if items.empty? || items.size < per_page
+ page += 1
+ end
+ end
+ end
+
+ def config
+ Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"}
+ end
+
+ def gitlab_options
+ OmniAuth::Strategies::GitLab.default_options[:client_options].dup
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
new file mode 100644
index 0000000000..c5304a0699
--- /dev/null
+++ b/lib/gitlab/gitlab_import/importer.rb
@@ -0,0 +1,50 @@
+module Gitlab
+ module GitlabImport
+ class Importer
+ attr_reader :project, :client
+
+ def initialize(project)
+ @project = project
+ @client = Client.new(project.creator.gitlab_access_token)
+ @formatter = Gitlab::ImportFormatter.new
+ end
+
+ def execute
+ project_identifier = URI.encode(project.import_source, '/')
+
+ #Issues && Comments
+ issues = client.issues(project_identifier)
+
+ issues.each do |issue|
+ body = @formatter.author_line(issue["author"]["name"], issue["description"])
+
+ comments = client.issue_comments(project_identifier, issue["id"])
+
+ if comments.any?
+ body += @formatter.comments_header
+ end
+
+ comments.each do |comment|
+ body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"])
+ end
+
+ project.issues.create!(
+ description: body,
+ title: issue["title"],
+ state: issue["state"],
+ author_id: gl_user_id(project, issue["author"]["id"])
+ )
+ end
+
+ true
+ end
+
+ private
+
+ def gl_user_id(project, gitlab_id)
+ user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'gitlab'", gitlab_id.to_s)
+ (user && user.id) || project.creator_id
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb
new file mode 100644
index 0000000000..6424d56f8f
--- /dev/null
+++ b/lib/gitlab/gitlab_import/project_creator.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module GitlabImport
+ class ProjectCreator
+ attr_reader :repo, :namespace, :current_user
+
+ def initialize(repo, namespace, current_user)
+ @repo = repo
+ @namespace = namespace
+ @current_user = current_user
+ end
+
+ def execute
+ @project = Project.new(
+ name: repo["name"],
+ path: repo["path"],
+ description: repo["description"],
+ namespace: namespace,
+ creator: current_user,
+ visibility_level: repo["visibility_level"],
+ import_type: "gitlab",
+ import_source: repo["path_with_namespace"],
+ import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{current_user.gitlab_access_token}@")
+ )
+
+ if @project.save!
+ @project.reload
+
+ if @project.import_failed?
+ @project.import_retry
+ else
+ @project.import_start
+ end
+ end
+
+ @project
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitorious_import/client.rb b/lib/gitlab/gitorious_import/client.rb
new file mode 100644
index 0000000000..5043f6a2eb
--- /dev/null
+++ b/lib/gitlab/gitorious_import/client.rb
@@ -0,0 +1,63 @@
+module Gitlab
+ module GitoriousImport
+ GITORIOUS_HOST = "https://gitorious.org"
+
+ class Client
+ attr_reader :repo_list
+
+ def initialize(repo_list)
+ @repo_list = repo_list
+ end
+
+ def authorize_url(redirect_uri)
+ "#{GITORIOUS_HOST}/gitlab-import?callback_url=#{redirect_uri}"
+ end
+
+ def repos
+ @repos ||= repo_names.map { |full_name| Repository.new(full_name) }
+ end
+
+ def repo(id)
+ repos.find { |repo| repo.id == id }
+ end
+
+ private
+
+ def repo_names
+ repo_list.to_s.split(',').map(&:strip).reject(&:blank?)
+ end
+ end
+
+ Repository = Struct.new(:full_name) do
+ def id
+ Digest::SHA1.hexdigest(full_name)
+ end
+
+ def namespace
+ segments.first
+ end
+
+ def path
+ segments.last
+ end
+
+ def name
+ path.titleize
+ end
+
+ def description
+ ""
+ end
+
+ def import_url
+ "#{GITORIOUS_HOST}/#{full_name}.git"
+ end
+
+ private
+
+ def segments
+ full_name.split('/')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitorious_import/project_creator.rb b/lib/gitlab/gitorious_import/project_creator.rb
new file mode 100644
index 0000000000..3cbebe5399
--- /dev/null
+++ b/lib/gitlab/gitorious_import/project_creator.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module GitoriousImport
+ class ProjectCreator
+ attr_reader :repo, :namespace, :current_user
+
+ def initialize(repo, namespace, current_user)
+ @repo = repo
+ @namespace = namespace
+ @current_user = current_user
+ end
+
+ def execute
+ @project = Project.new(
+ name: repo.name,
+ path: repo.path,
+ description: repo.description,
+ namespace: namespace,
+ creator: current_user,
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC,
+ import_type: "gitorious",
+ import_source: repo.full_name,
+ import_url: repo.import_url
+ )
+
+ if @project.save!
+ @project.reload
+
+ if @project.import_failed?
+ @project.import_retry
+ else
+ @project.import_start
+ end
+ end
+
+ @project
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb
new file mode 100644
index 0000000000..72e041a90b
--- /dev/null
+++ b/lib/gitlab/import_formatter.rb
@@ -0,0 +1,15 @@
+module Gitlab
+ class ImportFormatter
+ def comment(author, date, body)
+ "\n\n*By #{author} on #{date}*\n\n#{body}"
+ end
+
+ def comments_header
+ "\n\n\n**Imported comments:**\n"
+ end
+
+ def author_line(author, body)
+ "*Created by: #{author}*\n\n#{body}"
+ end
+ end
+end
diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb
index 0d34976736..1bec608829 100644
--- a/lib/gitlab/issues_labels.rb
+++ b/lib/gitlab/issues_labels.rb
@@ -15,7 +15,6 @@ module Gitlab
{ title: "support", color: yellow },
{ title: "discussion", color: blue },
{ title: "suggestion", color: blue },
- { title: "feature", color: green },
{ title: "enhancement", color: green }
]
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index d2235d2e3b..960fb3849b 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -1,18 +1,21 @@
+# LDAP authorization model
+#
+# * Check if we are allowed access (not blocked)
+#
module Gitlab
module LDAP
class Access
- attr_reader :adapter
+ attr_reader :adapter, :provider, :user
- def self.open(&block)
- Gitlab::LDAP::Adapter.open do |adapter|
- block.call(self.new(adapter))
+ def self.open(user, &block)
+ Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter|
+ block.call(self.new(user, adapter))
end
end
def self.allowed?(user)
- self.open do |access|
- if access.allowed?(user)
- # GitLab EE LDAP code goes here
+ self.open(user) do |access|
+ if access.allowed?
user.last_credential_check_at = Time.now
user.save
true
@@ -22,14 +25,23 @@ module Gitlab
end
end
- def initialize(adapter=nil)
+ def initialize(user, adapter=nil)
@adapter = adapter
+ @user = user
+ @provider = user.ldap_identity.provider
end
- def allowed?(user)
- if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
- if Gitlab.config.ldap.active_directory
- !Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter)
+ def allowed?
+ if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
+ return true unless ldap_config.active_directory
+
+ # Block user in GitLab if he/she was blocked in AD
+ if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
+ user.block unless user.blocked?
+ false
+ else
+ user.activate if user.blocked?
+ true
end
else
false
@@ -37,6 +49,14 @@ module Gitlab
rescue
false
end
+
+ def adapter
+ @adapter ||= Gitlab::LDAP::Adapter.new(provider)
+ end
+
+ def ldap_config
+ Gitlab::LDAP::Config.new(provider)
+ end
end
end
end
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index 68ac1b2290..577a890a7d 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -1,55 +1,28 @@
module Gitlab
module LDAP
class Adapter
- attr_reader :ldap
+ attr_reader :provider, :ldap
- def self.open(&block)
- Net::LDAP.open(adapter_options) do |ldap|
- block.call(self.new(ldap))
+ def self.open(provider, &block)
+ Net::LDAP.open(config(provider).adapter_options) do |ldap|
+ block.call(self.new(provider, ldap))
end
end
- def self.config
- Gitlab.config.ldap
+ def self.config(provider)
+ Gitlab::LDAP::Config.new(provider)
end
- def self.adapter_options
- encryption =
- case config['method'].to_s
- when 'ssl'
- :simple_tls
- when 'tls'
- :start_tls
- else
- nil
- end
-
- options = {
- host: config['host'],
- port: config['port'],
- encryption: encryption
- }
-
- auth_options = {
- auth: {
- method: :simple,
- username: config['bind_dn'],
- password: config['password']
- }
- }
-
- if config['password'] || config['bind_dn']
- options.merge!(auth_options)
- end
- options
+ def initialize(provider, ldap=nil)
+ @provider = provider
+ @ldap = ldap || Net::LDAP.new(config.adapter_options)
end
-
- def initialize(ldap=nil)
- @ldap = ldap || Net::LDAP.new(self.class.adapter_options)
+ def config
+ Gitlab::LDAP::Config.new(provider)
end
- def users(field, value)
+ def users(field, value, limit = nil)
if field.to_sym == :dn
options = {
base: value,
@@ -57,13 +30,13 @@ module Gitlab
}
else
options = {
- base: config['base'],
+ base: config.base,
filter: Net::LDAP::Filter.eq(field, value)
}
end
- if config['user_filter'].present?
- user_filter = Net::LDAP::Filter.construct(config['user_filter'])
+ if config.user_filter.present?
+ user_filter = Net::LDAP::Filter.construct(config.user_filter)
options[:filter] = if options[:filter]
Net::LDAP::Filter.join(options[:filter], user_filter)
@@ -72,12 +45,16 @@ module Gitlab
end
end
+ if limit.present?
+ options.merge!(size: limit)
+ end
+
entries = ldap_search(options).select do |entry|
entry.respond_to? config.uid
end
entries.map do |entry|
- Gitlab::LDAP::Person.new(entry)
+ Gitlab::LDAP::Person.new(entry, provider)
end
end
@@ -86,8 +63,10 @@ module Gitlab
end
def dn_matches_filter?(dn, filter)
- ldap_search(base: dn, filter: filter,
- scope: Net::LDAP::SearchScope_BaseObject, attributes: %w{dn}).any?
+ ldap_search(base: dn,
+ filter: filter,
+ scope: Net::LDAP::SearchScope_BaseObject,
+ attributes: %w{dn}).any?
end
def ldap_search(*args)
@@ -105,12 +84,6 @@ module Gitlab
results
end
end
-
- private
-
- def config
- @config ||= self.class.config
- end
end
end
end
diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb
new file mode 100644
index 0000000000..649cf3194b
--- /dev/null
+++ b/lib/gitlab/ldap/authentication.rb
@@ -0,0 +1,71 @@
+# This calls helps to authenticate to LDAP by providing username and password
+#
+# Since multiple LDAP servers are supported, it will loop through all of them
+# until a valid bind is found
+#
+
+module Gitlab
+ module LDAP
+ class Authentication
+ def self.login(login, password)
+ return unless Gitlab::LDAP::Config.enabled?
+ return unless login.present? && password.present?
+
+ auth = nil
+ # loop through providers until valid bind
+ providers.find do |provider|
+ auth = new(provider)
+ auth.login(login, password) # true will exit the loop
+ end
+
+ # If (login, password) was invalid for all providers, the value of auth is now the last
+ # Gitlab::LDAP::Authentication instance we tried.
+ auth.user
+ end
+
+ def self.providers
+ Gitlab::LDAP::Config.providers
+ end
+
+ attr_accessor :provider, :ldap_user
+
+ def initialize(provider)
+ @provider = provider
+ end
+
+ def login(login, password)
+ @ldap_user = adapter.bind_as(
+ filter: user_filter(login),
+ size: 1,
+ password: password
+ )
+ end
+
+ def adapter
+ OmniAuth::LDAP::Adaptor.new(config.options.symbolize_keys)
+ end
+
+ def config
+ Gitlab::LDAP::Config.new(provider)
+ end
+
+ def user_filter(login)
+ filter = Net::LDAP::Filter.equals(config.uid, login)
+
+ # Apply LDAP user filter if present
+ if config.user_filter.present?
+ filter = Net::LDAP::Filter.join(
+ filter,
+ Net::LDAP::Filter.construct(config.user_filter)
+ )
+ end
+ filter
+ end
+
+ def user
+ return nil unless ldap_user
+ Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
new file mode 100644
index 0000000000..0cb24d0ccc
--- /dev/null
+++ b/lib/gitlab/ldap/config.rb
@@ -0,0 +1,120 @@
+# Load a specific server configuration
+module Gitlab
+ module LDAP
+ class Config
+ attr_accessor :provider, :options
+
+ def self.enabled?
+ Gitlab.config.ldap.enabled
+ end
+
+ def self.servers
+ Gitlab.config.ldap.servers.values
+ end
+
+ def self.providers
+ servers.map {|server| server['provider_name'] }
+ end
+
+ def self.valid_provider?(provider)
+ providers.include?(provider)
+ end
+
+ def self.invalid_provider(provider)
+ raise "Unknown provider (#{provider}). Available providers: #{providers}"
+ end
+
+ def initialize(provider)
+ if self.class.valid_provider?(provider)
+ @provider = provider
+ elsif provider == 'ldap'
+ @provider = self.class.providers.first
+ else
+ self.class.invalid_provider(provider)
+ end
+ @options = config_for(@provider) # Use @provider, not provider
+ end
+
+ def enabled?
+ base_config.enabled
+ end
+
+ def adapter_options
+ {
+ host: options['host'],
+ port: options['port'],
+ encryption: encryption
+ }.tap do |options|
+ options.merge!(auth_options) if has_auth?
+ end
+ end
+
+ def base
+ options['base']
+ end
+
+ def uid
+ options['uid']
+ end
+
+ def sync_ssh_keys?
+ sync_ssh_keys.present?
+ end
+
+ # The LDAP attribute in which the ssh keys are stored
+ def sync_ssh_keys
+ options['sync_ssh_keys']
+ end
+
+ def user_filter
+ options['user_filter']
+ end
+
+ def group_base
+ options['group_base']
+ end
+
+ def admin_group
+ options['admin_group']
+ end
+
+ def active_directory
+ options['active_directory']
+ end
+
+ protected
+ def base_config
+ Gitlab.config.ldap
+ end
+
+ def config_for(provider)
+ base_config.servers.values.find { |server| server['provider_name'] == provider }
+ end
+
+ def encryption
+ case options['method'].to_s
+ when 'ssl'
+ :simple_tls
+ when 'tls'
+ :start_tls
+ else
+ nil
+ end
+ end
+
+ def auth_options
+ {
+ auth: {
+ method: :simple,
+ username: options['bind_dn'],
+ password: options['password']
+ }
+ }
+ end
+
+ def has_auth?
+ options['password'] || options['bind_dn']
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb
index 87c3d711db..3c42617937 100644
--- a/lib/gitlab/ldap/person.rb
+++ b/lib/gitlab/ldap/person.rb
@@ -6,24 +6,26 @@ module Gitlab
# Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
- def self.find_by_uid(uid, adapter=nil)
- adapter ||= Gitlab::LDAP::Adapter.new
- adapter.user(config.uid, uid)
+ attr_accessor :entry, :provider
+
+ def self.find_by_uid(uid, adapter)
+ uid = Net::LDAP::Filter.escape(uid)
+ adapter.user(adapter.config.uid, uid)
end
- def self.find_by_dn(dn, adapter=nil)
- adapter ||= Gitlab::LDAP::Adapter.new
+ def self.find_by_dn(dn, adapter)
+ dn = Net::LDAP::Filter.escape(dn)
adapter.user('dn', dn)
end
- def self.disabled_via_active_directory?(dn, adapter=nil)
- adapter ||= Gitlab::LDAP::Adapter.new
+ def self.disabled_via_active_directory?(dn, adapter)
adapter.dn_matches_filter?(dn, AD_USER_DISABLED)
end
- def initialize(entry)
+ def initialize(entry, provider)
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
@entry = entry
+ @provider = provider
end
def name
@@ -38,6 +40,10 @@ module Gitlab
uid
end
+ def email
+ entry.try(:mail)
+ end
+
def dn
entry.dn
end
@@ -48,12 +54,8 @@ module Gitlab
@entry
end
- def adapter
- @adapter ||= Gitlab::LDAP::Adapter.new
- end
-
def config
- @config ||= Gitlab.config.ldap
+ @config ||= Gitlab::LDAP::Config.new(provider)
end
end
end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 25b5a702f9..cfa8692659 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -10,77 +10,55 @@ module Gitlab
module LDAP
class User < Gitlab::OAuth::User
class << self
- def find_or_create(auth_hash)
- self.auth_hash = auth_hash
- find(auth_hash) || find_and_connect_by_email(auth_hash) || create(auth_hash)
- end
-
- def find_and_connect_by_email(auth_hash)
- self.auth_hash = auth_hash
- user = model.find_by(email: self.auth_hash.email)
-
- if user
- user.update_attributes(extern_uid: auth_hash.uid, provider: auth_hash.provider)
- Gitlab::AppLogger.info("(LDAP) Updating legacy LDAP user #{self.auth_hash.email} with extern_uid => #{auth_hash.uid}")
- return user
- end
- end
-
- def authenticate(login, password)
- # Check user against LDAP backend if user is not authenticated
- # Only check with valid login and password to prevent anonymous bind results
- return nil unless ldap_conf.enabled && login.present? && password.present?
-
- ldap_user = adapter.bind_as(
- filter: user_filter(login),
- size: 1,
- password: password
- )
-
- find_by_uid(ldap_user.dn) if ldap_user
- end
-
- def adapter
- @adapter ||= OmniAuth::LDAP::Adaptor.new(ldap_conf)
- end
-
- protected
-
- def find_by_uid_and_provider
- find_by_uid(auth_hash.uid)
- end
-
- def find_by_uid(uid)
+ def find_by_uid_and_provider(uid, provider)
# LDAP distinguished name is case-insensitive
- model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last
+ identity = ::Identity.
+ where(provider: [provider, :ldap]).
+ where('lower(extern_uid) = ?', uid.downcase).last
+ identity && identity.user
end
+ end
- def provider
- 'ldap'
- end
+ def initialize(auth_hash)
+ super
+ update_user_attributes
+ end
- def raise_error(message)
- raise OmniAuth::Error, "(LDAP) " + message
- end
+ # instance methods
+ def gl_user
+ @gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user
+ end
- def ldap_conf
- Gitlab.config.ldap
- end
+ def find_by_uid_and_provider
+ self.class.find_by_uid_and_provider(
+ auth_hash.uid.downcase, auth_hash.provider)
+ end
- def user_filter(login)
- filter = Net::LDAP::Filter.eq(adapter.uid, login)
- # Apply LDAP user filter if present
- if ldap_conf['user_filter'].present?
- user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter'])
- filter = Net::LDAP::Filter.join(filter, user_filter)
- end
- filter
- end
+ def find_by_email
+ ::User.find_by(email: auth_hash.email)
+ end
+
+ def update_user_attributes
+ gl_user.email = auth_hash.email
+
+ # Build new identity only if we dont have have same one
+ gl_user.identities.find_or_initialize_by(provider: auth_hash.provider,
+ extern_uid: auth_hash.uid)
+
+ gl_user
+ end
+
+ def changed?
+ gl_user.changed? || gl_user.identities.any?(&:changed?)
end
def needs_blocking?
false
end
+
+ def allowed?
+ Gitlab::LDAP::Access.allowed?(gl_user)
+ end
end
end
end
diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb
index 8a73ec5038..59b21149a9 100644
--- a/lib/gitlab/logger.rb
+++ b/lib/gitlab/logger.rb
@@ -1,5 +1,9 @@
module Gitlab
class Logger < ::Logger
+ def self.file_name
+ file_name_noext + '.log'
+ end
+
def self.error(message)
build.error(message)
end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 464b88d07e..32f04c866e 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -14,6 +14,7 @@ module Gitlab
# * !123 for merge requests
# * $123 for snippets
# * 123456 for commits
+ # * 123456...7890123 for commit ranges (comparisons)
#
# It also parses Emoji codes to insert images. See
# http://www.emoji-cheat-sheet.com/ for a list of the supported icons.
@@ -33,17 +34,23 @@ module Gitlab
attr_reader :html_options
- def gfm_with_tasks(text, project = @project, html_options = {})
- text = gfm(text, project, html_options)
- parse_tasks(text)
- end
-
# Public: Parse the provided text with GitLab-Flavored Markdown
#
# text - the source text
# project - extra options for the reference links as given to link_to
# html_options - extra options for the reference links as given to link_to
def gfm(text, project = @project, html_options = {})
+ gfm_with_options(text, {}, project, html_options)
+ end
+
+ # Public: Parse the provided text with GitLab-Flavored Markdown
+ #
+ # text - the source text
+ # options - parse_tasks: true - render tasks
+ # - xhtml: true - output XHTML instead of HTML
+ # project - extra options for the reference links as given to link_to
+ # html_options - extra options for the reference links as given to link_to
+ def gfm_with_options(text, options = {}, project = @project, html_options = {})
return text if text.nil?
# Duplicate the string so we don't alter the original, then call to_str
@@ -70,15 +77,27 @@ module Gitlab
insert_piece($1)
end
- # Context passed to the markdoqwn pipeline
+ # Used markdown pipelines in GitLab:
+ # GitlabEmojiFilter - performs emoji replacement.
+ #
+ # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters
+ filters = [
+ HTML::Pipeline::Gitlab::GitlabEmojiFilter
+ ]
+
markdown_context = {
- asset_root: File.join(root_url,
- Gitlab::Application.config.assets.prefix)
+ asset_root: Gitlab.config.gitlab.url,
+ asset_host: Gitlab::Application.config.asset_host
}
- result = HTML::Pipeline::Gitlab::MarkdownPipeline.call(text,
- markdown_context)
- text = result[:output].to_html(save_with: 0)
+ markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline
+
+ result = markdown_pipeline.call(text, markdown_context)
+ saveoptions = 0
+ if options[:xhtml]
+ saveoptions |= Nokogiri::XML::Node::SaveOptions::AS_XHTML
+ end
+ text = result[:output].to_html(save_with: saveoptions)
sanitize_html(text)
end
@@ -88,9 +107,13 @@ module Gitlab
allowed_attributes = ActionView::Base.sanitized_allowed_attributes
allowed_tags = ActionView::Base.sanitized_allowed_tags
- sanitize text.html_safe,
- attributes: allowed_attributes + %w(id class),
- tags: allowed_tags + %w(table tr td th)
+ text = sanitize text.html_safe,
+ attributes: allowed_attributes + %w(id class style),
+ tags: allowed_tags + %w(table tr td th)
+ if options[:parse_tasks]
+ text = parse_tasks(text)
+ end
+ text
end
private
@@ -118,24 +141,26 @@ module Gitlab
text
end
- NAME_STR = '[a-zA-Z][a-zA-Z0-9_\-\.]*'
+ NAME_STR = '[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*'
PROJ_STR = "(?#{NAME_STR}/#{NAME_STR})"
REFERENCE_PATTERN = %r{
(?\W)? # Prefix
( # Reference
@(?#{NAME_STR}) # User name
+ |~(?\d+) # Label ID
|(?([A-Z\-]+-)\d+) # JIRA Issue ID
|#{PROJ_STR}?\#(?([a-zA-Z\-]+-)?\d+) # Issue ID
|#{PROJ_STR}?!(?\d+) # MR ID
|\$(?\d+) # Snippet ID
+ |(#{PROJ_STR}@)?(?[\h]{6,40}\.{2,3}[\h]{6,40}) # Commit range
|(#{PROJ_STR}@)?(?[\h]{6,40}) # Commit ID
|(?gfm-extraction-[\h]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit
)
(?\W)? # Suffix
}x.freeze
- TYPES = [:user, :issue, :merge_request, :snippet, :commit].freeze
+ TYPES = [:user, :issue, :label, :merge_request, :snippet, :commit, :commit_range].freeze
def parse_references(text, project = @project)
# parse reference links
@@ -194,18 +219,38 @@ module Gitlab
def reference_user(identifier, project = @project, _ = nil)
options = html_options.merge(
- class: "gfm gfm-team_member #{html_options[:class]}"
+ class: "gfm gfm-project_member #{html_options[:class]}"
)
if identifier == "all"
- link_to("@all", project_url(project), options)
- elsif user = User.find_by(username: identifier)
- link_to("@#{identifier}", user_url(identifier), options)
+ link_to("@all", namespace_project_url(project.namespace, project), options)
+ elsif namespace = Namespace.find_by(path: identifier)
+ url =
+ if namespace.type == "Group"
+ group_url(identifier)
+ else
+ user_url(identifier)
+ end
+
+ link_to("@#{identifier}", url, options)
+ end
+ end
+
+ def reference_label(identifier, project = @project, _ = nil)
+ if label = project.labels.find_by(id: identifier)
+ options = html_options.merge(
+ class: "gfm gfm-label #{html_options[:class]}"
+ )
+ link_to(
+ render_colored_label(label),
+ namespace_project_issues_path(project.namespace, project, label_name: label.name),
+ options
+ )
end
end
def reference_issue(identifier, project = @project, prefix_text = nil)
- if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
+ if project.default_issues_tracker?
if project.issue_exists? identifier
url = url_for_issue(identifier, project)
title = title_for_issue(identifier, project)
@@ -217,10 +262,8 @@ module Gitlab
link_to("#{prefix_text}##{identifier}", url, options)
end
else
- config = Gitlab.config
- external_issue_tracker = config.issues_tracker[project.issues_tracker]
- if external_issue_tracker.present?
- reference_external_issue(identifier, external_issue_tracker, project,
+ if project.external_issue_tracker.present?
+ reference_external_issue(identifier, project,
prefix_text)
end
end
@@ -233,7 +276,8 @@ module Gitlab
title: "Merge Request: #{merge_request.title}",
class: "gfm gfm-merge_request #{html_options[:class]}"
)
- url = project_merge_request_url(project, merge_request)
+ url = namespace_project_merge_request_url(project.namespace, project,
+ merge_request)
link_to("#{prefix_text}!#{identifier}", url, options)
end
end
@@ -244,8 +288,11 @@ module Gitlab
title: "Snippet: #{snippet.title}",
class: "gfm gfm-snippet #{html_options[:class]}"
)
- link_to("$#{identifier}", project_snippet_url(project, snippet),
- options)
+ link_to(
+ "$#{identifier}",
+ namespace_project_snippet_url(project.namespace, project, snippet),
+ options
+ )
end
end
@@ -258,16 +305,40 @@ module Gitlab
prefix_text = "#{prefix_text}@" if prefix_text
link_to(
"#{prefix_text}#{identifier}",
- project_commit_url(project, commit),
+ namespace_project_commit_url(project.namespace, project, commit),
options
)
end
end
- def reference_external_issue(identifier, issue_tracker, project = @project,
+ def reference_commit_range(identifier, project = @project, prefix_text = nil)
+ from_id, to_id = identifier.split(/\.{2,3}/, 2)
+
+ inclusive = identifier !~ /\.{3}/
+ from_id << "^" if inclusive
+
+ if project.valid_repo? &&
+ from = project.repository.commit(from_id) &&
+ to = project.repository.commit(to_id)
+
+ options = html_options.merge(
+ title: "Commits #{from_id} through #{to_id}",
+ class: "gfm gfm-commit_range #{html_options[:class]}"
+ )
+ prefix_text = "#{prefix_text}@" if prefix_text
+
+ link_to(
+ "#{prefix_text}#{identifier}",
+ namespace_project_compare_url(project.namespace, project, from: from_id, to: to_id),
+ options
+ )
+ end
+ end
+
+ def reference_external_issue(identifier, project = @project,
prefix_text = nil)
url = url_for_issue(identifier, project)
- title = issue_tracker['title']
+ title = project.external_issue_tracker.title
options = html_options.merge(
title: "Issue in #{title}",
diff --git a/lib/gitlab/middleware/static.rb b/lib/gitlab/middleware/static.rb
new file mode 100644
index 0000000000..85ffa8aca6
--- /dev/null
+++ b/lib/gitlab/middleware/static.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ module Middleware
+ class Static < ActionDispatch::Static
+ UPLOADS_REGEX = /\A\/uploads(\/|\z)/.freeze
+
+ def call(env)
+ return @app.call(env) if env['PATH_INFO'] =~ UPLOADS_REGEX
+
+ super
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/middleware/timeout.rb b/lib/gitlab/middleware/timeout.rb
new file mode 100644
index 0000000000..015600392b
--- /dev/null
+++ b/lib/gitlab/middleware/timeout.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ module Middleware
+ class Timeout < Rack::Timeout
+ GRACK_REGEX = /[-\/\w\.]+\.git\//.freeze
+
+ def call(env)
+ return @app.call(env) if env['PATH_INFO'] =~ GRACK_REGEX
+
+ super
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb
new file mode 100644
index 0000000000..644dec45dc
--- /dev/null
+++ b/lib/gitlab/note_data_builder.rb
@@ -0,0 +1,77 @@
+module Gitlab
+ class NoteDataBuilder
+ class << self
+ # Produce a hash of post-receive data
+ #
+ # For all notes:
+ #
+ # data = {
+ # object_kind: "note",
+ # user: {
+ # name: String,
+ # username: String,
+ # avatar_url: String
+ # }
+ # project_id: Integer,
+ # repository: {
+ # name: String,
+ # url: String,
+ # description: String,
+ # homepage: String,
+ # }
+ # object_attributes: {
+ #
+ # }
+ # : {
+ # }
+ # note-specific data is a hash with one of the following keys and contains
+ # the hook data for that type.
+ # - commit
+ # - issue
+ # - merge_request
+ # - snippet
+ #
+ def build(note, user)
+ project = note.project
+ data = build_base_data(project, user, note)
+
+ if note.for_commit?
+ data[:commit] = build_data_for_commit(project, user, note)
+ elsif note.for_issue?
+ data[:issue] = note.noteable.hook_attrs
+ elsif note.for_merge_request?
+ data[:merge_request] = note.noteable.hook_attrs
+ elsif note.for_project_snippet?
+ data[:snippet] = note.noteable.hook_attrs
+ end
+
+ data
+ end
+
+ def build_base_data(project, user, note)
+ base_data = {
+ object_kind: "note",
+ user: user.hook_attrs,
+ project_id: project.id,
+ repository: {
+ name: project.name,
+ url: project.url_to_repo,
+ description: project.description,
+ homepage: project.web_url,
+ },
+ object_attributes: note.hook_attrs
+ }
+
+ base_data[:object_attributes][:url] =
+ Gitlab::UrlBuilder.new(:note).build(note.id)
+ base_data
+ end
+
+ def build_data_for_commit(project, user, note)
+ # commit_id is the SHA hash
+ commit = project.repository.commit(note.commit_id)
+ commit.hook_attrs(project)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/oauth/auth_hash.rb b/lib/gitlab/oauth/auth_hash.rb
index 0198f61f42..ce52beec78 100644
--- a/lib/gitlab/oauth/auth_hash.rb
+++ b/lib/gitlab/oauth/auth_hash.rb
@@ -21,7 +21,7 @@ module Gitlab
end
def name
- (info.name || full_name).to_s.force_encoding('utf-8')
+ (info.try(:name) || full_name).to_s.force_encoding('utf-8')
end
def full_name
diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb
index b768eda185..c023d27570 100644
--- a/lib/gitlab/oauth/user.rb
+++ b/lib/gitlab/oauth/user.rb
@@ -5,67 +5,92 @@
#
module Gitlab
module OAuth
+ class ForbiddenAction < StandardError; end
+
class User
- class << self
- attr_reader :auth_hash
-
- def find(auth_hash)
- self.auth_hash = auth_hash
- find_by_uid_and_provider
- end
-
- def create(auth_hash)
- user = new(auth_hash)
- user.save_and_trigger_callbacks
- end
-
- def model
- ::User
- end
-
- def auth_hash=(auth_hash)
- @auth_hash = AuthHash.new(auth_hash)
- end
-
- protected
- def find_by_uid_and_provider
- model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last
- end
- end
-
- # Instance methods
- attr_accessor :auth_hash, :user
+ attr_accessor :auth_hash, :gl_user
def initialize(auth_hash)
self.auth_hash = auth_hash
- self.user = self.class.model.new(user_attributes)
- user.skip_confirmation!
+ end
+
+ def persisted?
+ gl_user.try(:persisted?)
+ end
+
+ def new?
+ !persisted?
+ end
+
+ def valid?
+ gl_user.try(:valid?)
+ end
+
+ def save
+ unauthorized_to_create unless gl_user
+
+ if needs_blocking?
+ gl_user.save!
+ gl_user.block
+ else
+ gl_user.save!
+ end
+
+ log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
+ gl_user
+ rescue ActiveRecord::RecordInvalid => e
+ log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}"
+ return self, e.record.errors
+ end
+
+ def gl_user
+ @user ||= find_by_uid_and_provider
+
+ if signup_enabled?
+ @user ||= build_new_user
+ end
+
+ @user
+ end
+
+ protected
+
+ def needs_blocking?
+ new? && block_after_signup?
+ end
+
+ def signup_enabled?
+ Gitlab.config.omniauth.allow_single_sign_on
+ end
+
+ def block_after_signup?
+ Gitlab.config.omniauth.block_auto_created_users
end
def auth_hash=(auth_hash)
@auth_hash = AuthHash.new(auth_hash)
end
- def save_and_trigger_callbacks
- user.save!
- log.info "(OAuth) Creating user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
- user.block if needs_blocking?
+ def find_by_uid_and_provider
+ identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid)
+ identity && identity.user
+ end
+ def build_new_user
+ user = ::User.new(user_attributes)
+ user.skip_confirmation!
+ user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider)
user
- rescue ActiveRecord::RecordInvalid => e
- log.info "(OAuth) Email #{e.record.errors[:email]}. Username #{e.record.errors[:username]}"
- return nil, e.record.errors
end
def user_attributes
{
- extern_uid: auth_hash.uid,
- provider: auth_hash.provider,
- name: auth_hash.name,
- username: auth_hash.username,
- email: auth_hash.email,
- password: auth_hash.password,
- password_confirmation: auth_hash.password,
+ name: auth_hash.name,
+ username: ::User.clean_username(auth_hash.username),
+ email: auth_hash.email,
+ password: auth_hash.password,
+ password_confirmation: auth_hash.password,
+ password_automatically_set: true
}
end
@@ -73,12 +98,8 @@ module Gitlab
Gitlab::AppLogger
end
- def raise_error(message)
- raise OmniAuth::Error, "(OAuth) " + message
- end
-
- def needs_blocking?
- Gitlab.config.omniauth['block_auto_created_users']
+ def unauthorized_to_create
+ raise ForbiddenAction.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
end
end
end
diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb
index e2fbafb389..fea4d2d55d 100644
--- a/lib/gitlab/popen.rb
+++ b/lib/gitlab/popen.rb
@@ -21,6 +21,9 @@ module Gitlab
@cmd_output = ""
@cmd_status = 0
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ # We are not using stdin so we should close it, in case the command we
+ # are running waits for input.
+ stdin.close
@cmd_output << stdout.read
@cmd_output << stderr.read
@cmd_status = wait_thr.value.exitstatus
diff --git a/lib/gitlab/production_logger.rb b/lib/gitlab/production_logger.rb
new file mode 100644
index 0000000000..89ce7144b1
--- /dev/null
+++ b/lib/gitlab/production_logger.rb
@@ -0,0 +1,7 @@
+module Gitlab
+ class ProductionLogger < Gitlab::Logger
+ def self.file_name_noext
+ 'production'
+ end
+ end
+end
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
new file mode 100644
index 0000000000..ea9012b884
--- /dev/null
+++ b/lib/gitlab/push_data_builder.rb
@@ -0,0 +1,89 @@
+module Gitlab
+ class PushDataBuilder
+ class << self
+ # Produce a hash of post-receive data
+ #
+ # data = {
+ # before: String,
+ # after: String,
+ # ref: String,
+ # user_id: String,
+ # user_name: String,
+ # user_email: String
+ # project_id: String,
+ # repository: {
+ # name: String,
+ # url: String,
+ # description: String,
+ # homepage: String,
+ # },
+ # commits: Array,
+ # total_commits_count: Fixnum
+ # }
+ #
+ def build(project, user, oldrev, newrev, ref, commits = [])
+ # Total commits count
+ commits_count = commits.size
+
+ # Get latest 20 commits ASC
+ commits_limited = commits.last(20)
+
+ type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push"
+ # Hash to be passed as post_receive_data
+ data = {
+ object_kind: type,
+ before: oldrev,
+ after: newrev,
+ ref: ref,
+ checkout_sha: checkout_sha(project.repository, newrev, ref),
+ user_id: user.id,
+ user_name: user.name,
+ user_email: user.email,
+ project_id: project.id,
+ repository: {
+ name: project.name,
+ url: project.url_to_repo,
+ description: project.description,
+ homepage: project.web_url,
+ git_http_url: project.http_url_to_repo,
+ git_ssh_url: project.ssh_url_to_repo,
+ visibility_level: project.visibility_level
+ },
+ commits: [],
+ total_commits_count: commits_count
+ }
+
+ # For performance purposes maximum 20 latest commits
+ # will be passed as post receive hook data.
+ commits_limited.each do |commit|
+ data[:commits] << commit.hook_attrs(project)
+ end
+
+ data[:commits] = "" if data[:commits].count == 0
+ data
+ end
+
+ # This method provide a sample data generated with
+ # existing project and commits to test web hooks
+ def build_sample(project, user)
+ commits = project.repository.commits(project.default_branch, nil, 3)
+ ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
+ build(project, user, commits.last.id, commits.first.id, ref, commits)
+ end
+
+ def checkout_sha(repository, newrev, ref)
+ if newrev != Gitlab::Git::BLANK_SHA && Gitlab::Git.tag_ref?(ref)
+ tag_name = Gitlab::Git.ref_name(ref)
+ tag = repository.find_tag(tag_name)
+
+ if tag
+ commit = repository.commit(tag.target)
+ commit.try(:sha)
+ end
+ else
+ newrev
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 99165950ae..5b9772de16 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,12 +1,13 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor
- attr_accessor :users, :issues, :merge_requests, :snippets, :commits
+ attr_accessor :users, :labels, :issues, :merge_requests, :snippets, :commits, :commit_ranges
include Markdown
def initialize
- @users, @issues, @merge_requests, @snippets, @commits = [], [], [], [], []
+ @users, @labels, @issues, @merge_requests, @snippets, @commits, @commit_ranges =
+ [], [], [], [], [], [], []
end
def analyze(string, project)
@@ -22,6 +23,12 @@ module Gitlab
end.reject(&:nil?)
end
+ def labels_for(project = nil)
+ labels.map do |entry|
+ project.labels.where(id: entry[:id]).first
+ end.reject(&:nil?)
+ end
+
def issues_for(project = nil)
issues.map do |entry|
if should_lookup?(project, entry[:project])
@@ -53,6 +60,16 @@ module Gitlab
end.reject(&:nil?)
end
+ def commit_ranges_for(project = nil)
+ commit_ranges.map do |entry|
+ repo = entry[:project].repository if entry[:project]
+ if repo && should_lookup?(project, entry[:project])
+ from_id, to_id = entry[:id].split(/\.{2,3}/, 2)
+ [repo.commit(from_id), repo.commit(to_id)]
+ end
+ end.reject(&:nil?)
+ end
+
private
def reference_link(type, identifier, project, _)
@@ -64,7 +81,7 @@ module Gitlab
if entry_project.nil?
false
else
- project.nil? || project.id == entry_project.id
+ project.nil? || entry_project.default_issues_tracker?
end
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 4b8038843b..cf6e260f25 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def project_name_regex
- /\A[a-zA-Z0-9_][a-zA-Z0-9_\-\. ]*\z/
+ /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\. ]*\z/
end
def project_regex_message
@@ -67,8 +67,7 @@ module Gitlab
def default_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \
- "It must start with letter, digit or '_', optionally preceeded by '.'. " \
- "It must not end in '.git'."
+ "Cannot start with '-' or end in '.git'" \
end
def default_regex
diff --git a/lib/gitlab/satellite/action.rb b/lib/gitlab/satellite/action.rb
index be45cb5c98..4890ccf21e 100644
--- a/lib/gitlab/satellite/action.rb
+++ b/lib/gitlab/satellite/action.rb
@@ -44,7 +44,7 @@ module Gitlab
end
def default_options(options = {})
- {raise: true, timeout: true}.merge(options)
+ { raise: true, timeout: true }.merge(options)
end
def handle_exception(exception)
diff --git a/lib/gitlab/satellite/files/delete_file_action.rb b/lib/gitlab/satellite/files/delete_file_action.rb
index 30462999aa..0d37b9dea8 100644
--- a/lib/gitlab/satellite/files/delete_file_action.rb
+++ b/lib/gitlab/satellite/files/delete_file_action.rb
@@ -13,7 +13,7 @@ module Gitlab
prepare_satellite!(repo)
# create target branch in satellite at the corresponding commit from bare repo
- repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
+ repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
# update the file in the satellite's working dir
file_path_in_satellite = File.join(repo.working_dir, file_path)
@@ -36,7 +36,7 @@ module Gitlab
# push commit back to bare repo
# will raise CommandFailed when push fails
- repo.git.push({raise: true, timeout: true}, :origin, ref)
+ repo.git.push({ raise: true, timeout: true }, :origin, ref)
# everything worked
true
diff --git a/lib/gitlab/satellite/files/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb
index cbdf70f7d1..3cb9c0b5ec 100644
--- a/lib/gitlab/satellite/files/edit_file_action.rb
+++ b/lib/gitlab/satellite/files/edit_file_action.rb
@@ -10,12 +10,16 @@ module Gitlab
# Returns false if committing the change fails
# Returns false if pushing from the satellite to bare repo failed or was rejected
# Returns true otherwise
- def commit!(content, commit_message, encoding)
+ def commit!(content, commit_message, encoding, new_branch = nil)
in_locked_and_timed_satellite do |repo|
prepare_satellite!(repo)
# create target branch in satellite at the corresponding commit from bare repo
- repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
+ begin
+ repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
+ rescue Grit::Git::CommandFailed => ex
+ log_and_raise(CheckoutFailed, ex.message)
+ end
# update the file in the satellite's working dir
file_path_in_satellite = File.join(repo.working_dir, file_path)
@@ -31,19 +35,33 @@ module Gitlab
# commit the changes
# will raise CommandFailed when commit fails
- repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
+ begin
+ repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
+ rescue Grit::Git::CommandFailed => ex
+ log_and_raise(CommitFailed, ex.message)
+ end
+ target_branch = new_branch.present? ? "#{ref}:#{new_branch}" : ref
+
# push commit back to bare repo
# will raise CommandFailed when push fails
- repo.git.push({raise: true, timeout: true}, :origin, ref)
+ begin
+ repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
+ rescue Grit::Git::CommandFailed => ex
+ log_and_raise(PushFailed, ex.message)
+ end
# everything worked
true
end
- rescue Grit::Git::CommandFailed => ex
- Gitlab::GitLogger.error(ex.message)
- false
+ end
+
+ private
+
+ def log_and_raise(errorClass, message)
+ Gitlab::GitLogger.error(message)
+ raise(errorClass, message)
end
end
end
diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb
index 15e9b7a6f7..724dfa0d04 100644
--- a/lib/gitlab/satellite/files/new_file_action.rb
+++ b/lib/gitlab/satellite/files/new_file_action.rb
@@ -9,12 +9,19 @@ module Gitlab
# Returns false if committing the change fails
# Returns false if pushing from the satellite to bare repo failed or was rejected
# Returns true otherwise
- def commit!(content, commit_message, encoding)
+ def commit!(content, commit_message, encoding, new_branch = nil)
in_locked_and_timed_satellite do |repo|
prepare_satellite!(repo)
# create target branch in satellite at the corresponding commit from bare repo
- repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
+ current_ref =
+ if @project.empty_repo?
+ # skip this step if we want to add first file to empty repo
+ Satellite::PARKING_BRANCH
+ else
+ repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
+ ref
+ end
file_path_in_satellite = File.join(repo.working_dir, file_path)
dir_name_in_satellite = File.dirname(file_path_in_satellite)
@@ -38,10 +45,15 @@ module Gitlab
# will raise CommandFailed when commit fails
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
+ target_branch = if new_branch.present? && !@project.empty_repo?
+ "#{ref}:#{new_branch}"
+ else
+ "#{current_ref}:#{ref}"
+ end
# push commit back to bare repo
# will raise CommandFailed when push fails
- repo.git.push({raise: true, timeout: true}, :origin, ref)
+ repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
# everything worked
true
diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb
index e9141f735a..25122666f5 100644
--- a/lib/gitlab/satellite/merge_action.rb
+++ b/lib/gitlab/satellite/merge_action.rb
@@ -86,7 +86,7 @@ module Gitlab
in_locked_and_timed_satellite do |merge_repo|
prepare_satellite!(merge_repo)
update_satellite_source_and_target!(merge_repo)
- patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
+ patch = merge_repo.git.format_patch(default_options({ stdout: true }), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
end
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
@@ -128,7 +128,7 @@ module Gitlab
# merge the source branch into the satellite
# will raise CommandFailed when merge fails
- repo.git.merge(default_options({no_ff: true}), "-m#{message}", "source/#{merge_request.source_branch}")
+ repo.git.merge(default_options({ no_ff: true }), "-m#{message}", "source/#{merge_request.source_branch}")
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
@@ -137,7 +137,7 @@ module Gitlab
def update_satellite_source_and_target!(repo)
repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
repo.remote_fetch('source')
- repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}")
+ repo.git.checkout(default_options({ b: true }), merge_request.target_branch, "origin/#{merge_request.target_branch}")
rescue Grit::Git::CommandFailed => ex
handle_exception(ex)
end
diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb
index 1de84309d1..70125d539d 100644
--- a/lib/gitlab/satellite/satellite.rb
+++ b/lib/gitlab/satellite/satellite.rb
@@ -1,5 +1,9 @@
module Gitlab
module Satellite
+ class CheckoutFailed < StandardError; end
+ class CommitFailed < StandardError; end
+ class PushFailed < StandardError; end
+
class Satellite
include Gitlab::Popen
@@ -98,13 +102,13 @@ module Gitlab
if heads.include? PARKING_BRANCH
repo.git.checkout({}, PARKING_BRANCH)
else
- repo.git.checkout(default_options({b: true}), PARKING_BRANCH)
+ repo.git.checkout(default_options({ b: true }), PARKING_BRANCH)
end
# remove the parking branch from the list of heads ...
heads.delete(PARKING_BRANCH)
# ... and delete all others
- heads.each { |head| repo.git.branch(default_options({D: true}), head) }
+ heads.each { |head| repo.git.branch(default_options({ D: true }), head) }
end
# Deletes all remotes except origin
@@ -126,7 +130,7 @@ module Gitlab
end
def default_options(options = {})
- {raise: true, timeout: true}.merge(options)
+ { raise: true, timeout: true }.merge(options)
end
# Create directory for storing
diff --git a/lib/gitlab/sidekiq_logger.rb b/lib/gitlab/sidekiq_logger.rb
new file mode 100644
index 0000000000..c1dab87a43
--- /dev/null
+++ b/lib/gitlab/sidekiq_logger.rb
@@ -0,0 +1,7 @@
+module Gitlab
+ class SidekiqLogger < Gitlab::Logger
+ def self.file_name_noext
+ 'sidekiq'
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
new file mode 100644
index 0000000000..0f2db50e98
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -0,0 +1,53 @@
+module Gitlab
+ module SidekiqMiddleware
+ class MemoryKiller
+ # Default the RSS limit to 0, meaning the MemoryKiller is disabled
+ MAX_RSS = (ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] || 0).to_s.to_i
+ # Give Sidekiq 15 minutes of grace time after exceeding the RSS limit
+ GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
+ # Wait 30 seconds for running jobs to finish during graceful shutdown
+ SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
+
+ # Create a mutex used to ensure there will be only one thread waiting to
+ # shut Sidekiq down
+ MUTEX = Mutex.new
+
+ def call(worker, job, queue)
+ yield
+ current_rss = get_rss
+
+ return unless MAX_RSS > 0 && current_rss > MAX_RSS
+
+ Thread.new do
+ # Return if another thread is already waiting to shut Sidekiq down
+ return unless MUTEX.try_lock
+
+ Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\
+ "#{MAX_RSS}"
+ Sidekiq.logger.warn "spawned thread that will shut down PID "\
+ "#{Process.pid} in #{GRACE_TIME} seconds"
+ sleep(GRACE_TIME)
+
+ Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}"
+ Process.kill('SIGUSR1', Process.pid)
+
+ Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\
+ "SIGTERM to PID #{Process.pid}"
+ sleep(SHUTDOWN_WAIT)
+
+ Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}"
+ Process.kill('SIGTERM', Process.pid)
+ end
+ end
+
+ private
+
+ def get_rss
+ output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{Process.pid}))
+ return 0 unless status.zero?
+
+ output.to_i
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb
index b7c50cb734..9799e54de5 100644
--- a/lib/gitlab/theme.rb
+++ b/lib/gitlab/theme.rb
@@ -5,6 +5,7 @@ module Gitlab
MODERN = 3 unless const_defined?(:MODERN)
GRAY = 4 unless const_defined?(:GRAY)
COLOR = 5 unless const_defined?(:COLOR)
+ BLUE = 6 unless const_defined?(:BLUE)
def self.css_class_by_id(id)
themes = {
@@ -12,12 +13,27 @@ module Gitlab
MARS => "ui_mars",
MODERN => "ui_modern",
GRAY => "ui_gray",
- COLOR => "ui_color"
+ COLOR => "ui_color",
+ BLUE => "ui_blue"
}
id ||= Gitlab.config.gitlab.default_theme
return themes[id]
end
+
+ def self.type_css_class_by_id(id)
+ types = {
+ BASIC => 'light_theme',
+ MARS => 'dark_theme',
+ MODERN => 'dark_theme',
+ GRAY => 'dark_theme',
+ COLOR => 'dark_theme'
+ }
+
+ id ||= Gitlab.config.gitlab.default_theme
+
+ types[id]
+ end
end
end
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
index 74b049b514..0570c2fbeb 100644
--- a/lib/gitlab/upgrader.rb
+++ b/lib/gitlab/upgrader.rb
@@ -62,7 +62,7 @@ module Gitlab
end
def env
- {'RAILS_ENV' => 'production'}
+ { 'RAILS_ENV' => 'production' }
end
def upgrade
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index de7e040408..11b0d44f34 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -1,6 +1,7 @@
module Gitlab
class UrlBuilder
include Rails.application.routes.url_helpers
+ include GitlabRoutingHelper
def initialize(type)
@type = type
@@ -9,17 +10,51 @@ module Gitlab
def build(id)
case @type
when :issue
- issue_url(id)
+ build_issue_url(id)
+ when :merge_request
+ build_merge_request_url(id)
+ when :note
+ build_note_url(id)
+
end
end
private
- def issue_url(id)
+ def build_issue_url(id)
issue = Issue.find(id)
- project_issue_url(id: issue.iid,
- project_id: issue.project,
- host: Settings.gitlab['url'])
+ issue_url(issue, host: Gitlab.config.gitlab['url'])
+ end
+
+ def build_merge_request_url(id)
+ merge_request = MergeRequest.find(id)
+ merge_request_url(merge_request, host: Gitlab.config.gitlab['url'])
+ end
+
+ def build_note_url(id)
+ note = Note.find(id)
+ if note.for_commit?
+ namespace_project_commit_url(namespace_id: note.project.namespace,
+ id: note.commit_id,
+ project_id: note.project,
+ host: Gitlab.config.gitlab['url'],
+ anchor: "note_#{note.id}")
+ elsif note.for_issue?
+ issue = Issue.find(note.noteable_id)
+ issue_url(issue,
+ host: Gitlab.config.gitlab['url'],
+ anchor: "note_#{note.id}")
+ elsif note.for_merge_request?
+ merge_request = MergeRequest.find(note.noteable_id)
+ merge_request_url(merge_request,
+ host: Gitlab.config.gitlab['url'],
+ anchor: "note_#{note.id}")
+ elsif note.for_project_snippet?
+ snippet = Snippet.find(note.noteable_id)
+ project_snippet_url(snippet,
+ host: Gitlab.config.gitlab['url'],
+ anchor: "note_#{note.id}")
+ end
end
end
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
new file mode 100644
index 0000000000..bd184c2718
--- /dev/null
+++ b/lib/gitlab/utils.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ module Utils
+ extend self
+
+ # Run system command without outputting to stdout.
+ #
+ # @param cmd [Array]
+ # @return [Boolean]
+ def system_silent(cmd)
+ Popen::popen(cmd).last.zero?
+ end
+ end
+end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index d0b6cde3c7..1851e76067 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -5,6 +5,8 @@
#
module Gitlab
module VisibilityLevel
+ extend CurrentSettings
+
PRIVATE = 0 unless const_defined?(:PRIVATE)
INTERNAL = 10 unless const_defined?(:INTERNAL)
PUBLIC = 20 unless const_defined?(:PUBLIC)
@@ -23,21 +25,21 @@ module Gitlab
end
def allowed_for?(user, level)
- user.is_admin? || allowed_level?(level)
+ user.is_admin? || allowed_level?(level.to_i)
end
- # Level can be a string `"public"` or a value `20`, first check if valid,
- # then check if the corresponding string appears in the config
+ # Return true if the specified level is allowed for the current user.
+ # Level should be a numeric value, e.g. `20`.
def allowed_level?(level)
- if options.has_key?(level.to_s)
- non_restricted_level?(level)
- elsif options.has_value?(level.to_i)
- non_restricted_level?(options.key(level.to_i).downcase)
- end
+ valid_level?(level) && non_restricted_level?(level)
end
def non_restricted_level?(level)
- ! Gitlab.config.gitlab.restricted_visibility_levels.include?(level)
+ ! current_application_settings.restricted_visibility_levels.include?(level)
+ end
+
+ def valid_level?(level)
+ options.has_value?(level)
end
end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index c3378d6a18..10efff2ae9 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -3,30 +3,47 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
attr_reader :template
alias_method :h, :template
- def initialize(template, options = {})
+ def initialize(template, color_scheme, options = {})
@template = template
+ @color_scheme = color_scheme
@project = @template.instance_variable_get("@project")
@options = options.dup
super options
end
+ def preprocess(full_document)
+ # Redcarpet doesn't allow SMB links when `safe_links_only` is enabled.
+ # FTP links are allowed, so we trick Redcarpet.
+ full_document.gsub("smb://", "ftp://smb:")
+ end
+
+ # If project has issue number 39, apostrophe will be linked in
+ # regular text to the issue as Redcarpet will convert apostrophe to
+ # #39;
+ # We replace apostrophe with right single quote before Redcarpet
+ # does the processing and put the apostrophe back in postprocessing.
+ # This only influences regular text, code blocks are untouched.
+ def normal_text(text)
+ return text unless text.present?
+ text.gsub("'", "’")
+ end
+
+ # Stolen from Rugments::Plugins::Redcarpet as this module is not required
+ # from Rugments's gem root.
def block_code(code, language)
- # New lines are placed to fix an rendering issue
- # with code wrapped inside tag for next case:
- #
- # # Title kinda h1
- #
- # ruby code here
- #
- <<-HTML
+ lexer = Rugments::Lexer.find_fancy(language, code) || Rugments::Lexers::PlainText
-
-
-
#{h.send(:html_escape, code)}
-
-
+ # XXX HACK: Redcarpet strips hard tabs out of code blocks,
+ # so we assume you're not using leading spaces that aren't tabs,
+ # and just replace them here.
+ if lexer.tag == 'make'
+ code.gsub! /^ /, "\t"
+ end
- HTML
+ formatter = Rugments::Formatters::HTML.new(
+ cssclass: "code highlight #{@color_scheme} #{lexer.tag}"
+ )
+ formatter.format(lexer.lex(code))
end
def link(link, title, content)
@@ -44,13 +61,12 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end
def postprocess(full_document)
+ full_document.gsub!("ftp://smb:", "smb://")
+
+ full_document.gsub!("’", "'")
unless @template.instance_variable_get("@project_wiki") || @project.nil?
full_document = h.create_relative_links(full_document)
end
- if @options[:parse_tasks]
- h.gfm_with_tasks(full_document)
- else
- h.gfm(full_document)
- end
+ h.gfm_with_options(full_document, @options)
end
end
diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb
new file mode 100644
index 0000000000..fa016a170c
--- /dev/null
+++ b/lib/repository_cache.rb
@@ -0,0 +1,21 @@
+# Interface to the Redis-backed cache store used by the Repository model
+class RepositoryCache
+ attr_reader :namespace, :backend
+
+ def initialize(namespace, backend = Rails.cache)
+ @namespace = namespace
+ @backend = backend
+ end
+
+ def cache_key(type)
+ "#{type}:#{namespace}"
+ end
+
+ def expire(key)
+ backend.delete(cache_key(key))
+ end
+
+ def fetch(key, &block)
+ backend.fetch(cache_key(key), &block)
+ end
+end
diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh
index 4684957233..adea4c7a74 100755
--- a/lib/support/deploy/deploy.sh
+++ b/lib/support/deploy/deploy.sh
@@ -4,7 +4,7 @@
# If any command return non-zero status - stop deploy
set -e
-echo 'Deploy: Stoping sidekiq..'
+echo 'Deploy: Stopping sidekiq..'
cd /home/git/gitlab/ && sudo -u git -H bundle exec rake sidekiq:stop RAILS_ENV=production
echo 'Deploy: Show deploy index page'
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 49a68c6229..62a4276536 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -1,5 +1,5 @@
## GitLab
-## Maintainer: @randx
+## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller, DouweM
##
## Lines starting with two hashes (##) are comments with information.
## Lines starting with one hash (#) are configuration parameters that can be uncommented.
@@ -15,7 +15,7 @@
## - installing an old version of Nginx with the chunkin module [2] compiled in, or
## - using a newer version of Nginx.
##
-## At the time of writing we do not know if either of these theoretical solutions works.
+## At the time of writing we do not know if either of these theoretical solutions works.
## As a workaround users can use Git over SSH to push large files.
##
## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
@@ -26,6 +26,7 @@
## configuration ##
###################################
##
+## See installation.md#using-https for additional HTTPS configuration details.
upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
@@ -33,7 +34,8 @@ upstream gitlab {
## Normal HTTP host
server {
- listen *:80 default_server;
+ listen 0.0.0.0:80 default_server;
+ listen [::]:80 default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public;
@@ -42,6 +44,8 @@ server {
## Or if you want to accept large git objects over http
client_max_body_size 20m;
+ ## See app/controllers/application_controller.rb for headers set
+
## Individual nginx logs for this GitLab vhost
access_log /var/log/nginx/gitlab_access.log;
error_log /var/log/nginx/gitlab_error.log;
@@ -52,6 +56,27 @@ server {
try_files $uri $uri/index.html $uri.html @gitlab;
}
+ ## We route uploads through GitLab to prevent XSS and enforce access control.
+ location /uploads/ {
+ ## If you use HTTPS make sure you disable gzip compression
+ ## to be safe against BREACH attack.
+ # gzip off;
+
+ ## https://github.com/gitlabhq/gitlabhq/issues/694
+ ## Some requests take more than 30 seconds.
+ proxy_read_timeout 300;
+ proxy_connect_timeout 300;
+ proxy_redirect off;
+
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Frame-Options SAMEORIGIN;
+
+ proxy_pass http://gitlab;
+ }
+
## If a file, which is not found in the root folder is requested,
## then the proxy passes the request to the upsteam (gitlab unicorn).
location @gitlab {
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index d3fb467ef2..2aefc94469 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -1,5 +1,5 @@
## GitLab
-## Contributors: randx, yin8086, sashkab, orkoden, axilleas
+## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller, DouweM
##
## Modified from nginx http version
## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/
@@ -26,9 +26,8 @@
## [1] https://github.com/agentzh/chunkin-nginx-module#status
## [2] https://github.com/agentzh/chunkin-nginx-module
##
-##
###################################
-## SSL configuration ##
+## configuration ##
###################################
##
## See installation.md#using-https for additional HTTPS configuration details.
@@ -37,22 +36,24 @@ upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
}
-## Normal HTTP host
+## Redirects all HTTP traffic to the HTTPS host
server {
- listen *:80 default_server;
+ listen 0.0.0.0:80;
+ listen [::]:80 ipv6only=on default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
-
- ## Redirects all traffic to the HTTPS host
- root /nowhere; ## root doesn't have to be a valid path since we are redirecting
- rewrite ^ https://$server_name$request_uri? permanent;
+ return 301 https://$server_name$request_uri;
+ access_log /var/log/nginx/gitlab_access.log;
+ error_log /var/log/nginx/gitlab_error.log;
}
+
## HTTPS host
server {
- listen 443 ssl;
+ listen 0.0.0.0:443 ssl;
+ listen [::]:443 ipv6only=on ssl default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
- server_tokens off;
+ server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public;
## Increase this if you want to upload large attachments
@@ -60,24 +61,19 @@ server {
client_max_body_size 20m;
## Strong SSL Security
- ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+ ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
ssl on;
ssl_certificate /etc/nginx/ssl/gitlab.crt;
ssl_certificate_key /etc/nginx/ssl/gitlab.key;
# GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
- ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4';
+ ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_prefer_server_ciphers on;
+ ssl_session_cache shared:SSL:10m;
+ ssl_session_timeout 5m;
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- ssl_session_cache builtin:1000 shared:SSL:10m;
-
- ssl_prefer_server_ciphers on;
-
- ## [WARNING] The following header states that the browser should only communicate
- ## with your server over a secure connection for the next 24 months.
- add_header Strict-Transport-Security max-age=63072000;
- add_header X-Frame-Options SAMEORIGIN;
- add_header X-Content-Type-Options nosniff;
+ ## See app/controllers/application_controller.rb for headers set
## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL.
## Replace with your ssl_trusted_certificate. For more info see:
@@ -88,11 +84,10 @@ server {
# ssl_stapling_verify on;
# ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt;
# resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired
- # resolver_timeout 10s;
+ # resolver_timeout 5s;
## [Optional] Generate a stronger DHE parameter:
- ## cd /etc/ssl/certs
- ## sudo openssl dhparam -out dhparam.pem 4096
+ ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
##
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
@@ -106,6 +101,28 @@ server {
try_files $uri $uri/index.html $uri.html @gitlab;
}
+ ## We route uploads through GitLab to prevent XSS and enforce access control.
+ location /uploads/ {
+ ## If you use HTTPS make sure you disable gzip compression
+ ## to be safe against BREACH attack.
+ gzip off;
+
+ ## https://github.com/gitlabhq/gitlabhq/issues/694
+ ## Some requests take more than 30 seconds.
+ proxy_read_timeout 300;
+ proxy_connect_timeout 300;
+ proxy_redirect off;
+
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-Ssl on;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Frame-Options SAMEORIGIN;
+
+ proxy_pass http://gitlab;
+ }
+
## If a file, which is not found in the root folder is requested,
## then the proxy passes the request to the upsteam (gitlab unicorn).
location @gitlab {
diff --git a/lib/tasks/brakeman.rake b/lib/tasks/brakeman.rake
new file mode 100644
index 0000000000..abcb5f0ae4
--- /dev/null
+++ b/lib/tasks/brakeman.rake
@@ -0,0 +1,9 @@
+desc 'Security check via brakeman'
+task :brakeman do
+ if system("brakeman --skip-files lib/backup/repository.rb -w3 -z")
+ exit 0
+ else
+ puts 'Security check failed'
+ exit 1
+ end
+end
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 2eff1260b6..0230fbb010 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -6,6 +6,7 @@ namespace :gitlab do
desc "GITLAB | Create a backup of the GitLab system"
task create: :environment do
warn_user_is_not_gitlab
+ configure_cron_mode
Rake::Task["gitlab:backup:db:create"].invoke
Rake::Task["gitlab:backup:repo:create"].invoke
@@ -21,6 +22,7 @@ namespace :gitlab do
desc "GITLAB | Restore a previously created backup"
task restore: :environment do
warn_user_is_not_gitlab
+ configure_cron_mode
backup = Backup::Manager.new
backup.unpack
@@ -35,43 +37,54 @@ namespace :gitlab do
namespace :repo do
task create: :environment do
- puts "Dumping repositories ...".blue
+ $progress.puts "Dumping repositories ...".blue
Backup::Repository.new.dump
- puts "done".green
+ $progress.puts "done".green
end
task restore: :environment do
- puts "Restoring repositories ...".blue
+ $progress.puts "Restoring repositories ...".blue
Backup::Repository.new.restore
- puts "done".green
+ $progress.puts "done".green
end
end
namespace :db do
task create: :environment do
- puts "Dumping database ... ".blue
+ $progress.puts "Dumping database ... ".blue
Backup::Database.new.dump
- puts "done".green
+ $progress.puts "done".green
end
task restore: :environment do
- puts "Restoring database ... ".blue
+ $progress.puts "Restoring database ... ".blue
Backup::Database.new.restore
- puts "done".green
+ $progress.puts "done".green
end
end
namespace :uploads do
task create: :environment do
- puts "Dumping uploads ... ".blue
+ $progress.puts "Dumping uploads ... ".blue
Backup::Uploads.new.dump
- puts "done".green
+ $progress.puts "done".green
end
task restore: :environment do
- puts "Restoring uploads ... ".blue
+ $progress.puts "Restoring uploads ... ".blue
Backup::Uploads.new.restore
- puts "done".green
+ $progress.puts "done".green
+ end
+ end
+
+ def configure_cron_mode
+ if ENV['CRON']
+ # We need an object we can say 'puts' and 'print' to; let's use a
+ # StringIO.
+ require 'stringio'
+ $progress = StringIO.new
+ else
+ $progress = $stdout
end
end
end # namespace end: backup
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 9ec368254a..d791b7155f 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -29,6 +29,7 @@ namespace :gitlab do
check_redis_version
check_ruby_version
check_git_version
+ check_active_users
finished_checking "GitLab"
end
@@ -328,16 +329,20 @@ namespace :gitlab do
if correct_options.all?
puts "yes".green
else
- puts "no".red
- try_fixing_it(
- sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.name \"#{options["user.name"]}\""),
- sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.email \"#{options["user.email"]}\""),
- sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"")
- )
- for_more_information(
- see_installation_guide_section "GitLab"
- )
- fix_and_rerun
+ print "Trying to fix Git error automatically. ..."
+ if auto_fix_git_config(options)
+ puts "Success".green
+ else
+ puts "Failed".red
+ try_fixing_it(
+ sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.name \"#{options["user.name"]}\""),
+ sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.email \"#{options["user.email"]}\""),
+ sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"")
+ )
+ for_more_information(
+ see_installation_guide_section "GitLab"
+ )
+ end
end
end
end
@@ -574,24 +579,16 @@ namespace :gitlab do
Gitlab::Shell.new.version
end
- def required_gitlab_shell_version
- File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip
- end
-
def gitlab_shell_major_version
- required_gitlab_shell_version.split(".")[0].to_i
+ Gitlab::Shell.version_required.split('.')[0].to_i
end
def gitlab_shell_minor_version
- required_gitlab_shell_version.split(".")[1].to_i
+ Gitlab::Shell.version_required.split('.')[1].to_i
end
def gitlab_shell_patch_version
- required_gitlab_shell_version.split(".")[2].to_i
- end
-
- def has_gitlab_shell3?
- gitlab_shell_version.try(:start_with?, "v3.")
+ Gitlab::Shell.version_required.split('.')[2].to_i
end
end
@@ -664,7 +661,7 @@ namespace :gitlab do
warn_user_is_not_gitlab
start_checking "LDAP"
- if ldap_config.enabled
+ if Gitlab::LDAP::Config.enabled?
print_users(args.limit)
else
puts 'LDAP is disabled in config/gitlab.yml'
@@ -675,39 +672,19 @@ namespace :gitlab do
def print_users(limit)
puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)"
- ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry|
- puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}"
+
+ servers = Gitlab::LDAP::Config.providers
+
+ servers.each do |server|
+ puts "Server: #{server}"
+ Gitlab::LDAP::Adapter.open(server) do |adapter|
+ users = adapter.users(adapter.config.uid, '*', 100)
+ users.each do |user|
+ puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}"
+ end
+ end
end
end
-
- def attributes
- [ldap_config.uid]
- end
-
- def filter
- uid_filter = Net::LDAP::Filter.present?(ldap_config.uid)
- if user_filter
- Net::LDAP::Filter.join(uid_filter, user_filter)
- else
- uid_filter
- end
- end
-
- def user_filter
- if ldap_config['user_filter'] && ldap_config.user_filter.present?
- Net::LDAP::Filter.construct(ldap_config.user_filter)
- else
- nil
- end
- end
-
- def ldap
- @ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection
- end
-
- def ldap_config
- @ldap_config ||= Gitlab.config.ldap
- end
end
# Helper methods
@@ -809,19 +786,23 @@ namespace :gitlab do
end
end
+ def check_active_users
+ puts "Active users: #{User.active.count}"
+ end
+
def omnibus_gitlab?
Dir.pwd == '/opt/gitlab/embedded/service/gitlab-rails'
end
def sanitized_message(project)
- if sanitize
+ if should_sanitize?
"#{project.namespace_id.to_s.yellow}/#{project.id.to_s.yellow} ... "
else
"#{project.name_with_namespace.yellow} ... "
end
end
- def sanitize
+ def should_sanitize?
if ENV['SANITIZE'] == "true"
true
else
@@ -829,3 +810,4 @@ namespace :gitlab do
end
end
end
+
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 63dcdc5237..189ad6090a 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -92,11 +92,11 @@ namespace :gitlab do
User.ldap.each do |ldap_user|
print "#{ldap_user.name} (#{ldap_user.extern_uid}) ..."
- if Gitlab::LDAP::Access.open { |access| access.allowed?(ldap_user) }
+ if Gitlab::LDAP::Access.allowed?(ldap_user)
puts " [OK]".green
else
if block_flag
- ldap_user.block!
+ ldap_user.block! unless ldap_user.blocked?
puts " [BLOCKED]".red
else
puts " [NOT IN LDAP]".yellow
diff --git a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake
new file mode 100644
index 0000000000..e9cf0a9b5e
--- /dev/null
+++ b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake
@@ -0,0 +1,10 @@
+namespace :gitlab do
+ namespace :db do
+ task drop_all_postgres_sequences: :environment do
+ connection = ActiveRecord::Base.connection
+ connection.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';").each do |sequence|
+ connection.execute("DROP SEQUENCE #{sequence['relname']}")
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index b6ed874e11..20abb2fa50 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -15,26 +15,17 @@ namespace :gitlab do
git_base_path = Gitlab.config.gitlab_shell.repos_path
repos_to_import = Dir.glob(git_base_path + '/**/*.git')
- namespaces = Namespace.pluck(:path)
-
repos_to_import.each do |repo_path|
# strip repo base path
repo_path[0..git_base_path.length] = ''
path = repo_path.sub(/\.git$/, '')
- name = File.basename path
- group_name = File.dirname path
+ group_name, name = File.split(path)
group_name = nil if group_name == '.'
- # Skip if group or user
- if namespaces.include?(name)
- puts "Skipping #{project.name} due to namespace conflict with group or user".yellow
- next
- end
-
puts "Processing #{repo_path}".yellow
- if path =~ /.wiki\Z/
+ if path.end_with?('.wiki')
puts " * Skipping wiki repo"
next
end
@@ -53,9 +44,9 @@ namespace :gitlab do
# find group namespace
if group_name
- group = Group.find_by(path: group_name)
+ group = Namespace.find_by(path: group_name)
# create group namespace
- if !group
+ unless group
group = Group.new(:name => group_name)
group.path = group_name
group.owner = user
@@ -75,6 +66,7 @@ namespace :gitlab do
puts " * Created #{project.name} (#{repo_path})".green
else
puts " * Failed trying to create #{project.name} (#{repo_path})".red
+ puts " Validation Errors: #{project.errors.messages}".red
end
end
end
diff --git a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake
new file mode 100644
index 0000000000..102c6ae55d
--- /dev/null
+++ b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake
@@ -0,0 +1,73 @@
+require "#{Rails.root}/app/helpers/emails_helper"
+require 'action_view/helpers'
+extend ActionView::Helpers
+
+include ActionView::Context
+include EmailsHelper
+
+namespace :gitlab do
+ desc "Email google whitelisting email with example email for actions in inbox"
+ task mail_google_schema_whitelisting: :environment do
+ subject = "Rails | Implemented feature"
+ url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}"
+ schema = email_action(url)
+ body = email_template(schema, url)
+ mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe)
+ if send_now
+ mail.deliver
+ else
+ puts "WOULD SEND:"
+ end
+ puts mail
+ end
+
+ def email_template(schema, url)
+ "
+
+
+
+ GitLab
+
+
+
+
+
+
+
+
+
+ "
+ end
+
+ def send_now
+ if ENV['SEND'] == "true"
+ true
+ else
+ false
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index a8f26a7c02..9af93300e0 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -4,28 +4,32 @@ namespace :gitlab do
task :install, [:tag, :repo] => :environment do |t, args|
warn_user_is_not_gitlab
- default_version = File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip
+ default_version = Gitlab::Shell.version_required
args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git")
- user = Settings.gitlab.user
- home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home
- gitlab_url = Settings.gitlab.url
+ user = Gitlab.config.gitlab.user
+ home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
+ gitlab_url = Gitlab.config.gitlab.url
# gitlab-shell requires a / at the end of the url
- gitlab_url += "/" unless gitlab_url.match(/\/$/)
+ gitlab_url += '/' unless gitlab_url.end_with?('/')
repos_path = Gitlab.config.gitlab_shell.repos_path
target_dir = Gitlab.config.gitlab_shell.path
# Clone if needed
unless File.directory?(target_dir)
- sh "git clone '#{args.repo}' '#{target_dir}'"
+ system(*%W(git clone -- #{args.repo} #{target_dir}))
end
# Make sure we're on the right tag
Dir.chdir(target_dir) do
# First try to checkout without fetching
# to avoid stalling tests if the Internet is down.
- reset = "git reset --hard $(git describe #{args.tag} || git describe origin/#{args.tag})"
- sh "#{reset} || git fetch origin && #{reset}"
+ reseted = reset_to_commit(args)
+
+ unless reseted
+ system(*%W(git fetch origin))
+ reset_to_commit(args)
+ end
config = {
user: user,
@@ -54,7 +58,7 @@ namespace :gitlab do
File.open("config.yml", "w+") {|f| f.puts config.to_yaml}
# Launch installation process
- sh "bin/install"
+ system(*%W(bin/install))
end
# Required for debian packaging with PKGR: Setup .ssh/environment with
@@ -76,7 +80,7 @@ namespace :gitlab do
desc "GITLAB | Build missing projects"
task build_missing_projects: :environment do
Project.find_each(batch_size: 1000) do |project|
- path_to_repo = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
+ path_to_repo = project.repository.path_to_repo
if File.exists?(path_to_repo)
print '-'
else
@@ -118,5 +122,16 @@ namespace :gitlab do
puts "Quitting...".red
exit 1
end
+
+ def reset_to_commit(args)
+ tag, status = Gitlab::Popen.popen(%W(git describe -- #{args.tag}))
+
+ unless status.zero?
+ tag, status = Gitlab::Popen.popen(%W(git describe -- origin/#{args.tag}))
+ end
+
+ tag = tag.strip
+ system(*%W(git reset --hard #{tag}))
+ end
end
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index da61c6e007..14a130be2c 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -112,4 +112,20 @@ namespace :gitlab do
@warned_user_not_gitlab = true
end
end
+
+ # Tries to configure git itself
+ #
+ # Returns true if all subcommands were successfull (according to their exit code)
+ # Returns false if any or all subcommands failed.
+ def auto_fix_git_config(options)
+ if !@warned_user_not_gitlab && options['user.email'] != 'example@example.com' # default email should be overridden?
+ command_success = options.map do |name, value|
+ system(%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
+ end
+
+ command_success.all?
+ else
+ false
+ end
+ end
end
diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake
index c01b00bd1c..b4076f8238 100644
--- a/lib/tasks/gitlab/test.rake
+++ b/lib/tasks/gitlab/test.rake
@@ -2,6 +2,7 @@ namespace :gitlab do
desc "GITLAB | Run all tests"
task :test do
cmds = [
+ %W(rake rubocop),
%W(rake spinach),
%W(rake spec),
%W(rake jasmine:ci)
diff --git a/lib/tasks/rubocop.rake b/lib/tasks/rubocop.rake
new file mode 100644
index 0000000000..ddfaf5d51f
--- /dev/null
+++ b/lib/tasks/rubocop.rake
@@ -0,0 +1,4 @@
+unless Rails.env.production?
+ require 'rubocop/rake_task'
+ RuboCop::RakeTask.new
+end
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
index 507b315759..4aefc18ce1 100644
--- a/lib/tasks/spinach.rake
+++ b/lib/tasks/spinach.rake
@@ -2,9 +2,15 @@ Rake::Task["spinach"].clear if Rake::Task.task_defined?('spinach')
desc "GITLAB | Run spinach"
task :spinach do
+ tags = if ENV['SEMAPHORE']
+ '~@tricky'
+ else
+ '~@semaphore'
+ end
+
cmds = [
%W(rake gitlab:setup),
- %W(spinach),
+ %W(spinach --tags #{tags}),
]
run_commands(cmds)
end
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
index 583f4a876d..a39d964987 100644
--- a/lib/tasks/test.rake
+++ b/lib/tasks/test.rake
@@ -9,5 +9,5 @@ unless Rails.env.production?
require 'coveralls/rake/task'
Coveralls::RakeTask.new
desc "GITLAB | Run all tests on CI with simplecov"
- task :test_ci => [:spinach, :spec, 'coveralls:push']
+ task :test_ci => [:rubocop, :brakeman, 'jasmine:ci', :spinach, :spec, 'coveralls:push']
end
diff --git a/public/503.html b/public/503.html
new file mode 100644
index 0000000000..efdae0f512
--- /dev/null
+++ b/public/503.html
@@ -0,0 +1,13 @@
+
+
+
+ Page took too long to load (503)
+
+
+
+ 503
+ Page took too long to load.
+
+ Please contact your GitLab administrator if this problem persists.
+
+
diff --git a/safe/public.pem b/safe/public.pem
new file mode 100644
index 0000000000..c5ffe20a5c
--- /dev/null
+++ b/safe/public.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnp2mUaLBoHFX127ysonX
+OihiGpI4098eFfH1iAxpKHIof0vs0jFF05IUScNXJZ1U3w8G1U/unY/wGGa3NzAb
+ZfDd22eOF6X2Gfiey6U4w9dFf0/UT5x1bphlpX357yh4O9oWWuNaWD062DTbOOsJ
+U6UW2U/sZAu/QScys0Nw+gJ58t93hb4jFq+nO5IAQc6g4S8ek5YvIXOshFEpF2in
+ZLbSYowx92+9GzfjvdQ7fk0Q2ssg0zfScVa6FY8n019osz0SC3wcSd/qicdfecpu
+7oycpd9YDqk4lufE1qVMOsgE8OO4KXMrByz2f+T0p/bH9zdBa5HYylf1T7i60hIL
+kQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index cc32805f5e..186239d309 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -7,26 +7,26 @@ describe ApplicationController do
it 'should redirect if the user is over their password expiry' do
user.password_expires_at = Time.new(2002)
- user.ldap_user?.should be_false
- controller.stub(:current_user).and_return(user)
- controller.should_receive(:redirect_to)
- controller.should_receive(:new_profile_password_path)
+ expect(user.ldap_user?).to be_falsey
+ allow(controller).to receive(:current_user).and_return(user)
+ expect(controller).to receive(:redirect_to)
+ expect(controller).to receive(:new_profile_password_path)
controller.send(:check_password_expiration)
end
it 'should not redirect if the user is under their password expiry' do
user.password_expires_at = Time.now + 20010101
- user.ldap_user?.should be_false
- controller.stub(:current_user).and_return(user)
- controller.should_not_receive(:redirect_to)
+ expect(user.ldap_user?).to be_falsey
+ allow(controller).to receive(:current_user).and_return(user)
+ expect(controller).not_to receive(:redirect_to)
controller.send(:check_password_expiration)
end
it 'should not redirect if the user is over their password expiry but they are an ldap user' do
user.password_expires_at = Time.new(2002)
- user.stub(:ldap_user?).and_return(true)
- controller.stub(:current_user).and_return(user)
- controller.should_not_receive(:redirect_to)
+ allow(user).to receive(:ldap_user?).and_return(true)
+ allow(controller).to receive(:current_user).and_return(user)
+ expect(controller).not_to receive(:redirect_to)
controller.send(:check_password_expiration)
end
end
diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb
index 11d748ca77..a1102f2834 100644
--- a/spec/controllers/blob_controller_spec.rb
+++ b/spec/controllers/blob_controller_spec.rb
@@ -9,29 +9,32 @@ describe Projects::BlobController do
project.team << [user, :master]
- project.stub(:branches).and_return(['master', 'foo/bar/baz'])
- project.stub(:tags).and_return(['v1.0.0', 'v2.0.0'])
+ allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz'])
+ allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0'])
controller.instance_variable_set(:@project, project)
end
describe "GET show" do
render_views
- before { get :show, project_id: project.to_param, id: id }
+ before do
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: id)
+ end
context "valid branch, valid file" do
let(:id) { 'master/README.md' }
- it { should respond_with(:success) }
+ it { is_expected.to respond_with(:success) }
end
context "valid branch, invalid file" do
let(:id) { 'master/invalid-path.rb' }
- it { should respond_with(:not_found) }
+ it { is_expected.to respond_with(:not_found) }
end
context "invalid branch, valid file" do
let(:id) { 'invalid-branch/README.md' }
- it { should respond_with(:not_found) }
+ it { is_expected.to respond_with(:not_found) }
end
end
@@ -39,13 +42,17 @@ describe Projects::BlobController do
render_views
before do
- get :show, project_id: project.to_param, id: id
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: id)
controller.instance_variable_set(:@blob, nil)
end
context 'redirect to tree' do
let(:id) { 'markdown/doc' }
- it { should redirect_to("/#{project.path_with_namespace}/tree/markdown/doc") }
+ it 'redirects' do
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/tree/markdown/doc")
+ end
end
end
end
diff --git a/spec/controllers/branches_controller_spec.rb b/spec/controllers/branches_controller_spec.rb
new file mode 100644
index 0000000000..51397382cf
--- /dev/null
+++ b/spec/controllers/branches_controller_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Projects::BranchesController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ project.team << [user, :master]
+
+ allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz'])
+ allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0'])
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "POST create" do
+ render_views
+
+ before {
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ ref: ref
+ }
+
+ context "valid branch name, valid source" do
+ let(:branch) { "merge_branch" }
+ let(:ref) { "master" }
+ it 'redirects' do
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/tree/merge_branch")
+ end
+ end
+
+ context "invalid branch name, valid ref" do
+ let(:branch) { "" }
+ let(:ref) { "master" }
+ it 'redirects' do
+ expect(subject).
+ to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');")
+ end
+ end
+
+ context "valid branch name, invalid ref" do
+ let(:branch) { "merge_branch" }
+ let(:ref) { "" }
+ it { is_expected.to render_template('new') }
+ end
+
+ context "invalid branch name, invalid ref" do
+ let(:branch) { "" }
+ let(:ref) { "" }
+ it { is_expected.to render_template('new') }
+ end
+ end
+end
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
index f5822157ea..3394a1f863 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -13,27 +13,32 @@ describe Projects::CommitController do
describe "#show" do
shared_examples "export as" do |format|
it "should generally work" do
- get :show, project_id: project.to_param, id: commit.id, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: commit.id, format: format)
expect(response).to be_success
end
it "should generate it" do
- Commit.any_instance.should_receive(:"to_#{format}")
+ expect_any_instance_of(Commit).to receive(:"to_#{format}")
- get :show, project_id: project.to_param, id: commit.id, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: commit.id, format: format)
end
it "should render it" do
- get :show, project_id: project.to_param, id: commit.id, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: commit.id, format: format)
expect(response.body).to eq(commit.send(:"to_#{format}"))
end
it "should not escape Html" do
- Commit.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ')
+ allow_any_instance_of(Commit).to receive(:"to_#{format}").
+ and_return('HTML entities &<>" ')
- get :show, project_id: project.to_param, id: commit.id, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: commit.id, format: format)
expect(response.body).to_not include('&')
expect(response.body).to_not include('>')
@@ -47,7 +52,8 @@ describe Projects::CommitController do
let(:format) { :diff }
it "should really only be a git diff" do
- get :show, project_id: project.to_param, id: commit.id, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: commit.id, format: format)
expect(response.body).to start_with("diff --git")
end
@@ -58,16 +64,28 @@ describe Projects::CommitController do
let(:format) { :patch }
it "should really be a git email patch" do
- get :show, project_id: project.to_param, id: commit.id, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: commit.id, format: format)
expect(response.body).to start_with("From #{commit.id}")
end
it "should contain a git diff" do
- get :show, project_id: project.to_param, id: commit.id, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: commit.id, format: format)
expect(response.body).to match(/^diff --git/)
end
end
end
+
+ describe "#branches" do
+ it "contains branch and tags information" do
+ get(:branches, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: commit.id)
+
+ expect(assigns(:branches)).to include("master", "feature_conflict")
+ expect(assigns(:tags)).to include("v1.1.0")
+ end
+ end
end
diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/commits_controller_spec.rb
index 0c19d755eb..2184b35152 100644
--- a/spec/controllers/commits_controller_spec.rb
+++ b/spec/controllers/commits_controller_spec.rb
@@ -12,9 +12,10 @@ describe Projects::CommitsController do
describe "GET show" do
context "as atom feed" do
it "should render as atom" do
- get :show, project_id: project.to_param, id: "master", format: "atom"
- response.should be_success
- response.content_type.should == 'application/atom+xml'
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: "master", format: "atom")
+ expect(response).to be_success
+ expect(response.content_type).to eq('application/atom+xml')
end
end
end
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
new file mode 100644
index 0000000000..5dd4124061
--- /dev/null
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+describe Import::BitbucketController do
+ let(:user) { create(:user, bitbucket_access_token: 'asd123', bitbucket_access_token_secret: "sekret") }
+
+ before do
+ sign_in(user)
+ controller.stub(:bitbucket_import_enabled?).and_return(true)
+ end
+
+ describe "GET callback" do
+ before do
+ session[:oauth_request_token] = {}
+ end
+
+ it "updates access token" do
+ token = "asdasd12345"
+ secret = "sekrettt"
+ access_token = double(token: token, secret: secret)
+ Gitlab::BitbucketImport::Client.any_instance.stub(:get_token).and_return(access_token)
+ Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket")
+
+ get :callback
+
+ expect(user.reload.bitbucket_access_token).to eq(token)
+ expect(user.reload.bitbucket_access_token_secret).to eq(secret)
+ expect(controller).to redirect_to(status_import_bitbucket_url)
+ end
+ end
+
+ describe "GET status" do
+ before do
+ @repo = OpenStruct.new(slug: 'vim', owner: 'asd')
+ end
+
+ it "assigns variables" do
+ @project = create(:project, import_type: 'bitbucket', creator_id: user.id)
+ controller.stub_chain(:client, :projects).and_return([@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([@repo])
+ end
+
+ it "does not show already added project" do
+ @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim')
+ controller.stub_chain(:client, :projects).and_return([@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([])
+ end
+ end
+
+ describe "POST create" do
+ before do
+ @repo = {
+ slug: 'vim',
+ owner: "john"
+ }.with_indifferent_access
+ end
+
+ it "takes already existing namespace" do
+ namespace = create(:namespace, name: "john", owner: user)
+ expect(Gitlab::BitbucketImport::KeyAdder).
+ to receive(:new).with(@repo, user).
+ and_return(double(execute: true))
+ expect(Gitlab::BitbucketImport::ProjectCreator).
+ to receive(:new).with(@repo, namespace, user).
+ and_return(double(execute: true))
+ controller.stub_chain(:client, :project).and_return(@repo)
+
+ post :create, format: :js
+ end
+ end
+end
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
new file mode 100644
index 0000000000..5b967bfcc0
--- /dev/null
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Import::GithubController do
+ let(:user) { create(:user, github_access_token: 'asd123') }
+
+ before do
+ sign_in(user)
+ controller.stub(:github_import_enabled?).and_return(true)
+ end
+
+ describe "GET callback" do
+ it "updates access token" do
+ token = "asdasd12345"
+ allow_any_instance_of(Gitlab::GithubImport::Client).
+ to receive(:get_token).and_return(token)
+ Gitlab.config.omniauth.providers << OpenStruct.new(app_id: 'asd123',
+ app_secret: 'asd123',
+ name: 'github')
+
+ get :callback
+
+ expect(user.reload.github_access_token).to eq(token)
+ expect(controller).to redirect_to(status_import_github_url)
+ end
+ end
+
+ describe "GET status" do
+ before do
+ @repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim')
+ @org = OpenStruct.new(login: 'company')
+ @org_repo = OpenStruct.new(login: 'company', full_name: 'company/repo')
+ end
+
+ it "assigns variables" do
+ @project = create(:project, import_type: 'github', creator_id: user.id)
+ controller.stub_chain(:client, :repos).and_return([@repo])
+ controller.stub_chain(:client, :orgs).and_return([@org])
+ controller.stub_chain(:client, :org_repos).with(@org.login).and_return([@org_repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([@repo, @org_repo])
+ end
+
+ it "does not show already added project" do
+ @project = create(:project, import_type: 'github', creator_id: user.id, import_source: 'asd/vim')
+ controller.stub_chain(:client, :repos).and_return([@repo])
+ controller.stub_chain(:client, :orgs).and_return([])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([])
+ end
+ end
+
+ describe "POST create" do
+ before do
+ @repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim', owner: OpenStruct.new(login: "john"))
+ end
+
+ it "takes already existing namespace" do
+ namespace = create(:namespace, name: "john", owner: user)
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).with(@repo, namespace, user).
+ and_return(double(execute: true))
+ controller.stub_chain(:client, :repo).and_return(@repo)
+
+ post :create, format: :js
+ end
+ end
+end
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
new file mode 100644
index 0000000000..b6b86b1bce
--- /dev/null
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe Import::GitlabController do
+ let(:user) { create(:user, gitlab_access_token: 'asd123') }
+
+ before do
+ sign_in(user)
+ controller.stub(:gitlab_import_enabled?).and_return(true)
+ end
+
+ describe "GET callback" do
+ it "updates access token" do
+ token = "asdasd12345"
+ Gitlab::GitlabImport::Client.any_instance.stub_chain(:client, :auth_code, :get_token, :token).and_return(token)
+ Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "gitlab")
+
+ get :callback
+
+ expect(user.reload.gitlab_access_token).to eq(token)
+ expect(controller).to redirect_to(status_import_gitlab_url)
+ end
+ end
+
+ describe "GET status" do
+ before do
+ @repo = OpenStruct.new(path: 'vim', path_with_namespace: 'asd/vim')
+ end
+
+ it "assigns variables" do
+ @project = create(:project, import_type: 'gitlab', creator_id: user.id)
+ controller.stub_chain(:client, :projects).and_return([@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([@repo])
+ end
+
+ it "does not show already added project" do
+ @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim')
+ controller.stub_chain(:client, :projects).and_return([@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([])
+ end
+ end
+
+ describe "POST create" do
+ before do
+ @repo = {
+ path: 'vim',
+ path_with_namespace: 'asd/vim',
+ owner: {name: "john"},
+ namespace: {path: "john"}
+ }.with_indifferent_access
+ end
+
+ it "takes already existing namespace" do
+ namespace = create(:namespace, name: "john", owner: user)
+ expect(Gitlab::GitlabImport::ProjectCreator).
+ to receive(:new).with(@repo, namespace, user).
+ and_return(double(execute: true))
+ controller.stub_chain(:client, :project).and_return(@repo)
+
+ post :create, format: :js
+ end
+ end
+end
diff --git a/spec/controllers/import/gitorious_controller_spec.rb b/spec/controllers/import/gitorious_controller_spec.rb
new file mode 100644
index 0000000000..07c9484bf1
--- /dev/null
+++ b/spec/controllers/import/gitorious_controller_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe Import::GitoriousController do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe "GET new" do
+ it "redirects to import endpoint on gitorious.org" do
+ get :new
+
+ expect(controller).to redirect_to("https://gitorious.org/gitlab-import?callback_url=http://test.host/import/gitorious/callback")
+ end
+ end
+
+ describe "GET callback" do
+ it "stores repo list in session" do
+ get :callback, repos: 'foo/bar,baz/qux'
+
+ expect(session[:gitorious_repos]).to eq('foo/bar,baz/qux')
+ end
+ end
+
+ describe "GET status" do
+ before do
+ @repo = OpenStruct.new(full_name: 'asd/vim')
+ end
+
+ it "assigns variables" do
+ @project = create(:project, import_type: 'gitorious', creator_id: user.id)
+ controller.stub_chain(:client, :repos).and_return([@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([@repo])
+ end
+
+ it "does not show already added project" do
+ @project = create(:project, import_type: 'gitorious', creator_id: user.id, import_source: 'asd/vim')
+ controller.stub_chain(:client, :repos).and_return([@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([])
+ end
+ end
+
+ describe "POST create" do
+ before do
+ @repo = Gitlab::GitoriousImport::Repository.new('asd/vim')
+ end
+
+ it "takes already existing namespace" do
+ namespace = create(:namespace, name: "asd", owner: user)
+ expect(Gitlab::GitoriousImport::ProjectCreator).
+ to receive(:new).with(@repo, namespace, user).
+ and_return(double(execute: true))
+ controller.stub_chain(:client, :repo).and_return(@repo)
+
+ post :create, format: :js
+ end
+ end
+end
diff --git a/spec/controllers/merge_requests_controller_spec.rb b/spec/controllers/merge_requests_controller_spec.rb
index 300527e4ff..d6f56ed33d 100644
--- a/spec/controllers/merge_requests_controller_spec.rb
+++ b/spec/controllers/merge_requests_controller_spec.rb
@@ -13,27 +13,32 @@ describe Projects::MergeRequestsController do
describe "#show" do
shared_examples "export merge as" do |format|
it "should generally work" do
- get :show, project_id: project.to_param, id: merge_request.iid, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: merge_request.iid, format: format)
expect(response).to be_success
end
it "should generate it" do
- MergeRequest.any_instance.should_receive(:"to_#{format}")
+ expect_any_instance_of(MergeRequest).to receive(:"to_#{format}")
- get :show, project_id: project.to_param, id: merge_request.iid, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: merge_request.iid, format: format)
end
it "should render it" do
- get :show, project_id: project.to_param, id: merge_request.iid, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: merge_request.iid, format: format)
expect(response.body).to eq((merge_request.send(:"to_#{format}",user)).to_s)
end
it "should not escape Html" do
- MergeRequest.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ')
+ allow_any_instance_of(MergeRequest).to receive(:"to_#{format}").
+ and_return('HTML entities &<>" ')
- get :show, project_id: project.to_param, id: merge_request.iid, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: merge_request.iid, format: format)
expect(response.body).to_not include('&')
expect(response.body).to_not include('>')
@@ -47,7 +52,8 @@ describe Projects::MergeRequestsController do
let(:format) { :diff }
it "should really only be a git diff" do
- get :show, project_id: project.to_param, id: merge_request.iid, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: merge_request.iid, format: format)
expect(response.body).to start_with("diff --git")
end
@@ -58,13 +64,15 @@ describe Projects::MergeRequestsController do
let(:format) { :patch }
it "should really be a git email patch with commit" do
- get :show, project_id: project.to_param, id: merge_request.iid, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: merge_request.iid, format: format)
expect(response.body[0..100]).to start_with("From #{merge_request.commits.last.id}")
end
it "should contain git diffs" do
- get :show, project_id: project.to_param, id: merge_request.iid, format: format
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: merge_request.iid, format: format)
expect(response.body).to match(/^diff --git/)
end
diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb
new file mode 100644
index 0000000000..596d8d34b7
--- /dev/null
+++ b/spec/controllers/projects/protected_branches_controller_spec.rb
@@ -0,0 +1,10 @@
+require('spec_helper')
+
+describe Projects::ProtectedBranchesController do
+ describe "GET #index" do
+ let(:project) { create(:project_empty_repo, :public) }
+ it "redirect empty repo to projects page" do
+ get(:index, namespace_id: project.namespace.to_param, project_id: project.to_param)
+ end
+ end
+end
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
new file mode 100644
index 0000000000..029f48b2d7
--- /dev/null
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -0,0 +1,57 @@
+require('spec_helper')
+
+describe Projects::UploadsController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
+ let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
+
+ describe "POST #create" do
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
+ context "without params['file']" do
+ it "returns an error" do
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ format: :json
+ expect(response.status).to eq(422)
+ end
+ end
+
+ context 'with valid image' do
+ before do
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ file: jpg,
+ format: :json
+ end
+
+ it 'returns a content with original filename, new link, and correct type.' do
+ expect(response.body).to match '\"alt\":\"rails_sample\"'
+ expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads"
+ expect(response.body).to match '\"is_image\":true'
+ end
+ end
+
+ context 'with valid non-image file' do
+ before do
+ post :create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ file: txt,
+ format: :json
+ end
+
+ it 'returns a content with original filename, new link, and correct type.' do
+ expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
+ expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads"
+ expect(response.body).to match '\"is_image\":false'
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 71bc49787c..a1b82a3215 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -7,56 +7,41 @@ describe ProjectsController do
let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
- describe "POST #upload_image" do
- before do
- sign_in(user)
- project.team << [user, :developer]
- end
+ describe "GET show" do
- context "without params['markdown_img']" do
- it "returns an error" do
- post :upload_image, id: project.to_param, format: :json
- expect(response.status).to eq(422)
- end
- end
+ context "when requested by `go get`" do
+ render_views
- context "with invalid file" do
- before do
- post :upload_image, id: project.to_param, markdown_img: txt, format: :json
- end
+ it "renders the go-import meta tag" do
+ get :show, "go-get" => "1", namespace_id: "bogus_namespace", id: "bogus_project"
- it "returns an error" do
- expect(response.status).to eq(422)
- end
- end
-
- context "with valid file" do
- before do
- post :upload_image, id: project.to_param, markdown_img: jpg, format: :json
- end
-
- it "returns a content with original filename and new link." do
- expect(response.body).to match "\"alt\":\"rails_sample\""
- expect(response.body).to match "\"url\":\"http://test.host/uploads/#{project.path_with_namespace}"
+ expect(response.body).to include("name='go-import'")
+
+ content = "localhost/bogus_namespace/bogus_project git http://localhost/bogus_namespace/bogus_project.git"
+ expect(response.body).to include("content='#{content}'")
end
end
end
-
+
describe "POST #toggle_star" do
it "toggles star if user is signed in" do
sign_in(user)
- expect(user.starred?(public_project)).to be_false
- post :toggle_star, id: public_project.to_param
- expect(user.starred?(public_project)).to be_true
- post :toggle_star, id: public_project.to_param
- expect(user.starred?(public_project)).to be_false
+ expect(user.starred?(public_project)).to be_falsey
+ post(:toggle_star, namespace_id: public_project.namespace.to_param,
+ id: public_project.to_param)
+ expect(user.starred?(public_project)).to be_truthy
+ post(:toggle_star, namespace_id: public_project.namespace.to_param,
+ id: public_project.to_param)
+ expect(user.starred?(public_project)).to be_falsey
end
it "does nothing if user is not signed in" do
- post :toggle_star, id: public_project.to_param
- expect(user.starred?(public_project)).to be_false
- post :toggle_star, id: public_project.to_param
- expect(user.starred?(public_project)).to be_false
+ post(:toggle_star, namespace_id: project.namespace.to_param,
+ id: public_project.to_param)
+ expect(user.starred?(public_project)).to be_falsey
+ post(:toggle_star, namespace_id: project.namespace.to_param,
+ id: public_project.to_param)
+ expect(user.starred?(public_project)).to be_falsey
end
end
end
diff --git a/spec/controllers/tree_controller_spec.rb b/spec/controllers/tree_controller_spec.rb
index 8147fb0e6f..7b219819bb 100644
--- a/spec/controllers/tree_controller_spec.rb
+++ b/spec/controllers/tree_controller_spec.rb
@@ -9,8 +9,8 @@ describe Projects::TreeController do
project.team << [user, :master]
- project.stub(:branches).and_return(['master', 'foo/bar/baz'])
- project.stub(:tags).and_return(['v1.0.0', 'v2.0.0'])
+ allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz'])
+ allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0'])
controller.instance_variable_set(:@project, project)
end
@@ -18,26 +18,29 @@ describe Projects::TreeController do
# Make sure any errors accessing the tree in our views bubble up to this spec
render_views
- before { get :show, project_id: project.to_param, id: id }
+ before do
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: id)
+ end
context "valid branch, no path" do
let(:id) { 'master' }
- it { should respond_with(:success) }
+ it { is_expected.to respond_with(:success) }
end
context "valid branch, valid path" do
let(:id) { 'master/encoding/' }
- it { should respond_with(:success) }
+ it { is_expected.to respond_with(:success) }
end
context "valid branch, invalid path" do
let(:id) { 'master/invalid-path/' }
- it { should respond_with(:not_found) }
+ it { is_expected.to respond_with(:not_found) }
end
context "invalid branch, valid path" do
let(:id) { 'invalid-branch/encoding/' }
- it { should respond_with(:not_found) }
+ it { is_expected.to respond_with(:not_found) }
end
end
@@ -45,12 +48,17 @@ describe Projects::TreeController do
render_views
before do
- get :show, project_id: project.to_param, id: id
+ get(:show, namespace_id: project.namespace.to_param,
+ project_id: project.to_param, id: id)
end
context 'redirect to blob' do
let(:id) { 'master/README.md' }
- it { should redirect_to("/#{project.path_with_namespace}/blob/master/README.md") }
+ it 'redirects' do
+ redirect_url = "/#{project.path_with_namespace}/blob/master/README.md"
+ expect(subject).
+ to redirect_to(redirect_url)
+ end
end
end
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
new file mode 100644
index 0000000000..0f9780356b
--- /dev/null
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -0,0 +1,296 @@
+require 'spec_helper'
+
+describe UploadsController do
+ let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+
+ describe "GET show" do
+ context "when viewing a user avatar" do
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ context "when the user is blocked" do
+ before do
+ user.block
+ end
+
+ it "redirects to the sign in page" do
+ get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png"
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when the user isn't blocked" do
+ it "responds with status 200" do
+ get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context "when not signed in" do
+ it "responds with status 200" do
+ get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context "when viewing a project avatar" do
+ let!(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+
+ context "when the project is public" do
+ before do
+ project.update_attribute(:visibility_level, Project::PUBLIC)
+ end
+
+ context "when not signed in" do
+ it "responds with status 200" do
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ it "responds with status 200" do
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context "when the project is private" do
+ before do
+ project.update_attribute(:visibility_level, Project::PRIVATE)
+ end
+
+ context "when not signed in" do
+ it "redirects to the sign in page" do
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ context "when the user has access to the project" do
+ before do
+ project.team << [user, :master]
+ end
+
+ context "when the user is blocked" do
+ before do
+ user.block
+ project.team << [user, :master]
+ end
+
+ it "redirects to the sign in page" do
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when the user isn't blocked" do
+ it "responds with status 200" do
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context "when the user doesn't have access to the project" do
+ it "responds with status 404" do
+ get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png"
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+ end
+
+ context "when viewing a group avatar" do
+ let!(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let!(:project) { create(:project, namespace: group) }
+
+ context "when the group has public projects" do
+ before do
+ project.update_attribute(:visibility_level, Project::PUBLIC)
+ end
+
+ context "when not signed in" do
+ it "responds with status 200" do
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ it "responds with status 200" do
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context "when the project doesn't have public projects" do
+ context "when not signed in" do
+ it "redirects to the sign in page" do
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ context "when the user has access to the project" do
+ before do
+ project.team << [user, :master]
+ end
+
+ context "when the user is blocked" do
+ before do
+ user.block
+ project.team << [user, :master]
+ end
+
+ it "redirects to the sign in page" do
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when the user isn't blocked" do
+ it "responds with status 200" do
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context "when the user doesn't have access to the project" do
+ it "responds with status 404" do
+ get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+ end
+
+ context "when viewing a note attachment" do
+ let!(:note) { create(:note, :with_attachment) }
+ let(:project) { note.project }
+
+ context "when the project is public" do
+ before do
+ project.update_attribute(:visibility_level, Project::PUBLIC)
+ end
+
+ context "when not signed in" do
+ it "responds with status 200" do
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ it "responds with status 200" do
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context "when the project is private" do
+ before do
+ project.update_attribute(:visibility_level, Project::PRIVATE)
+ end
+
+ context "when not signed in" do
+ it "redirects to the sign in page" do
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when signed in" do
+ before do
+ sign_in(user)
+ end
+
+ context "when the user has access to the project" do
+ before do
+ project.team << [user, :master]
+ end
+
+ context "when the user is blocked" do
+ before do
+ user.block
+ project.team << [user, :master]
+ end
+
+ it "redirects to the sign in page" do
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+
+ context "when the user isn't blocked" do
+ it "responds with status 200" do
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context "when the user doesn't have access to the project" do
+ it "responds with status 404" do
+ get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png"
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
new file mode 100644
index 0000000000..44225c054f
--- /dev/null
+++ b/spec/controllers/users_controller_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe UsersController do
+ let(:user) { create(:user, username: "user1", name: "User 1", email: "user1@gitlab.com") }
+
+ before do
+ sign_in(user)
+ end
+
+ describe "GET #show" do
+ render_views
+
+ it "renders the show template" do
+ get :show, username: user.username
+ expect(response.status).to eq(200)
+ expect(response).to render_template("show")
+ end
+ end
+
+ describe "GET #calendar" do
+ it "renders calendar" do
+ get :calendar, username: user.username
+ expect(response).to render_template("calendar")
+ end
+ end
+end
+
diff --git a/spec/factories.rb b/spec/factories.rb
index a960571206..fc103e5b13 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -5,10 +5,14 @@ FactoryGirl.define do
Faker::Lorem.sentence
end
- sequence :name, aliases: [:file_name] do
+ sequence :name do
Faker::Name.name
end
+ sequence :file_name do
+ Faker::Internet.user_name
+ end
+
sequence(:url) { Faker::Internet.uri('http') }
factory :user, aliases: [:author, :assignee, :owner, :creator] do
@@ -16,7 +20,6 @@ FactoryGirl.define do
name
sequence(:username) { |n| "#{Faker::Internet.user_name}#{n}" }
password "12345678"
- password_confirmation { password }
confirmed_at { Time.now }
confirmation_token { nil }
@@ -24,6 +27,20 @@ FactoryGirl.define do
admin true
end
+ factory :omniauth_user do
+ ignore do
+ extern_uid '123456'
+ provider 'ldapmain'
+ end
+
+ after(:create) do |user, evaluator|
+ user.identities << create(:identity,
+ provider: evaluator.provider,
+ extern_uid: evaluator.extern_uid
+ )
+ end
+ end
+
factory :admin, traits: [:admin]
end
@@ -177,4 +194,9 @@ FactoryGirl.define do
deploy_key
project
end
+
+ factory :identity do
+ provider 'ldapmain'
+ extern_uid 'my-ldap-id'
+ end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 0ae8ea5f87..77cd37c22d 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -18,6 +18,7 @@
# iid :integer
# description :text
# position :integer default(0)
+# locked_at :datetime
#
FactoryGirl.define do
@@ -39,7 +40,7 @@ FactoryGirl.define do
source_branch "master"
target_branch "feature"
- merge_status :can_be_merged
+ merge_status "can_be_merged"
trait :with_diffs do
end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 83d0cc62db..f1c33461b5 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -30,6 +30,7 @@ FactoryGirl.define do
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
factory :note_on_merge_request, traits: [:on_merge_request]
factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff]
+ factory :note_on_project_snippet, traits: [:on_project_snippet]
trait :on_commit do
project factory: :project
@@ -52,6 +53,11 @@ FactoryGirl.define do
noteable_type "Issue"
end
+ trait :on_project_snippet do
+ noteable_id 1
+ noteable_type "Snippet"
+ end
+
trait :with_attachment do
attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 23314b3b1a..0899a7603f 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -24,9 +24,16 @@
# import_status :string(255)
# repository_size :float default(0.0)
# star_count :integer default(0), not null
+# import_type :string(255)
+# import_source :string(255)
+# avatar :string(255)
#
FactoryGirl.define do
+ # Project without repository
+ #
+ # Project does not have bare repository.
+ # Use this factory if you dont need repository in tests
factory :empty_project, class: 'Project' do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
@@ -47,6 +54,20 @@ FactoryGirl.define do
end
end
+ # Project with empty repository
+ #
+ # This is a case when you just created a project
+ # but not pushed any code there yet
+ factory :project_empty_repo, parent: :empty_project do
+ after :create do |project|
+ project.create_repository
+ end
+ end
+
+ # Project with test repository
+ #
+ # Test repository source can be found at
+ # https://gitlab.com/gitlab-org/gitlab-test
factory :project, parent: :empty_project do
path { 'gitlabhq' }
@@ -56,7 +77,19 @@ FactoryGirl.define do
end
factory :redmine_project, parent: :project do
- issues_tracker { "redmine" }
- issues_tracker_id { "project_name_in_redmine" }
+ after :create do |project|
+ project.create_redmine_service(
+ active: true,
+ properties: {
+ 'project_url' => 'http://redmine/projects/project_name_in_redmine',
+ 'issues_url' => "http://redmine/#{project.id}/project_name_in_redmine/:id",
+ 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
+ }
+ )
+ end
+ after :create do |project|
+ project.issues_tracker = 'redmine'
+ project.issues_tracker_id = 'project_name_in_redmine'
+ end
end
end
diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb
index 66bef0761c..c8e218d4d0 100644
--- a/spec/factories_spec.rb
+++ b/spec/factories_spec.rb
@@ -9,7 +9,7 @@ FactoryGirl.factories.map(&:name).each do |factory_name|
next if INVALID_FACTORIES.include?(factory_name)
describe "#{factory_name} factory" do
it 'should be valid' do
- build(factory_name).should be_valid
+ expect(build(factory_name)).to be_valid
end
end
end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index b557567bd0..25862614d2 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -12,15 +12,15 @@ describe "Admin::Hooks", feature: true do
describe "GET /admin/hooks" do
it "should be ok" do
visit admin_root_path
- within ".main-nav" do
+ within ".sidebar-wrapper" do
click_on "Hooks"
end
- current_path.should == admin_hooks_path
+ expect(current_path).to eq(admin_hooks_path)
end
it "should have hooks list" do
visit admin_hooks_path
- page.should have_content(@system_hook.url)
+ expect(page).to have_content(@system_hook.url)
end
end
@@ -33,8 +33,8 @@ describe "Admin::Hooks", feature: true do
end
it "should open new hook popup" do
- current_path.should == admin_hooks_path
- page.should have_content(@url)
+ expect(current_path).to eq(admin_hooks_path)
+ expect(page).to have_content(@url)
end
end
@@ -45,7 +45,7 @@ describe "Admin::Hooks", feature: true do
click_link "Test Hook"
end
- it { current_path.should == admin_hooks_path }
+ it { expect(current_path).to eq(admin_hooks_path) }
end
end
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 3b3d027ab7..101d955d69 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -8,27 +8,27 @@ describe "Admin::Projects", feature: true do
describe "GET /admin/projects" do
before do
- visit admin_projects_path
+ visit admin_namespaces_projects_path
end
it "should be ok" do
- current_path.should == admin_projects_path
+ expect(current_path).to eq(admin_namespaces_projects_path)
end
it "should have projects list" do
- page.should have_content(@project.name)
+ expect(page).to have_content(@project.name)
end
end
describe "GET /admin/projects/:id" do
before do
- visit admin_projects_path
+ visit admin_namespaces_projects_path
click_link "#{@project.name}"
end
it "should have project info" do
- page.should have_content(@project.path)
- page.should have_content(@project.name)
+ expect(page).to have_content(@project.path)
+ expect(page).to have_content(@project.name)
end
end
end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 82da19746f..f97b69713c 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -9,12 +9,12 @@ describe "Admin::Users", feature: true do
end
it "should be ok" do
- current_path.should == admin_users_path
+ expect(current_path).to eq(admin_users_path)
end
it "should have users list" do
- page.should have_content(@user.email)
- page.should have_content(@user.name)
+ expect(page).to have_content(@user.email)
+ expect(page).to have_content(@user.name)
end
end
@@ -32,31 +32,33 @@ describe "Admin::Users", feature: true do
it "should apply defaults to user" do
click_button "Create user"
- user = User.last
- user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
- user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
+ user = User.find_by(username: 'bang')
+ expect(user.projects_limit).
+ to eq(Gitlab.config.gitlab.default_projects_limit)
+ expect(user.can_create_group).
+ to eq(Gitlab.config.gitlab.default_can_create_group)
end
it "should create user with valid data" do
click_button "Create user"
- user = User.last
- user.name.should == "Big Bang"
- user.email.should == "bigbang@mail.com"
+ user = User.find_by(username: 'bang')
+ expect(user.name).to eq('Big Bang')
+ expect(user.email).to eq('bigbang@mail.com')
end
it "should call send mail" do
- Notify.should_receive(:new_user_email)
+ expect(Notify).to receive(:new_user_email)
click_button "Create user"
end
it "should send valid email to user with email & password" do
click_button "Create user"
- user = User.last
+ user = User.find_by(username: 'bang')
email = ActionMailer::Base.deliveries.last
- email.subject.should have_content("Account was created")
- email.text_part.body.should have_content(user.email)
- email.text_part.body.should have_content('password')
+ expect(email.subject).to have_content('Account was created')
+ expect(email.text_part.body).to have_content(user.email)
+ expect(email.text_part.body).to have_content('password')
end
end
@@ -67,8 +69,8 @@ describe "Admin::Users", feature: true do
end
it "should have user info" do
- page.should have_content(@user.email)
- page.should have_content(@user.name)
+ expect(page).to have_content(@user.email)
+ expect(page).to have_content(@user.name)
end
end
@@ -80,8 +82,8 @@ describe "Admin::Users", feature: true do
end
it "should have user edit page" do
- page.should have_content("Name")
- page.should have_content("Password")
+ expect(page).to have_content('Name')
+ expect(page).to have_content('Password')
end
describe "Update user" do
@@ -93,14 +95,14 @@ describe "Admin::Users", feature: true do
end
it "should show page with new data" do
- page.should have_content("bigbang@mail.com")
- page.should have_content("Big Bang")
+ expect(page).to have_content('bigbang@mail.com')
+ expect(page).to have_content('Big Bang')
end
it "should change user entry" do
@simple_user.reload
- @simple_user.name.should == "Big Bang"
- @simple_user.is_admin?.should be_true
+ expect(@simple_user.name).to eq('Big Bang')
+ expect(@simple_user.is_admin?).to be_truthy
end
end
end
diff --git a/spec/features/admin/security_spec.rb b/spec/features/admin/security_spec.rb
index 21b0d8b965..175fa9d464 100644
--- a/spec/features/admin/security_spec.rb
+++ b/spec/features/admin/security_spec.rb
@@ -2,26 +2,26 @@ require 'spec_helper'
describe "Admin::Projects", feature: true do
describe "GET /admin/projects" do
- subject { admin_projects_path }
+ subject { admin_namespaces_projects_path }
- it { should be_allowed_for :admin }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /admin/users" do
subject { admin_users_path }
- it { should be_allowed_for :admin }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /admin/hooks" do
subject { admin_hooks_path }
- it { should be_allowed_for :admin }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index 187f2ffcff..b710cb3c72 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -17,12 +17,13 @@ describe "Dashboard Issues Feed", feature: true do
it "should render atom feed via private token" do
visit issues_dashboard_path(:atom, private_token: user.private_token)
- response_headers['Content-Type'].should have_content("application/atom+xml")
- body.should have_selector("title", text: "#{user.name} issues")
- body.should have_selector("author email", text: issue1.author_email)
- body.should have_selector("entry summary", text: issue1.title)
- body.should have_selector("author email", text: issue2.author_email)
- body.should have_selector("entry summary", text: issue2.title)
+ expect(response_headers['Content-Type']).
+ to have_content('application/atom+xml')
+ expect(body).to have_selector('title', text: "#{user.name} issues")
+ expect(body).to have_selector('author email', text: issue1.author_email)
+ expect(body).to have_selector('entry summary', text: issue1.title)
+ expect(body).to have_selector('author email', text: issue2.author_email)
+ expect(body).to have_selector('entry summary', text: issue2.title)
end
end
end
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index a7f87906b2..ad157d742f 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -2,12 +2,12 @@ require 'spec_helper'
describe "Dashboard Feed", feature: true do
describe "GET /" do
- let!(:user) { create(:user) }
+ let!(:user) { create(:user, name: "Jonh") }
context "projects atom feed via private token" do
it "should render projects atom feed" do
visit dashboard_path(:atom, private_token: user.private_token)
- body.should have_selector("feed title")
+ expect(body).to have_selector('feed title')
end
end
@@ -24,11 +24,12 @@ describe "Dashboard Feed", feature: true do
end
it "should have issue opened event" do
- body.should have_content("#{user.name} opened issue ##{issue.iid}")
+ expect(body).to have_content("#{user.name} opened issue ##{issue.iid}")
end
it "should have issue comment event" do
- body.should have_content("#{user.name} commented on issue ##{issue.iid}")
+ expect(body).
+ to have_content("#{user.name} commented on issue ##{issue.iid}")
end
end
end
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 453dca6909..baa7814e96 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -1,33 +1,36 @@
require 'spec_helper'
-describe "Issues Feed", feature: true do
- describe "GET /issues" do
+describe 'Issues Feed', feature: true do
+ describe 'GET /issues' do
let!(:user) { create(:user) }
let!(:project) { create(:project) }
let!(:issue) { create(:issue, author: user, project: project) }
before { project.team << [user, :developer] }
- context "when authenticated" do
- it "should render atom feed" do
+ context 'when authenticated' do
+ it 'should render atom feed' do
login_with user
- visit project_issues_path(project, :atom)
+ visit namespace_project_issues_path(project.namespace, project, :atom)
- response_headers['Content-Type'].should have_content("application/atom+xml")
- body.should have_selector("title", text: "#{project.name} issues")
- body.should have_selector("author email", text: issue.author_email)
- body.should have_selector("entry summary", text: issue.title)
+ expect(response_headers['Content-Type']).
+ to have_content('application/atom+xml')
+ expect(body).to have_selector('title', text: "#{project.name} issues")
+ expect(body).to have_selector('author email', text: issue.author_email)
+ expect(body).to have_selector('entry summary', text: issue.title)
end
end
- context "when authenticated via private token" do
- it "should render atom feed" do
- visit project_issues_path(project, :atom, private_token: user.private_token)
+ context 'when authenticated via private token' do
+ it 'should render atom feed' do
+ visit namespace_project_issues_path(project.namespace, project, :atom,
+ private_token: user.private_token)
- response_headers['Content-Type'].should have_content("application/atom+xml")
- body.should have_selector("title", text: "#{project.name} issues")
- body.should have_selector("author email", text: issue.author_email)
- body.should have_selector("entry summary", text: issue.title)
+ expect(response_headers['Content-Type']).
+ to have_content('application/atom+xml')
+ expect(body).to have_selector('title', text: "#{project.name} issues")
+ expect(body).to have_selector('author email', text: issue.author_email)
+ expect(body).to have_selector('entry summary', text: issue.title)
end
end
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
new file mode 100644
index 0000000000..770ac04c2c
--- /dev/null
+++ b/spec/features/atom/users_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe "User Feed", feature: true do
+ describe "GET /" do
+ let!(:user) { create(:user) }
+
+ context 'user atom feed via private token' do
+ it "should render user atom feed" do
+ visit user_path(user, :atom, private_token: user.private_token)
+ expect(body).to have_selector('feed title')
+ end
+ end
+
+ context 'feed content' do
+ let(:project) { create(:project) }
+ let(:issue) do
+ create(:issue, project: project,
+ author: user, description: "Houston, we have a bug!\n\n***\n\nI guess.")
+ end
+ let(:note) do
+ create(:note, noteable: issue, author: user,
+ note: 'Bug confirmed :+1:', project: project)
+ end
+ let(:merge_request) do
+ create(:merge_request,
+ title: 'Fix bug', author: user,
+ source_project: project, target_project: project,
+ description: "Here is the fix: ")
+ end
+
+ before do
+ project.team << [user, :master]
+ issue_event(issue, user)
+ note_event(note, user)
+ merge_request_event(merge_request, user)
+ visit user_path(user, :atom, private_token: user.private_token)
+ end
+
+ it 'should have issue opened event' do
+ expect(body).to have_content("#{safe_name} opened issue ##{issue.iid}")
+ end
+
+ it 'should have issue comment event' do
+ expect(body).
+ to have_content("#{safe_name} commented on issue ##{issue.iid}")
+ end
+
+ it 'should have XHTML summaries in issue descriptions' do
+ expect(body).to match /we have a bug!<\/p>\n\n \n\nI guess/
+ end
+
+ it 'should have XHTML summaries in notes' do
+ expect(body).to match /Bug confirmed ]*\/>/
+ end
+
+ it 'should have XHTML summaries in merge request descriptions' do
+ expect(body).to match /Here is the fix: ]*\/>/
+ end
+ end
+ end
+
+ def issue_event(issue, user)
+ EventCreateService.new.open_issue(issue, user)
+ end
+
+ def note_event(note, user)
+ EventCreateService.new.leave_note(note, user)
+ end
+
+ def merge_request_event(request, user)
+ EventCreateService.new.open_mr(request, user)
+ end
+
+ def safe_name
+ html_escape(user.name)
+ end
+end
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 9f50d1c973..fca1a06eb8 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -23,27 +23,27 @@ describe "GitLab Flavored Markdown", feature: true do
describe "for commits" do
it "should render title in commits#index" do
- visit project_commits_path(project, 'master', limit: 1)
+ visit namespace_project_commits_path(project.namespace, project, 'master', limit: 1)
- page.should have_link("##{issue.iid}")
+ expect(page).to have_link("##{issue.iid}")
end
it "should render title in commits#show" do
- visit project_commit_path(project, commit)
+ visit namespace_project_commit_path(project.namespace, project, commit)
- page.should have_link("##{issue.iid}")
+ expect(page).to have_link("##{issue.iid}")
end
it "should render description in commits#show" do
- visit project_commit_path(project, commit)
+ visit namespace_project_commit_path(project.namespace, project, commit)
- page.should have_link("@#{fred.username}")
+ expect(page).to have_link("@#{fred.username}")
end
it "should render title in repositories#branches" do
- visit project_branches_path(project)
+ visit namespace_project_branches_path(project.namespace, project)
- page.should have_link("##{issue.iid}")
+ expect(page).to have_link("##{issue.iid}")
end
end
@@ -62,21 +62,21 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "should render subject in issues#index" do
- visit project_issues_path(project)
+ visit namespace_project_issues_path(project.namespace, project)
- page.should have_link("##{@other_issue.iid}")
+ expect(page).to have_link("##{@other_issue.iid}")
end
it "should render subject in issues#show" do
- visit project_issue_path(project, @issue)
+ visit namespace_project_issue_path(project.namespace, project, @issue)
- page.should have_link("##{@other_issue.iid}")
+ expect(page).to have_link("##{@other_issue.iid}")
end
it "should render details in issues#show" do
- visit project_issue_path(project, @issue)
+ visit namespace_project_issue_path(project.namespace, project, @issue)
- page.should have_link("@#{fred.username}")
+ expect(page).to have_link("@#{fred.username}")
end
end
@@ -87,15 +87,15 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "should render title in merge_requests#index" do
- visit project_merge_requests_path(project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
- page.should have_link("##{issue.iid}")
+ expect(page).to have_link("##{issue.iid}")
end
it "should render title in merge_requests#show" do
- visit project_merge_request_path(project, @merge_request)
+ visit namespace_project_merge_request_path(project.namespace, project, @merge_request)
- page.should have_link("##{issue.iid}")
+ expect(page).to have_link("##{issue.iid}")
end
end
@@ -109,21 +109,21 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "should render title in milestones#index" do
- visit project_milestones_path(project)
+ visit namespace_project_milestones_path(project.namespace, project)
- page.should have_link("##{issue.iid}")
+ expect(page).to have_link("##{issue.iid}")
end
it "should render title in milestones#show" do
- visit project_milestone_path(project, @milestone)
+ visit namespace_project_milestone_path(project.namespace, project, @milestone)
- page.should have_link("##{issue.iid}")
+ expect(page).to have_link("##{issue.iid}")
end
it "should render description in milestones#show" do
- visit project_milestone_path(project, @milestone)
+ visit namespace_project_milestone_path(project.namespace, project, @milestone)
- page.should have_link("@#{fred.username}")
+ expect(page).to have_link("@#{fred.username}")
end
end
end
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
new file mode 100644
index 0000000000..41088ce827
--- /dev/null
+++ b/spec/features/help_pages_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe 'Help Pages', feature: true do
+ describe 'Show SSH page' do
+ before do
+ login_as :user
+ end
+ it 'replace the variable $your_email with the email of the user' do
+ visit help_page_path(category: 'ssh', file: 'README.md')
+ expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"")
+ end
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 26607b0090..a2db57ad90 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
-describe "Issues", feature: true do
+describe 'Issues', feature: true do
+ include SortingHelper
+
let(:project) { create(:project) }
before do
@@ -10,7 +12,7 @@ describe "Issues", feature: true do
project.team << [[@user, user2], :developer]
end
- describe "Edit issue" do
+ describe 'Edit issue' do
let!(:issue) do
create(:issue,
author: @user,
@@ -19,34 +21,38 @@ describe "Issues", feature: true do
end
before do
- visit project_issues_path(project)
+ visit namespace_project_issues_path(project.namespace, project)
click_link "Edit"
end
- it "should open new issue popup" do
- page.should have_content("Issue ##{issue.iid}")
+ it 'should open new issue popup' do
+ expect(page).to have_content("Issue ##{issue.iid}")
end
- describe "fill in" do
+ describe 'fill in' do
before do
- fill_in "issue_title", with: "bug 345"
- fill_in "issue_description", with: "bug description"
+ fill_in 'issue_title', with: 'bug 345'
+ fill_in 'issue_description', with: 'bug description'
end
- it { expect { click_button "Save changes" }.to_not change {Issue.count} }
+ it 'does not change issue count' do
+ expect {
+ click_button 'Save changes'
+ }.to_not change { Issue.count }
+ end
- it "should update issue fields" do
- click_button "Save changes"
+ it 'should update issue fields' do
+ click_button 'Save changes'
- page.should have_content @user.name
- page.should have_content "bug 345"
- page.should have_content project.name
+ expect(page).to have_content @user.name
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content project.name
end
end
end
- describe "Editing issue assignee" do
+ describe 'Editing issue assignee' do
let!(:issue) do
create(:issue,
author: @user,
@@ -54,23 +60,23 @@ describe "Issues", feature: true do
project: project)
end
- it 'allows user to select unasigned', :js => true do
- visit edit_project_issue_path(project, issue)
+ it 'allows user to select unasigned', js: true do
+ visit edit_namespace_project_issue_path(project.namespace, project, issue)
- page.should have_content "Assign to #{@user.name}"
+ expect(page).to have_content "Assign to #{@user.name}"
first('#s2id_issue_assignee_id').click
sleep 2 # wait for ajax stuff to complete
first('.user-result').click
- click_button "Save changes"
+ click_button 'Save changes'
- page.should have_content "Assignee: Select assignee"
- issue.reload.assignee.should be_nil
+ expect(page).to have_content 'Assignee: none'
+ expect(issue.reload.assignee).to be_nil
end
end
- describe "Filter issue" do
+ describe 'Filter issue' do
before do
['foobar', 'barbaz', 'gitlab'].each do |title|
create(:issue,
@@ -80,7 +86,7 @@ describe "Issues", feature: true do
title: title)
end
- @issue = Issue.first # with title 'foobar'
+ @issue = Issue.find_by(title: 'foobar')
@issue.milestone = create(:milestone, project: project)
@issue.assignee = nil
@issue.save
@@ -88,75 +94,79 @@ describe "Issues", feature: true do
let(:issue) { @issue }
- it "should allow filtering by issues with no specified milestone" do
- visit project_issues_path(project, milestone_id: '0')
+ it 'should allow filtering by issues with no specified milestone' do
+ visit namespace_project_issues_path(project.namespace, project, milestone_id: '0')
- page.should_not have_content 'foobar'
- page.should have_content 'barbaz'
- page.should have_content 'gitlab'
+ expect(page).not_to have_content 'foobar'
+ expect(page).to have_content 'barbaz'
+ expect(page).to have_content 'gitlab'
end
- it "should allow filtering by a specified milestone" do
- visit project_issues_path(project, milestone_id: issue.milestone.id)
+ it 'should allow filtering by a specified milestone' do
+ visit namespace_project_issues_path(project.namespace, project, milestone_id: issue.milestone.id)
- page.should have_content 'foobar'
- page.should_not have_content 'barbaz'
- page.should_not have_content 'gitlab'
+ expect(page).to have_content 'foobar'
+ expect(page).not_to have_content 'barbaz'
+ expect(page).not_to have_content 'gitlab'
end
- it "should allow filtering by issues with no specified assignee" do
- visit project_issues_path(project, assignee_id: '0')
+ it 'should allow filtering by issues with no specified assignee' do
+ visit namespace_project_issues_path(project.namespace, project, assignee_id: '0')
- page.should have_content 'foobar'
- page.should_not have_content 'barbaz'
- page.should_not have_content 'gitlab'
+ expect(page).to have_content 'foobar'
+ expect(page).not_to have_content 'barbaz'
+ expect(page).not_to have_content 'gitlab'
end
- it "should allow filtering by a specified assignee" do
- visit project_issues_path(project, assignee_id: @user.id)
+ it 'should allow filtering by a specified assignee' do
+ visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id)
- page.should_not have_content 'foobar'
- page.should have_content 'barbaz'
- page.should have_content 'gitlab'
+ expect(page).not_to have_content 'foobar'
+ expect(page).to have_content 'barbaz'
+ expect(page).to have_content 'gitlab'
end
end
describe 'filter issue' do
titles = ['foo','bar','baz']
titles.each_with_index do |title, index|
- let!(title.to_sym) { create(:issue, title: title, project: project, created_at: Time.now - (index * 60)) }
+ let!(title.to_sym) do
+ create(:issue, title: title,
+ project: project,
+ created_at: Time.now - (index * 60))
+ end
end
let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') }
let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
it 'sorts by newest' do
- visit project_issues_path(project, sort: 'newest')
+ visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_created)
- first_issue.should include("foo")
- last_issue.should include("baz")
+ expect(first_issue).to include('foo')
+ expect(last_issue).to include('baz')
end
it 'sorts by oldest' do
- visit project_issues_path(project, sort: 'oldest')
+ visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_created)
- first_issue.should include("baz")
- last_issue.should include("foo")
+ expect(first_issue).to include('baz')
+ expect(last_issue).to include('foo')
end
it 'sorts by most recently updated' do
baz.updated_at = Time.now + 100
baz.save
- visit project_issues_path(project, sort: 'recently_updated')
+ visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_updated)
- first_issue.should include("baz")
+ expect(first_issue).to include('baz')
end
it 'sorts by least recently updated' do
baz.updated_at = Time.now - 100
baz.save
- visit project_issues_path(project, sort: 'last_updated')
+ visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_updated)
- first_issue.should include("baz")
+ expect(first_issue).to include('baz')
end
describe 'sorting by milestone' do
@@ -168,15 +178,15 @@ describe "Issues", feature: true do
end
it 'sorts by recently due milestone' do
- visit project_issues_path(project, sort: 'milestone_due_soon')
+ visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_soon)
- first_issue.should include("foo")
+ expect(first_issue).to include('foo')
end
it 'sorts by least recently due milestone' do
- visit project_issues_path(project, sort: 'milestone_due_later')
+ visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_later)
- first_issue.should include("bar")
+ expect(first_issue).to include('bar')
end
end
@@ -191,11 +201,13 @@ describe "Issues", feature: true do
end
it 'sorts with a filter applied' do
- visit project_issues_path(project, sort: 'oldest', assignee_id: user2.id)
+ visit namespace_project_issues_path(project.namespace, project,
+ sort: sort_value_oldest_created,
+ assignee_id: user2.id)
- first_issue.should include("bar")
- last_issue.should include("foo")
- page.should_not have_content 'baz'
+ expect(first_issue).to include('bar')
+ expect(last_issue).to include('foo')
+ expect(page).not_to have_content 'baz'
end
end
end
@@ -206,13 +218,15 @@ describe "Issues", feature: true do
context 'by autorized user' do
it 'with dropdown menu' do
- visit project_issue_path(project, issue)
+ visit namespace_project_issue_path(project.namespace, project, issue)
- find('.edit-issue.inline-update #issue_assignee_id').set project.team.members.first.id
+ find('.edit-issue.inline-update #issue_assignee_id').
+ set project.team.members.first.id
click_button 'Update Issue'
- page.should have_content "Assignee:"
- has_select?('issue_assignee_id', :selected => project.team.members.first.name)
+ expect(page).to have_content 'Assignee:'
+ has_select?('issue_assignee_id',
+ selected: project.team.members.first.name)
end
end
@@ -226,12 +240,12 @@ describe "Issues", feature: true do
issue.save
end
- it "shows assignee text", js: true do
+ it 'shows assignee text', js: true do
logout
login_with guest
- visit project_issue_path(project, issue)
- page.should have_content issue.assignee.name
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ expect(page).to have_content issue.assignee.name
end
end
end
@@ -243,13 +257,15 @@ describe "Issues", feature: true do
context 'by authorized user' do
it 'with dropdown menu' do
- visit project_issue_path(project, issue)
+ visit namespace_project_issue_path(project.namespace, project, issue)
- find('.edit-issue.inline-update').select(milestone.title, from: 'issue_milestone_id')
+ find('.edit-issue.inline-update').
+ select(milestone.title, from: 'issue_milestone_id')
click_button 'Update Issue'
- page.should have_content "Milestone changed to #{milestone.title}"
- has_select?('issue_assignee_id', :selected => milestone.title)
+ expect(page).to have_content "Milestone changed to #{milestone.title}"
+ expect(page).to have_content "Milestone: #{milestone.title}"
+ has_select?('issue_assignee_id', selected: milestone.title)
end
end
@@ -262,12 +278,12 @@ describe "Issues", feature: true do
issue.save
end
- it "shows milestone text", js: true do
+ it 'shows milestone text', js: true do
logout
login_with guest
- visit project_issue_path(project, issue)
- page.should have_content milestone.title
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ expect(page).to have_content milestone.title
end
end
@@ -280,25 +296,25 @@ describe "Issues", feature: true do
end
it 'allows user to remove assignee', :js => true do
- visit project_issue_path(project, issue)
- page.should have_content "Assignee: #{user2.name}"
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ expect(page).to have_content "Assignee: #{user2.name}"
first('#s2id_issue_assignee_id').click
sleep 2 # wait for ajax stuff to complete
first('.user-result').click
- page.should have_content "Assignee: Unassigned"
+ expect(page).to have_content 'Assignee: none'
sleep 2 # wait for ajax stuff to complete
- issue.reload.assignee.should be_nil
+ expect(issue.reload.assignee).to be_nil
end
end
end
def first_issue
- all("ul.issues-list li").first.text
+ all('ul.issues-list li').first.text
end
def last_issue
- all("ul.issues-list li").last.text
+ all('ul.issues-list li').last.text
end
end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 92f3a6c092..c47368b1fd 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -3,197 +3,213 @@ require 'spec_helper'
describe 'Comments' do
include RepoHelpers
- describe "On a merge request", js: true, feature: true do
+ describe 'On a merge request', js: true, feature: true do
let!(:merge_request) { create(:merge_request) }
let!(:project) { merge_request.source_project }
- let!(:note) { create(:note_on_merge_request, :with_attachment, project: project) }
+ let!(:note) do
+ create(:note_on_merge_request, :with_attachment, project: project)
+ end
before do
login_as :admin
- visit project_merge_request_path(project, merge_request)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
subject { page }
- describe "the note form" do
+ describe 'the note form' do
it 'should be valid' do
- should have_css(".js-main-target-form", visible: true, count: 1)
- find(".js-main-target-form input[type=submit]").value.should == "Add Comment"
- within(".js-main-target-form") { should_not have_link("Cancel") }
- within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) }
+ is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
+ expect(find('.js-main-target-form input[type=submit]').value).
+ to eq('Add Comment')
+ within('.js-main-target-form') do
+ expect(page).not_to have_link('Cancel')
+ end
end
- describe "with text" do
+ describe 'with text' do
before do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "This is awesome"
+ within('.js-main-target-form') do
+ fill_in 'note[note]', with: 'This is awesome'
end
end
it 'should have enable submit button and preview button' do
- within(".js-main-target-form") { should_not have_css(".js-comment-button[disabled]") }
- within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: true) }
+ within('.js-main-target-form') do
+ expect(page).not_to have_css('.js-comment-button[disabled]')
+ expect(page).to have_css('.js-md-preview-button', visible: true)
+ end
end
end
end
- describe "when posting a note" do
+ describe 'when posting a note' do
before do
- within(".js-main-target-form") do
- fill_in "note[note]", with: "This is awsome!"
- find(".js-note-preview-button").trigger("click")
- click_button "Add Comment"
+ within('.js-main-target-form') do
+ fill_in 'note[note]', with: 'This is awsome!'
+ find('.js-md-preview-button').click
+ click_button 'Add Comment'
end
end
it 'should be added and form reset' do
- should have_content("This is awsome!")
- within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") }
- within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) }
- within(".js-main-target-form") { should have_css(".js-note-text", visible: true) }
+ is_expected.to have_content('This is awsome!')
+ within('.js-main-target-form') do
+ expect(page).to have_no_field('note[note]', with: 'This is awesome!')
+ expect(page).to have_css('.js-md-preview', visible: :hidden)
+ end
+ within('.js-main-target-form') do
+ is_expected.to have_css('.js-note-text', visible: true)
+ end
end
end
- describe "when editing a note", js: true do
- it "should contain the hidden edit form" do
- within("#note_#{note.id}") { should have_css(".note-edit-form", visible: false) }
+ describe 'when editing a note', js: true do
+ it 'should contain the hidden edit form' do
+ within("#note_#{note.id}") do
+ is_expected.to have_css('.note-edit-form', visible: false)
+ end
end
- describe "editing the note" do
+ describe 'editing the note' do
before do
find('.note').hover
find(".js-note-edit").click
end
- it "should show the note edit form and hide the note body" do
+ it 'should show the note edit form and hide the note body' do
within("#note_#{note.id}") do
- find(".note-edit-form", visible: true).should be_visible
- find(".note-text", visible: false).should_not be_visible
+ expect(find('.current-note-edit-form', visible: true)).to be_visible
+ expect(find('.note-edit-form', visible: true)).to be_visible
+ expect(find(:css, '.note-body > .note-text', visible: false)).not_to be_visible
end
end
- it "should reset the edit note form textarea with the original content of the note if cancelled" do
- find('.note').hover
- find(".js-note-edit").click
+ # TODO: fix after 7.7 release
+ #it "should reset the edit note form textarea with the original content of the note if cancelled" do
+ #within(".current-note-edit-form") do
+ #fill_in "note[note]", with: "Some new content"
+ #find(".btn-cancel").click
+ #find(".js-note-text", visible: false).text.should == note.note
+ #end
+ #end
- within(".note-edit-form") do
- fill_in "note[note]", with: "Some new content"
- find(".btn-cancel").click
- find(".js-note-text", visible: false).text.should == note.note
- end
- end
-
- it "appends the edited at time to the note" do
- find('.note').hover
- find(".js-note-edit").click
-
- within(".note-edit-form") do
- fill_in "note[note]", with: "Some new content"
- find(".btn-save").click
+ it 'appends the edited at time to the note' do
+ within('.current-note-edit-form') do
+ fill_in 'note[note]', with: 'Some new content'
+ find('.btn-save').click
end
within("#note_#{note.id}") do
- should have_css(".note-last-update small")
- find(".note-last-update small").text.should match(/Edited less than a minute ago/)
+ is_expected.to have_css('.note_edited_ago')
+ expect(find('.note_edited_ago').text).
+ to match(/less than a minute ago/)
end
end
end
- describe "deleting an attachment" do
+ describe 'deleting an attachment' do
before do
find('.note').hover
- find(".js-note-edit").click
+ find('.js-note-edit').click
end
- it "shows the delete link" do
- within(".note-attachment") do
- should have_css(".js-note-attachment-delete")
+ it 'shows the delete link' do
+ within('.note-attachment') do
+ is_expected.to have_css('.js-note-attachment-delete')
end
end
- it "removes the attachment div and resets the edit form" do
- find(".js-note-attachment-delete").click
- should_not have_css(".note-attachment")
- find(".note-edit-form", visible: false).should_not be_visible
+ it 'removes the attachment div and resets the edit form' do
+ find('.js-note-attachment-delete').click
+ is_expected.not_to have_css('.note-attachment')
+ expect(find('.current-note-edit-form', visible: false)).
+ not_to be_visible
end
end
end
end
- describe "On a merge request diff", js: true, feature: true do
+ describe 'On a merge request diff', js: true, feature: true do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
before do
login_as :admin
- visit diffs_project_merge_request_path(project, merge_request)
+ visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
end
subject { page }
- describe "when adding a note" do
+ describe 'when adding a note' do
before do
click_diff_line
end
- describe "the notes holder" do
- it { should have_css(".js-temp-notes-holder") }
+ describe 'the notes holder' do
+ it { is_expected.to have_css('.js-temp-notes-holder') }
- it { within(".js-temp-notes-holder") { should have_css(".new_note") } }
+ it 'has .new_note css class' do
+ within('.js-temp-notes-holder') do
+ expect(subject).to have_css('.new_note')
+ end
+ end
end
- describe "the note form" do
+ describe 'the note form' do
it "shouldn't add a second form for same row" do
click_diff_line
- should have_css("tr[id='#{line_code}'] + .js-temp-notes-holder form", count: 1)
+ is_expected.
+ to have_css("tr[id='#{line_code}'] + .js-temp-notes-holder form",
+ count: 1)
end
- it "should be removed when canceled" do
+ it 'should be removed when canceled' do
within(".diff-file form[rel$='#{line_code}']") do
- find(".js-close-discussion-note-form").trigger("click")
+ find('.js-close-discussion-note-form').trigger('click')
end
- should have_no_css(".js-temp-notes-holder")
+ is_expected.to have_no_css('.js-temp-notes-holder')
end
end
end
- describe "with muliple note forms" do
+ describe 'with muliple note forms' do
before do
click_diff_line
click_diff_line(line_code_2)
end
- it { should have_css(".js-temp-notes-holder", count: 2) }
+ it { is_expected.to have_css('.js-temp-notes-holder', count: 2) }
- describe "previewing them separately" do
+ describe 'previewing them separately' do
before do
# add two separate texts and trigger previews on both
within("tr[id='#{line_code}'] + .js-temp-notes-holder") do
- fill_in "note[note]", with: "One comment on line 7"
- find(".js-note-preview-button").trigger("click")
+ fill_in 'note[note]', with: 'One comment on line 7'
+ find('.js-md-preview-button').click
end
within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
- fill_in "note[note]", with: "Another comment on line 10"
- find(".js-note-preview-button").trigger("click")
+ fill_in 'note[note]', with: 'Another comment on line 10'
+ find('.js-md-preview-button').click
end
end
end
- describe "posting a note" do
+ describe 'posting a note' do
before do
within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do
- fill_in "note[note]", with: "Another comment on line 10"
- click_button("Add Comment")
+ fill_in 'note[note]', with: 'Another comment on line 10'
+ click_button('Add Comment')
end
end
it 'should be added as discussion' do
- should have_content("Another comment on line 10")
- should have_css(".notes_holder")
- should have_css(".notes_holder .note", count: 1)
- should have_button('Reply')
+ is_expected.to have_content('Another comment on line 10')
+ is_expected.to have_css('.notes_holder')
+ is_expected.to have_css('.notes_holder .note', count: 1)
+ is_expected.to have_button('Reply')
end
end
end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index bdf7b59114..3d36a3c02d 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -1,35 +1,35 @@
require 'spec_helper'
-describe "Profile account page", feature: true do
+describe 'Profile account page', feature: true do
let(:user) { create(:user) }
before do
login_as :user
end
- describe "when signup is enabled" do
+ describe 'when signup is enabled' do
before do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
+ ApplicationSetting.any_instance.stub(signup_enabled?: true)
visit profile_account_path
end
- it { page.should have_content("Remove account") }
+ it { expect(page).to have_content('Remove account') }
- it "should delete the account" do
- expect { click_link "Delete account" }.to change {User.count}.by(-1)
- current_path.should == new_user_session_path
+ it 'should delete the account' do
+ expect { click_link 'Delete account' }.to change { User.count }.by(-1)
+ expect(current_path).to eq(new_user_session_path)
end
end
- describe "when signup is disabled" do
+ describe 'when signup is disabled' do
before do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(false)
+ ApplicationSetting.any_instance.stub(signup_enabled?: false)
visit profile_account_path
end
- it "should not have option to remove account" do
- page.should_not have_content("Remove account")
- current_path.should == profile_account_path
+ it 'should not have option to remove account' do
+ expect(page).not_to have_content('Remove account')
+ expect(current_path).to eq(profile_account_path)
end
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 98ba5a47ee..cae11be7cd 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,21 +1,32 @@
require 'spec_helper'
-describe "Projects", feature: true do
+describe "Projects", feature: true, js: true do
before { login_as :user }
describe "DELETE /projects/:id" do
before do
@project = create(:project, namespace: @user.namespace)
@project.team << [@user, :master]
- visit edit_project_path(@project)
+ visit edit_namespace_project_path(@project.namespace, @project)
end
- it "should be correct path", js: true do
- expect {
- click_link "Remove project"
- fill_in 'confirm_name_input', with: @project.path
- click_button 'Confirm'
- }.to change {Project.count}.by(-1)
+ it "should remove project" do
+ expect { remove_project }.to change {Project.count}.by(-1)
+ end
+
+ it 'should delete the project from disk' do
+ expect(GitlabShellWorker).to(
+ receive(:perform_async).with(:remove_repository,
+ /#{@project.path_with_namespace}/)
+ ).twice
+
+ remove_project
end
end
+
+ def remove_project
+ click_link "Remove project"
+ fill_in 'confirm_name_input', with: @project.path
+ click_button 'Confirm'
+ end
end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index cce9f06cb6..73987739a7 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -14,7 +14,7 @@ describe "Search", feature: true do
end
it "should show project in search results" do
- page.should have_content @project.name
+ expect(page).to have_content @project.name
end
end
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
index 1cca82cef6..67238e3ab7 100644
--- a/spec/features/security/dashboard_access_spec.rb
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -4,52 +4,60 @@ describe "Dashboard access", feature: true do
describe "GET /dashboard" do
subject { dashboard_path }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /dashboard/issues" do
subject { issues_dashboard_path }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /dashboard/merge_requests" do
subject { merge_requests_dashboard_path }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
- describe "GET /dashboard/projects" do
- subject { projects_dashboard_path }
+ describe "GET /dashboard/projects/starred" do
+ subject { starred_dashboard_projects_path }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /help" do
subject { help_path }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /projects/new" do
- it { new_project_path.should be_allowed_for :admin }
- it { new_project_path.should be_allowed_for :user }
- it { new_project_path.should be_denied_for :visitor }
+ it { expect(new_project_path).to be_allowed_for :admin }
+ it { expect(new_project_path).to be_allowed_for :user }
+ it { expect(new_project_path).to be_denied_for :visitor }
end
describe "GET /groups/new" do
- it { new_group_path.should be_allowed_for :admin }
- it { new_group_path.should be_allowed_for :user }
- it { new_group_path.should be_denied_for :visitor }
+ it { expect(new_group_path).to be_allowed_for :admin }
+ it { expect(new_group_path).to be_allowed_for :user }
+ it { expect(new_group_path).to be_denied_for :visitor }
+ end
+
+ describe "GET /profile/groups" do
+ subject { dashboard_groups_path }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/group/group_access_spec.rb b/spec/features/security/group/group_access_spec.rb
index 44de499e6d..6379314945 100644
--- a/spec/features/security/group/group_access_spec.rb
+++ b/spec/features/security/group/group_access_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
describe "Group access", feature: true do
describe "GET /projects/new" do
- it { new_group_path.should be_allowed_for :admin }
- it { new_group_path.should be_allowed_for :user }
- it { new_group_path.should be_denied_for :visitor }
+ it { expect(new_group_path).to be_allowed_for :admin }
+ it { expect(new_group_path).to be_allowed_for :user }
+ it { expect(new_group_path).to be_denied_for :visitor }
end
describe "Group" do
@@ -26,73 +26,73 @@ describe "Group access", feature: true do
describe "GET /groups/:path" do
subject { group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /groups/:path/issues" do
subject { issues_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /groups/:path/merge_requests" do
subject { merge_requests_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
- describe "GET /groups/:path/members" do
- subject { members_group_path(group) }
+ describe "GET /groups/:path/group_members" do
+ subject { group_group_members_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /groups/:path/edit" do
subject { edit_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_denied_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_denied_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /groups/:path/projects" do
subject { projects_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_denied_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_denied_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
end
diff --git a/spec/features/security/group/internal_group_access_spec.rb b/spec/features/security/group/internal_group_access_spec.rb
index da5c6eb4e9..d17a7412e4 100644
--- a/spec/features/security/group/internal_group_access_spec.rb
+++ b/spec/features/security/group/internal_group_access_spec.rb
@@ -22,61 +22,61 @@ describe "Group with internal project access", feature: true do
describe "GET /groups/:path" do
subject { group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /groups/:path/issues" do
subject { issues_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /groups/:path/merge_requests" do
subject { merge_requests_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
- describe "GET /groups/:path/members" do
- subject { members_group_path(group) }
+ describe "GET /groups/:path/group_members" do
+ subject { group_group_members_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /groups/:path/edit" do
subject { edit_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_denied_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_denied_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
end
diff --git a/spec/features/security/group/mixed_group_access_spec.rb b/spec/features/security/group/mixed_group_access_spec.rb
index c9889d9959..b3db7b5dea 100644
--- a/spec/features/security/group/mixed_group_access_spec.rb
+++ b/spec/features/security/group/mixed_group_access_spec.rb
@@ -23,61 +23,61 @@ describe "Group access", feature: true do
describe "GET /groups/:path" do
subject { group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /groups/:path/issues" do
subject { issues_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /groups/:path/merge_requests" do
subject { merge_requests_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
- describe "GET /groups/:path/members" do
- subject { members_group_path(group) }
+ describe "GET /groups/:path/group_members" do
+ subject { group_group_members_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /groups/:path/edit" do
subject { edit_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_denied_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_denied_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
end
diff --git a/spec/features/security/group/public_group_access_spec.rb b/spec/features/security/group/public_group_access_spec.rb
index 2e76ab154f..c16f0c0d1e 100644
--- a/spec/features/security/group/public_group_access_spec.rb
+++ b/spec/features/security/group/public_group_access_spec.rb
@@ -22,61 +22,61 @@ describe "Group with public project access", feature: true do
describe "GET /groups/:path" do
subject { group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /groups/:path/issues" do
subject { issues_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /groups/:path/merge_requests" do
subject { merge_requests_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
- describe "GET /groups/:path/members" do
- subject { members_group_path(group) }
+ describe "GET /groups/:path/group_members" do
+ subject { group_group_members_path(group) }
- it { should be_allowed_for owner }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /groups/:path/edit" do
subject { edit_group_path(group) }
- it { should be_allowed_for owner }
- it { should be_denied_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_denied_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
end
diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index 4efc0ffdcd..2512a9c0e3 100644
--- a/spec/features/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -1,76 +1,65 @@
require 'spec_helper'
-describe "Users Security", feature: true do
- describe "Project" do
- before do
- @u1 = create(:user)
- end
+describe "Profile access", feature: true do
+ before do
+ @u1 = create(:user)
+ end
- describe "GET /login" do
- it { new_user_session_path.should_not be_404_for :visitor }
- end
+ describe "GET /login" do
+ it { expect(new_user_session_path).not_to be_404_for :visitor }
+ end
- describe "GET /profile/keys" do
- subject { profile_keys_path }
+ describe "GET /profile/keys" do
+ subject { profile_keys_path }
- it { should be_allowed_for @u1 }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
- end
+ it { is_expected.to be_allowed_for @u1 }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
- describe "GET /profile" do
- subject { profile_path }
+ describe "GET /profile" do
+ subject { profile_path }
- it { should be_allowed_for @u1 }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
- end
+ it { is_expected.to be_allowed_for @u1 }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
- describe "GET /profile/account" do
- subject { profile_account_path }
+ describe "GET /profile/account" do
+ subject { profile_account_path }
- it { should be_allowed_for @u1 }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
- end
+ it { is_expected.to be_allowed_for @u1 }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
- describe "GET /profile/design" do
- subject { design_profile_path }
+ describe "GET /profile/design" do
+ subject { design_profile_path }
- it { should be_allowed_for @u1 }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
- end
+ it { is_expected.to be_allowed_for @u1 }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
- describe "GET /profile/history" do
- subject { history_profile_path }
+ describe "GET /profile/history" do
+ subject { history_profile_path }
- it { should be_allowed_for @u1 }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
- end
+ it { is_expected.to be_allowed_for @u1 }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
+ end
- describe "GET /profile/notifications" do
- subject { profile_notifications_path }
+ describe "GET /profile/notifications" do
+ subject { profile_notifications_path }
- it { should be_allowed_for @u1 }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
- end
-
- describe "GET /profile/groups" do
- subject { profile_groups_path }
-
- it { should be_allowed_for @u1 }
- it { should be_allowed_for :admin }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
- end
+ it { is_expected.to be_allowed_for @u1 }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 598d554a94..8d1bfd2522 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -18,207 +18,210 @@ describe "Internal Project Access", feature: true do
describe "Project should be internal" do
subject { project }
- its(:internal?) { should be_true }
+ describe '#internal?' do
+ subject { super().internal? }
+ it { is_expected.to be_truthy }
+ end
end
describe "GET /:project_path" do
- subject { project_path(project) }
+ subject { namespace_project_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/tree/master" do
- subject { project_tree_path(project, project.repository.root_ref) }
+ subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/commits/master" do
- subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
+ subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/commit/:sha" do
- subject { project_commit_path(project, project.repository.commit) }
+ subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/compare" do
- subject { project_compare_index_path(project) }
+ subject { namespace_project_compare_index_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
- describe "GET /:project_path/team" do
- subject { project_team_index_path(project) }
+ describe "GET /:project_path/project_members" do
+ subject { namespace_project_project_members_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/blob" do
before do
commit = project.repository.commit
path = '.gitignore'
- @blob_path = project_blob_path(project, File.join(commit.id, path))
+ @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path))
end
- it { @blob_path.should be_allowed_for master }
- it { @blob_path.should be_allowed_for reporter }
- it { @blob_path.should be_allowed_for :admin }
- it { @blob_path.should be_allowed_for guest }
- it { @blob_path.should be_allowed_for :user }
- it { @blob_path.should be_denied_for :visitor }
+ it { expect(@blob_path).to be_allowed_for master }
+ it { expect(@blob_path).to be_allowed_for reporter }
+ it { expect(@blob_path).to be_allowed_for :admin }
+ it { expect(@blob_path).to be_allowed_for guest }
+ it { expect(@blob_path).to be_allowed_for :user }
+ it { expect(@blob_path).to be_denied_for :visitor }
end
describe "GET /:project_path/edit" do
- subject { edit_project_path(project) }
+ subject { edit_namespace_project_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/deploy_keys" do
- subject { project_deploy_keys_path(project) }
+ subject { namespace_project_deploy_keys_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/issues" do
- subject { project_issues_path(project) }
+ subject { namespace_project_issues_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/snippets" do
- subject { project_snippets_path(project) }
+ subject { namespace_project_snippets_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/snippets/new" do
- subject { new_project_snippet_path(project) }
+ subject { new_namespace_project_snippet_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/merge_requests" do
- subject { project_merge_requests_path(project) }
+ subject { namespace_project_merge_requests_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/merge_requests/new" do
- subject { new_project_merge_request_path(project) }
+ subject { new_namespace_project_merge_request_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/branches" do
- subject { project_branches_path(project) }
+ subject { namespace_project_branches_path(project.namespace, project) }
before do
# Speed increase
- Project.any_instance.stub(:branches).and_return([])
+ allow_any_instance_of(Project).to receive(:branches).and_return([])
end
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/tags" do
- subject { project_tags_path(project) }
+ subject { namespace_project_tags_path(project.namespace, project) }
before do
# Speed increase
- Project.any_instance.stub(:tags).and_return([])
+ allow_any_instance_of(Project).to receive(:tags).and_return([])
end
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/hooks" do
- subject { project_hooks_path(project) }
+ subject { namespace_project_hooks_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index b1d4c79e05..9021ff3318 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -18,185 +18,188 @@ describe "Private Project Access", feature: true do
describe "Project should be private" do
subject { project }
- its(:private?) { should be_true }
+ describe '#private?' do
+ subject { super().private? }
+ it { is_expected.to be_truthy }
+ end
end
describe "GET /:project_path" do
- subject { project_path(project) }
+ subject { namespace_project_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/tree/master" do
- subject { project_tree_path(project, project.repository.root_ref) }
+ subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/commits/master" do
- subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
+ subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/commit/:sha" do
- subject { project_commit_path(project, project.repository.commit) }
+ subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/compare" do
- subject { project_compare_index_path(project) }
+ subject { namespace_project_compare_index_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
- describe "GET /:project_path/team" do
- subject { project_team_index_path(project) }
+ describe "GET /:project_path/project_members" do
+ subject { namespace_project_project_members_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/blob" do
before do
commit = project.repository.commit
path = '.gitignore'
- @blob_path = project_blob_path(project, File.join(commit.id, path))
+ @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path))
end
- it { @blob_path.should be_allowed_for master }
- it { @blob_path.should be_allowed_for reporter }
- it { @blob_path.should be_allowed_for :admin }
- it { @blob_path.should be_denied_for guest }
- it { @blob_path.should be_denied_for :user }
- it { @blob_path.should be_denied_for :visitor }
+ it { expect(@blob_path).to be_allowed_for master }
+ it { expect(@blob_path).to be_allowed_for reporter }
+ it { expect(@blob_path).to be_allowed_for :admin }
+ it { expect(@blob_path).to be_denied_for guest }
+ it { expect(@blob_path).to be_denied_for :user }
+ it { expect(@blob_path).to be_denied_for :visitor }
end
describe "GET /:project_path/edit" do
- subject { edit_project_path(project) }
+ subject { edit_namespace_project_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/deploy_keys" do
- subject { project_deploy_keys_path(project) }
+ subject { namespace_project_deploy_keys_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/issues" do
- subject { project_issues_path(project) }
+ subject { namespace_project_issues_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/snippets" do
- subject { project_snippets_path(project) }
+ subject { namespace_project_snippets_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/merge_requests" do
- subject { project_merge_requests_path(project) }
+ subject { namespace_project_merge_requests_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/branches" do
- subject { project_branches_path(project) }
+ subject { namespace_project_branches_path(project.namespace, project) }
before do
# Speed increase
- Project.any_instance.stub(:branches).and_return([])
+ allow_any_instance_of(Project).to receive(:branches).and_return([])
end
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/tags" do
- subject { project_tags_path(project) }
+ subject { namespace_project_tags_path(project.namespace, project) }
before do
# Speed increase
- Project.any_instance.stub(:tags).and_return([])
+ allow_any_instance_of(Project).to receive(:tags).and_return([])
end
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/hooks" do
- subject { project_hooks_path(project) }
+ subject { namespace_project_hooks_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index a4c8a2be25..6ec190ed77 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -23,207 +23,210 @@ describe "Public Project Access", feature: true do
describe "Project should be public" do
subject { project }
- its(:public?) { should be_true }
+ describe '#public?' do
+ subject { super().public? }
+ it { is_expected.to be_truthy }
+ end
end
describe "GET /:project_path" do
- subject { project_path(project) }
+ subject { namespace_project_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/tree/master" do
- subject { project_tree_path(project, project.repository.root_ref) }
+ subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/commits/master" do
- subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
+ subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/commit/:sha" do
- subject { project_commit_path(project, project.repository.commit) }
+ subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/compare" do
- subject { project_compare_index_path(project) }
+ subject { namespace_project_compare_index_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
- describe "GET /:project_path/team" do
- subject { project_team_index_path(project) }
+ describe "GET /:project_path/project_members" do
+ subject { namespace_project_project_members_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/blob" do
before do
commit = project.repository.commit
path = '.gitignore'
- @blob_path = project_blob_path(project, File.join(commit.id, path))
+ @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path))
end
- it { @blob_path.should be_allowed_for master }
- it { @blob_path.should be_allowed_for reporter }
- it { @blob_path.should be_allowed_for :admin }
- it { @blob_path.should be_allowed_for guest }
- it { @blob_path.should be_allowed_for :user }
- it { @blob_path.should be_allowed_for :visitor }
+ it { expect(@blob_path).to be_allowed_for master }
+ it { expect(@blob_path).to be_allowed_for reporter }
+ it { expect(@blob_path).to be_allowed_for :admin }
+ it { expect(@blob_path).to be_allowed_for guest }
+ it { expect(@blob_path).to be_allowed_for :user }
+ it { expect(@blob_path).to be_allowed_for :visitor }
end
describe "GET /:project_path/edit" do
- subject { edit_project_path(project) }
+ subject { edit_namespace_project_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/deploy_keys" do
- subject { project_deploy_keys_path(project) }
+ subject { namespace_project_deploy_keys_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/issues" do
- subject { project_issues_path(project) }
+ subject { namespace_project_issues_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/snippets" do
- subject { project_snippets_path(project) }
+ subject { namespace_project_snippets_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/snippets/new" do
- subject { new_project_snippet_path(project) }
+ subject { new_namespace_project_snippet_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/merge_requests" do
- subject { project_merge_requests_path(project) }
+ subject { namespace_project_merge_requests_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/merge_requests/new" do
- subject { new_project_merge_request_path(project) }
+ subject { new_namespace_project_merge_request_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/branches" do
- subject { project_branches_path(project) }
+ subject { namespace_project_branches_path(project.namespace, project) }
before do
# Speed increase
- Project.any_instance.stub(:branches).and_return([])
+ allow_any_instance_of(Project).to receive(:branches).and_return([])
end
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/tags" do
- subject { project_tags_path(project) }
+ subject { namespace_project_tags_path(project.namespace, project) }
before do
# Speed increase
- Project.any_instance.stub(:tags).and_return([])
+ allow_any_instance_of(Project).to receive(:tags).and_return([])
end
- it { should be_allowed_for master }
- it { should be_allowed_for reporter }
- it { should be_allowed_for :admin }
- it { should be_allowed_for guest }
- it { should be_allowed_for :user }
- it { should be_allowed_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /:project_path/hooks" do
- subject { project_hooks_path(project) }
+ subject { namespace_project_hooks_path(project.namespace, project) }
- it { should be_allowed_for master }
- it { should be_denied_for reporter }
- it { should be_allowed_for :admin }
- it { should be_denied_for guest }
- it { should be_denied_for :user }
- it { should be_denied_for :visitor }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :visitor }
end
end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index 7b831c4861..21a3a4bf93 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -1,19 +1,14 @@
require 'spec_helper'
describe 'Users', feature: true do
- describe "GET /users/sign_up" do
- before do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
- end
-
+ describe "GET /users/sign_in" do
it "should create a new user account" do
- visit new_user_registration_path
+ visit new_user_session_path
fill_in "user_name", with: "Name Surname"
fill_in "user_username", with: "Great"
fill_in "user_email", with: "name@mail.com"
- fill_in "user_password", with: "password1234"
- fill_in "user_password_confirmation", with: "password1234"
- expect { click_button "Sign up" }.to change {User.count}.by(1)
+ fill_in "user_password_sign_up", with: "password1234"
+ expect { click_button "Sign up" }.to change { User.count }.by(1)
end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 7489e56f42..479fa95038 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -5,9 +5,10 @@ describe IssuesFinder do
let(:user2) { create :user }
let(:project1) { create(:project) }
let(:project2) { create(:project) }
- let(:issue1) { create(:issue, assignee: user, project: project1) }
- let(:issue2) { create(:issue, assignee: user, project: project2) }
- let(:issue3) { create(:issue, assignee: user2, project: project2) }
+ let(:milestone) { create(:milestone, project: project1) }
+ let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) }
+ let(:issue2) { create(:issue, author: user, assignee: user, project: project2) }
+ let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) }
before do
project1.team << [user, :master]
@@ -22,37 +23,59 @@ describe IssuesFinder do
issue3
end
- it 'should filter by all' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
- issues.size.should == 3
+ context 'scope: all' do
+ it 'should filter by all' do
+ params = { scope: "all", state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ expect(issues.size).to eq(3)
+ end
+
+ it 'should filter by assignee id' do
+ params = { scope: "all", assignee_id: user.id, state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ expect(issues.size).to eq(2)
+ end
+
+ it 'should filter by author id' do
+ params = { scope: "all", author_id: user2.id, state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ expect(issues).to eq([issue3])
+ end
+
+ it 'should filter by milestone id' do
+ params = { scope: "all", milestone_id: milestone.id, state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ expect(issues).to eq([issue1])
+ end
+
+ it 'should be empty for unauthorized user' do
+ params = { scope: "all", state: 'opened' }
+ issues = IssuesFinder.new.execute(nil, params)
+ expect(issues.size).to be_zero
+ end
+
+ it 'should not include unauthorized issues' do
+ params = { scope: "all", state: 'opened' }
+ issues = IssuesFinder.new.execute(user2, params)
+ expect(issues.size).to eq(2)
+ expect(issues).not_to include(issue1)
+ expect(issues).to include(issue2)
+ expect(issues).to include(issue3)
+ end
end
- it 'should filter by assignee' do
- params = { scope: "assigned-to-me", state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
- issues.size.should == 2
- end
+ context 'personal scope' do
+ it 'should filter by assignee' do
+ params = { scope: "assigned-to-me", state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ expect(issues.size).to eq(2)
+ end
- it 'should filter by project' do
- params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
- issues = IssuesFinder.new.execute(user, params)
- issues.size.should == 1
- end
-
- it 'should be empty for unauthorized user' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(nil, params)
- issues.size.should be_zero
- end
-
- it 'should not include unauthorized issues' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(user2, params)
- issues.size.should == 2
- issues.should_not include(issue1)
- issues.should include(issue2)
- issues.should include(issue3)
+ it 'should filter by project' do
+ params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
+ issues = IssuesFinder.new.execute(user, params)
+ expect(issues.size).to eq(1)
+ end
end
end
end
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 94b4d4c4ff..8536377a7f 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -21,13 +21,13 @@ describe MergeRequestsFinder do
it 'should filter by scope' do
params = { scope: 'authored', state: 'opened' }
merge_requests = MergeRequestsFinder.new.execute(user, params)
- merge_requests.size.should == 2
+ expect(merge_requests.size).to eq(2)
end
it 'should filter by project' do
params = { project_id: project1.id, scope: 'authored', state: 'opened' }
merge_requests = MergeRequestsFinder.new.execute(user, params)
- merge_requests.size.should == 1
+ expect(merge_requests.size).to eq(1)
end
end
end
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 4f8a5f909d..c83824b900 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -21,7 +21,7 @@ describe NotesFinder do
it 'should find all notes' do
notes = NotesFinder.new.execute(project, user, params)
- notes.size.should eq(2)
+ expect(notes.size).to eq(2)
end
it 'should raise an exception for an invalid target_type' do
@@ -32,7 +32,7 @@ describe NotesFinder do
it 'filters out old notes' do
note2.update_attribute(:updated_at, 2.hours.ago)
notes = NotesFinder.new.execute(project, user, params)
- notes.should eq([note1])
+ expect(notes).to eq([note1])
end
end
end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 6e3ae4d615..2ab71b0596 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -12,19 +12,19 @@ describe ProjectsFinder do
context 'non authenticated' do
subject { ProjectsFinder.new.execute(nil, group: group) }
- it { should include(project1) }
- it { should_not include(project2) }
- it { should_not include(project3) }
- it { should_not include(project4) }
+ it { is_expected.to include(project1) }
+ it { is_expected.not_to include(project2) }
+ it { is_expected.not_to include(project3) }
+ it { is_expected.not_to include(project4) }
end
context 'authenticated' do
subject { ProjectsFinder.new.execute(user, group: group) }
- it { should include(project1) }
- it { should include(project2) }
- it { should_not include(project3) }
- it { should_not include(project4) }
+ it { is_expected.to include(project1) }
+ it { is_expected.to include(project2) }
+ it { is_expected.not_to include(project3) }
+ it { is_expected.not_to include(project4) }
end
context 'authenticated, project member' do
@@ -32,10 +32,10 @@ describe ProjectsFinder do
subject { ProjectsFinder.new.execute(user, group: group) }
- it { should include(project1) }
- it { should include(project2) }
- it { should include(project3) }
- it { should_not include(project4) }
+ it { is_expected.to include(project1) }
+ it { is_expected.to include(project2) }
+ it { is_expected.to include(project3) }
+ it { is_expected.not_to include(project4) }
end
context 'authenticated, group member' do
@@ -43,9 +43,9 @@ describe ProjectsFinder do
subject { ProjectsFinder.new.execute(user, group: group) }
- it { should include(project1) }
- it { should include(project2) }
- it { should include(project3) }
- it { should include(project4) }
+ it { is_expected.to include(project1) }
+ it { is_expected.to include(project2) }
+ it { is_expected.to include(project3) }
+ it { is_expected.to include(project4) }
end
end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 5af7696818..1b4ffc2d71 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -18,14 +18,14 @@ describe SnippetsFinder do
it "returns all private and internal snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :all)
- snippets.should include(@snippet2, @snippet3)
- snippets.should_not include(@snippet1)
+ expect(snippets).to include(@snippet2, @snippet3)
+ expect(snippets).not_to include(@snippet1)
end
it "returns all public snippets" do
snippets = SnippetsFinder.new.execute(nil, filter: :all)
- snippets.should include(@snippet3)
- snippets.should_not include(@snippet1, @snippet2)
+ expect(snippets).to include(@snippet3)
+ expect(snippets).not_to include(@snippet1, @snippet2)
end
end
@@ -38,32 +38,39 @@ describe SnippetsFinder do
it "returns all public and internal snippets" do
snippets = SnippetsFinder.new.execute(user1, filter: :by_user, user: user)
- snippets.should include(@snippet2, @snippet3)
- snippets.should_not include(@snippet1)
+ expect(snippets).to include(@snippet2, @snippet3)
+ expect(snippets).not_to include(@snippet1)
end
it "returns internal snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_internal")
- snippets.should include(@snippet2)
- snippets.should_not include(@snippet1, @snippet3)
+ expect(snippets).to include(@snippet2)
+ expect(snippets).not_to include(@snippet1, @snippet3)
end
it "returns private snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_private")
- snippets.should include(@snippet1)
- snippets.should_not include(@snippet2, @snippet3)
+ expect(snippets).to include(@snippet1)
+ expect(snippets).not_to include(@snippet2, @snippet3)
end
it "returns public snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_public")
- snippets.should include(@snippet3)
- snippets.should_not include(@snippet1, @snippet2)
+ expect(snippets).to include(@snippet3)
+ expect(snippets).not_to include(@snippet1, @snippet2)
end
it "returns all snippets" do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user)
- snippets.should include(@snippet1, @snippet2, @snippet3)
+ expect(snippets).to include(@snippet1, @snippet2, @snippet3)
end
+
+ it "returns only public snippets if unauthenticated user" do
+ snippets = SnippetsFinder.new.execute(nil, filter: :by_user, user: user)
+ expect(snippets).to include(@snippet3)
+ expect(snippets).not_to include(@snippet2, @snippet1)
+ end
+
end
context 'by_project filter' do
@@ -75,20 +82,20 @@ describe SnippetsFinder do
it "returns public snippets for unauthorized user" do
snippets = SnippetsFinder.new.execute(nil, filter: :by_project, project: project1)
- snippets.should include(@snippet3)
- snippets.should_not include(@snippet1, @snippet2)
+ expect(snippets).to include(@snippet3)
+ expect(snippets).not_to include(@snippet1, @snippet2)
end
it "returns public and internal snippets for none project members" do
snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
- snippets.should include(@snippet2, @snippet3)
- snippets.should_not include(@snippet1)
+ expect(snippets).to include(@snippet2, @snippet3)
+ expect(snippets).not_to include(@snippet1)
end
it "returns all snippets for project members" do
project1.team << [user, :developer]
snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1)
- snippets.should include(@snippet1, @snippet2, @snippet3)
+ expect(snippets).to include(@snippet1, @snippet2, @snippet3)
end
end
end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 2db67cfdf9..4c11709ed6 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -3,20 +3,20 @@ require 'spec_helper'
describe ApplicationHelper do
describe 'current_controller?' do
before do
- controller.stub(:controller_name).and_return('foo')
+ allow(controller).to receive(:controller_name).and_return('foo')
end
- it "returns true when controller matches argument" do
- current_controller?(:foo).should be_true
+ it 'returns true when controller matches argument' do
+ expect(current_controller?(:foo)).to be_truthy
end
- it "returns false when controller does not match argument" do
- current_controller?(:bar).should_not be_true
+ it 'returns false when controller does not match argument' do
+ expect(current_controller?(:bar)).not_to be_truthy
end
- it "should take any number of arguments" do
- current_controller?(:baz, :bar).should_not be_true
- current_controller?(:baz, :bar, :foo).should be_true
+ it 'should take any number of arguments' do
+ expect(current_controller?(:baz, :bar)).not_to be_truthy
+ expect(current_controller?(:baz, :bar, :foo)).to be_truthy
end
end
@@ -25,99 +25,124 @@ describe ApplicationHelper do
allow(self).to receive(:action_name).and_return('foo')
end
- it "returns true when action matches argument" do
- current_action?(:foo).should be_true
+ it 'returns true when action matches argument' do
+ expect(current_action?(:foo)).to be_truthy
end
- it "returns false when action does not match argument" do
- current_action?(:bar).should_not be_true
+ it 'returns false when action does not match argument' do
+ expect(current_action?(:bar)).not_to be_truthy
end
- it "should take any number of arguments" do
- current_action?(:baz, :bar).should_not be_true
- current_action?(:baz, :bar, :foo).should be_true
+ it 'should take any number of arguments' do
+ expect(current_action?(:baz, :bar)).not_to be_truthy
+ expect(current_action?(:baz, :bar, :foo)).to be_truthy
end
end
- describe "group_icon" do
+ describe 'project_icon' do
avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
- it "should return an url for the avatar" do
- group = create(:group)
- group.avatar = File.open(avatar_file_path)
- group.save!
- group_icon(group.path).to_s.should match("/uploads/group/avatar/#{ group.id }/gitlab_logo.png")
+ it 'should return an url for the avatar' do
+ project = create(:project)
+ project.avatar = File.open(avatar_file_path)
+ project.save!
+ avatar_url = "http://localhost/uploads/project/avatar/#{ project.id }/gitlab_logo.png"
+ expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to eq(
+ " "
+ )
end
- it "should give default avatar_icon when no avatar is present" do
- group = create(:group)
- group.save!
- group_icon(group.path).should match("group_avatar.png")
+ it 'should give uploaded icon when present' do
+ project = create(:project)
+ project.save!
+
+ allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
+
+ avatar_url = 'http://localhost' + namespace_project_avatar_path(project.namespace, project)
+ expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to match(
+ image_tag(avatar_url))
end
end
- describe "avatar_icon" do
+ describe 'avatar_icon' do
avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
- it "should return an url for the avatar" do
+ it 'should return an url for the avatar' do
user = create(:user)
user.avatar = File.open(avatar_file_path)
user.save!
- avatar_icon(user.email).to_s.should match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png")
+ expect(avatar_icon(user.email).to_s).
+ to match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png")
end
- it "should call gravatar_icon when no avatar is present" do
+ it 'should return an url for the avatar with relative url' do
+ Gitlab.config.gitlab.stub(relative_url_root: '/gitlab')
+ Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url))
+
+ user = create(:user)
+ user.avatar = File.open(avatar_file_path)
+ user.save!
+ expect(avatar_icon(user.email).to_s).
+ to match("/gitlab/uploads/user/avatar/#{ user.id }/gitlab_logo.png")
+ end
+
+ it 'should call gravatar_icon when no avatar is present' do
user = create(:user, email: 'test@example.com')
user.save!
- avatar_icon(user.email).to_s.should == "http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=40&d=identicon"
+ expect(avatar_icon(user.email).to_s).to eq('http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=40&d=identicon')
end
end
- describe "gravatar_icon" do
+ describe 'gravatar_icon' do
let(:user_email) { 'user@email.com' }
- it "should return a generic avatar path when Gravatar is disabled" do
- Gitlab.config.gravatar.stub(:enabled).and_return(false)
- gravatar_icon(user_email).should match('no_avatar.png')
+ it 'should return a generic avatar path when Gravatar is disabled' do
+ ApplicationSetting.any_instance.stub(gravatar_enabled?: false)
+ expect(gravatar_icon(user_email)).to match('no_avatar.png')
end
- it "should return a generic avatar path when email is blank" do
- gravatar_icon('').should match('no_avatar.png')
+ it 'should return a generic avatar path when email is blank' do
+ expect(gravatar_icon('')).to match('no_avatar.png')
end
- it "should return default gravatar url" do
+ it 'should return default gravatar url' do
Gitlab.config.gitlab.stub(https: false)
- gravatar_icon(user_email).should match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118')
+ url = 'http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118'
+ expect(gravatar_icon(user_email)).to match(url)
end
- it "should use SSL when appropriate" do
+ it 'should use SSL when appropriate' do
Gitlab.config.gitlab.stub(https: true)
- gravatar_icon(user_email).should match('https://secure.gravatar.com')
+ expect(gravatar_icon(user_email)).to match('https://secure.gravatar.com')
end
- it "should return custom gravatar path when gravatar_url is set" do
+ it 'should return custom gravatar path when gravatar_url is set' do
allow(self).to receive(:request).and_return(double(:ssl? => false))
- Gitlab.config.gravatar.stub(:plain_url).and_return('http://example.local/?s=%{size}&hash=%{hash}')
- gravatar_icon(user_email, 20).should == 'http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118'
+ allow(Gitlab.config.gravatar).
+ to receive(:plain_url).
+ and_return('http://example.local/?s=%{size}&hash=%{hash}')
+ url = 'http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118'
+ expect(gravatar_icon(user_email, 20)).to eq(url)
end
- it "should accept a custom size" do
+ it 'should accept a custom size' do
allow(self).to receive(:request).and_return(double(:ssl? => false))
- gravatar_icon(user_email, 64).should match(/\?s=64/)
+ expect(gravatar_icon(user_email, 64)).to match(/\?s=64/)
end
- it "should use default size when size is wrong" do
+ it 'should use default size when size is wrong' do
allow(self).to receive(:request).and_return(double(:ssl? => false))
- gravatar_icon(user_email, nil).should match(/\?s=40/)
+ expect(gravatar_icon(user_email, nil)).to match(/\?s=40/)
end
- it "should be case insensitive" do
+ it 'should be case insensitive' do
allow(self).to receive(:request).and_return(double(:ssl? => false))
- gravatar_icon(user_email).should == gravatar_icon(user_email.upcase + " ")
+ expect(gravatar_icon(user_email)).
+ to eq(gravatar_icon(user_email.upcase + ' '))
end
end
- describe "grouped_options_refs" do
+ describe 'grouped_options_refs' do
# Override Rails' grouped_options_for_select helper since HTML is harder to work with
def grouped_options_for_select(options, *args)
options
@@ -130,90 +155,94 @@ describe ApplicationHelper do
@project = create(:project)
end
- it "includes a list of branch names" do
- options[0][0].should == 'Branches'
- options[0][1].should include('master', 'feature')
+ it 'includes a list of branch names' do
+ expect(options[0][0]).to eq('Branches')
+ expect(options[0][1]).to include('master', 'feature')
end
- it "includes a list of tag names" do
- options[1][0].should == 'Tags'
- options[1][1].should include('v1.0.0','v1.1.0')
+ it 'includes a list of tag names' do
+ expect(options[1][0]).to eq('Tags')
+ expect(options[1][1]).to include('v1.0.0', 'v1.1.0')
end
- it "includes a specific commit ref if defined" do
+ it 'includes a specific commit ref if defined' do
# Must be an instance variable
@ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8'
- options[2][0].should == 'Commit'
- options[2][1].should == [@ref]
+ expect(options[2][0]).to eq('Commit')
+ expect(options[2][1]).to eq([@ref])
end
- it "sorts tags in a natural order" do
+ it 'sorts tags in a natural order' do
# Stub repository.tag_names to make sure we get some valid testing data
- expect(@project.repository).to receive(:tag_names).and_return(["v1.0.9", "v1.0.10", "v2.0", "v3.1.4.2", "v1.0.9a"])
+ expect(@project.repository).to receive(:tag_names).
+ and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿',
+ 'v1.0.9a', 'v2.0-rc1', 'v2.0rc2'])
- options[1][1].should == ["v3.1.4.2", "v2.0", "v1.0.10", "v1.0.9a", "v1.0.9"]
+ expect(options[1][1]).
+ to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0rc1¿', 'v2.0-rc1', 'v1.0.10',
+ 'v1.0.9', 'v1.0.9a'])
end
end
- describe "user_color_scheme_class" do
- context "with current_user is nil" do
- it "should return a string" do
+ describe 'user_color_scheme_class' do
+ context 'with current_user is nil' do
+ it 'should return a string' do
allow(self).to receive(:current_user).and_return(nil)
- user_color_scheme_class.should be_kind_of(String)
+ expect(user_color_scheme_class).to be_kind_of(String)
end
end
- context "with a current_user" do
+ context 'with a current_user' do
(1..5).each do |color_scheme_id|
context "with color_scheme_id == #{color_scheme_id}" do
- it "should return a string" do
+ it 'should return a string' do
current_user = double(:color_scheme_id => color_scheme_id)
allow(self).to receive(:current_user).and_return(current_user)
- user_color_scheme_class.should be_kind_of(String)
+ expect(user_color_scheme_class).to be_kind_of(String)
end
end
end
end
end
- describe "simple_sanitize" do
+ describe 'simple_sanitize' do
let(:a_tag) { 'Foo ' }
- it "allows the a tag" do
- simple_sanitize(a_tag).should == a_tag
+ it 'allows the a tag' do
+ expect(simple_sanitize(a_tag)).to eq(a_tag)
end
- it "allows the span tag" do
+ it 'allows the span tag' do
input = 'Bar '
- simple_sanitize(input).should == input
+ expect(simple_sanitize(input)).to eq(input)
end
- it "disallows other tags" do
+ it 'disallows other tags' do
input = "#{a_tag} "
- simple_sanitize(input).should == a_tag
+ expect(simple_sanitize(input)).to eq(a_tag)
end
end
- describe "link_to" do
+ describe 'link_to' do
- it "should not include rel=nofollow for internal links" do
- expect(link_to("Home", root_path)).to eq("Home ")
+ it 'should not include rel=nofollow for internal links' do
+ expect(link_to('Home', root_path)).to eq("Home ")
end
- it "should include rel=nofollow for external links" do
- expect(link_to("Example", "http://www.example.com")).to eq("Example ")
+ it 'should include rel=nofollow for external links' do
+ expect(link_to('Example', 'http://www.example.com')).to eq("Example ")
end
- it "should include re=nofollow for external links and honor existing html_options" do
+ it 'should include re=nofollow for external links and honor existing html_options' do
expect(
- link_to("Example", "http://www.example.com", class: "toggle", data: {toggle: "dropdown"})
+ link_to('Example', 'http://www.example.com', class: 'toggle', data: {toggle: 'dropdown'})
).to eq("Example ")
end
- it "should include rel=nofollow for external links and preserver other rel values" do
+ it 'should include rel=nofollow for external links and preserver other rel values' do
expect(
- link_to("Example", "http://www.example.com", rel: "noreferrer")
+ link_to('Example', 'http://www.example.com', rel: 'noreferrer')
).to eq("Example ")
end
end
@@ -222,7 +251,7 @@ describe ApplicationHelper do
let(:content) { 'Noël' }
it 'should preserve encoding' do
- content.encoding.name.should == 'UTF-8'
+ expect(content.encoding.name).to eq('UTF-8')
expect(render_markup('foo.rst', content).encoding.name).to eq('UTF-8')
end
end
diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb
index 1338ce4873..f6df12662b 100644
--- a/spec/helpers/broadcast_messages_helper_spec.rb
+++ b/spec/helpers/broadcast_messages_helper_spec.rb
@@ -6,7 +6,7 @@ describe BroadcastMessagesHelper do
context "default style" do
it "should have no style" do
- broadcast_styling(broadcast_message).should match('')
+ expect(broadcast_styling(broadcast_message)).to match('')
end
end
@@ -14,7 +14,8 @@ describe BroadcastMessagesHelper do
before { broadcast_message.stub(color: "#f2dede", font: "#b94a48") }
it "should have a customized style" do
- broadcast_styling(broadcast_message).should match('background-color:#f2dede;color:#b94a48')
+ expect(broadcast_styling(broadcast_message)).
+ to match('background-color:#f2dede;color:#b94a48')
end
end
end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index b07742a6ee..5bd09793b1 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -10,58 +10,61 @@ describe DiffHelper do
describe 'diff_hard_limit_enabled?' do
it 'should return true if param is provided' do
- controller.stub(:params).and_return { { :force_show_diff => true } }
- diff_hard_limit_enabled?.should be_true
+ allow(controller).to receive(:params) { { force_show_diff: true } }
+ expect(diff_hard_limit_enabled?).to be_truthy
end
it 'should return false if param is not provided' do
- diff_hard_limit_enabled?.should be_false
+ expect(diff_hard_limit_enabled?).to be_falsey
end
end
describe 'allowed_diff_size' do
it 'should return hard limit for a diff if force diff is true' do
- controller.stub(:params).and_return { { :force_show_diff => true } }
- allowed_diff_size.should eq(1000)
+ allow(controller).to receive(:params) { { force_show_diff: true } }
+ expect(allowed_diff_size).to eq(1000)
end
it 'should return safe limit for a diff if force diff is false' do
- allowed_diff_size.should eq(100)
+ expect(allowed_diff_size).to eq(100)
end
end
describe 'parallel_diff' do
it 'should return an array of arrays containing the parsed diff' do
- parallel_diff(diff_file, 0).should match_array(parallel_diff_result_array)
+ expect(parallel_diff(diff_file, 0)).
+ to match_array(parallel_diff_result_array)
end
end
describe 'generate_line_code' do
it 'should generate correct line code' do
- generate_line_code(diff_file.file_path, diff_file.diff_lines.first).should == '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6'
+ expect(generate_line_code(diff_file.file_path, diff_file.diff_lines.first)).
+ to eq('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6')
end
end
describe 'unfold_bottom_class' do
it 'should return empty string when bottom line shouldnt be unfolded' do
- unfold_bottom_class(false).should == ''
+ expect(unfold_bottom_class(false)).to eq('')
end
it 'should return js class when bottom lines should be unfolded' do
- unfold_bottom_class(true).should == 'js-unfold-bottom'
+ expect(unfold_bottom_class(true)).to eq('js-unfold-bottom')
end
end
describe 'diff_line_content' do
it 'should return non breaking space when line is empty' do
- diff_line_content(nil).should eq(" ")
+ expect(diff_line_content(nil)).to eq(' ')
end
it 'should return the line itself' do
- diff_line_content(diff_file.diff_lines.first.text).should eq("@@ -6,12 +6,18 @@ module Popen")
- diff_line_content(diff_file.diff_lines.first.type).should eq("match")
- diff_line_content(diff_file.diff_lines.first.new_pos).should eq(6)
+ expect(diff_line_content(diff_file.diff_lines.first.text)).
+ to eq('@@ -6,12 +6,18 @@ module Popen')
+ expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match')
+ expect(diff_line_content(diff_file.diff_lines.first.new_pos)).to eq(6)
end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
new file mode 100644
index 0000000000..b392371deb
--- /dev/null
+++ b/spec/helpers/events_helper_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe EventsHelper do
+ include ApplicationHelper
+ include GitlabMarkdownHelper
+
+ let(:current_user) { create(:user, email: "current@email.com") }
+
+ it 'should display one line of plain text without alteration' do
+ input = 'A short, plain note'
+ expect(event_note(input)).to match(input)
+ expect(event_note(input)).not_to match(/\.\.\.\z/)
+ end
+
+ it 'should display inline code' do
+ input = 'A note with `inline code`'
+ expected = 'A note with inline code'
+
+ expect(event_note(input)).to match(expected)
+ end
+
+ it 'should truncate a note with multiple paragraphs' do
+ input = "Paragraph 1\n\nParagraph 2"
+ expected = 'Paragraph 1...'
+
+ expect(event_note(input)).to match(expected)
+ end
+
+ it 'should display the first line of a code block' do
+ input = "```\nCode block\nwith two lines\n```"
+ expected = '
' \
+ 'Code block... '
+
+ expect(event_note(input)).to match(expected)
+ end
+
+ it 'should truncate a single long line of text' do
+ text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
+ input = "#{text}#{text}#{text}#{text}" # 200 chars
+ expected = "#{text}#{text}".sub(/.{3}/, '...')
+
+ expect(event_note(input)).to match(expected)
+ end
+
+ it 'should preserve a link href when link text is truncated' do
+ text = 'The quick brown fox jumped over the lazy dog' # 44 chars
+ input = "#{text}#{text}#{text} " # 133 chars
+ link_url = 'http://example.com/foo/bar/baz' # 30 chars
+ input << link_url
+ expected_link_text = 'http://example...'
+
+ expect(event_note(input)).to match(link_url)
+ expect(event_note(input)).to match(expected_link_text)
+ end
+
+ it 'should preserve code color scheme' do
+ input = "```ruby\ndef test\n 'hello world'\nend\n```"
+ expected = '' \
+ "def test \n" \
+ " \'hello world\' \n" \
+ "end \n" \
+ ' '
+ expect(event_note(input)).to eq(expected)
+ end
+end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index c75773ad32..3a884d39bf 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -1,4 +1,4 @@
-require "spec_helper"
+require 'spec_helper'
describe GitlabMarkdownHelper do
include ApplicationHelper
@@ -9,6 +9,7 @@ describe GitlabMarkdownHelper do
let(:user) { create(:user, username: 'gfm') }
let(:commit) { project.repository.commit }
+ let(:earlier_commit){ project.repository.commit("HEAD~2") }
let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:snippet) { create(:project_snippet, project: project) }
@@ -23,71 +24,120 @@ describe GitlabMarkdownHelper do
@project = project
@ref = 'markdown'
@repository = project.repository
+ @request.host = Gitlab.config.gitlab.host
end
describe "#gfm" do
it "should return unaltered text if project is nil" do
actual = "Testing references: ##{issue.iid}"
- gfm(actual).should_not == actual
+ expect(gfm(actual)).not_to eq(actual)
@project = nil
- gfm(actual).should == actual
+ expect(gfm(actual)).to eq(actual)
end
it "should not alter non-references" do
actual = expected = "_Please_ *stop* 'helping' and all the other b*$#%' you do."
- gfm(actual).should == expected
+ expect(gfm(actual)).to eq(expected)
end
it "should not touch HTML entities" do
- @project.issues.stub(:where).with(id: '39').and_return([issue])
+ allow(@project.issues).to receive(:where).
+ with(id: '39').and_return([issue])
actual = 'We'll accept good pull requests.'
- gfm(actual).should == "We'll accept good pull requests."
+ expect(gfm(actual)).to eq("We'll accept good pull requests.")
end
it "should forward HTML options to links" do
- gfm("Fixed in #{commit.id}", @project, class: 'foo').
- should have_selector('a.gfm.foo')
+ expect(gfm("Fixed in #{commit.id}", @project, class: 'foo')).
+ to have_selector('a.gfm.foo')
end
- describe "referencing a commit" do
- let(:expected) { project_commit_path(project, commit) }
+ describe "referencing a commit range" do
+ let(:expected) { namespace_project_compare_path(project.namespace, project, from: earlier_commit.id, to: commit.id) }
it "should link using a full id" do
- actual = "Reverts #{commit.id}"
- gfm(actual).should match(expected)
+ actual = "What happened in #{earlier_commit.id}...#{commit.id}"
+ expect(gfm(actual)).to match(expected)
end
it "should link using a short id" do
- actual = "Backported from #{commit.short_id(6)}"
- gfm(actual).should match(expected)
+ actual = "What happened in #{earlier_commit.short_id}...#{commit.short_id}"
+ expected = namespace_project_compare_path(project.namespace, project, from: earlier_commit.short_id, to: commit.short_id)
+ expect(gfm(actual)).to match(expected)
+ end
+
+ it "should link inclusively" do
+ actual = "What happened in #{earlier_commit.id}..#{commit.id}"
+ expected = namespace_project_compare_path(project.namespace, project, from: "#{earlier_commit.id}^", to: commit.id)
+ expect(gfm(actual)).to match(expected)
+ end
+
+ it "should link with adjacent text" do
+ actual = "(see #{earlier_commit.id}...#{commit.id})"
+ expect(gfm(actual)).to match(expected)
+ end
+
+ it "should keep whitespace intact" do
+ actual = "Changes #{earlier_commit.id}...#{commit.id} dramatically"
+ expected = /Changes #{earlier_commit.id}...#{commit.id}<\/a> dramatically/
+ expect(gfm(actual)).to match(expected)
+ end
+
+ it "should not link with an invalid id" do
+ actual = expected = "What happened in #{earlier_commit.id.reverse}...#{commit.id.reverse}"
+ expect(gfm(actual)).to eq(expected)
+ end
+
+ it "should include a title attribute" do
+ actual = "What happened in #{earlier_commit.id}...#{commit.id}"
+ expect(gfm(actual)).to match(/title="Commits #{earlier_commit.id} through #{commit.id}"/)
+ end
+
+ it "should include standard gfm classes" do
+ actual = "What happened in #{earlier_commit.id}...#{commit.id}"
+ expect(gfm(actual)).to match(/class="\s?gfm gfm-commit_range\s?"/)
+ end
+ end
+
+ describe "referencing a commit" do
+ let(:expected) { namespace_project_commit_path(project.namespace, project, commit) }
+
+ it "should link using a full id" do
+ actual = "Reverts #{commit.id}"
+ expect(gfm(actual)).to match(expected)
+ end
+
+ it "should link using a short id" do
+ actual = "Backported from #{commit.short_id}"
+ expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
actual = "Reverted (see #{commit.id})"
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should keep whitespace intact" do
actual = "Changes #{commit.id} dramatically"
expected = /Changes #{commit.id}<\/a> dramatically/
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
actual = expected = "What happened in #{commit.id.reverse}"
- gfm(actual).should == expected
+ expect(gfm(actual)).to eq(expected)
end
it "should include a title attribute" do
actual = "Reverts #{commit.id}"
- gfm(actual).should match(/title="#{commit.link_title}"/)
+ expect(gfm(actual)).to match(/title="#{commit.link_title}"/)
end
it "should include standard gfm classes" do
actual = "Reverts #{commit.id}"
- gfm(actual).should match(/class="\s?gfm gfm-commit\s?"/)
+ expect(gfm(actual)).to match(/class="\s?gfm gfm-commit\s?"/)
end
end
@@ -100,37 +150,37 @@ describe GitlabMarkdownHelper do
end
it "should link using a simple name" do
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should link using a name with dots" do
user.update_attributes(name: "alphA.Beta")
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should link using name with underscores" do
user.update_attributes(name: "ping_pong_king")
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
actual = "Mail the admin (@#{user.username})"
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should keep whitespace intact" do
actual = "Yes, @#{user.username} is right."
expected = /Yes, @#{user.username}<\/a> is right/
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
actual = expected = "@#{user.username.reverse} you are right."
- gfm(actual).should == expected
+ expect(gfm(actual)).to eq(expected)
end
it "should include standard gfm classes" do
- gfm(actual).should match(/class="\s?gfm gfm-team_member\s?"/)
+ expect(gfm(actual)).to match(/class="\s?gfm gfm-project_member\s?"/)
end
end
@@ -144,40 +194,41 @@ describe GitlabMarkdownHelper do
# Currently limited to Snippets, Issues and MergeRequests
shared_examples 'referenced object' do
let(:actual) { "Reference to #{reference}" }
- let(:expected) { polymorphic_path([project, object]) }
+ let(:expected) { polymorphic_path([project.namespace, project, object]) }
it "should link using a valid id" do
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
# Wrap the reference in parenthesis
- gfm(actual.gsub(reference, "(#{reference})")).should match(expected)
+ expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
# Append some text to the end of the reference
- gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected)
+ expect(gfm(actual.gsub(reference, "#{reference}, right?"))).
+ to match(expected)
end
it "should keep whitespace intact" do
actual = "Referenced #{reference} already."
expected = /Referenced [^\s]+<\/a> already/
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
# Modify the reference string so it's still parsed, but is invalid
reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
- gfm(actual).should == actual
+ expect(gfm(actual)).to eq(actual)
end
it "should include a title attribute" do
title = "#{object.class.to_s.titlecase}: #{object.title}"
- gfm(actual).should match(/title="#{title}"/)
+ expect(gfm(actual)).to match(/title="#{title}"/)
end
it "should include standard gfm classes" do
css = object.class.to_s.underscore
- gfm(actual).should match(/class="\s?gfm gfm-#{css}\s?"/)
+ expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/)
end
end
@@ -196,34 +247,33 @@ describe GitlabMarkdownHelper do
let(:actual) { "Reference to #{full_reference}" }
let(:expected) do
if object.is_a?(Commit)
- project_commit_path(@other_project, object)
+ namespace_project_commit_path(@other_project.namespace, @other_project, object)
else
- polymorphic_path([@other_project, object])
+ polymorphic_path([@other_project.namespace, @other_project, object])
end
end
it 'should link using a valid id' do
- gfm(actual).should match(
+ expect(gfm(actual)).to match(
/#{expected}.*#{Regexp.escape(full_reference)}/
)
end
it 'should link with adjacent text' do
# Wrap the reference in parenthesis
- gfm(actual.gsub(full_reference, "(#{full_reference})")).should(
+ expect(gfm(actual.gsub(full_reference, "(#{full_reference})"))).to(
match(expected)
)
# Append some text to the end of the reference
- gfm(actual.gsub(full_reference, "#{full_reference}, right?")).should(
- match(expected)
- )
+ expect(gfm(actual.gsub(full_reference, "#{full_reference}, right?"))).
+ to(match(expected))
end
it 'should keep whitespace intact' do
actual = "Referenced #{full_reference} already."
expected = /Referenced [^\s]+<\/a> already/
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it 'should not link with an invalid id' do
@@ -233,7 +283,7 @@ describe GitlabMarkdownHelper do
else
reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
end
- gfm(actual).should == actual
+ expect(gfm(actual)).to eq(actual)
end
it 'should include a title attribute' do
@@ -242,12 +292,12 @@ describe GitlabMarkdownHelper do
else
title = "#{object.class.to_s.titlecase}: #{object.title}"
end
- gfm(actual).should match(/title="#{title}"/)
+ expect(gfm(actual)).to match(/title="#{title}"/)
end
it 'should include standard gfm classes' do
css = object.class.to_s.underscore
- gfm(actual).should match(/class="\s?gfm gfm-#{css}\s?"/)
+ expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/)
end
end
@@ -296,43 +346,47 @@ describe GitlabMarkdownHelper do
let(:reference) { "JIRA-#{issue.iid}" }
before do
- issue_tracker_config = { "jira" => { "title" => "JIRA tracker", "issues_url" => "http://jira.example/browse/:id" } }
- Gitlab.config.stub(:issues_tracker).and_return(issue_tracker_config)
- @project.stub(:issues_tracker).and_return("jira")
- @project.stub(:issues_tracker_id).and_return("JIRA")
+ jira = @project.create_jira_service if @project.jira_service.nil?
+ properties = {"title"=>"JIRA tracker", "project_url"=>"http://jira.example/issues/?jql=project=A", "issues_url"=>"http://jira.example/browse/:id", "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa"}
+ jira.update_attributes(properties: properties, active: true)
+ end
+
+ after do
+ @project.jira_service.destroy! unless @project.jira_service.nil?
end
it "should link using a valid id" do
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
# Wrap the reference in parenthesis
- gfm(actual.gsub(reference, "(#{reference})")).should match(expected)
+ expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
# Append some text to the end of the reference
- gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected)
+ expect(gfm(actual.gsub(reference, "#{reference}, right?"))).
+ to match(expected)
end
it "should keep whitespace intact" do
actual = "Referenced #{reference} already."
expected = /Referenced [^\s]+<\/a> already/
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
# Modify the reference string so it's still parsed, but is invalid
invalid_reference = actual.gsub(/(\d+)$/, "r45")
- gfm(invalid_reference).should == invalid_reference
+ expect(gfm(invalid_reference)).to eq(invalid_reference)
end
it "should include a title attribute" do
title = "Issue in JIRA tracker"
- gfm(actual).should match(/title="#{title}"/)
+ expect(gfm(actual)).to match(/title="#{title}"/)
end
it "should include standard gfm classes" do
- gfm(actual).should match(/class="\s?gfm gfm-issue\s?"/)
+ expect(gfm(actual)).to match(/class="\s?gfm gfm-issue\s?"/)
end
end
@@ -347,40 +401,40 @@ describe GitlabMarkdownHelper do
let(:object) { snippet }
let(:reference) { "$#{snippet.id}" }
let(:actual) { "Reference to #{reference}" }
- let(:expected) { project_snippet_path(project, object) }
+ let(:expected) { namespace_project_snippet_path(project.namespace, project, object) }
it "should link using a valid id" do
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should link with adjacent text" do
# Wrap the reference in parenthesis
- gfm(actual.gsub(reference, "(#{reference})")).should match(expected)
+ expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected)
# Append some text to the end of the reference
- gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected)
+ expect(gfm(actual.gsub(reference, "#{reference}, right?"))).to match(expected)
end
it "should keep whitespace intact" do
actual = "Referenced #{reference} already."
expected = /Referenced [^\s]+<\/a> already/
- gfm(actual).should match(expected)
+ expect(gfm(actual)).to match(expected)
end
it "should not link with an invalid id" do
# Modify the reference string so it's still parsed, but is invalid
reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
- gfm(actual).should == actual
+ expect(gfm(actual)).to eq(actual)
end
it "should include a title attribute" do
title = "Snippet: #{object.title}"
- gfm(actual).should match(/title="#{title}"/)
+ expect(gfm(actual)).to match(/title="#{title}"/)
end
it "should include standard gfm classes" do
css = object.class.to_s.underscore
- gfm(actual).should match(/class="\s?gfm gfm-snippet\s?"/)
+ expect(gfm(actual)).to match(/class="\s?gfm gfm-snippet\s?"/)
end
end
@@ -389,70 +443,70 @@ describe GitlabMarkdownHelper do
let(:actual) { "!#{merge_request.iid} -> #{commit.id} -> ##{issue.iid}" }
it "should link to the merge request" do
- expected = project_merge_request_path(project, merge_request)
- gfm(actual).should match(expected)
+ expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
+ expect(gfm(actual)).to match(expected)
end
it "should link to the commit" do
- expected = project_commit_path(project, commit)
- gfm(actual).should match(expected)
+ expected = namespace_project_commit_path(project.namespace, project, commit)
+ expect(gfm(actual)).to match(expected)
end
it "should link to the issue" do
- expected = project_issue_path(project, issue)
- gfm(actual).should match(expected)
+ expected = namespace_project_issue_path(project.namespace, project, issue)
+ expect(gfm(actual)).to match(expected)
end
end
describe "emoji" do
it "matches at the start of a string" do
- gfm(":+1:").should match(/ big time/)
+ expect(gfm('This deserves a :+1: big time.')).
+ to match(/deserves a big time/)
end
it "ignores invalid emoji" do
- gfm(":invalid-emoji:").should_not match(/ ")
# Leading commit link
- groups[0].should match(/href="#{commit_path}"/)
- groups[0].should match(/This should finally fix $/)
+ expect(groups[0]).to match(/href="#{commit_path}"/)
+ expect(groups[0]).to match(/This should finally fix $/)
# First issue link
- groups[1].should match(/href="#{project_issue_url(project, issues[0])}"/)
- groups[1].should match(/##{issues[0].iid}$/)
+ expect(groups[1]).
+ to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[0])}"/)
+ expect(groups[1]).to match(/##{issues[0].iid}$/)
# Internal commit link
- groups[2].should match(/href="#{commit_path}"/)
- groups[2].should match(/ and /)
+ expect(groups[2]).to match(/href="#{commit_path}"/)
+ expect(groups[2]).to match(/ and /)
# Second issue link
- groups[3].should match(/href="#{project_issue_url(project, issues[1])}"/)
- groups[3].should match(/##{issues[1].iid}$/)
+ expect(groups[3]).
+ to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[1])}"/)
+ expect(groups[3]).to match(/##{issues[1].iid}$/)
# Trailing commit link
- groups[4].should match(/href="#{commit_path}"/)
- groups[4].should match(/ for real$/)
+ expect(groups[4]).to match(/href="#{commit_path}"/)
+ expect(groups[4]).to match(/ for real$/)
end
it "should forward HTML options" do
actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
- actual.should have_selector 'a.gfm.gfm-commit.foo'
+ expect(actual).to have_selector 'a.gfm.gfm-commit.foo'
end
it "escapes HTML passed in as the body" do
actual = "This is a test - see ##{issues[0].iid}"
- link_to_gfm(actual, commit_path).should match('<h1>test</h1>')
+ expect(link_to_gfm(actual, commit_path)).
+ to match('<h1>test</h1>')
end
end
describe "#markdown" do
it "should handle references in paragraphs" do
actual = "\n\nLorem ipsum dolor sit amet. #{commit.id} Nam pulvinar sapien eget.\n"
- expected = project_commit_path(project, commit)
- markdown(actual).should match(expected)
+ expected = namespace_project_commit_path(project.namespace, project, commit)
+ expect(markdown(actual)).to match(expected)
end
it "should handle references in headers" do
actual = "\n# Working around ##{issue.iid}\n## Apply !#{merge_request.iid}"
- markdown(actual, {no_header_anchors:true}).should match(%r{Working around ##{issue.iid} })
- markdown(actual, {no_header_anchors:true}).should match(%r{Apply !#{merge_request.iid} })
+ expect(markdown(actual, no_header_anchors: true)).
+ to match(%r{Working around ##{issue.iid} })
+ expect(markdown(actual, no_header_anchors: true)).
+ to match(%r{Apply !#{merge_request.iid} })
end
it "should add ids and links to headers" do
# Test every rule except nested tags.
text = '..Ab_c-d. e..'
id = 'ab_c-d-e'
- markdown("# #{text}").should match(%r{#{text} })
- markdown("# #{text}", {no_header_anchors:true}).should == "#{text} "
+ expect(markdown("# #{text}")).
+ to match(%r{#{text} })
+ expect(markdown("# #{text}", {no_header_anchors:true})).
+ to eq("#{text} ")
id = 'link-text'
- markdown("# [link text](url) ").should match(
+ expect(markdown("# [link text](url) ")).to match(
%r{}
)
end
@@ -526,14 +587,37 @@ describe GitlabMarkdownHelper do
actual = "\n* dark: ##{issue.iid}\n* light by @#{member.user.username}"
- markdown(actual).should match(%r{dark: ##{issue.iid} })
- markdown(actual).should match(%r{light by @#{member.user.username} })
+ expect(markdown(actual)).
+ to match(%r{dark: ##{issue.iid} })
+ expect(markdown(actual)).
+ to match(%r{light by @#{member.user.username} })
+ end
+
+ it "should not link the apostrophe to issue 39" do
+ project.team << [user, :master]
+ allow(project.issues).
+ to receive(:where).with(iid: '39').and_return([issue])
+
+ actual = "Yes, it is @#{member.user.username}'s task."
+ expected = /Yes, it is @#{member.user.username}<\/a>'s task/
+ expect(markdown(actual)).to match(expected)
+ end
+
+ it "should not link the apostrophe to issue 39 in code blocks" do
+ project.team << [user, :master]
+ allow(project.issues).
+ to receive(:where).with(iid: '39').and_return([issue])
+
+ actual = "Yes, `it is @#{member.user.username}'s task.`"
+ expected = /Yes, it is @gfm\'s task.<\/code>/
+ expect(markdown(actual)).to match(expected)
end
it "should handle references in " do
actual = "Apply _!#{merge_request.iid}_ ASAP"
- markdown(actual).should match(%r{Apply !#{merge_request.iid} })
+ expect(markdown(actual)).
+ to match(%r{Apply !#{merge_request.iid} })
end
it "should handle tables" do
@@ -542,77 +626,106 @@ describe GitlabMarkdownHelper do
| cell 1 | cell 2 |
| cell 3 | cell 4 |}
- markdown(actual).should match(/\A\n \n
some code from $#{snippet.id}\nhere too\n\n
\n\n\n"
+ target_html = "some code from $#{snippet.id}\nhere too\n \n"
- helper.markdown("\n some code from $#{snippet.id}\n here too\n").should == target_html
- helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should == target_html
+ expect(helper.markdown("\n some code from $#{snippet.id}\n here too\n")).
+ to eq(target_html)
+ expect(helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n")).
+ to eq(target_html)
end
it "should leave inline code untouched" do
- markdown("\nDon't use `$#{snippet.id}` here.\n").should ==
+ expect(markdown("\nDon't use `$#{snippet.id}` here.\n")).to eq(
"Don't use $#{snippet.id} here.
\n"
+ )
end
it "should leave ref-like autolinks untouched" do
- markdown("look at http://example.tld/#!#{merge_request.iid}").should == "look at http://example.tld/#!#{merge_request.iid}
\n"
+ expect(markdown("look at http://example.tld/#!#{merge_request.iid}")).to eq("look at http://example.tld/#!#{merge_request.iid}
\n")
end
it "should leave ref-like href of 'manual' links untouched" do
- markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})").should == "why not inspect !#{merge_request.iid}
\n"
+ expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("why not inspect !#{merge_request.iid}
\n")
end
it "should leave ref-like src of images untouched" do
- markdown("screen shot: ").should == "screen shot:
\n"
+ expect(markdown("screen shot: ")).to eq("screen shot:
\n")
end
it "should generate absolute urls for refs" do
- markdown("##{issue.iid}").should include(project_issue_url(project, issue))
+ expect(markdown("##{issue.iid}")).to include(namespace_project_issue_path(project.namespace, project, issue))
end
it "should generate absolute urls for emoji" do
- markdown(":smile:").should include("src=\"#{url_helper('emoji/smile')}")
+ expect(markdown(':smile:')).to(
+ include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/#{Emoji.emoji_filename('smile')}.png))
+ )
end
+ it "should generate absolute urls for emoji if relative url is present" do
+ allow(Gitlab.config.gitlab).to receive(:url).and_return('http://localhost/gitlab/root')
+ expect(markdown(":smile:")).to include("src=\"http://localhost/gitlab/root/assets/emoji/#{Emoji.emoji_filename('smile')}.png")
+ end
+
+ it "should generate absolute urls for emoji if asset_host is present" do
+ allow(Gitlab::Application.config).to receive(:asset_host).and_return("https://cdn.example.com")
+ ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com")
+ expect(markdown(":smile:")).to include("src=\"https://cdn.example.com/assets/emoji/#{Emoji.emoji_filename('smile')}.png")
+ end
+
+
it "should handle relative urls for a file in master" do
actual = "[GitLab API doc](doc/api/README.md)\n"
expected = "GitLab API doc
\n"
- markdown(actual).should match(expected)
+ expect(markdown(actual)).to match(expected)
+ end
+
+ it "should handle relative urls for a file in master with an anchor" do
+ actual = "[GitLab API doc](doc/api/README.md#section)\n"
+ expected = "GitLab API doc
\n"
+ expect(markdown(actual)).to match(expected)
+ end
+
+ it "should not handle relative urls for the current file with an anchor" do
+ actual = "[GitLab API doc](#section)\n"
+ expected = "GitLab API doc
\n"
+ expect(markdown(actual)).to match(expected)
end
it "should handle relative urls for a directory in master" do
actual = "[GitLab API doc](doc/api)\n"
expected = "GitLab API doc
\n"
- markdown(actual).should match(expected)
+ expect(markdown(actual)).to match(expected)
end
it "should handle absolute urls" do
actual = "[GitLab](https://www.gitlab.com)\n"
expected = "GitLab
\n"
- markdown(actual).should match(expected)
+ expect(markdown(actual)).to match(expected)
end
it "should handle relative urls in reference links for a file in master" do
actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
expected = "GitLab API doc
\n"
- markdown(actual).should match(expected)
+ expect(markdown(actual)).to match(expected)
end
it "should handle relative urls in reference links for a directory in master" do
actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n"
expected = "GitLab API doc directory
\n"
- markdown(actual).should match(expected)
+ expect(markdown(actual)).to match(expected)
end
it "should not handle malformed relative urls in reference links for a file in master" do
actual = "[GitLab readme]: doc/api/README.md\n"
expected = ""
- markdown(actual).should match(expected)
+ expect(markdown(actual)).to match(expected)
end
it 'should allow whitelisted HTML tags from the user' do
@@ -655,29 +768,29 @@ describe GitlabMarkdownHelper do
it "should not touch relative urls" do
actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n"
expected = "GitLab API doc
\n"
- markdown(actual).should match(expected)
+ expect(markdown(actual)).to match(expected)
end
end
describe "#render_wiki_content" do
before do
@wiki = double('WikiPage')
- @wiki.stub(:content).and_return('wiki content')
+ allow(@wiki).to receive(:content).and_return('wiki content')
end
it "should use GitLab Flavored Markdown for markdown files" do
- @wiki.stub(:format).and_return(:markdown)
+ allow(@wiki).to receive(:format).and_return(:markdown)
- helper.should_receive(:markdown).with('wiki content')
+ expect(helper).to receive(:markdown).with('wiki content')
helper.render_wiki_content(@wiki)
end
it "should use the Gollum renderer for all other file types" do
- @wiki.stub(:format).and_return(:rdoc)
+ allow(@wiki).to receive(:format).and_return(:rdoc)
formatted_content_stub = double('formatted_content')
- formatted_content_stub.should_receive(:html_safe)
- @wiki.stub(:formatted_content).and_return(formatted_content_stub)
+ expect(formatted_content_stub).to receive(:html_safe)
+ allow(@wiki).to receive(:formatted_content).and_return(formatted_content_stub)
helper.render_wiki_content(@wiki)
end
diff --git a/spec/helpers/groups_helper.rb b/spec/helpers/groups_helper.rb
new file mode 100644
index 0000000000..3e99ab84ec
--- /dev/null
+++ b/spec/helpers/groups_helper.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe GroupsHelper do
+ describe 'group_icon' do
+ avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
+
+ it 'should return an url for the avatar' do
+ group = create(:group)
+ group.avatar = File.open(avatar_file_path)
+ group.save!
+ expect(group_icon(group.path).to_s).
+ to match("/uploads/group/avatar/#{ group.id }/gitlab_logo.png")
+ end
+
+ it 'should give default avatar_icon when no avatar is present' do
+ group = create(:group)
+ group.save!
+ expect(group_icon(group.path)).to match('group_avatar.png')
+ end
+ end
+end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 9c95bc044f..54dd8d4aa6 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -5,133 +5,132 @@ describe IssuesHelper do
let(:issue) { create :issue, project: project }
let(:ext_project) { create :redmine_project }
- describe :title_for_issue do
+ describe "title_for_issue" do
it "should return issue title if used internal tracker" do
@project = project
- title_for_issue(issue.iid).should eq issue.title
+ expect(title_for_issue(issue.iid)).to eq issue.title
end
it "should always return empty string if used external tracker" do
@project = ext_project
- title_for_issue(rand(100)).should eq ""
+ expect(title_for_issue(rand(100))).to eq ""
end
it "should always return empty string if project nil" do
@project = nil
- title_for_issue(rand(100)).should eq ""
+ expect(title_for_issue(rand(100))).to eq ""
end
end
- describe :url_for_project_issues do
- let(:project_url) { Gitlab.config.issues_tracker.redmine.project_url}
+ describe "url_for_project_issues" do
+ let(:project_url) { ext_project.external_issue_tracker.project_url }
let(:ext_expected) do
project_url.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end
- let(:int_expected) { polymorphic_path([project]) }
+ let(:int_expected) { polymorphic_path([@project.namespace, project]) }
it "should return internal path if used internal tracker" do
@project = project
- url_for_project_issues.should match(int_expected)
+ expect(url_for_project_issues).to match(int_expected)
end
it "should return path to external tracker" do
@project = ext_project
- url_for_project_issues.should match(ext_expected)
+ expect(url_for_project_issues).to match(ext_expected)
end
it "should return empty string if project nil" do
@project = nil
- url_for_project_issues.should eq ""
+ expect(url_for_project_issues).to eq ""
end
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
- Gitlab.config.stub(:issues_tracker).and_return(nil)
+ allow(Gitlab.config).to receive(:issues_tracker).and_return(nil)
end
- it "should return path to internal tracker" do
- url_for_project_issues.should match(polymorphic_path([@project]))
+ it "should return path to external tracker" do
+ expect(url_for_project_issues).to match(ext_expected)
end
end
end
- describe :url_for_issue do
- let(:issue_id) { 3 }
- let(:issues_url) { Gitlab.config.issues_tracker.redmine.issues_url}
+ describe "url_for_issue" do
+ let(:issues_url) { ext_project.external_issue_tracker.issues_url}
let(:ext_expected) do
- issues_url.gsub(':id', issue_id.to_s)
+ issues_url.gsub(':id', issue.iid.to_s)
.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end
- let(:int_expected) { polymorphic_path([project, issue]) }
+ let(:int_expected) { polymorphic_path([@project.namespace, project, issue]) }
it "should return internal path if used internal tracker" do
@project = project
- url_for_issue(issue.iid).should match(int_expected)
+ expect(url_for_issue(issue.iid)).to match(int_expected)
end
it "should return path to external tracker" do
@project = ext_project
- url_for_issue(issue_id).should match(ext_expected)
+ expect(url_for_issue(issue.iid)).to match(ext_expected)
end
it "should return empty string if project nil" do
@project = nil
- url_for_issue(issue.iid).should eq ""
+ expect(url_for_issue(issue.iid)).to eq ""
end
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
- Gitlab.config.stub(:issues_tracker).and_return(nil)
+ allow(Gitlab.config).to receive(:issues_tracker).and_return(nil)
end
- it "should return internal path" do
- url_for_issue(issue.iid).should match(polymorphic_path([@project, issue]))
+ it "should return external path" do
+ expect(url_for_issue(issue.iid)).to match(ext_expected)
end
end
end
- describe :url_for_new_issue do
- let(:issues_url) { Gitlab.config.issues_tracker.redmine.new_issue_url}
+ describe '#url_for_new_issue' do
+ let(:issues_url) { ext_project.external_issue_tracker.new_issue_url }
let(:ext_expected) do
issues_url.gsub(':project_id', ext_project.id.to_s)
.gsub(':issues_tracker_id', ext_project.issues_tracker_id.to_s)
end
- let(:int_expected) { new_project_issue_path(project) }
+ let(:int_expected) { new_namespace_project_issue_path(project.namespace, project) }
it "should return internal path if used internal tracker" do
@project = project
- url_for_new_issue.should match(int_expected)
+ expect(url_for_new_issue).to match(int_expected)
end
it "should return path to external tracker" do
@project = ext_project
- url_for_new_issue.should match(ext_expected)
+ expect(url_for_new_issue).to match(ext_expected)
end
it "should return empty string if project nil" do
@project = nil
- url_for_new_issue.should eq ""
+ expect(url_for_new_issue).to eq ""
end
describe "when external tracker was enabled and then config removed" do
before do
@project = ext_project
- Gitlab.config.stub(:issues_tracker).and_return(nil)
+ allow(Gitlab.config).to receive(:issues_tracker).and_return(nil)
end
it "should return internal path" do
- url_for_new_issue.should match(new_project_issue_path(@project))
+ expect(url_for_new_issue).to match(ext_expected)
end
end
end
diff --git a/spec/helpers/merge_requests_helper.rb b/spec/helpers/merge_requests_helper.rb
index 5a317c4886..5262d64404 100644
--- a/spec/helpers/merge_requests_helper.rb
+++ b/spec/helpers/merge_requests_helper.rb
@@ -7,6 +7,6 @@ describe MergeRequestsHelper do
[build(:issue, iid: 1), build(:issue, iid: 2), build(:issue, iid: 3)]
end
- it { should eq('#1, #2, and #3') }
+ it { is_expected.to eq('#1, #2, and #3') }
end
end
diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb
new file mode 100644
index 0000000000..e4d18d8bfc
--- /dev/null
+++ b/spec/helpers/nav_helper_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+# Specs in this file have access to a helper object that includes
+# the NavHelper. For example:
+#
+# describe NavHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+describe NavHelper do
+ describe '#nav_menu_collapsed?' do
+ it 'returns true when the nav is collapsed in the cookie' do
+ helper.request.cookies[:collapsed_nav] = 'true'
+ expect(helper.nav_menu_collapsed?).to eq true
+ end
+
+ it 'returns false when the nav is not collapsed in the cookie' do
+ helper.request.cookies[:collapsed_nav] = 'false'
+ expect(helper.nav_menu_collapsed?).to eq false
+ end
+ end
+end
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index 31ecdacf28..482cb33e94 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -1,6 +1,9 @@
require 'spec_helper'
describe NotificationsHelper do
+ include FontAwesome::Rails::IconHelper
+ include IconsHelper
+
describe 'notification_icon' do
let(:notification) { double(disabled?: false, participating?: false, watch?: false) }
@@ -8,7 +11,7 @@ describe NotificationsHelper do
before { notification.stub(disabled?: true) }
it "has a red icon" do
- notification_icon(notification).should match('class="fa fa-volume-off ns-mute"')
+ expect(notification_icon(notification)).to match('class="fa fa-volume-off ns-mute"')
end
end
@@ -16,7 +19,7 @@ describe NotificationsHelper do
before { notification.stub(participating?: true) }
it "has a blue icon" do
- notification_icon(notification).should match('class="fa fa-volume-down ns-part"')
+ expect(notification_icon(notification)).to match('class="fa fa-volume-down ns-part"')
end
end
@@ -24,12 +27,12 @@ describe NotificationsHelper do
before { notification.stub(watch?: true) }
it "has a green icon" do
- notification_icon(notification).should match('class="fa fa-volume-up ns-watch"')
+ expect(notification_icon(notification)).to match('class="fa fa-volume-up ns-watch"')
end
end
it "has a blue icon" do
- notification_icon(notification).should match('class="fa fa-circle-o ns-default"')
+ expect(notification_icon(notification)).to match('class="fa fa-circle-o ns-default"')
end
end
end
diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb
new file mode 100644
index 0000000000..088c342fa1
--- /dev/null
+++ b/spec/helpers/oauth_helper_spec.rb
@@ -0,0 +1,20 @@
+require "spec_helper"
+
+describe OauthHelper do
+ describe "additional_providers" do
+ it 'returns all enabled providers' do
+ allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github] }
+ expect(helper.additional_providers).to include(*[:twitter, :github])
+ end
+
+ it 'does not return ldap provider' do
+ allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] }
+ expect(helper.additional_providers).to include(:twitter)
+ end
+
+ it 'returns empty array' do
+ allow(helper).to receive(:enabled_oauth_providers) { [] }
+ expect(helper.additional_providers).to eq([])
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 114058e309..0f78725e3d 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -1,23 +1,11 @@
require 'spec_helper'
describe ProjectsHelper do
- describe '#project_issues_trackers' do
- it "returns the correct issues trackers available" do
- project_issues_trackers.should ==
- "Redmine \n" \
- "GitLab "
- end
-
- it "returns the correct issues trackers available with current tracker 'gitlab' selected" do
- project_issues_trackers('gitlab').should ==
- "Redmine \n" \
- "GitLab "
- end
-
- it "returns the correct issues trackers available with current tracker 'redmine' selected" do
- project_issues_trackers('redmine').should ==
- "Redmine \n" \
- "GitLab "
+ describe "#project_status_css_class" do
+ it "returns appropriate class" do
+ expect(project_status_css_class("started")).to eq("active")
+ expect(project_status_css_class("failed")).to eq("danger")
+ expect(project_status_css_class("finished")).to eq("success")
end
end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 733f275472..b327f4f911 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -13,7 +13,7 @@ describe SearchHelper do
end
it "it returns nil" do
- search_autocomplete_opts("q").should be_nil
+ expect(search_autocomplete_opts("q")).to be_nil
end
end
@@ -25,29 +25,29 @@ describe SearchHelper do
end
it "includes Help sections" do
- search_autocomplete_opts("hel").size.should == 9
+ expect(search_autocomplete_opts("hel").size).to eq(9)
end
it "includes default sections" do
- search_autocomplete_opts("adm").size.should == 1
+ expect(search_autocomplete_opts("adm").size).to eq(1)
end
it "includes the user's groups" do
create(:group).add_owner(user)
- search_autocomplete_opts("gro").size.should == 1
+ expect(search_autocomplete_opts("gro").size).to eq(1)
end
it "includes the user's projects" do
project = create(:project, namespace: create(:namespace, owner: user))
- search_autocomplete_opts(project.name).size.should == 1
+ expect(search_autocomplete_opts(project.name).size).to eq(1)
end
context "with a current project" do
before { @project = create(:project) }
it "includes project-specific sections" do
- search_autocomplete_opts("Files").size.should == 1
- search_autocomplete_opts("Commits").size.should == 1
+ expect(search_autocomplete_opts("Files").size).to eq(1)
+ expect(search_autocomplete_opts("Commits").size).to eq(1)
end
end
end
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index 41c9f038c2..aef1108e33 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -19,28 +19,28 @@ describe SubmoduleHelper do
Gitlab.config.gitlab_shell.stub(ssh_port: 22) # set this just to be sure
Gitlab.config.gitlab_shell.stub(ssh_path_prefix: Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url([ config.user, '@', config.host, ':gitlab-org/gitlab-ce.git' ].join(''))
- submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ]
+ expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
it 'should detect ssh on non-standard port' do
Gitlab.config.gitlab_shell.stub(ssh_port: 2222)
Gitlab.config.gitlab_shell.stub(ssh_path_prefix: Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url([ 'ssh://', config.user, '@', config.host, ':2222/gitlab-org/gitlab-ce.git' ].join(''))
- submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ]
+ expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
it 'should detect http on standard port' do
Gitlab.config.gitlab.stub(port: 80)
Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url))
stub_url([ 'http://', config.host, '/gitlab-org/gitlab-ce.git' ].join(''))
- submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ]
+ expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
it 'should detect http on non-standard port' do
Gitlab.config.gitlab.stub(port: 3000)
Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url))
stub_url([ 'http://', config.host, ':3000/gitlab-org/gitlab-ce.git' ].join(''))
- submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ]
+ expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
it 'should work with relative_url_root' do
@@ -48,67 +48,67 @@ describe SubmoduleHelper do
Gitlab.config.gitlab.stub(relative_url_root: '/gitlab/root')
Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url))
stub_url([ 'http://', config.host, '/gitlab/root/gitlab-org/gitlab-ce.git' ].join(''))
- submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ]
+ expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
end
context 'submodule on github.com' do
it 'should detect ssh' do
stub_url('git@github.com:gitlab-org/gitlab-ce.git')
- submodule_links(submodule_item).should == [ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ]
+ expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ])
end
it 'should detect http' do
stub_url('http://github.com/gitlab-org/gitlab-ce.git')
- submodule_links(submodule_item).should == [ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ]
+ expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ])
end
it 'should detect https' do
stub_url('https://github.com/gitlab-org/gitlab-ce.git')
- submodule_links(submodule_item).should == [ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ]
+ expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ])
end
it 'should return original with non-standard url' do
stub_url('http://github.com/gitlab-org/gitlab-ce')
- submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ]
+ expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
stub_url('http://github.com/another/gitlab-org/gitlab-ce.git')
- submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ]
+ expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
end
end
context 'submodule on gitlab.com' do
it 'should detect ssh' do
stub_url('git@gitlab.com:gitlab-org/gitlab-ce.git')
- submodule_links(submodule_item).should == [ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ]
+ expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ])
end
it 'should detect http' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git')
- submodule_links(submodule_item).should == [ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ]
+ expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ])
end
it 'should detect https' do
stub_url('https://gitlab.com/gitlab-org/gitlab-ce.git')
- submodule_links(submodule_item).should == [ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ]
+ expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ])
end
it 'should return original with non-standard url' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce')
- submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ]
+ expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
stub_url('http://gitlab.com/another/gitlab-org/gitlab-ce.git')
- submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ]
+ expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
end
end
context 'submodule on unsupported' do
it 'should return original' do
stub_url('http://mygitserver.com/gitlab-org/gitlab-ce')
- submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ]
+ expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
stub_url('http://mygitserver.com/gitlab-org/gitlab-ce.git')
- submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ]
+ expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
end
end
end
diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb
index fa8a3f554f..fc0ceecfbe 100644
--- a/spec/helpers/tab_helper_spec.rb
+++ b/spec/helpers/tab_helper_spec.rb
@@ -5,40 +5,40 @@ describe TabHelper do
describe 'nav_link' do
before do
- controller.stub(:controller_name).and_return('foo')
+ allow(controller).to receive(:controller_name).and_return('foo')
allow(self).to receive(:action_name).and_return('foo')
end
it "captures block output" do
- nav_link { "Testing Blocks" }.should match(/Testing Blocks/)
+ expect(nav_link { "Testing Blocks" }).to match(/Testing Blocks/)
end
it "performs checks on the current controller" do
- nav_link(controller: :foo).should match(//)
- nav_link(controller: :bar).should_not match(/active/)
- nav_link(controller: [:foo, :bar]).should match(/active/)
+ expect(nav_link(controller: :foo)).to match(/ /)
+ expect(nav_link(controller: :bar)).not_to match(/active/)
+ expect(nav_link(controller: [:foo, :bar])).to match(/active/)
end
it "performs checks on the current action" do
- nav_link(action: :foo).should match(/ /)
- nav_link(action: :bar).should_not match(/active/)
- nav_link(action: [:foo, :bar]).should match(/active/)
+ expect(nav_link(action: :foo)).to match(/ /)
+ expect(nav_link(action: :bar)).not_to match(/active/)
+ expect(nav_link(action: [:foo, :bar])).to match(/active/)
end
it "performs checks on both controller and action when both are present" do
- nav_link(controller: :bar, action: :foo).should_not match(/active/)
- nav_link(controller: :foo, action: :bar).should_not match(/active/)
- nav_link(controller: :foo, action: :foo).should match(/active/)
+ expect(nav_link(controller: :bar, action: :foo)).not_to match(/active/)
+ expect(nav_link(controller: :foo, action: :bar)).not_to match(/active/)
+ expect(nav_link(controller: :foo, action: :foo)).to match(/active/)
end
it "accepts a path shorthand" do
- nav_link(path: 'foo#bar').should_not match(/active/)
- nav_link(path: 'foo#foo').should match(/active/)
+ expect(nav_link(path: 'foo#bar')).not_to match(/active/)
+ expect(nav_link(path: 'foo#foo')).to match(/active/)
end
it "passes extra html options to the list element" do
- nav_link(action: :foo, html_options: {class: 'home'}).should match(/ /)
- nav_link(html_options: {class: 'active'}).should match(/ /)
+ expect(nav_link(action: :foo, html_options: {class: 'home'})).to match(/ /)
+ expect(nav_link(html_options: {class: 'active'})).to match(/ /)
end
end
end
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
new file mode 100644
index 0000000000..8271e00f41
--- /dev/null
+++ b/spec/helpers/tree_helper_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe TreeHelper do
+ describe 'flatten_tree' do
+ let(:project) { create(:project) }
+
+ before {
+ @repository = project.repository
+ @commit = project.repository.commit("e56497bb")
+ }
+
+ context "on a directory containing more than one file/directory" do
+ let(:tree_item) { double(name: "files", path: "files") }
+
+ it "should return the directory name" do
+ expect(flatten_tree(tree_item)).to match('files')
+ end
+ end
+
+ context "on a directory containing only one directory" do
+ let(:tree_item) { double(name: "foo", path: "foo") }
+
+ it "should return the flattened path" do
+ expect(flatten_tree(tree_item)).to match('foo/bar')
+ end
+ end
+ end
+end
diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb
new file mode 100644
index 0000000000..06d5450688
--- /dev/null
+++ b/spec/lib/disable_email_interceptor_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe DisableEmailInterceptor do
+ before do
+ ActionMailer::Base.register_interceptor(DisableEmailInterceptor)
+ end
+
+ it 'should not send emails' do
+ allow(Gitlab.config.gitlab).to receive(:email_enabled).and_return(false)
+ expect {
+ deliver_mail
+ }.not_to change(ActionMailer::Base.deliveries, :count)
+ end
+
+ after do
+ # Removing interceptor from the list because unregister_interceptor is
+ # implemented in later version of mail gem
+ # See: https://github.com/mikel/mail/pull/705
+ Mail.class_variable_set(:@@delivery_interceptors, [])
+ end
+
+ def deliver_mail
+ key = create :personal_key
+ Notify.new_ssh_key_email(key.id)
+ end
+end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 7b3818ea5c..ac602eac15 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -14,44 +14,46 @@ describe ExtractsPath do
describe '#extract_ref' do
it "returns an empty pair when no @project is set" do
@project = nil
- extract_ref('master/CHANGELOG').should == ['', '']
+ expect(extract_ref('master/CHANGELOG')).to eq(['', ''])
end
context "without a path" do
it "extracts a valid branch" do
- extract_ref('master').should == ['master', '']
+ expect(extract_ref('master')).to eq(['master', ''])
end
it "extracts a valid tag" do
- extract_ref('v2.0.0').should == ['v2.0.0', '']
+ expect(extract_ref('v2.0.0')).to eq(['v2.0.0', ''])
end
it "extracts a valid commit ref without a path" do
- extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062').should ==
+ expect(extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062')).to eq(
['f4b14494ef6abf3d144c28e4af0c20143383e062', '']
+ )
end
it "falls back to a primitive split for an invalid ref" do
- extract_ref('stable').should == ['stable', '']
+ expect(extract_ref('stable')).to eq(['stable', ''])
end
end
context "with a path" do
it "extracts a valid branch" do
- extract_ref('foo/bar/baz/CHANGELOG').should == ['foo/bar/baz', 'CHANGELOG']
+ expect(extract_ref('foo/bar/baz/CHANGELOG')).to eq(['foo/bar/baz', 'CHANGELOG'])
end
it "extracts a valid tag" do
- extract_ref('v2.0.0/CHANGELOG').should == ['v2.0.0', 'CHANGELOG']
+ expect(extract_ref('v2.0.0/CHANGELOG')).to eq(['v2.0.0', 'CHANGELOG'])
end
it "extracts a valid commit SHA" do
- extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG').should ==
+ expect(extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG')).to eq(
['f4b14494ef6abf3d144c28e4af0c20143383e062', 'CHANGELOG']
+ )
end
it "falls back to a primitive split for an invalid ref" do
- extract_ref('stable/CHANGELOG').should == ['stable', 'CHANGELOG']
+ expect(extract_ref('stable/CHANGELOG')).to eq(['stable', 'CHANGELOG'])
end
end
end
diff --git a/spec/lib/git_ref_validator_spec.rb b/spec/lib/git_ref_validator_spec.rb
index b2469c1839..4633b6f393 100644
--- a/spec/lib/git_ref_validator_spec.rb
+++ b/spec/lib/git_ref_validator_spec.rb
@@ -1,20 +1,20 @@
require 'spec_helper'
describe Gitlab::GitRefValidator do
- it { Gitlab::GitRefValidator.validate('feature/new').should be_true }
- it { Gitlab::GitRefValidator.validate('implement_@all').should be_true }
- it { Gitlab::GitRefValidator.validate('my_new_feature').should be_true }
- it { Gitlab::GitRefValidator.validate('#1').should be_true }
- it { Gitlab::GitRefValidator.validate('feature/~new/').should be_false }
- it { Gitlab::GitRefValidator.validate('feature/^new/').should be_false }
- it { Gitlab::GitRefValidator.validate('feature/:new/').should be_false }
- it { Gitlab::GitRefValidator.validate('feature/?new/').should be_false }
- it { Gitlab::GitRefValidator.validate('feature/*new/').should be_false }
- it { Gitlab::GitRefValidator.validate('feature/[new/').should be_false }
- it { Gitlab::GitRefValidator.validate('feature/new/').should be_false }
- it { Gitlab::GitRefValidator.validate('feature/new.').should be_false }
- it { Gitlab::GitRefValidator.validate('feature\@{').should be_false }
- it { Gitlab::GitRefValidator.validate('feature\new').should be_false }
- it { Gitlab::GitRefValidator.validate('feature//new').should be_false }
- it { Gitlab::GitRefValidator.validate('feature new').should be_false }
+ it { expect(Gitlab::GitRefValidator.validate('feature/new')).to be_truthy }
+ it { expect(Gitlab::GitRefValidator.validate('implement_@all')).to be_truthy }
+ it { expect(Gitlab::GitRefValidator.validate('my_new_feature')).to be_truthy }
+ it { expect(Gitlab::GitRefValidator.validate('#1')).to be_truthy }
+ it { expect(Gitlab::GitRefValidator.validate('feature/~new/')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature/^new/')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature/:new/')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature/?new/')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature/*new/')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature/[new/')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature/new/')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature/new.')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature\@{')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature\new')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature//new')).to be_falsey }
+ it { expect(Gitlab::GitRefValidator.validate('feature new')).to be_falsey }
end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 551fb3fb5f..95fc7e16a1 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -10,13 +10,21 @@ describe Gitlab::Auth do
password: password,
password_confirmation: password)
end
- let(:username) { 'john' }
+ let(:username) { 'John' } # username isn't lowercase, test this
let(:password) { 'my-secret' }
it "should find user by valid login/password" do
expect( gl_auth.find(username, password) ).to eql user
end
+ it 'should find user by valid email/password with case-insensitive email' do
+ expect(gl_auth.find(user.email.upcase, password)).to eql user
+ end
+
+ it 'should find user by valid username/password with case-insensitive username' do
+ expect(gl_auth.find(username.upcase, password)).to eql user
+ end
+
it "should not find user with invalid password" do
password = 'wrong'
expect( gl_auth.find(username, password) ).to_not eql user
@@ -28,17 +36,16 @@ describe Gitlab::Auth do
end
context "with ldap enabled" do
- before { Gitlab.config.ldap['enabled'] = true }
- after { Gitlab.config.ldap['enabled'] = false }
+ before { Gitlab::LDAP::Config.stub(enabled?: true) }
it "tries to autheticate with db before ldap" do
- expect(Gitlab::LDAP::User).not_to receive(:authenticate)
+ expect(Gitlab::LDAP::Authentication).not_to receive(:login)
gl_auth.find(username, password)
end
it "uses ldap as fallback to for authentication" do
- expect(Gitlab::LDAP::User).to receive(:authenticate)
+ expect(Gitlab::LDAP::Authentication).to receive(:login)
gl_auth.find('ldap_user', 'password')
end
diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb
new file mode 100644
index 0000000000..768312f002
--- /dev/null
+++ b/spec/lib/gitlab/backend/grack_auth_spec.rb
@@ -0,0 +1,146 @@
+require "spec_helper"
+
+describe Grack::Auth do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ let(:app) { lambda { |env| [200, {}, "Success!"] } }
+ let!(:auth) { Grack::Auth.new(app) }
+ let(:env) {
+ {
+ "rack.input" => "",
+ "REQUEST_METHOD" => "GET",
+ "QUERY_STRING" => "service=git-upload-pack"
+ }
+ }
+ let(:status) { auth.call(env).first }
+
+ describe "#call" do
+ context "when the project doesn't exist" do
+ before do
+ env["PATH_INFO"] = "doesnt/exist.git"
+ end
+
+ context "when no authentication is provided" do
+ it "responds with status 401" do
+ expect(status).to eq(401)
+ end
+ end
+
+ context "when username and password are provided" do
+ context "when authentication fails" do
+ before do
+ env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, "nope")
+ end
+
+ it "responds with status 401" do
+ expect(status).to eq(401)
+ end
+ end
+
+ context "when authentication succeeds" do
+ before do
+ env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
+ end
+
+ it "responds with status 404" do
+ expect(status).to eq(404)
+ end
+ end
+ end
+ end
+
+ context "when the project exists" do
+ before do
+ env["PATH_INFO"] = project.path_with_namespace + ".git"
+ end
+
+ context "when the project is public" do
+ before do
+ project.update_attribute(:visibility_level, Project::PUBLIC)
+ end
+
+ it "responds with status 200" do
+ expect(status).to eq(200)
+ end
+ end
+
+ context "when the project is private" do
+ before do
+ project.update_attribute(:visibility_level, Project::PRIVATE)
+ end
+
+ context "when no authentication is provided" do
+ it "responds with status 401" do
+ expect(status).to eq(401)
+ end
+ end
+
+ context "when username and password are provided" do
+ context "when authentication fails" do
+ before do
+ env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, "nope")
+ end
+
+ it "responds with status 401" do
+ expect(status).to eq(401)
+ end
+ end
+
+ context "when authentication succeeds" do
+ before do
+ env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
+ end
+
+ context "when the user has access to the project" do
+ before do
+ project.team << [user, :master]
+ end
+
+ context "when the user is blocked" do
+ before do
+ user.block
+ project.team << [user, :master]
+ end
+
+ it "responds with status 404" do
+ expect(status).to eq(404)
+ end
+ end
+
+ context "when the user isn't blocked" do
+ it "responds with status 200" do
+ expect(status).to eq(200)
+ end
+ end
+ end
+
+ context "when the user doesn't have access to the project" do
+ it "responds with status 404" do
+ expect(status).to eq(404)
+ end
+ end
+ end
+ end
+
+ context "when a gitlab ci token is provided" do
+ let(:token) { "123" }
+
+ before do
+ gitlab_ci_service = project.build_gitlab_ci_service
+ gitlab_ci_service.active = true
+ gitlab_ci_service.token = token
+ gitlab_ci_service.project_url = "http://google.com"
+ gitlab_ci_service.save
+
+ env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials("gitlab-ci-token", token)
+ end
+
+ it "responds with status 200" do
+ expect(status).to eq(200)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index f00ec0fa40..27279465c1 100644
--- a/spec/lib/gitlab/backend/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -8,11 +8,11 @@ describe Gitlab::Shell do
Project.stub(find: project)
end
- it { should respond_to :add_key }
- it { should respond_to :remove_key }
- it { should respond_to :add_repository }
- it { should respond_to :remove_repository }
- it { should respond_to :fork_repository }
+ it { is_expected.to respond_to :add_key }
+ it { is_expected.to respond_to :remove_key }
+ it { is_expected.to respond_to :add_repository }
+ it { is_expected.to respond_to :remove_repository }
+ it { is_expected.to respond_to :fork_repository }
- it { gitlab_shell.url_to_repo('diaspora').should == Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git" }
+ it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") }
end
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
new file mode 100644
index 0000000000..f552310584
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Gitlab::BitbucketImport::ProjectCreator do
+ let(:user) { create(:user, bitbucket_access_token: "asdffg", bitbucket_access_token_secret: "sekret") }
+ let(:repo) { {
+ name: 'Vim',
+ slug: 'vim',
+ is_private: true,
+ owner: "asd"}.with_indifferent_access
+ }
+ let(:namespace){ create(:namespace) }
+
+ it 'creates project' do
+ allow_any_instance_of(Project).to receive(:add_import_job)
+
+ project_creator = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, user)
+ project = project_creator.execute
+
+ expect(project.import_url).to eq("ssh://git@bitbucket.org/asd/vim.git")
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+end
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
new file mode 100644
index 0000000000..c96ee78e5f
--- /dev/null
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -0,0 +1,174 @@
+require 'spec_helper'
+
+describe Gitlab::ClosingIssueExtractor do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:iid1) { issue.iid }
+
+ describe :closed_by_message_in_project do
+ context 'with a single reference' do
+ it do
+ message = "Awesome commit (Closes ##{iid1})"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Awesome commit (closes ##{iid1})"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Closed ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "closed ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Closing ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "closing ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Close ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "close ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Awesome commit (Fixes ##{iid1})"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Awesome commit (fixes ##{iid1})"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Fixed ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "fixed ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Fixing ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "fixing ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Fix ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "fix ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Awesome commit (Resolves ##{iid1})"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Awesome commit (resolves ##{iid1})"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Resolved ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "resolved ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Resolving ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "resolving ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "Resolve ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+
+ it do
+ message = "resolve ##{iid1}"
+ expect(subject.closed_by_message_in_project(message, project)).to eq([issue])
+ end
+ end
+
+ context 'with multiple references' do
+ let(:other_issue) { create(:issue, project: project) }
+ let(:third_issue) { create(:issue, project: project) }
+ let(:iid2) { other_issue.iid }
+ let(:iid3) { third_issue.iid }
+
+ it 'fetches issues in single line message' do
+ message = "Closes ##{iid1} and fix ##{iid2}"
+
+ expect(subject.closed_by_message_in_project(message, project)).
+ to eq([issue, other_issue])
+ end
+
+ it 'fetches comma-separated issues references in single line message' do
+ message = "Closes ##{iid1}, closes ##{iid2}"
+
+ expect(subject.closed_by_message_in_project(message, project)).
+ to eq([issue, other_issue])
+ end
+
+ it 'fetches comma-separated issues numbers in single line message' do
+ message = "Closes ##{iid1}, ##{iid2} and ##{iid3}"
+
+ expect(subject.closed_by_message_in_project(message, project)).
+ to eq([issue, other_issue, third_issue])
+ end
+
+ it 'fetches issues in multi-line message' do
+ message = "Awesome commit (closes ##{iid1})\nAlso fixes ##{iid2}"
+
+ expect(subject.closed_by_message_in_project(message, project)).
+ to eq([issue, other_issue])
+ end
+
+ it 'fetches issues in hybrid message' do
+ message = "Awesome commit (closes ##{iid1})\n"\
+ "Also fixing issues ##{iid2}, ##{iid3} and #4"
+
+ expect(subject.closed_by_message_in_project(message, project)).
+ to eq([issue, other_issue, third_issue])
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index cf0b5c282c..40eb45e37c 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -11,11 +11,11 @@ describe Gitlab::Diff::File do
describe :diff_lines do
let(:diff_lines) { diff_file.diff_lines }
- it { diff_lines.size.should == 30 }
- it { diff_lines.first.should be_kind_of(Gitlab::Diff::Line) }
+ it { expect(diff_lines.size).to eq(30) }
+ it { expect(diff_lines.first).to be_kind_of(Gitlab::Diff::Line) }
end
describe :mode_changed? do
- it { diff_file.mode_changed?.should be_false }
+ it { expect(diff_file.mode_changed?).to be_falsey }
end
end
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index 35b78260ac..918f6d0ead 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -50,43 +50,43 @@ eos
@lines = parser.parse(diff.lines)
end
- it { @lines.size.should == 30 }
+ it { expect(@lines.size).to eq(30) }
describe 'lines' do
describe 'first line' do
let(:line) { @lines.first }
- it { line.type.should == 'match' }
- it { line.old_pos.should == 6 }
- it { line.new_pos.should == 6 }
- it { line.text.should == '@@ -6,12 +6,18 @@ module Popen' }
+ it { expect(line.type).to eq('match') }
+ it { expect(line.old_pos).to eq(6) }
+ it { expect(line.new_pos).to eq(6) }
+ it { expect(line.text).to eq('@@ -6,12 +6,18 @@ module Popen') }
end
describe 'removal line' do
let(:line) { @lines[10] }
- it { line.type.should == 'old' }
- it { line.old_pos.should == 14 }
- it { line.new_pos.should == 13 }
- it { line.text.should == '- options = { chdir: path }' }
+ it { expect(line.type).to eq('old') }
+ it { expect(line.old_pos).to eq(14) }
+ it { expect(line.new_pos).to eq(13) }
+ it { expect(line.text).to eq('- options = { chdir: path }') }
end
describe 'addition line' do
let(:line) { @lines[16] }
- it { line.type.should == 'new' }
- it { line.old_pos.should == 15 }
- it { line.new_pos.should == 18 }
- it { line.text.should == '+ options = {' }
+ it { expect(line.type).to eq('new') }
+ it { expect(line.old_pos).to eq(15) }
+ it { expect(line.new_pos).to eq(18) }
+ it { expect(line.text).to eq('+ options = {') }
end
describe 'unchanged line' do
let(:line) { @lines.last }
- it { line.type.should == nil }
- it { line.old_pos.should == 24 }
- it { line.new_pos.should == 31 }
- it { line.text.should == ' @cmd_output << stderr.read' }
+ it { expect(line.type).to eq(nil) }
+ it { expect(line.old_pos).to eq(24) }
+ it { expect(line.new_pos).to eq(31) }
+ it { expect(line.text).to eq(' @cmd_output << stderr.read') }
end
end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 570b03827a..666398eedd 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -5,14 +5,76 @@ describe Gitlab::GitAccess do
let(:project) { create(:project) }
let(:user) { create(:user) }
- describe 'download_allowed?' do
+ describe 'can_push_to_branch?' do
+ describe 'push to none protected branch' do
+ it "returns true if user is a master" do
+ project.team << [user, :master]
+ expect(Gitlab::GitAccess.can_push_to_branch?(user, project, "random_branch")).to be_truthy
+ end
+
+ it "returns true if user is a developer" do
+ project.team << [user, :developer]
+ expect(Gitlab::GitAccess.can_push_to_branch?(user, project, "random_branch")).to be_truthy
+ end
+
+ it "returns false if user is a reporter" do
+ project.team << [user, :reporter]
+ expect(Gitlab::GitAccess.can_push_to_branch?(user, project, "random_branch")).to be_falsey
+ end
+ end
+
+ describe 'push to protected branch' do
+ before do
+ @branch = create :protected_branch, project: project
+ end
+
+ it "returns true if user is a master" do
+ project.team << [user, :master]
+ expect(Gitlab::GitAccess.can_push_to_branch?(user, project, @branch.name)).to be_truthy
+ end
+
+ it "returns false if user is a developer" do
+ project.team << [user, :developer]
+ expect(Gitlab::GitAccess.can_push_to_branch?(user, project, @branch.name)).to be_falsey
+ end
+
+ it "returns false if user is a reporter" do
+ project.team << [user, :reporter]
+ expect(Gitlab::GitAccess.can_push_to_branch?(user, project, @branch.name)).to be_falsey
+ end
+ end
+
+ describe 'push to protected branch if allowed for developers' do
+ before do
+ @branch = create :protected_branch, project: project, developers_can_push: true
+ end
+
+ it "returns true if user is a master" do
+ project.team << [user, :master]
+ expect(Gitlab::GitAccess.can_push_to_branch?(user, project, @branch.name)).to be_truthy
+ end
+
+ it "returns true if user is a developer" do
+ project.team << [user, :developer]
+ expect(Gitlab::GitAccess.can_push_to_branch?(user, project, @branch.name)).to be_truthy
+ end
+
+ it "returns false if user is a reporter" do
+ project.team << [user, :reporter]
+ expect(Gitlab::GitAccess.can_push_to_branch?(user, project, @branch.name)).to be_falsey
+ end
+ end
+
+ end
+
+ describe 'download_access_check' do
describe 'master permissions' do
before { project.team << [user, :master] }
context 'pull code' do
- subject { access.download_allowed?(user, project) }
+ subject { access.download_access_check(user, project) }
- it { should be_true }
+ it { expect(subject.allowed?).to be_truthy }
end
end
@@ -20,9 +82,9 @@ describe Gitlab::GitAccess do
before { project.team << [user, :guest] }
context 'pull code' do
- subject { access.download_allowed?(user, project) }
+ subject { access.download_access_check(user, project) }
- it { should be_false }
+ it { expect(subject.allowed?).to be_falsey }
end
end
@@ -33,34 +95,54 @@ describe Gitlab::GitAccess do
end
context 'pull code' do
- subject { access.download_allowed?(user, project) }
+ subject { access.download_access_check(user, project) }
- it { should be_false }
+ it { expect(subject.allowed?).to be_falsey }
end
end
describe 'without acccess to project' do
context 'pull code' do
- subject { access.download_allowed?(user, project) }
+ subject { access.download_access_check(user, project) }
- it { should be_false }
+ it { expect(subject.allowed?).to be_falsey }
+ end
+ end
+
+ describe 'deploy key permissions' do
+ let(:key) { create(:deploy_key) }
+
+ context 'pull code' do
+ context 'allowed' do
+ before { key.projects << project }
+ subject { access.download_access_check(key, project) }
+
+ it { expect(subject.allowed?).to be_truthy }
+ end
+
+ context 'denied' do
+ subject { access.download_access_check(key, project) }
+
+ it { expect(subject.allowed?).to be_falsey }
+ end
end
end
end
- describe 'push_allowed?' do
+ describe 'push_access_check' do
def protect_feature_branch
create(:protected_branch, name: 'feature', project: project)
end
def changes
{
- push_new_branch: '000000000 570e7b2ab refs/heads/wow',
+ push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
push_master: '6f6d7e7ed 570e7b2ab refs/heads/master',
push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature',
- push_remove_protected_branch: '570e7b2ab 000000000 refs/heads/feature',
+ push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\
+ 'refs/heads/feature',
push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0',
- push_new_tag: '000000000 570e7b2ab refs/tags/v7.8.9',
+ push_new_tag: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/tags/v7.8.9",
push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature']
}
end
@@ -109,6 +191,13 @@ describe Gitlab::GitAccess do
}
end
+ def self.updated_permissions_matrix
+ updated_permissions_matrix = permissions_matrix.dup
+ updated_permissions_matrix[:developer][:push_protected_branch] = true
+ updated_permissions_matrix[:developer][:push_all] = true
+ updated_permissions_matrix
+ end
+
permissions_matrix.keys.each do |role|
describe "#{role} access" do
before { protect_feature_branch }
@@ -116,9 +205,26 @@ describe Gitlab::GitAccess do
permissions_matrix[role].each do |action, allowed|
context action do
- subject { access.push_allowed?(user, project, changes[action]) }
+ subject { access.push_access_check(user, project, changes[action]) }
- it { should allowed ? be_true : be_false }
+ it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
+ end
+ end
+ end
+ end
+
+ context "with enabled developers push to protected branches " do
+ updated_permissions_matrix.keys.each do |role|
+ describe "#{role} access" do
+ before { create(:protected_branch, name: 'feature', developers_can_push: true, project: project) }
+ before { project.team << [user, role] }
+
+ updated_permissions_matrix[role].each do |action, allowed|
+ context action do
+ subject { access.push_access_check(user, project, changes[action]) }
+
+ it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
+ end
end
end
end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index ed5785b31e..c31c676409 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -11,9 +11,9 @@ describe Gitlab::GitAccessWiki do
project.team << [user, :developer]
end
- subject { access.push_allowed?(user, project, changes) }
+ subject { access.push_access_check(user, project, changes) }
- it { should be_true }
+ it { expect(subject.allowed?).to be_truthy }
end
def changes
diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb
new file mode 100644
index 0000000000..8d594a112d
--- /dev/null
+++ b/spec/lib/gitlab/github_import/project_creator_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::ProjectCreator do
+ let(:user) { create(:user, github_access_token: "asdffg") }
+ let(:repo) { OpenStruct.new(
+ login: 'vim',
+ name: 'vim',
+ private: true,
+ full_name: 'asd/vim',
+ clone_url: "https://gitlab.com/asd/vim.git",
+ owner: OpenStruct.new(login: "john"))
+ }
+ let(:namespace){ create(:namespace) }
+
+ it 'creates project' do
+ allow_any_instance_of(Project).to receive(:add_import_job)
+
+ project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user)
+ project = project_creator.execute
+
+ expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git")
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+end
diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
new file mode 100644
index 0000000000..4c0d64ed13
--- /dev/null
+++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::GitlabImport::ProjectCreator do
+ let(:user) { create(:user, gitlab_access_token: "asdffg") }
+ let(:repo) { {
+ name: 'vim',
+ path: 'vim',
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE,
+ path_with_namespace: 'asd/vim',
+ http_url_to_repo: "https://gitlab.com/asd/vim.git",
+ owner: {name: "john"}}.with_indifferent_access
+ }
+ let(:namespace){ create(:namespace) }
+
+ it 'creates project' do
+ allow_any_instance_of(Project).to receive(:add_import_job)
+
+ project_creator = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, user)
+ project = project_creator.execute
+
+ expect(project.import_url).to eq("https://oauth2:asdffg@gitlab.com/asd/vim.git")
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+end
diff --git a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb b/spec/lib/gitlab/gitlab_markdown_helper_spec.rb
index 540618a560..ab613193f4 100644
--- a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb
+++ b/spec/lib/gitlab/gitlab_markdown_helper_spec.rb
@@ -5,24 +5,24 @@ describe Gitlab::MarkdownHelper do
%w(textile rdoc org creole wiki
mediawiki rst adoc asciidoc asc).each do |type|
it "returns true for #{type} files" do
- Gitlab::MarkdownHelper.markup?("README.#{type}").should be_true
+ expect(Gitlab::MarkdownHelper.markup?("README.#{type}")).to be_truthy
end
end
it 'returns false when given a non-markup filename' do
- Gitlab::MarkdownHelper.markup?('README.rb').should_not be_true
+ expect(Gitlab::MarkdownHelper.markup?('README.rb')).not_to be_truthy
end
end
describe '#gitlab_markdown?' do
%w(mdown md markdown).each do |type|
it "returns true for #{type} files" do
- Gitlab::MarkdownHelper.gitlab_markdown?("README.#{type}").should be_true
+ expect(Gitlab::MarkdownHelper.gitlab_markdown?("README.#{type}")).to be_truthy
end
end
it 'returns false when given a non-markdown filename' do
- Gitlab::MarkdownHelper.gitlab_markdown?('README.rb').should_not be_true
+ expect(Gitlab::MarkdownHelper.gitlab_markdown?('README.rb')).not_to be_truthy
end
end
end
diff --git a/spec/lib/gitlab/gitorious_import/project_creator.rb b/spec/lib/gitlab/gitorious_import/project_creator.rb
new file mode 100644
index 0000000000..cf2318bb3a
--- /dev/null
+++ b/spec/lib/gitlab/gitorious_import/project_creator.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::GitoriousImport::ProjectCreator do
+ let(:user) { create(:user) }
+ let(:repo) { Gitlab::GitoriousImport::Repository.new('foo/bar-baz-qux') }
+ let(:namespace){ create(:namespace) }
+
+ it 'creates project' do
+ allow_any_instance_of(Project).to receive(:add_import_job)
+
+ project_creator = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, user)
+ project_creator.execute
+ project = Project.last
+
+ expect(project.name).to eq("Bar Baz Qux")
+ expect(project.path).to eq("bar-baz-qux")
+ expect(project.namespace).to eq(namespace)
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ expect(project.import_type).to eq("gitorious")
+ expect(project.import_source).to eq("foo/bar-baz-qux")
+ expect(project.import_url).to eq("https://gitorious.org/foo/bar-baz-qux.git")
+ end
+end
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index d50f605e05..707a0521ab 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -1,16 +1,16 @@
require 'spec_helper'
describe Gitlab::LDAP::Access do
- let(:access) { Gitlab::LDAP::Access.new }
- let(:user) { create(:user) }
+ let(:access) { Gitlab::LDAP::Access.new user }
+ let(:user) { create(:omniauth_user) }
describe :allowed? do
- subject { access.allowed?(user) }
+ subject { access.allowed? }
context 'when the user cannot be found' do
before { Gitlab::LDAP::Person.stub(find_by_dn: nil) }
- it { should be_false }
+ it { is_expected.to be_falsey }
end
context 'when the user is found' do
@@ -19,28 +19,36 @@ describe Gitlab::LDAP::Access do
context 'and the user is diabled via active directory' do
before { Gitlab::LDAP::Person.stub(disabled_via_active_directory?: true) }
- it { should be_false }
+ it { is_expected.to be_falsey }
+
+ it "should block user in GitLab" do
+ access.allowed?
+ user.should be_blocked
+ end
end
context 'and has no disabled flag in active diretory' do
- before { Gitlab::LDAP::Person.stub(disabled_via_active_directory?: false) }
-
- it { should be_true }
- end
-
- context 'and has no disabled flag in active diretory' do
- before {
+ before do
+ user.block
+
Gitlab::LDAP::Person.stub(disabled_via_active_directory?: false)
- Gitlab.config.ldap['enabled'] = true
- Gitlab.config.ldap['active_directory'] = false
- }
+ end
- after {
- Gitlab.config.ldap['enabled'] = false
- Gitlab.config.ldap['active_directory'] = true
- }
+ it { is_expected.to be_truthy }
- it { should be_false }
+ it "should unblock user in GitLab" do
+ access.allowed?
+ user.should_not be_blocked
+ end
+ end
+
+ context 'without ActiveDirectory enabled' do
+ before do
+ Gitlab::LDAP::Config.stub(enabled?: true)
+ Gitlab::LDAP::Config.any_instance.stub(active_directory: false)
+ end
+
+ it { is_expected.to be_truthy }
end
end
end
diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb
index c3f0733443..b609e4b38f 100644
--- a/spec/lib/gitlab/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/ldap/adapter_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::LDAP::Adapter do
- let(:adapter) { Gitlab::LDAP::Adapter.new }
+ let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' }
describe :dn_matches_filter? do
let(:ldap) { double(:ldap) }
@@ -12,20 +12,20 @@ describe Gitlab::LDAP::Adapter do
context "and the result is non-empty" do
before { ldap.stub(search: [:foo]) }
- it { should be_true }
+ it { is_expected.to be_truthy }
end
context "and the result is empty" do
before { ldap.stub(search: []) }
- it { should be_false }
+ it { is_expected.to be_falsey }
end
end
context "when the search encounters an error" do
before { ldap.stub(search: nil, get_operation_result: double(code: 1, message: 'some error')) }
- it { should be_false }
+ it { is_expected.to be_falsey }
end
end
end
diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb
new file mode 100644
index 0000000000..8afc2b21f4
--- /dev/null
+++ b/spec/lib/gitlab/ldap/authentication_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Gitlab::LDAP::Authentication do
+ let(:klass) { Gitlab::LDAP::Authentication }
+ let(:user) { create(:omniauth_user, extern_uid: dn) }
+ let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
+ let(:login) { 'john' }
+ let(:password) { 'password' }
+
+ describe :login do
+ let(:adapter) { double :adapter }
+ before do
+ Gitlab::LDAP::Config.stub(enabled?: true)
+ end
+
+ it "finds the user if authentication is successful" do
+ user
+ # try only to fake the LDAP call
+ klass.any_instance.stub(adapter: double(:adapter,
+ bind_as: double(:ldap_user, dn: dn)
+ ))
+ expect(klass.login(login, password)).to be_truthy
+ end
+
+ it "is false if the user does not exist" do
+ # try only to fake the LDAP call
+ klass.any_instance.stub(adapter: double(:adapter,
+ bind_as: double(:ldap_user, dn: dn)
+ ))
+ expect(klass.login(login, password)).to be_falsey
+ end
+
+ it "is false if authentication fails" do
+ user
+ # try only to fake the LDAP call
+ klass.any_instance.stub(adapter: double(:adapter, bind_as: nil))
+ expect(klass.login(login, password)).to be_falsey
+ end
+
+ it "fails if ldap is disabled" do
+ Gitlab::LDAP::Config.stub(enabled?: false)
+ expect(klass.login(login, password)).to be_falsey
+ end
+
+ it "fails if no login is supplied" do
+ expect(klass.login('', password)).to be_falsey
+ end
+
+ it "fails if no password is supplied" do
+ expect(klass.login(login, '')).to be_falsey
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
new file mode 100644
index 0000000000..2df2beca7a
--- /dev/null
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Gitlab::LDAP::Config do
+ let(:config) { Gitlab::LDAP::Config.new provider }
+ let(:provider) { 'ldapmain' }
+
+ describe '#initalize' do
+ it 'requires a provider' do
+ expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError
+ end
+
+ it "works" do
+ expect(config).to be_a described_class
+ end
+
+ it "raises an error if a unknow provider is used" do
+ expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error
+ end
+
+ context "if 'ldap' is the provider name" do
+ let(:provider) { 'ldap' }
+
+ context "and 'ldap' is not in defined as a provider" do
+ before { Gitlab::LDAP::Config.stub(providers: %w{ldapmain}) }
+
+ it "uses the first provider" do
+ # Fetch the provider_name attribute from 'options' so that we know
+ # that the 'options' Hash is not empty/nil.
+ expect(config.options['provider_name']).to eq('ldapmain')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index d232cb2075..4f93545feb 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -1,54 +1,53 @@
require 'spec_helper'
describe Gitlab::LDAP::User do
- let(:gl_user) { Gitlab::LDAP::User }
+ let(:gl_user) { Gitlab::LDAP::User.new(auth_hash) }
let(:info) do
- double(
+ {
name: 'John',
email: 'john@example.com',
nickname: 'john'
- )
+ }
+ end
+ let(:auth_hash) do
+ double(uid: 'my-uid', provider: 'ldapmain', info: double(info))
end
- before { Gitlab.config.stub(omniauth: {}) }
- describe :find_or_create do
- let(:auth) do
- double(info: info, provider: 'ldap', uid: 'my-uid')
+ describe :changed? do
+ it "marks existing ldap user as changed" do
+ existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
+ expect(gl_user.changed?).to be_truthy
end
- it "finds the user if already existing" do
- existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldap')
+ it "marks existing non-ldap user if the email matches as changed" do
+ existing_user = create(:user, email: 'john@example.com')
+ expect(gl_user.changed?).to be_truthy
+ end
- expect{ gl_user.find_or_create(auth) }.to_not change{ User.count }
+ it "dont marks existing ldap user as changed" do
+ existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain')
+ expect(gl_user.changed?).to be_falsey
+ end
+ end
+
+ describe :find_or_create do
+ it "finds the user if already existing" do
+ existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
+
+ expect{ gl_user.save }.to_not change{ User.count }
end
it "connects to existing non-ldap user if the email matches" do
- existing_user = create(:user, email: 'john@example.com')
- expect{ gl_user.find_or_create(auth) }.to_not change{ User.count }
+ existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter")
+ expect{ gl_user.save }.to_not change{ User.count }
existing_user.reload
- expect(existing_user.extern_uid).to eql 'my-uid'
- expect(existing_user.provider).to eql 'ldap'
+ expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
+ expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
end
it "creates a new user if not found" do
- expect{ gl_user.find_or_create(auth) }.to change{ User.count }.by(1)
- end
- end
-
- describe "authenticate" do
- let(:login) { 'john' }
- let(:password) { 'my-secret' }
-
- before {
- Gitlab.config.ldap['enabled'] = true
- Gitlab.config.ldap['user_filter'] = 'employeeType=developer'
- }
- after { Gitlab.config.ldap['enabled'] = false }
-
- it "send an authentication request to ldap" do
- expect( Gitlab::LDAP::User.adapter ).to receive(:bind_as)
- Gitlab::LDAP::User.authenticate(login, password)
+ expect{ gl_user.save }.to change{ User.count }.by(1)
end
end
end
diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb
new file mode 100644
index 0000000000..448cd0c688
--- /dev/null
+++ b/spec/lib/gitlab/note_data_builder_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe 'Gitlab::NoteDataBuilder' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:data) { Gitlab::NoteDataBuilder.build(note, user) }
+ let(:note_url) { Gitlab::UrlBuilder.new(:note).build(note.id) }
+ let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors
+
+ before(:each) do
+ expect(data).to have_key(:object_attributes)
+ expect(data[:object_attributes]).to have_key(:url)
+ expect(data[:object_attributes][:url]).to eq(note_url)
+ expect(data[:object_kind]).to eq('note')
+ expect(data[:user]).to eq(user.hook_attrs)
+ end
+
+ describe 'When asking for a note on commit' do
+ let(:note) { create(:note_on_commit) }
+
+ it 'returns the note and commit-specific data' do
+ expect(data).to have_key(:commit)
+ end
+ end
+
+ describe 'When asking for a note on commit diff' do
+ let(:note) { create(:note_on_commit_diff) }
+
+ it 'returns the note and commit-specific data' do
+ expect(data).to have_key(:commit)
+ end
+ end
+
+ describe 'When asking for a note on issue' do
+ let(:issue) { create(:issue, created_at: fixed_time, updated_at: fixed_time) }
+ let(:note) { create(:note_on_issue, noteable_id: issue.id) }
+
+ it 'returns the note and issue-specific data' do
+ expect(data).to have_key(:issue)
+ expect(data[:issue]).to eq(issue.hook_attrs)
+ end
+ end
+
+ describe 'When asking for a note on merge request' do
+ let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
+ let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) }
+
+ it 'returns the note and merge request data' do
+ expect(data).to have_key(:merge_request)
+ expect(data[:merge_request]).to eq(merge_request.hook_attrs)
+ end
+ end
+
+ describe 'When asking for a note on merge request diff' do
+ let(:merge_request) { create(:merge_request, created_at: fixed_time, updated_at: fixed_time) }
+ let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) }
+
+ it 'returns the note and merge request diff data' do
+ expect(data).to have_key(:merge_request)
+ expect(data[:merge_request]).to eq(merge_request.hook_attrs)
+ end
+ end
+
+ describe 'When asking for a note on project snippet' do
+ let!(:snippet) { create(:project_snippet, created_at: fixed_time, updated_at: fixed_time) }
+ let!(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) }
+
+ it 'returns the note and project snippet data' do
+ expect(data).to have_key(:snippet)
+ expect(data[:snippet]).to eq(snippet.hook_attrs)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/oauth/auth_hash_spec.rb b/spec/lib/gitlab/oauth/auth_hash_spec.rb
new file mode 100644
index 0000000000..5eb77b492b
--- /dev/null
+++ b/spec/lib/gitlab/oauth/auth_hash_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Gitlab::OAuth::AuthHash do
+ let(:auth_hash) do
+ Gitlab::OAuth::AuthHash.new(double({
+ provider: 'twitter',
+ uid: uid,
+ info: double(info_hash)
+ }))
+ end
+ let(:uid) { 'my-uid' }
+ let(:email) { 'my-email@example.com' }
+ let(:nickname) { 'my-nickname' }
+ let(:info_hash) {
+ {
+ email: email,
+ nickname: nickname,
+ name: 'John',
+ first_name: "John",
+ last_name: "Who"
+ }
+ }
+
+ context "defaults" do
+ it { expect(auth_hash.provider).to eql 'twitter' }
+ it { expect(auth_hash.uid).to eql uid }
+ it { expect(auth_hash.email).to eql email }
+ it { expect(auth_hash.username).to eql nickname }
+ it { expect(auth_hash.name).to eql "John" }
+ it { expect(auth_hash.password).to_not be_empty }
+ end
+
+ context "email not provided" do
+ before { info_hash.delete(:email) }
+ it "generates a temp email" do
+ expect( auth_hash.email).to start_with('temp-email-for-oauth')
+ end
+ end
+
+ context "username not provided" do
+ before { info_hash.delete(:nickname) }
+
+ it "takes the first part of the email as username" do
+ expect( auth_hash.username ).to eql "my-email"
+ end
+ end
+
+ context "name not provided" do
+ before { info_hash.delete(:name) }
+
+ it "concats first and lastname as the name" do
+ expect( auth_hash.name ).to eql "John Who"
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb
index c241e19860..44cdd1e4fa 100644
--- a/spec/lib/gitlab/oauth/user_spec.rb
+++ b/spec/lib/gitlab/oauth/user_spec.rb
@@ -1,83 +1,109 @@
require 'spec_helper'
describe Gitlab::OAuth::User do
- let(:gl_auth) { Gitlab::OAuth::User }
- let(:info) do
- double(
- nickname: 'john',
+ let(:oauth_user) { Gitlab::OAuth::User.new(auth_hash) }
+ let(:gl_user) { oauth_user.gl_user }
+ let(:uid) { 'my-uid' }
+ let(:provider) { 'my-provider' }
+ let(:auth_hash) { double(uid: uid, provider: provider, info: double(info_hash)) }
+ let(:info_hash) do
+ {
+ nickname: '-john+gitlab-ETC%.git@gmail.com',
name: 'John',
email: 'john@mail.com'
- )
+ }
end
- before do
- Gitlab.config.stub(omniauth: {})
- end
-
- describe :find do
- let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') }
+ describe :persisted? do
+ let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
it "finds an existing user based on uid and provider (facebook)" do
auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider')
- assert gl_auth.find(auth)
+ expect( oauth_user.persisted? ).to be_truthy
end
- it "finds an existing user based on nested uid and provider" do
- auth = double(info: info, uid: 'my-uid', provider: 'my-provider')
- assert gl_auth.find(auth)
+ it "returns false if use is not found in database" do
+ auth_hash.stub(uid: 'non-existing')
+ expect( oauth_user.persisted? ).to be_falsey
end
end
- describe :create do
- it "should create user from LDAP" do
- auth = double(info: info, uid: 'my-uid', provider: 'ldap')
- user = gl_auth.create(auth)
+ describe :save do
+ let(:provider) { 'twitter' }
- user.should be_valid
- user.extern_uid.should == auth.uid
- user.provider.should == 'ldap'
+ describe 'signup' do
+ context "with allow_single_sign_on enabled" do
+ before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
+
+ it "creates a user from Omniauth" do
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'twitter'
+ end
+ end
+
+ context "with allow_single_sign_on disabled (Default)" do
+ it "throws an error" do
+ expect{ oauth_user.save }.to raise_error StandardError
+ end
+ end
end
- it "should create user from Omniauth" do
- auth = double(info: info, uid: 'my-uid', provider: 'twitter')
- user = gl_auth.create(auth)
+ describe 'blocking' do
+ let(:provider) { 'twitter' }
+ before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
- user.should be_valid
- user.extern_uid.should == auth.uid
- user.provider.should == 'twitter'
- end
+ context 'signup' do
+ context 'dont block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: false }
- it "should apply defaults to user" do
- auth = double(info: info, uid: 'my-uid', provider: 'ldap')
- user = gl_auth.create(auth)
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
- user.should be_valid
- user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
- user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
- end
+ context 'block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: true }
- it "Set a temp email address if not provided (like twitter does)" do
- info = double(
- uid: 'my-uid',
- nickname: 'john',
- name: 'John'
- )
- auth = double(info: info, uid: 'my-uid', provider: 'my-provider')
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to be_blocked
+ end
+ end
+ end
- user = gl_auth.create(auth)
- expect(user.email).to_not be_empty
- end
+ context 'sign-in' do
+ before do
+ oauth_user.save
+ oauth_user.gl_user.activate
+ end
- it 'generates a username if non provided (google)' do
- info = double(
- uid: 'my-uid',
- name: 'John',
- email: 'john@example.com'
- )
- auth = double(info: info, uid: 'my-uid', provider: 'my-provider')
+ context 'dont block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: false }
- user = gl_auth.create(auth)
- expect(user.username).to eql 'john'
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: true }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index 76d506eb3c..cd9d0456b2 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -13,8 +13,8 @@ describe 'Gitlab::Popen', no_db: true do
@output, @status = @klass.new.popen(%W(ls), path)
end
- it { @status.should be_zero }
- it { @output.should include('cache') }
+ it { expect(@status).to be_zero }
+ it { expect(@output).to include('cache') }
end
context 'non-zero status' do
@@ -22,8 +22,8 @@ describe 'Gitlab::Popen', no_db: true do
@output, @status = @klass.new.popen(%W(cat NOTHING), path)
end
- it { @status.should == 1 }
- it { @output.should include('No such file or directory') }
+ it { expect(@status).to eq(1) }
+ it { expect(@output).to include('No such file or directory') }
end
context 'unsafe string command' do
@@ -37,8 +37,8 @@ describe 'Gitlab::Popen', no_db: true do
@output, @status = @klass.new.popen(%W(ls))
end
- it { @status.should be_zero }
- it { @output.should include('spec') }
+ it { expect(@status).to be_zero }
+ it { expect(@output).to include('spec') }
end
end
diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb
new file mode 100644
index 0000000000..1b8ba7b4d4
--- /dev/null
+++ b/spec/lib/gitlab/push_data_builder_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe 'Gitlab::PushDataBuilder' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+
+ describe :build_sample do
+ let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+
+ it { expect(data).to be_a(Hash) }
+ it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
+ it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
+ it { expect(data[:ref]).to eq('refs/heads/master') }
+ it { expect(data[:commits].size).to eq(3) }
+ it { expect(data[:repository][:git_http_url]).to eq(project.http_url_to_repo) }
+ it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) }
+ it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) }
+ it { expect(data[:total_commits_count]).to eq(3) }
+ end
+
+ describe :build do
+ let(:data) do
+ Gitlab::PushDataBuilder.build(project,
+ user,
+ Gitlab::Git::BLANK_SHA,
+ '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
+ 'refs/tags/v1.1.0')
+ end
+
+ it { expect(data).to be_a(Hash) }
+ it { expect(data[:before]).to eq(Gitlab::Git::BLANK_SHA) }
+ it { expect(data[:checkout_sha]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
+ it { expect(data[:after]).to eq('8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b') }
+ it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
+ it { expect(data[:commits]).to be_empty }
+ it { expect(data[:total_commits_count]).to be_zero }
+ end
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 23867df39d..034f8ee7c4 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -3,52 +3,56 @@ require 'spec_helper'
describe Gitlab::ReferenceExtractor do
it 'extracts username references' do
subject.analyze('this contains a @user reference', nil)
- subject.users.should == [{ project: nil, id: 'user' }]
+ expect(subject.users).to eq([{ project: nil, id: 'user' }])
end
it 'extracts issue references' do
subject.analyze('this one talks about issue #1234', nil)
- subject.issues.should == [{ project: nil, id: '1234' }]
+ expect(subject.issues).to eq([{ project: nil, id: '1234' }])
end
it 'extracts JIRA issue references' do
- Gitlab.config.gitlab.stub(:issues_tracker).and_return('jira')
subject.analyze('this one talks about issue JIRA-1234', nil)
- subject.issues.should == [{ project: nil, id: 'JIRA-1234' }]
+ expect(subject.issues).to eq([{ project: nil, id: 'JIRA-1234' }])
end
it 'extracts merge request references' do
subject.analyze("and here's !43, a merge request", nil)
- subject.merge_requests.should == [{ project: nil, id: '43' }]
+ expect(subject.merge_requests).to eq([{ project: nil, id: '43' }])
end
it 'extracts snippet ids' do
subject.analyze('snippets like $12 get extracted as well', nil)
- subject.snippets.should == [{ project: nil, id: '12' }]
+ expect(subject.snippets).to eq([{ project: nil, id: '12' }])
end
it 'extracts commit shas' do
subject.analyze('commit shas 98cf0ae3 are pulled out as Strings', nil)
- subject.commits.should == [{ project: nil, id: '98cf0ae3' }]
+ expect(subject.commits).to eq([{ project: nil, id: '98cf0ae3' }])
+ end
+
+ it 'extracts commit ranges' do
+ subject.analyze('here you go, a commit range: 98cf0ae3...98cf0ae4', nil)
+ expect(subject.commit_ranges).to eq([{ project: nil, id: '98cf0ae3...98cf0ae4' }])
end
it 'extracts multiple references and preserves their order' do
subject.analyze('@me and @you both care about this', nil)
- subject.users.should == [
+ expect(subject.users).to eq([
{ project: nil, id: 'me' },
{ project: nil, id: 'you' }
- ]
+ ])
end
it 'leaves the original note unmodified' do
text = 'issue #123 is just the worst, @user'
subject.analyze(text, nil)
- text.should == 'issue #123 is just the worst, @user'
+ expect(text).to eq('issue #123 is just the worst, @user')
end
it 'handles all possible kinds of references' do
accessors = Gitlab::Markdown::TYPES.map { |t| "#{t}s".to_sym }
- subject.should respond_to(*accessors)
+ expect(subject).to respond_to(*accessors)
end
context 'with a project' do
@@ -63,7 +67,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_bar, :guest]
subject.analyze('@foo, @baduser, @bar, and @offteam', project)
- subject.users_for(project).should == [@u_foo, @u_bar]
+ expect(subject.users_for(project)).to eq([@u_foo, @u_bar])
end
it 'accesses valid issue objects' do
@@ -71,7 +75,7 @@ describe Gitlab::ReferenceExtractor do
@i1 = create(:issue, project: project)
subject.analyze("##{@i0.iid}, ##{@i1.iid}, and #999.", project)
- subject.issues_for(project).should == [@i0, @i1]
+ expect(subject.issues_for(project)).to eq([@i0, @i1])
end
it 'accesses valid merge requests' do
@@ -79,7 +83,7 @@ describe Gitlab::ReferenceExtractor do
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.", project)
- subject.merge_requests_for(project).should == [@m1, @m0]
+ expect(subject.merge_requests_for(project)).to eq([@m1, @m0])
end
it 'accesses valid snippets' do
@@ -88,7 +92,7 @@ describe Gitlab::ReferenceExtractor do
@s2 = create(:project_snippet)
subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}", project)
- subject.snippets_for(project).should == [@s0, @s1]
+ expect(subject.snippets_for(project)).to eq([@s0, @s1])
end
it 'accesses valid commits' do
@@ -97,9 +101,23 @@ describe Gitlab::ReferenceExtractor do
subject.analyze("this references commits #{commit.sha[0..6]} and 012345",
project)
extracted = subject.commits_for(project)
- extracted.should have(1).item
- extracted[0].sha.should == commit.sha
- extracted[0].message.should == commit.message
+ expect(extracted.size).to eq(1)
+ expect(extracted[0].sha).to eq(commit.sha)
+ expect(extracted[0].message).to eq(commit.message)
+ end
+
+ it 'accesses valid commit ranges' do
+ commit = project.repository.commit('master')
+ earlier_commit = project.repository.commit('master~2')
+
+ subject.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}",
+ project)
+ extracted = subject.commit_ranges_for(project)
+ expect(extracted.size).to eq(1)
+ expect(extracted[0][0].sha).to eq(earlier_commit.sha)
+ expect(extracted[0][0].message).to eq(earlier_commit.message)
+ expect(extracted[0][1].sha).to eq(commit.sha)
+ expect(extracted[0][1].message).to eq(commit.message)
end
end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index a3aae7771b..1db9f15b79 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -2,20 +2,20 @@ require 'spec_helper'
describe Gitlab::Regex do
describe 'path regex' do
- it { 'gitlab-ce'.should match(Gitlab::Regex.path_regex) }
- it { 'gitlab_git'.should match(Gitlab::Regex.path_regex) }
- it { '_underscore.js'.should match(Gitlab::Regex.path_regex) }
- it { '100px.com'.should match(Gitlab::Regex.path_regex) }
- it { '?gitlab'.should_not match(Gitlab::Regex.path_regex) }
- it { 'git lab'.should_not match(Gitlab::Regex.path_regex) }
- it { 'gitlab.git'.should_not match(Gitlab::Regex.path_regex) }
+ it { expect('gitlab-ce').to match(Gitlab::Regex.path_regex) }
+ it { expect('gitlab_git').to match(Gitlab::Regex.path_regex) }
+ it { expect('_underscore.js').to match(Gitlab::Regex.path_regex) }
+ it { expect('100px.com').to match(Gitlab::Regex.path_regex) }
+ it { expect('?gitlab').not_to match(Gitlab::Regex.path_regex) }
+ it { expect('git lab').not_to match(Gitlab::Regex.path_regex) }
+ it { expect('gitlab.git').not_to match(Gitlab::Regex.path_regex) }
end
describe 'project name regex' do
- it { 'gitlab-ce'.should match(Gitlab::Regex.project_name_regex) }
- it { 'GitLab CE'.should match(Gitlab::Regex.project_name_regex) }
- it { '100 lines'.should match(Gitlab::Regex.project_name_regex) }
- it { 'gitlab.git'.should match(Gitlab::Regex.project_name_regex) }
- it { '?gitlab'.should_not match(Gitlab::Regex.project_name_regex) }
+ it { expect('gitlab-ce').to match(Gitlab::Regex.project_name_regex) }
+ it { expect('GitLab CE').to match(Gitlab::Regex.project_name_regex) }
+ it { expect('100 lines').to match(Gitlab::Regex.project_name_regex) }
+ it { expect('gitlab.git').to match(Gitlab::Regex.project_name_regex) }
+ it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
end
end
diff --git a/spec/lib/gitlab/satellite/action_spec.rb b/spec/lib/gitlab/satellite/action_spec.rb
index 3eb1258d67..28e3d64ee2 100644
--- a/spec/lib/gitlab/satellite/action_spec.rb
+++ b/spec/lib/gitlab/satellite/action_spec.rb
@@ -6,7 +6,7 @@ describe 'Gitlab::Satellite::Action' do
describe '#prepare_satellite!' do
it 'should be able to fetch timeout from conf' do
- Gitlab::Satellite::Action::DEFAULT_OPTIONS[:git_timeout].should == 30.seconds
+ expect(Gitlab::Satellite::Action::DEFAULT_OPTIONS[:git_timeout]).to eq(30.seconds)
end
it 'create a repository with a parking branch and one remote: origin' do
@@ -15,22 +15,22 @@ describe 'Gitlab::Satellite::Action' do
#now lets dirty it up
starting_remote_count = repo.git.list_remotes.size
- starting_remote_count.should >= 1
+ expect(starting_remote_count).to be >= 1
#kind of hookey way to add a second remote
origin_uri = repo.git.remote({v: true}).split(" ")[1]
begin
repo.git.remote({raise: true}, 'add', 'another-remote', origin_uri)
repo.git.branch({raise: true}, 'a-new-branch')
- repo.heads.size.should > (starting_remote_count)
- repo.git.remote().split(" ").size.should > (starting_remote_count)
+ expect(repo.heads.size).to be > (starting_remote_count)
+ expect(repo.git.remote().split(" ").size).to be > (starting_remote_count)
rescue
end
repo.git.config({}, "user.name", "#{user.name} -- foo")
repo.git.config({}, "user.email", "#{user.email} -- foo")
- repo.config['user.name'].should =="#{user.name} -- foo"
- repo.config['user.email'].should =="#{user.email} -- foo"
+ expect(repo.config['user.name']).to eq("#{user.name} -- foo")
+ expect(repo.config['user.email']).to eq("#{user.email} -- foo")
#These must happen in the context of the satellite directory...
@@ -42,13 +42,13 @@ describe 'Gitlab::Satellite::Action' do
#verify it's clean
heads = repo.heads.map(&:name)
- heads.size.should == 1
- heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH).should == true
+ expect(heads.size).to eq(1)
+ expect(heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH)).to eq(true)
remotes = repo.git.remote().split(' ')
- remotes.size.should == 1
- remotes.include?('origin').should == true
- repo.config['user.name'].should ==user.name
- repo.config['user.email'].should ==user.email
+ expect(remotes.size).to eq(1)
+ expect(remotes.include?('origin')).to eq(true)
+ expect(repo.config['user.name']).to eq(user.name)
+ expect(repo.config['user.email']).to eq(user.email)
end
end
@@ -61,16 +61,16 @@ describe 'Gitlab::Satellite::Action' do
#set assumptions
FileUtils.rm_f(project.satellite.lock_file)
- File.exists?(project.satellite.lock_file).should be_false
+ expect(File.exists?(project.satellite.lock_file)).to be_falsey
satellite_action = Gitlab::Satellite::Action.new(user, project)
satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
- repo.should == sat_repo
- (File.exists? project.satellite.lock_file).should be_true
+ expect(repo).to eq(sat_repo)
+ expect(File.exists? project.satellite.lock_file).to be_truthy
called = true
end
- called.should be_true
+ expect(called).to be_truthy
end
@@ -80,19 +80,19 @@ describe 'Gitlab::Satellite::Action' do
# Set base assumptions
if File.exists? project.satellite.lock_file
- FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
+ expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_falsey
end
satellite_action = Gitlab::Satellite::Action.new(user, project)
satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo|
called = true
- repo.should == sat_repo
- (File.exists? project.satellite.lock_file).should be_true
- FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_true
+ expect(repo).to eq(sat_repo)
+ expect(File.exists? project.satellite.lock_file).to be_truthy
+ expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_truthy
end
- called.should be_true
- FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false
+ expect(called).to be_truthy
+ expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_falsey
end
diff --git a/spec/lib/gitlab/satellite/merge_action_spec.rb b/spec/lib/gitlab/satellite/merge_action_spec.rb
index 479a73a108..915e3ff0e5 100644
--- a/spec/lib/gitlab/satellite/merge_action_spec.rb
+++ b/spec/lib/gitlab/satellite/merge_action_spec.rb
@@ -13,9 +13,9 @@ describe 'Gitlab::Satellite::MergeAction' do
describe '#commits_between' do
def verify_commits(commits, first_commit_sha, last_commit_sha)
- commits.each { |commit| commit.class.should == Gitlab::Git::Commit }
- commits.first.id.should == first_commit_sha
- commits.last.id.should == last_commit_sha
+ commits.each { |commit| expect(commit.class).to eq(Gitlab::Git::Commit) }
+ expect(commits.first.id).to eq(first_commit_sha)
+ expect(commits.last.id).to eq(last_commit_sha)
end
context 'on fork' do
@@ -35,7 +35,7 @@ describe 'Gitlab::Satellite::MergeAction' do
describe '#format_patch' do
def verify_content(patch)
sample_compare.commits.each do |commit|
- patch.include?(commit).should be_true
+ expect(patch.include?(commit)).to be_truthy
end
end
@@ -57,11 +57,11 @@ describe 'Gitlab::Satellite::MergeAction' do
describe '#diffs_between_satellite tested against diff_in_satellite' do
def is_a_matching_diff(diff, diffs)
diff_count = diff.scan('diff --git').size
- diff_count.should >= 1
- diffs.size.should == diff_count
+ expect(diff_count).to be >= 1
+ expect(diffs.size).to eq(diff_count)
diffs.each do |a_diff|
- a_diff.class.should == Gitlab::Git::Diff
- (diff.include? a_diff.diff).should be_true
+ expect(a_diff.class).to eq(Gitlab::Git::Diff)
+ expect(diff.include? a_diff.diff).to be_truthy
end
end
@@ -82,23 +82,23 @@ describe 'Gitlab::Satellite::MergeAction' do
describe '#can_be_merged?' do
context 'on fork' do
- it { Gitlab::Satellite::MergeAction.new(
+ it { expect(Gitlab::Satellite::MergeAction.new(
merge_request_fork.author,
- merge_request_fork).can_be_merged?.should be_true }
+ merge_request_fork).can_be_merged?).to be_truthy }
- it { Gitlab::Satellite::MergeAction.new(
+ it { expect(Gitlab::Satellite::MergeAction.new(
merge_request_fork_with_conflict.author,
- merge_request_fork_with_conflict).can_be_merged?.should be_false }
+ merge_request_fork_with_conflict).can_be_merged?).to be_falsey }
end
context 'between branches' do
- it { Gitlab::Satellite::MergeAction.new(
+ it { expect(Gitlab::Satellite::MergeAction.new(
merge_request.author,
- merge_request).can_be_merged?.should be_true }
+ merge_request).can_be_merged?).to be_truthy }
- it { Gitlab::Satellite::MergeAction.new(
+ it { expect(Gitlab::Satellite::MergeAction.new(
merge_request_with_conflict.author,
- merge_request_with_conflict).can_be_merged?.should be_false }
+ merge_request_with_conflict).can_be_merged?).to be_falsey }
end
end
end
diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
index 2b254d6b3a..ce3ea6c260 100644
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ b/spec/lib/gitlab/upgrader_spec.rb
@@ -5,20 +5,20 @@ describe Gitlab::Upgrader do
let(:current_version) { Gitlab::VERSION }
describe 'current_version_raw' do
- it { upgrader.current_version_raw.should == current_version }
+ it { expect(upgrader.current_version_raw).to eq(current_version) }
end
describe 'latest_version?' do
it 'should be true if newest version' do
upgrader.stub(latest_version_raw: current_version)
- upgrader.latest_version?.should be_true
+ expect(upgrader.latest_version?).to be_truthy
end
end
describe 'latest_version_raw' do
it 'should be latest version for GitLab 5' do
upgrader.stub(current_version_raw: "5.3.0")
- upgrader.latest_version_raw.should == "v5.4.2"
+ expect(upgrader.latest_version_raw).to eq("v5.4.2")
end
end
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index eb47bee833..5153ed15af 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -5,7 +5,73 @@ describe Gitlab::UrlBuilder do
it 'returns the issue url' do
issue = create(:issue)
url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
- expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.to_param}/issues/#{issue.iid}"
+ expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}"
+ end
+ end
+
+ describe 'When asking for an merge request' do
+ it 'returns the merge request url' do
+ merge_request = create(:merge_request)
+ url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id)
+ expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}"
+ end
+ end
+
+ describe 'When asking for a note on commit' do
+ let(:note) { create(:note_on_commit) }
+ let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) }
+
+ it 'returns the note url' do
+ expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}"
+ end
+ end
+
+ describe 'When asking for a note on commit diff' do
+ let(:note) { create(:note_on_commit_diff) }
+ let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) }
+
+ it 'returns the note url' do
+ expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}"
+ end
+ end
+
+ describe 'When asking for a note on issue' do
+ let(:issue) { create(:issue) }
+ let(:note) { create(:note_on_issue, noteable_id: issue.id) }
+ let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) }
+
+ it 'returns the note url' do
+ expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}#note_#{note.id}"
+ end
+ end
+
+ describe 'When asking for a note on merge request' do
+ let(:merge_request) { create(:merge_request) }
+ let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) }
+ let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) }
+
+ it 'returns the note url' do
+ expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}"
+ end
+ end
+
+ describe 'When asking for a note on merge request diff' do
+ let(:merge_request) { create(:merge_request) }
+ let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) }
+ let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) }
+
+ it 'returns the note url' do
+ expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}"
+ end
+ end
+
+ describe 'When asking for a note on project snippet' do
+ let(:snippet) { create(:project_snippet) }
+ let(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) }
+ let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) }
+
+ it 'returns the note url' do
+ expect(url).to eq "#{Settings.gitlab['url']}/#{snippet.project.path_with_namespace}/snippets/#{note.noteable_id}#note_#{note.id}"
end
end
end
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index 94dccf7a4e..5afeb1c1ec 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -12,58 +12,58 @@ describe 'Gitlab::VersionInfo', no_db: true do
end
context '>' do
- it { @v2_0_0.should > @v1_1_0 }
- it { @v1_1_0.should > @v1_0_1 }
- it { @v1_0_1.should > @v1_0_0 }
- it { @v1_0_0.should > @v0_1_0 }
- it { @v0_1_0.should > @v0_0_1 }
+ it { expect(@v2_0_0).to be > @v1_1_0 }
+ it { expect(@v1_1_0).to be > @v1_0_1 }
+ it { expect(@v1_0_1).to be > @v1_0_0 }
+ it { expect(@v1_0_0).to be > @v0_1_0 }
+ it { expect(@v0_1_0).to be > @v0_0_1 }
end
context '>=' do
- it { @v2_0_0.should >= Gitlab::VersionInfo.new(2, 0, 0) }
- it { @v2_0_0.should >= @v1_1_0 }
+ it { expect(@v2_0_0).to be >= Gitlab::VersionInfo.new(2, 0, 0) }
+ it { expect(@v2_0_0).to be >= @v1_1_0 }
end
context '<' do
- it { @v0_0_1.should < @v0_1_0 }
- it { @v0_1_0.should < @v1_0_0 }
- it { @v1_0_0.should < @v1_0_1 }
- it { @v1_0_1.should < @v1_1_0 }
- it { @v1_1_0.should < @v2_0_0 }
+ it { expect(@v0_0_1).to be < @v0_1_0 }
+ it { expect(@v0_1_0).to be < @v1_0_0 }
+ it { expect(@v1_0_0).to be < @v1_0_1 }
+ it { expect(@v1_0_1).to be < @v1_1_0 }
+ it { expect(@v1_1_0).to be < @v2_0_0 }
end
context '<=' do
- it { @v0_0_1.should <= Gitlab::VersionInfo.new(0, 0, 1) }
- it { @v0_0_1.should <= @v0_1_0 }
+ it { expect(@v0_0_1).to be <= Gitlab::VersionInfo.new(0, 0, 1) }
+ it { expect(@v0_0_1).to be <= @v0_1_0 }
end
context '==' do
- it { @v0_0_1.should == Gitlab::VersionInfo.new(0, 0, 1) }
- it { @v0_1_0.should == Gitlab::VersionInfo.new(0, 1, 0) }
- it { @v1_0_0.should == Gitlab::VersionInfo.new(1, 0, 0) }
+ it { expect(@v0_0_1).to eq(Gitlab::VersionInfo.new(0, 0, 1)) }
+ it { expect(@v0_1_0).to eq(Gitlab::VersionInfo.new(0, 1, 0)) }
+ it { expect(@v1_0_0).to eq(Gitlab::VersionInfo.new(1, 0, 0)) }
end
context '!=' do
- it { @v0_0_1.should_not == @v0_1_0 }
+ it { expect(@v0_0_1).not_to eq(@v0_1_0) }
end
context 'unknown' do
- it { @unknown.should_not be @v0_0_1 }
- it { @unknown.should_not be Gitlab::VersionInfo.new }
+ it { expect(@unknown).not_to be @v0_0_1 }
+ it { expect(@unknown).not_to be Gitlab::VersionInfo.new }
it { expect{@unknown > @v0_0_1}.to raise_error(ArgumentError) }
it { expect{@unknown < @v0_0_1}.to raise_error(ArgumentError) }
end
context 'parse' do
- it { Gitlab::VersionInfo.parse("1.0.0").should == @v1_0_0 }
- it { Gitlab::VersionInfo.parse("1.0.0.1").should == @v1_0_0 }
- it { Gitlab::VersionInfo.parse("git 1.0.0b1").should == @v1_0_0 }
- it { Gitlab::VersionInfo.parse("git 1.0b1").should_not be_valid }
+ it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) }
+ it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) }
+ it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) }
+ it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid }
end
context 'to_s' do
- it { @v1_0_0.to_s.should == "1.0.0" }
- it { @unknown.to_s.should == "Unknown" }
+ it { expect(@v1_0_0.to_s).to eq("1.0.0") }
+ it { expect(@unknown.to_s).to eq("Unknown") }
end
end
diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb
new file mode 100644
index 0000000000..af399f3a73
--- /dev/null
+++ b/spec/lib/repository_cache_spec.rb
@@ -0,0 +1,34 @@
+require 'rspec'
+require_relative '../../lib/repository_cache'
+
+describe RepositoryCache do
+ let(:backend) { double('backend').as_null_object }
+ let(:cache) { RepositoryCache.new('example', backend) }
+
+ describe '#cache_key' do
+ it 'includes the namespace' do
+ expect(cache.cache_key(:foo)).to eq 'foo:example'
+ end
+ end
+
+ describe '#expire' do
+ it 'expires the given key from the cache' do
+ cache.expire(:foo)
+ expect(backend).to have_received(:delete).with('foo:example')
+ end
+ end
+
+ describe '#fetch' do
+ it 'fetches the given key from the cache' do
+ cache.fetch(:bar)
+ expect(backend).to have_received(:fetch).with('bar:example')
+ end
+
+ it 'accepts a block' do
+ p = -> {}
+
+ cache.fetch(:baz, &p)
+ expect(backend).to have_received(:fetch).with('baz:example', &p)
+ end
+ end
+end
diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb
index a3c353d5ea..df243a2600 100644
--- a/spec/lib/votes_spec.rb
+++ b/spec/lib/votes_spec.rb
@@ -5,132 +5,181 @@ describe Issue, 'Votes' do
describe "#upvotes" do
it "with no notes has a 0/0 score" do
- issue.upvotes.should == 0
+ expect(issue.upvotes).to eq(0)
end
it "should recognize non-+1 notes" do
add_note "No +1 here"
- issue.should have(1).note
- issue.notes.first.upvote?.should be_false
- issue.upvotes.should == 0
+ expect(issue.notes.size).to eq(1)
+ expect(issue.notes.first.upvote?).to be_falsey
+ expect(issue.upvotes).to eq(0)
end
it "should recognize a single +1 note" do
add_note "+1 This is awesome"
- issue.upvotes.should == 1
+ expect(issue.upvotes).to eq(1)
end
- it "should recognize multiple +1 notes" do
- add_note "+1 This is awesome"
- add_note "+1 I want this"
- issue.upvotes.should == 2
+ it 'should recognize multiple +1 notes' do
+ add_note '+1 This is awesome', create(:user)
+ add_note '+1 I want this', create(:user)
+ expect(issue.upvotes).to eq(2)
+ end
+
+ it 'should not count 2 +1 votes from the same user' do
+ add_note '+1 This is awesome'
+ add_note '+1 I want this'
+ expect(issue.upvotes).to eq(1)
end
end
describe "#downvotes" do
it "with no notes has a 0/0 score" do
- issue.downvotes.should == 0
+ expect(issue.downvotes).to eq(0)
end
it "should recognize non--1 notes" do
add_note "Almost got a -1"
- issue.should have(1).note
- issue.notes.first.downvote?.should be_false
- issue.downvotes.should == 0
+ expect(issue.notes.size).to eq(1)
+ expect(issue.notes.first.downvote?).to be_falsey
+ expect(issue.downvotes).to eq(0)
end
it "should recognize a single -1 note" do
add_note "-1 This is bad"
- issue.downvotes.should == 1
+ expect(issue.downvotes).to eq(1)
end
it "should recognize multiple -1 notes" do
- add_note "-1 This is bad"
- add_note "-1 Away with this"
- issue.downvotes.should == 2
+ add_note('-1 This is bad', create(:user))
+ add_note('-1 Away with this', create(:user))
+ expect(issue.downvotes).to eq(2)
end
end
describe "#votes_count" do
it "with no notes has a 0/0 score" do
- issue.votes_count.should == 0
+ expect(issue.votes_count).to eq(0)
end
it "should recognize non notes" do
add_note "No +1 here"
- issue.should have(1).note
- issue.votes_count.should == 0
+ expect(issue.notes.size).to eq(1)
+ expect(issue.votes_count).to eq(0)
end
it "should recognize a single +1 note" do
add_note "+1 This is awesome"
- issue.votes_count.should == 1
+ expect(issue.votes_count).to eq(1)
end
it "should recognize a single -1 note" do
add_note "-1 This is bad"
- issue.votes_count.should == 1
+ expect(issue.votes_count).to eq(1)
end
it "should recognize multiple notes" do
- add_note "+1 This is awesome"
- add_note "-1 This is bad"
- add_note "+1 I want this"
- issue.votes_count.should == 3
+ add_note('+1 This is awesome', create(:user))
+ add_note('-1 This is bad', create(:user))
+ add_note('+1 I want this', create(:user))
+ expect(issue.votes_count).to eq(3)
+ end
+
+ it 'should not count 2 -1 votes from the same user' do
+ add_note '-1 This is suspicious'
+ add_note '-1 This is bad'
+ expect(issue.votes_count).to eq(1)
end
end
describe "#upvotes_in_percent" do
it "with no notes has a 0% score" do
- issue.upvotes_in_percent.should == 0
+ expect(issue.upvotes_in_percent).to eq(0)
end
it "should count a single 1 note as 100%" do
add_note "+1 This is awesome"
- issue.upvotes_in_percent.should == 100
+ expect(issue.upvotes_in_percent).to eq(100)
end
- it "should count multiple +1 notes as 100%" do
- add_note "+1 This is awesome"
- add_note "+1 I want this"
- issue.upvotes_in_percent.should == 100
+ it 'should count multiple +1 notes as 100%' do
+ add_note('+1 This is awesome', create(:user))
+ add_note('+1 I want this', create(:user))
+ expect(issue.upvotes_in_percent).to eq(100)
end
- it "should count fractions for multiple +1 and -1 notes correctly" do
- add_note "+1 This is awesome"
- add_note "+1 I want this"
- add_note "-1 This is bad"
- add_note "+1 me too"
- issue.upvotes_in_percent.should == 75
+ it 'should count fractions for multiple +1 and -1 notes correctly' do
+ add_note('+1 This is awesome', create(:user))
+ add_note('+1 I want this', create(:user))
+ add_note('-1 This is bad', create(:user))
+ add_note('+1 me too', create(:user))
+ expect(issue.upvotes_in_percent).to eq(75)
end
end
describe "#downvotes_in_percent" do
it "with no notes has a 0% score" do
- issue.downvotes_in_percent.should == 0
+ expect(issue.downvotes_in_percent).to eq(0)
end
it "should count a single -1 note as 100%" do
add_note "-1 This is bad"
- issue.downvotes_in_percent.should == 100
+ expect(issue.downvotes_in_percent).to eq(100)
end
- it "should count multiple -1 notes as 100%" do
- add_note "-1 This is bad"
- add_note "-1 Away with this"
- issue.downvotes_in_percent.should == 100
+ it 'should count multiple -1 notes as 100%' do
+ add_note('-1 This is bad', create(:user))
+ add_note('-1 Away with this', create(:user))
+ expect(issue.downvotes_in_percent).to eq(100)
end
- it "should count fractions for multiple +1 and -1 notes correctly" do
- add_note "+1 This is awesome"
- add_note "+1 I want this"
- add_note "-1 This is bad"
- add_note "+1 me too"
- issue.downvotes_in_percent.should == 25
+ it 'should count fractions for multiple +1 and -1 notes correctly' do
+ add_note('+1 This is awesome', create(:user))
+ add_note('+1 I want this', create(:user))
+ add_note('-1 This is bad', create(:user))
+ add_note('+1 me too', create(:user))
+ expect(issue.downvotes_in_percent).to eq(25)
end
end
- def add_note(text)
- issue.notes << create(:note, note: text, project: issue.project)
+ describe '#filter_superceded_votes' do
+
+ it 'should count a users vote only once amongst multiple votes' do
+ add_note('-1 This needs work before I will accept it')
+ add_note('+1 I want this', create(:user))
+ add_note('+1 This is is awesome', create(:user))
+ add_note('+1 this looks good now')
+ add_note('+1 This is awesome', create(:user))
+ add_note('+1 me too', create(:user))
+ expect(issue.downvotes).to eq(0)
+ expect(issue.upvotes).to eq(5)
+ end
+
+ it 'should count each users vote only once' do
+ add_note '-1 This needs work before it will be accepted'
+ add_note '+1 I like this'
+ add_note '+1 I still like this'
+ add_note '+1 I really like this'
+ add_note '+1 Give me this now!!!!'
+ expect(issue.downvotes).to eq(0)
+ expect(issue.upvotes).to eq(1)
+ end
+
+ it 'should count a users vote only once without caring about comments' do
+ add_note '-1 This needs work before it will be accepted'
+ add_note 'Comment 1'
+ add_note 'Another comment'
+ add_note '+1 vote'
+ add_note 'final comment'
+ expect(issue.downvotes).to eq(0)
+ expect(issue.upvotes).to eq(1)
+ end
+
+ end
+
+ def add_note(text, author = issue.author)
+ created_at = Time.now - 1.hour + Note.count.seconds
+ issue.notes << create(:note, note: text, project: issue.project,
+ author_id: author.id, created_at: created_at)
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index e06e8826e5..e3a3b54235 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -5,38 +5,46 @@ describe Notify do
include EmailSpec::Matchers
include RepoHelpers
+ let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
let(:gitlab_sender) { Gitlab.config.gitlab.email_from }
let(:recipient) { create(:user, email: 'recipient@example.com') }
let(:project) { create(:project) }
+ before(:each) do
+ email = recipient.emails.create(email: "notifications@example.com")
+ recipient.update_attribute(:notification_email, email.email)
+ end
+
shared_examples 'a multiple recipients email' do
it 'is sent to the given recipient' do
- should deliver_to recipient.email
+ is_expected.to deliver_to recipient.notification_email
end
end
shared_examples 'an email sent from GitLab' do
it 'is sent from GitLab' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq('GitLab')
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(gitlab_sender_display_name)
+ expect(sender.address).to eq(gitlab_sender)
end
end
shared_examples 'an email starting a new thread' do |message_id_prefix|
it 'has a discussion identifier' do
- should have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
end
end
shared_examples 'an answer to an existing thread' do |thread_id_prefix|
it 'has a subject that begins with Re: ' do
- should have_subject /^Re: /
+ is_expected.to have_subject /^Re: /
end
it 'has headers that reference an existing thread' do
- should have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
- should have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
+ is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
end
end
@@ -46,35 +54,35 @@ describe Notify do
token = 'kETLwRaayvigPq_x3SNM'
- subject { Notify.new_user_email(new_user.id, new_user.password, token) }
+ subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
it 'is sent to the new user' do
- should deliver_to new_user.email
+ is_expected.to deliver_to new_user.email
end
it 'has the correct subject' do
- should have_subject /^Account was created for you$/i
+ is_expected.to have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
- should have_body_text /#{new_user.email}/
+ is_expected.to have_body_text /#{new_user.email}/
end
it 'contains the password text' do
- should have_body_text /Click here to set your password/
+ is_expected.to have_body_text /Click here to set your password/
end
it 'includes a link for user to set password' do
params = "reset_password_token=#{token}"
- should have_body_text(
+ is_expected.to have_body_text(
%r{http://localhost(:\d+)?/users/password/edit\?#{params}}
)
end
it 'includes a link to the site' do
- should have_body_text /#{example_site_path}/
+ is_expected.to have_body_text /#{example_site_path}/
end
end
@@ -83,28 +91,28 @@ describe Notify do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: 'newguy@example.com', password: "securePassword") }
- subject { Notify.new_user_email(new_user.id, new_user.password) }
+ subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
it 'is sent to the new user' do
- should deliver_to new_user.email
+ is_expected.to deliver_to new_user.email
end
it 'has the correct subject' do
- should have_subject /^Account was created for you$/i
+ is_expected.to have_subject /^Account was created for you$/i
end
it 'contains the new user\'s login name' do
- should have_body_text /#{new_user.email}/
+ is_expected.to have_body_text /#{new_user.email}/
end
it 'should not contain the new user\'s password' do
- should_not have_body_text /password/
+ is_expected.not_to have_body_text /password/
end
it 'includes a link to the site' do
- should have_body_text /#{example_site_path}/
+ is_expected.to have_body_text /#{example_site_path}/
end
end
@@ -116,19 +124,19 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it 'is sent to the new user' do
- should deliver_to key.user.email
+ is_expected.to deliver_to key.user.email
end
it 'has the correct subject' do
- should have_subject /^SSH key was added to your account$/i
+ is_expected.to have_subject /^SSH key was added to your account$/i
end
it 'contains the new ssh key title' do
- should have_body_text /#{key.title}/
+ is_expected.to have_body_text /#{key.title}/
end
it 'includes a link to ssh keys page' do
- should have_body_text /#{profile_keys_path}/
+ is_expected.to have_body_text /#{profile_keys_path}/
end
end
@@ -138,19 +146,19 @@ describe Notify do
subject { Notify.new_email_email(email.id) }
it 'is sent to the new user' do
- should deliver_to email.user.email
+ is_expected.to deliver_to email.user.email
end
it 'has the correct subject' do
- should have_subject /^Email was added to your account$/i
+ is_expected.to have_subject /^Email was added to your account$/i
end
it 'contains the new email address' do
- should have_body_text /#{email.email}/
+ is_expected.to have_body_text /#{email.email}/
end
it 'includes a link to emails page' do
- should have_body_text /#{profile_emails_path}/
+ is_expected.to have_body_text /#{profile_emails_path}/
end
end
@@ -163,18 +171,25 @@ describe Notify do
shared_examples 'an assignee email' do
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq(current_user.name)
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to the assignee' do
- should deliver_to assignee.email
+ is_expected.to deliver_to assignee.email
end
end
context 'for issues' do
let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project) }
let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: Faker::Lorem.sentence) }
+ let(:issue_with_image) do
+ create(:issue,
+ author: current_user,
+ assignee: assignee,
+ project: project,
+ description: "")
+ end
describe 'that are new' do
subject { Notify.new_issue_email(issue.assignee_id, issue.id) }
@@ -183,11 +198,11 @@ describe Notify do
it_behaves_like 'an email starting a new thread', 'issue'
it 'has the correct subject' do
- should have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/
+ is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/
end
it 'contains a link to the new issue' do
- should have_body_text /#{project_issue_path project, issue}/
+ is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
end
@@ -195,7 +210,23 @@ describe Notify do
subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) }
it 'contains the description' do
- should have_body_text /#{issue_with_description.description}/
+ is_expected.to have_body_text /#{issue_with_description.description}/
+ end
+ end
+
+ describe 'that contain images' do
+ let(:png) { File.read("#{Rails.root}/spec/fixtures/dk.png") }
+ let(:png_encoded) { Base64::encode64(png) }
+
+ before :each do
+ file_path = File.join(Rails.root, 'public', 'uploads', issue_with_image.project.path_with_namespace, '12345/test.jpg')
+ allow(File).to receive(:file?).with(file_path).and_return(true)
+ allow(File).to receive(:read).with(file_path).and_return(png)
+ end
+
+ subject { Notify.new_issue_email(issue_with_image.assignee_id, issue_with_image.id) }
+ it 'replaces attached images with inline images' do
+ is_expected.to have_body_text URI.encode(png_encoded)
end
end
@@ -207,24 +238,24 @@ describe Notify do
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq(current_user.name)
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
end
it 'has the correct subject' do
- should have_subject /#{issue.title} \(##{issue.iid}\)/
+ is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
end
it 'contains the name of the previous assignee' do
- should have_body_text /#{previous_assignee.name}/
+ is_expected.to have_body_text /#{previous_assignee.name}/
end
it 'contains the name of the new assignee' do
- should have_body_text /#{assignee.name}/
+ is_expected.to have_body_text /#{assignee.name}/
end
it 'contains a link to the issue' do
- should have_body_text /#{project_issue_path project, issue}/
+ is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
end
@@ -236,24 +267,24 @@ describe Notify do
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq(current_user.name)
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
end
it 'has the correct subject' do
- should have_subject /#{issue.title} \(##{issue.iid}\)/i
+ is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/i
end
it 'contains the new status' do
- should have_body_text /#{status}/i
+ is_expected.to have_body_text /#{status}/i
end
it 'contains the user name' do
- should have_body_text /#{current_user.name}/i
+ is_expected.to have_body_text /#{current_user.name}/i
end
it 'contains a link to the issue' do
- should have_body_text /#{project_issue_path project, issue}/
+ is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
end
end
@@ -263,6 +294,14 @@ describe Notify do
let(:merge_author) { create(:user) }
let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) }
let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: Faker::Lorem.sentence) }
+ let(:merge_request_with_image) do
+ create(:merge_request,
+ author: current_user,
+ assignee: assignee,
+ source_project: project,
+ target_project: project,
+ description: "")
+ end
describe 'that are new' do
subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) }
@@ -271,23 +310,23 @@ describe Notify do
it_behaves_like 'an email starting a new thread', 'merge_request'
it 'has the correct subject' do
- should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
end
it 'contains a link to the new merge request' do
- should have_body_text /#{project_merge_request_path(project, merge_request)}/
+ is_expected.to have_body_text /#{namespace_project_merge_request_path(project.namespace, project, merge_request)}/
end
it 'contains the source branch for the merge request' do
- should have_body_text /#{merge_request.source_branch}/
+ is_expected.to have_body_text /#{merge_request.source_branch}/
end
it 'contains the target branch for the merge request' do
- should have_body_text /#{merge_request.target_branch}/
+ is_expected.to have_body_text /#{merge_request.target_branch}/
end
it 'has the correct message-id set' do
- should have_header 'Message-ID', ""
+ is_expected.to have_header 'Message-ID', ""
end
end
@@ -295,7 +334,23 @@ describe Notify do
subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) }
it 'contains the description' do
- should have_body_text /#{merge_request_with_description.description}/
+ is_expected.to have_body_text /#{merge_request_with_description.description}/
+ end
+ end
+
+ describe 'that are new and contain contain images in the description' do
+ let(:png) {File.read("#{Rails.root}/spec/fixtures/dk.png")}
+ let(:png_encoded) { Base64::encode64(png) }
+
+ before :each do
+ file_path = File.join(Rails.root, 'public', 'uploads', merge_request_with_image.project.path_with_namespace, '/12345/test.jpg')
+ allow(File).to receive(:file?).with(file_path).and_return(true)
+ allow(File).to receive(:read).with(file_path).and_return(png)
+ end
+
+ subject { Notify.new_merge_request_email(merge_request_with_image.assignee_id, merge_request_with_image.id) }
+ it 'replaces attached images with inline images' do
+ is_expected.to have_body_text URI.encode(png_encoded)
end
end
@@ -307,24 +362,24 @@ describe Notify do
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq(current_user.name)
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
end
it 'has the correct subject' do
- should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
end
it 'contains the name of the previous assignee' do
- should have_body_text /#{previous_assignee.name}/
+ is_expected.to have_body_text /#{previous_assignee.name}/
end
it 'contains the name of the new assignee' do
- should have_body_text /#{assignee.name}/
+ is_expected.to have_body_text /#{assignee.name}/
end
it 'contains a link to the merge request' do
- should have_body_text /#{project_merge_request_path project, merge_request}/
+ is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
end
end
@@ -336,24 +391,24 @@ describe Notify do
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq(current_user.name)
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(current_user.name)
+ expect(sender.address).to eq(gitlab_sender)
end
it 'has the correct subject' do
- should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/i
+ is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/i
end
it 'contains the new status' do
- should have_body_text /#{status}/i
+ is_expected.to have_body_text /#{status}/i
end
it 'contains the user name' do
- should have_body_text /#{current_user.name}/i
+ is_expected.to have_body_text /#{current_user.name}/i
end
it 'contains a link to the merge request' do
- should have_body_text /#{project_merge_request_path project, merge_request}/
+ is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
end
end
@@ -365,20 +420,20 @@ describe Notify do
it 'is sent as the merge author' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq(merge_author.name)
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(merge_author.name)
+ expect(sender.address).to eq(gitlab_sender)
end
it 'has the correct subject' do
- should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
end
it 'contains the new status' do
- should have_body_text /merged/i
+ is_expected.to have_body_text /merged/i
end
it 'contains a link to the merge request' do
- should have_body_text /#{project_merge_request_path project, merge_request}/
+ is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
end
end
end
@@ -392,36 +447,39 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it 'has the correct subject' do
- should have_subject /Project was moved/
+ is_expected.to have_subject /Project was moved/
end
it 'contains name of project' do
- should have_body_text /#{project.name_with_namespace}/
+ is_expected.to have_body_text /#{project.name_with_namespace}/
end
it 'contains new user role' do
- should have_body_text /#{project.ssh_url_to_repo}/
+ is_expected.to have_body_text /#{project.ssh_url_to_repo}/
end
end
describe 'project access changed' do
let(:project) { create(:project) }
let(:user) { create(:user) }
- let(:project_member) { create(:project_member,
- project: project,
- user: user) }
+ let(:project_member) do
+ create(:project_member,
+ project: project,
+ user: user)
+ end
+
subject { Notify.project_access_granted_email(project_member.id) }
it_behaves_like 'an email sent from GitLab'
it 'has the correct subject' do
- should have_subject /Access to project was granted/
+ is_expected.to have_subject /Access to project was granted/
end
it 'contains name of project' do
- should have_body_text /#{project.name}/
+ is_expected.to have_body_text /#{project.name}/
end
it 'contains new user role' do
- should have_body_text /#{project_member.human_access}/
+ is_expected.to have_body_text /#{project_member.human_access}/
end
end
@@ -430,29 +488,55 @@ describe Notify do
let(:note) { create(:note, project: project, author: note_author) }
before :each do
- Note.stub(:find).with(note.id).and_return(note)
+ allow(Note).to receive(:find).with(note.id).and_return(note)
end
shared_examples 'a note email' do
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq(note_author.name)
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(note_author.name)
+ expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to the given recipient' do
- should deliver_to recipient.email
+ is_expected.to deliver_to recipient.notification_email
end
it 'contains the message from the note' do
- should have_body_text /#{note.note}/
+ is_expected.to have_body_text /#{note.note}/
+ end
+ end
+
+ describe 'on a commit that contains an image' do
+ let(:commit) { project.repository.commit }
+ let(:note_with_image) do
+ create(:note,
+ project: project,
+ author: note_author,
+ note: "")
+ end
+
+ let(:png) {File.read("#{Rails.root}/spec/fixtures/dk.png")}
+ let(:png_encoded) { Base64::encode64(png) }
+
+ before :each do
+ file_path = File.join(Rails.root, 'public', 'uploads', note_with_image.project.path_with_namespace, '12345/test.jpg')
+ allow(File).to receive(:file?).with(file_path).and_return(true)
+ allow(File).to receive(:read).with(file_path).and_return(png)
+ allow(Note).to receive(:find).with(note_with_image.id).and_return(note_with_image)
+ allow(note_with_image).to receive(:noteable).and_return(commit)
+ end
+
+ subject { Notify.note_commit_email(recipient.id, note_with_image.id) }
+ it 'replaces attached images with inline images' do
+ is_expected.to have_body_text URI.encode(png_encoded)
end
end
describe 'on a commit' do
let(:commit) { project.repository.commit }
- before(:each) { note.stub(:noteable).and_return(commit) }
+ before(:each) { allow(note).to receive(:noteable).and_return(commit) }
subject { Notify.note_commit_email(recipient.id, note.id) }
@@ -460,18 +544,18 @@ describe Notify do
it_behaves_like 'an answer to an existing thread', 'commits'
it 'has the correct subject' do
- should have_subject /#{commit.title} \(#{commit.short_id}\)/
+ is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/
end
it 'contains a link to the commit' do
- should have_body_text commit.short_id
+ is_expected.to have_body_text commit.short_id
end
end
describe 'on a merge request' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
- before(:each) { note.stub(:noteable).and_return(merge_request) }
+ let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") }
+ before(:each) { allow(note).to receive(:noteable).and_return(merge_request) }
subject { Notify.note_merge_request_email(recipient.id, note.id) }
@@ -479,18 +563,18 @@ describe Notify do
it_behaves_like 'an answer to an existing thread', 'merge_request'
it 'has the correct subject' do
- should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
+ is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
end
it 'contains a link to the merge request note' do
- should have_body_text /#{note_on_merge_request_path}/
+ is_expected.to have_body_text /#{note_on_merge_request_path}/
end
end
describe 'on an issue' do
let(:issue) { create(:issue, project: project) }
- let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") }
- before(:each) { note.stub(:noteable).and_return(issue) }
+ let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") }
+ before(:each) { allow(note).to receive(:noteable).and_return(issue) }
subject { Notify.note_issue_email(recipient.id, note.id) }
@@ -498,11 +582,11 @@ describe Notify do
it_behaves_like 'an answer to an existing thread', 'issue'
it 'has the correct subject' do
- should have_subject /#{issue.title} \(##{issue.iid}\)/
+ is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
end
it 'contains a link to the issue note' do
- should have_body_text /#{note_on_issue_path}/
+ is_expected.to have_body_text /#{note_on_issue_path}/
end
end
end
@@ -518,15 +602,15 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it 'has the correct subject' do
- should have_subject /Access to group was granted/
+ is_expected.to have_subject /Access to group was granted/
end
it 'contains name of project' do
- should have_body_text /#{group.name}/
+ is_expected.to have_body_text /#{group.name}/
end
it 'contains new user role' do
- should have_body_text /#{membership.human_access}/
+ is_expected.to have_body_text /#{membership.human_access}/
end
end
@@ -544,15 +628,15 @@ describe Notify do
it_behaves_like 'an email sent from GitLab'
it 'is sent to the new user' do
- should deliver_to 'new-email@mail.com'
+ is_expected.to deliver_to 'new-email@mail.com'
end
it 'has the correct subject' do
- should have_subject "Confirmation instructions"
+ is_expected.to have_subject "Confirmation instructions"
end
it 'includes a link to the site' do
- should have_body_text /#{example_site_path}/
+ is_expected.to have_body_text /#{example_site_path}/
end
end
@@ -561,34 +645,87 @@ describe Notify do
let(:user) { create(:user) }
let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) }
let(:commits) { Commit.decorate(compare.commits) }
- let(:diff_path) { project_compare_path(project, from: commits.first, to: commits.last) }
+ let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base), to: Commit.new(compare.head)) }
+ let(:send_from_committer_email) { false }
- subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) }
+ subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare, false, send_from_committer_email) }
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq(user.name)
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(user.name)
+ expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to recipient' do
- should deliver_to 'devs@company.name'
+ is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do
- should have_subject /#{commits.length} new commits pushed to repository/
+ is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/
end
it 'includes commits list' do
- should have_body_text /Change some files/
+ is_expected.to have_body_text /Change some files/
end
it 'includes diffs' do
- should have_body_text /def archive_formats_regex/
+ is_expected.to have_body_text /def archive_formats_regex/
end
it 'contains a link to the diff' do
- should have_body_text /#{diff_path}/
+ is_expected.to have_body_text /#{diff_path}/
+ end
+
+ it 'doesn not contain the misleading footer' do
+ is_expected.not_to have_body_text /you are a member of/
+ end
+
+ context "when set to send from committer email if domain matches" do
+
+ let(:send_from_committer_email) { true }
+
+ before do
+ allow(Gitlab.config.gitlab).to receive(:host).and_return("gitlab.corp.company.com")
+ end
+
+ context "when the committer email domain is within the GitLab domain" do
+
+ before do
+ user.update_attribute(:email, "user@company.com")
+ user.confirm!
+ end
+
+ it "is sent from the committer email" do
+ sender = subject.header[:from].addrs[0]
+ expect(sender.address).to eq(user.email)
+ end
+ end
+
+ context "when the committer email domain is not completely within the GitLab domain" do
+
+ before do
+ user.update_attribute(:email, "user@something.company.com")
+ user.confirm!
+ end
+
+ it "is sent from the default email" do
+ sender = subject.header[:from].addrs[0]
+ expect(sender.address).to eq(gitlab_sender)
+ end
+ end
+
+ context "when the committer email domain is outside the GitLab domain" do
+
+ before do
+ user.update_attribute(:email, "user@mpany.com")
+ user.confirm!
+ end
+
+ it "is sent from the default email" do
+ sender = subject.header[:from].addrs[0]
+ expect(sender.address).to eq(gitlab_sender)
+ end
+ end
end
end
@@ -597,34 +734,34 @@ describe Notify do
let(:user) { create(:user) }
let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) }
let(:commits) { Commit.decorate(compare.commits) }
- let(:diff_path) { project_commit_path(project, commits.first) }
+ let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) }
it 'is sent as the author' do
sender = subject.header[:from].addrs[0]
- sender.display_name.should eq(user.name)
- sender.address.should eq(gitlab_sender)
+ expect(sender.display_name).to eq(user.name)
+ expect(sender.address).to eq(gitlab_sender)
end
it 'is sent to recipient' do
- should deliver_to 'devs@company.name'
+ is_expected.to deliver_to 'devs@company.name'
end
it 'has the correct subject' do
- should have_subject /#{commits.first.title}/
+ is_expected.to have_subject /#{commits.first.title}/
end
it 'includes commits list' do
- should have_body_text /Change some files/
+ is_expected.to have_body_text /Change some files/
end
it 'includes diffs' do
- should have_body_text /def archive_formats_regex/
+ is_expected.to have_body_text /def archive_formats_regex/
end
it 'contains a link to the diff' do
- should have_body_text /#{diff_path}/
+ is_expected.to have_body_text /#{diff_path}/
end
end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
new file mode 100644
index 0000000000..b4f0b2c201
--- /dev/null
+++ b/spec/models/application_setting_spec.rb
@@ -0,0 +1,24 @@
+# == Schema Information
+#
+# Table name: application_settings
+#
+# id :integer not null, primary key
+# default_projects_limit :integer
+# default_branch_protection :integer
+# signup_enabled :boolean
+# signin_enabled :boolean
+# gravatar_enabled :boolean
+# sign_in_text :text
+# created_at :datetime
+# updated_at :datetime
+# home_page_url :string(255)
+# default_branch_protection :integer default(2)
+# twitter_sharing_enabled :boolean default(TRUE)
+# restricted_visibility_levels :text
+#
+
+require 'spec_helper'
+
+describe ApplicationSetting, models: true do
+ it { expect(ApplicationSetting.create_from_defaults).to be_valid }
+end
diff --git a/spec/models/asana_service_spec.rb b/spec/models/asana_service_spec.rb
new file mode 100644
index 0000000000..13c8d54a2a
--- /dev/null
+++ b/spec/models/asana_service_spec.rb
@@ -0,0 +1,65 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe AsanaService, models: true do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of :api_key }
+ end
+ end
+
+ describe 'Execute' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ @asana = AsanaService.new
+ @asana.stub(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ api_key: 'verySecret',
+ restrict_to_branch: 'master'
+ )
+ end
+
+ it 'should call Asana service to created a story' do
+ expect(Asana::Task).to receive(:find).with('123456').once
+
+ @asana.check_commit('related to #123456', 'pushed')
+ end
+
+ it 'should call Asana service to created a story and close a task' do
+ expect(Asana::Task).to receive(:find).with('456789').twice
+
+ @asana.check_commit('fix #456789', 'pushed')
+ end
+ end
+end
diff --git a/spec/models/assembla_service_spec.rb b/spec/models/assembla_service_spec.rb
deleted file mode 100644
index 4300090eb1..0000000000
--- a/spec/models/assembla_service_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-require 'spec_helper'
-
-describe AssemblaService, models: true do
- describe "Associations" do
- it { should belong_to :project }
- it { should have_one :service_hook }
- end
-
- describe "Execute" do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
-
- before do
- @assembla_service = AssemblaService.new
- @assembla_service.stub(
- project_id: project.id,
- project: project,
- service_hook: true,
- token: 'verySecret',
- subdomain: 'project_name'
- )
- @sample_data = GitPushService.new.sample_data(project, user)
- @api_url = 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret'
- WebMock.stub_request(:post, @api_url)
- end
-
- it "should call Assembla API" do
- @assembla_service.execute(@sample_data)
- WebMock.should have_requested(:post, @api_url).with(
- body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/
- ).once
- end
- end
-end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 0f31c407c9..8ab72151a6 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -18,22 +18,22 @@ require 'spec_helper'
describe BroadcastMessage do
subject { create(:broadcast_message) }
- it { should be_valid }
+ it { is_expected.to be_valid }
describe :current do
it "should return last message if time match" do
broadcast_message = create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow)
- BroadcastMessage.current.should == broadcast_message
+ expect(BroadcastMessage.current).to eq(broadcast_message)
end
it "should return nil if time not come" do
broadcast_message = create(:broadcast_message, starts_at: Time.now.tomorrow, ends_at: Time.now + 2.days)
- BroadcastMessage.current.should be_nil
+ expect(BroadcastMessage.current).to be_nil
end
it "should return nil if time has passed" do
broadcast_message = create(:broadcast_message, starts_at: Time.now - 2.days, ends_at: Time.now.yesterday)
- BroadcastMessage.current.should be_nil
+ expect(BroadcastMessage.current).to be_nil
end
end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 6f201adc4e..8b3d88640d 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -6,22 +6,22 @@ describe Commit do
describe '#title' do
it "returns no_commit_message when safe_message is blank" do
- commit.stub(:safe_message).and_return('')
- commit.title.should == "--no commit message"
+ allow(commit).to receive(:safe_message).and_return('')
+ expect(commit.title).to eq("--no commit message")
end
it "truncates a message without a newline at 80 characters" do
message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis id blandit. Vivamus egestas lacinia lacus, sed rutrum mauris.'
- commit.stub(:safe_message).and_return(message)
- commit.title.should == "#{message[0..79]}…"
+ allow(commit).to receive(:safe_message).and_return(message)
+ expect(commit.title).to eq("#{message[0..79]}…")
end
it "truncates a message with a newline before 80 characters at the newline" do
message = commit.safe_message.split(" ").first
- commit.stub(:safe_message).and_return(message + "\n" + message)
- commit.title.should == message
+ allow(commit).to receive(:safe_message).and_return(message + "\n" + message)
+ expect(commit.title).to eq(message)
end
it "does not truncates a message with a newline after 80 but less 100 characters" do
@@ -30,25 +30,25 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis
Vivamus egestas lacinia lacus, sed rutrum mauris.
eos
- commit.stub(:safe_message).and_return(message)
- commit.title.should == message.split("\n").first
+ allow(commit).to receive(:safe_message).and_return(message)
+ expect(commit.title).to eq(message.split("\n").first)
end
end
describe "delegation" do
subject { commit }
- it { should respond_to(:message) }
- it { should respond_to(:authored_date) }
- it { should respond_to(:committed_date) }
- it { should respond_to(:committer_email) }
- it { should respond_to(:author_email) }
- it { should respond_to(:parents) }
- it { should respond_to(:date) }
- it { should respond_to(:diffs) }
- it { should respond_to(:tree) }
- it { should respond_to(:id) }
- it { should respond_to(:to_patch) }
+ it { is_expected.to respond_to(:message) }
+ it { is_expected.to respond_to(:authored_date) }
+ it { is_expected.to respond_to(:committed_date) }
+ it { is_expected.to respond_to(:committer_email) }
+ it { is_expected.to respond_to(:author_email) }
+ it { is_expected.to respond_to(:parents) }
+ it { is_expected.to respond_to(:date) }
+ it { is_expected.to respond_to(:diffs) }
+ it { is_expected.to respond_to(:tree) }
+ it { is_expected.to respond_to(:id) }
+ it { is_expected.to respond_to(:to_patch) }
end
describe '#closes_issues' do
@@ -57,25 +57,21 @@ eos
let(:other_issue) { create :issue, project: other_project }
it 'detects issues that this commit is marked as closing' do
- stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX',
- /Fixes #\d+/)
commit.stub(safe_message: "Fixes ##{issue.iid}")
- commit.closes_issues(project).should == [issue]
+ expect(commit.closes_issues(project)).to eq([issue])
end
it 'does not detect issues from other projects' do
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
- stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX',
- /^([Cc]loses|[Ff]ixes)/)
commit.stub(safe_message: "Fixes #{ext_ref}")
- commit.closes_issues(project).should be_empty
+ expect(commit.closes_issues(project)).to be_empty
end
end
it_behaves_like 'a mentionable' do
let(:subject) { commit }
let(:mauthor) { create :user, email: commit.author_email }
- let(:backref_text) { "commit #{subject.sha[0..5]}" }
+ let(:backref_text) { "commit #{subject.id}" }
let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } }
# Include the subject in the repository stub.
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 9cbc899067..557c71b4d2 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -4,63 +4,63 @@ describe Issue, "Issuable" do
let(:issue) { create(:issue) }
describe "Associations" do
- it { should belong_to(:project) }
- it { should belong_to(:author) }
- it { should belong_to(:assignee) }
- it { should have_many(:notes).dependent(:destroy) }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:author) }
+ it { is_expected.to belong_to(:assignee) }
+ it { is_expected.to have_many(:notes).dependent(:destroy) }
end
describe "Validation" do
before { subject.stub(set_iid: false) }
- it { should validate_presence_of(:project) }
- it { should validate_presence_of(:iid) }
- it { should validate_presence_of(:author) }
- it { should validate_presence_of(:title) }
- it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) }
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:iid) }
+ it { is_expected.to validate_presence_of(:author) }
+ it { is_expected.to validate_presence_of(:title) }
+ it { is_expected.to ensure_length_of(:title).is_at_least(0).is_at_most(255) }
end
describe "Scope" do
- it { described_class.should respond_to(:opened) }
- it { described_class.should respond_to(:closed) }
- it { described_class.should respond_to(:assigned) }
+ it { expect(described_class).to respond_to(:opened) }
+ it { expect(described_class).to respond_to(:closed) }
+ it { expect(described_class).to respond_to(:assigned) }
end
describe ".search" do
let!(:searchable_issue) { create(:issue, title: "Searchable issue") }
it "matches by title" do
- described_class.search('able').should == [searchable_issue]
+ expect(described_class.search('able')).to eq([searchable_issue])
end
end
describe "#today?" do
it "returns true when created today" do
# Avoid timezone differences and just return exactly what we want
- Date.stub(:today).and_return(issue.created_at.to_date)
- issue.today?.should be_true
+ allow(Date).to receive(:today).and_return(issue.created_at.to_date)
+ expect(issue.today?).to be_truthy
end
it "returns false when not created today" do
- Date.stub(:today).and_return(Date.yesterday)
- issue.today?.should be_false
+ allow(Date).to receive(:today).and_return(Date.yesterday)
+ expect(issue.today?).to be_falsey
end
end
describe "#new?" do
it "returns true when created today and record hasn't been updated" do
- issue.stub(:today?).and_return(true)
- issue.new?.should be_true
+ allow(issue).to receive(:today?).and_return(true)
+ expect(issue.new?).to be_truthy
end
it "returns false when not created today" do
- issue.stub(:today?).and_return(false)
- issue.new?.should be_false
+ allow(issue).to receive(:today?).and_return(false)
+ expect(issue.new?).to be_falsey
end
it "returns false when record has been updated" do
- issue.stub(:today?).and_return(true)
+ allow(issue).to receive(:today?).and_return(true)
issue.touch
- issue.new?.should be_false
+ expect(issue.new?).to be_falsey
end
end
end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
new file mode 100644
index 0000000000..eadb941a3f
--- /dev/null
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Issue, "Mentionable" do
+ describe :mentioned_users do
+ let!(:user) { create(:user, username: 'stranger') }
+ let!(:user2) { create(:user, username: 'john') }
+ let!(:issue) { create(:issue, description: '@stranger mentioned') }
+
+ subject { issue.mentioned_users }
+
+ it { is_expected.to include(user) }
+ it { is_expected.not_to include(user2) }
+ end
+end
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index adbbbac875..b32be8d7a7 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -19,7 +19,7 @@ describe DeployKey do
let(:deploy_key) { create(:deploy_key, projects: [project]) }
describe "Associations" do
- it { should have_many(:deploy_keys_projects) }
- it { should have_many(:projects) }
+ it { is_expected.to have_many(:deploy_keys_projects) }
+ it { is_expected.to have_many(:projects) }
end
end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 3e0e25ee39..f351aab923 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -13,12 +13,45 @@ require 'spec_helper'
describe DeployKeysProject do
describe "Associations" do
- it { should belong_to(:deploy_key) }
- it { should belong_to(:project) }
+ it { is_expected.to belong_to(:deploy_key) }
+ it { is_expected.to belong_to(:project) }
end
describe "Validation" do
- it { should validate_presence_of(:project_id) }
- it { should validate_presence_of(:deploy_key_id) }
+ it { is_expected.to validate_presence_of(:project_id) }
+ it { is_expected.to validate_presence_of(:deploy_key_id) }
+ end
+
+ describe "Destroying" do
+ let(:project) { create(:project) }
+ subject { create(:deploy_keys_project, project: project) }
+ let(:deploy_key) { subject.deploy_key }
+
+ context "when the deploy key is only used by this project" do
+ it "destroys the deploy key" do
+ subject.destroy
+
+ expect {
+ deploy_key.reload
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context "when the deploy key is used by more than one project" do
+
+ let!(:other_project) { create(:project) }
+
+ before do
+ other_project.deploy_keys << deploy_key
+ end
+
+ it "doesn't destroy the deploy key" do
+ subject.destroy
+
+ expect {
+ deploy_key.reload
+ }.not_to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
end
end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 1fdd959da9..0f32f162a1 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -18,16 +18,16 @@ require 'spec_helper'
describe Event do
describe "Associations" do
- it { should belong_to(:project) }
- it { should belong_to(:target) }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:target) }
end
describe "Respond to" do
- it { should respond_to(:author_name) }
- it { should respond_to(:author_email) }
- it { should respond_to(:issue_title) }
- it { should respond_to(:merge_request_title) }
- it { should respond_to(:commits) }
+ it { is_expected.to respond_to(:author_name) }
+ it { is_expected.to respond_to(:author_email) }
+ it { is_expected.to respond_to(:issue_title) }
+ it { is_expected.to respond_to(:merge_request_title) }
+ it { is_expected.to respond_to(:commits) }
end
describe "Push event" do
@@ -36,7 +36,7 @@ describe Event do
@user = project.owner
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/master",
user_id: @user.id,
@@ -58,11 +58,10 @@ describe Event do
)
end
- it { @event.push?.should be_true }
- it { @event.proper?.should be_true }
- it { @event.new_branch?.should be_true }
- it { @event.tag?.should be_false }
- it { @event.branch_name.should == "master" }
- it { @event.author.should == @user }
+ it { expect(@event.push?).to be_truthy }
+ it { expect(@event.proper?).to be_truthy }
+ it { expect(@event.tag?).to be_falsey }
+ it { expect(@event.branch_name).to eq("master") }
+ it { expect(@event.author).to eq(@user) }
end
end
diff --git a/spec/models/flowdock_service_spec.rb b/spec/models/flowdock_service_spec.rb
deleted file mode 100644
index 5540f0fa98..0000000000
--- a/spec/models/flowdock_service_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-require 'spec_helper'
-
-describe FlowdockService do
- describe "Associations" do
- it { should belong_to :project }
- it { should have_one :service_hook }
- end
-
- describe "Execute" do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
-
- before do
- @flowdock_service = FlowdockService.new
- @flowdock_service.stub(
- project_id: project.id,
- project: project,
- service_hook: true,
- token: 'verySecret'
- )
- @sample_data = GitPushService.new.sample_data(project, user)
- @api_url = 'https://api.flowdock.com/v1/git/verySecret'
- WebMock.stub_request(:post, @api_url)
- end
-
- it "should call FlowDock API" do
- @flowdock_service.execute(@sample_data)
- WebMock.should have_requested(:post, @api_url).with(
- body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/
- ).once
- end
- end
-end
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index 1845c6103f..7d0ad44a92 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -21,11 +21,11 @@ describe ForkedProjectLink, "add link on fork" do
end
it "project_to should know it is forked" do
- @project_to.forked?.should be_true
+ expect(@project_to.forked?).to be_truthy
end
it "project should know who it is forked from" do
- @project_to.forked_from_project.should == project_from
+ expect(@project_to.forked_from_project).to eq(project_from)
end
end
@@ -43,15 +43,15 @@ describe :forked_from_project do
it "project_to should know it is forked" do
- project_to.forked?.should be_true
+ expect(project_to.forked?).to be_truthy
end
it "project_from should not be forked" do
- project_from.forked?.should be_false
+ expect(project_from.forked?).to be_falsey
end
it "project_to.destroy should destroy fork_link" do
- forked_project_link.should_receive(:destroy)
+ expect(forked_project_link).to receive(:destroy)
project_to.destroy
end
diff --git a/spec/models/gemnasium_service_spec.rb b/spec/models/gemnasium_service_spec.rb
deleted file mode 100644
index 60ffa6f8b0..0000000000
--- a/spec/models/gemnasium_service_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-require 'spec_helper'
-
-describe GemnasiumService do
- describe "Associations" do
- it { should belong_to :project }
- it { should have_one :service_hook }
- end
-
- describe "Execute" do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
-
- before do
- @gemnasium_service = GemnasiumService.new
- @gemnasium_service.stub(
- project_id: project.id,
- project: project,
- service_hook: true,
- token: 'verySecret',
- api_key: 'GemnasiumUserApiKey'
- )
- @sample_data = GitPushService.new.sample_data(project, user)
- end
- it "should call Gemnasium service" do
- Gemnasium::GitlabService.should_receive(:execute).with(an_instance_of(Hash)).once
- @gemnasium_service.execute(@sample_data)
- end
- end
-end
diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb
deleted file mode 100644
index ebc377047b..0000000000
--- a/spec/models/gitlab_ci_service_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-require 'spec_helper'
-
-describe GitlabCiService do
- describe "Associations" do
- it { should belong_to :project }
- it { should have_one :service_hook }
- end
-
- describe "Mass assignment" do
- end
-
- describe 'commits methods' do
- before do
- @service = GitlabCiService.new
- @service.stub(
- service_hook: true,
- project_url: 'http://ci.gitlab.org/projects/2',
- token: 'verySecret'
- )
- end
-
- describe :commit_status_path do
- it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c/status.json?token=verySecret"}
- end
-
- describe :build_page do
- it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c"}
- end
- end
-end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 1d4ba8a2b8..9428224a64 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -19,29 +19,29 @@ describe Group do
let!(:group) { create(:group) }
describe "Associations" do
- it { should have_many :projects }
- it { should have_many :group_members }
+ it { is_expected.to have_many :projects }
+ it { is_expected.to have_many :group_members }
end
- it { should validate_presence_of :name }
- it { should validate_uniqueness_of(:name) }
- it { should validate_presence_of :path }
- it { should validate_uniqueness_of(:path) }
- it { should_not validate_presence_of :owner }
+ it { is_expected.to validate_presence_of :name }
+ it { is_expected.to validate_uniqueness_of(:name) }
+ it { is_expected.to validate_presence_of :path }
+ it { is_expected.to validate_uniqueness_of(:path) }
+ it { is_expected.not_to validate_presence_of :owner }
describe :users do
- it { group.users.should == group.owners }
+ it { expect(group.users).to eq(group.owners) }
end
describe :human_name do
- it { group.human_name.should == group.name }
+ it { expect(group.human_name).to eq(group.name) }
end
describe :add_users do
let(:user) { create(:user) }
before { group.add_user(user, GroupMember::MASTER) }
- it { group.group_members.masters.map(&:user).should include(user) }
+ it { expect(group.group_members.masters.map(&:user)).to include(user) }
end
describe :add_users do
@@ -49,10 +49,10 @@ describe Group do
before { group.add_users([user.id], GroupMember::GUEST) }
it "should update the group permission" do
- group.group_members.guests.map(&:user).should include(user)
+ expect(group.group_members.guests.map(&:user)).to include(user)
group.add_users([user.id], GroupMember::DEVELOPER)
- group.group_members.developers.map(&:user).should include(user)
- group.group_members.guests.map(&:user).should_not include(user)
+ expect(group.group_members.developers.map(&:user)).to include(user)
+ expect(group.group_members.guests.map(&:user)).not_to include(user)
end
end
@@ -62,12 +62,12 @@ describe Group do
it "should be true if avatar is image" do
group.update_attribute(:avatar, 'uploads/avatar.png')
- group.avatar_type.should be_true
+ expect(group.avatar_type).to be_truthy
end
it "should be false if avatar is html page" do
group.update_attribute(:avatar, 'uploads/avatar.html')
- group.avatar_type.should == ["only images allowed"]
+ expect(group.avatar_type).to eq(["only images allowed"])
end
end
end
diff --git a/spec/models/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
similarity index 100%
rename from spec/models/project_hook_spec.rb
rename to spec/models/hooks/project_hook_spec.rb
diff --git a/spec/models/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
similarity index 94%
rename from spec/models/service_hook_spec.rb
rename to spec/models/hooks/service_hook_spec.rb
index 6ec82438df..96bf74d45d 100644
--- a/spec/models/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -19,6 +19,6 @@ require "spec_helper"
describe ServiceHook do
describe "Associations" do
- it { should belong_to :service }
+ it { is_expected.to belong_to :service }
end
end
diff --git a/spec/models/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
similarity index 50%
rename from spec/models/system_hook_spec.rb
rename to spec/models/hooks/system_hook_spec.rb
index 4ab5261dc9..810b311a40 100644
--- a/spec/models/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -26,32 +26,32 @@ describe SystemHook do
it "project_create hook" do
Projects::CreateService.new(create(:user), name: 'empty').execute
- WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /project_create/).once
end
it "project_destroy hook" do
user = create(:user)
project = create(:empty_project, namespace: user.namespace)
Projects::DestroyService.new(project, user, {}).execute
- WebMock.should have_requested(:post, @system_hook.url).with(body: /project_destroy/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /project_destroy/).once
end
it "user_create hook" do
create(:user)
- WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_create/).once
end
it "user_destroy hook" do
user = create(:user)
user.destroy
- WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_destroy/).once
end
it "project_create hook" do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once
end
it "project_destroy hook" do
@@ -59,7 +59,42 @@ describe SystemHook do
project = create(:project)
project.team << [user, :master]
project.project_members.destroy_all
- WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once
end
+
+ it 'group create hook' do
+ create(:group)
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(
+ body: /group_create/
+ ).once
+ end
+
+ it 'group destroy hook' do
+ group = create(:group)
+ group.destroy
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(
+ body: /group_destroy/
+ ).once
+ end
+
+ it 'group member create hook' do
+ group = create(:group)
+ user = create(:user)
+ group.add_user(user, Gitlab::Access::MASTER)
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(
+ body: /user_add_to_group/
+ ).once
+ end
+
+ it 'group member destroy hook' do
+ group = create(:group)
+ user = create(:user)
+ group.add_user(user, Gitlab::Access::MASTER)
+ group.group_members.destroy_all
+ expect(WebMock).to have_requested(:post, @system_hook.url).with(
+ body: /user_remove_from_group/
+ ).once
+ end
+
end
end
diff --git a/spec/models/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
similarity index 59%
rename from spec/models/web_hook_spec.rb
rename to spec/models/hooks/web_hook_spec.rb
index e9c04ee89c..67ec9193ad 100644
--- a/spec/models/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -19,25 +19,25 @@ require 'spec_helper'
describe ProjectHook do
describe "Associations" do
- it { should belong_to :project }
+ it { is_expected.to belong_to :project }
end
describe "Mass assignment" do
end
describe "Validations" do
- it { should validate_presence_of(:url) }
+ it { is_expected.to validate_presence_of(:url) }
context "url format" do
- it { should allow_value("http://example.com").for(:url) }
- it { should allow_value("https://excample.com").for(:url) }
- it { should allow_value("http://test.com/api").for(:url) }
- it { should allow_value("http://test.com/api?key=abc").for(:url) }
- it { should allow_value("http://test.com/api?key=abc&type=def").for(:url) }
+ it { is_expected.to allow_value("http://example.com").for(:url) }
+ it { is_expected.to allow_value("https://excample.com").for(:url) }
+ it { is_expected.to allow_value("http://test.com/api").for(:url) }
+ it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) }
+ it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) }
- it { should_not allow_value("example.com").for(:url) }
- it { should_not allow_value("ftp://example.com").for(:url) }
- it { should_not allow_value("herp-and-derp").for(:url) }
+ it { is_expected.not_to allow_value("example.com").for(:url) }
+ it { is_expected.not_to allow_value("ftp://example.com").for(:url) }
+ it { is_expected.not_to allow_value("herp-and-derp").for(:url) }
end
end
@@ -53,22 +53,22 @@ describe ProjectHook do
it "POSTs to the web hook URL" do
@project_hook.execute(@data)
- WebMock.should have_requested(:post, @project_hook.url).once
+ expect(WebMock).to have_requested(:post, @project_hook.url).once
end
it "POSTs the data as JSON" do
json = @data.to_json
@project_hook.execute(@data)
- WebMock.should have_requested(:post, @project_hook.url).with(body: json).once
+ expect(WebMock).to have_requested(:post, @project_hook.url).with(body: json).once
end
it "catches exceptions" do
- WebHook.should_receive(:post).and_raise("Some HTTP Post error")
+ expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
- lambda {
+ expect {
@project_hook.execute(@data)
- }.should raise_error
+ }.to raise_error
end
end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 6b6efe832e..087e40c3d8 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -21,14 +21,14 @@ require 'spec_helper'
describe Issue do
describe "Associations" do
- it { should belong_to(:milestone) }
+ it { is_expected.to belong_to(:milestone) }
end
describe "Mass assignment" do
end
describe 'modules' do
- it { should include_module(Issuable) }
+ it { is_expected.to include_module(Issuable) }
end
subject { create(:issue) }
@@ -36,10 +36,10 @@ describe Issue do
describe '#is_being_reassigned?' do
it 'returns true if the issue assignee has changed' do
subject.assignee = create(:user)
- subject.is_being_reassigned?.should be_true
+ expect(subject.is_being_reassigned?).to be_truthy
end
it 'returns false if the issue assignee has not changed' do
- subject.is_being_reassigned?.should be_false
+ expect(subject.is_being_reassigned?).to be_falsey
end
end
@@ -51,7 +51,7 @@ describe Issue do
issue = create :issue, assignee: user
end
- Issue.open_for(user).count.should eq 2
+ expect(Issue.open_for(user).count).to eq 2
end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 95c0aed0ff..a212b95a7d 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -16,67 +16,67 @@ require 'spec_helper'
describe Key do
describe "Associations" do
- it { should belong_to(:user) }
+ it { is_expected.to belong_to(:user) }
end
describe "Mass assignment" do
end
describe "Validation" do
- it { should validate_presence_of(:title) }
- it { should validate_presence_of(:key) }
- it { should ensure_length_of(:title).is_within(0..255) }
- it { should ensure_length_of(:key).is_within(0..5000) }
+ it { is_expected.to validate_presence_of(:title) }
+ it { is_expected.to validate_presence_of(:key) }
+ it { is_expected.to ensure_length_of(:title).is_within(0..255) }
+ it { is_expected.to ensure_length_of(:key).is_within(0..5000) }
end
describe "Methods" do
- it { should respond_to :projects }
+ it { is_expected.to respond_to :projects }
end
context "validation of uniqueness" do
let(:user) { create(:user) }
it "accepts the key once" do
- build(:key, user: user).should be_valid
+ expect(build(:key, user: user)).to be_valid
end
it "does not accept the exact same key twice" do
create(:key, user: user)
- build(:key, user: user).should_not be_valid
+ expect(build(:key, user: user)).not_to be_valid
end
it "does not accept a duplicate key with a different comment" do
create(:key, user: user)
duplicate = build(:key, user: user)
duplicate.key << ' extra comment'
- duplicate.should_not be_valid
+ expect(duplicate).not_to be_valid
end
end
context "validate it is a fingerprintable key" do
it "accepts the fingerprintable key" do
- build(:key).should be_valid
+ expect(build(:key)).to be_valid
end
it "rejects the unfingerprintable key (contains space in middle)" do
- build(:key_with_a_space_in_the_middle).should_not be_valid
+ expect(build(:key_with_a_space_in_the_middle)).not_to be_valid
end
it "rejects the unfingerprintable key (not a key)" do
- build(:invalid_key).should_not be_valid
+ expect(build(:invalid_key)).not_to be_valid
end
end
context 'callbacks' do
it 'should add new key to authorized_file' do
@key = build(:personal_key, id: 7)
- GitlabShellWorker.should_receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
+ expect(GitlabShellWorker).to receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
@key.save
end
it 'should remove key from authorized_file' do
@key = create(:personal_key)
- GitlabShellWorker.should_receive(:perform_async).with(:remove_key, @key.shell_id, @key.key)
+ expect(GitlabShellWorker).to receive(:perform_async).with(:remove_key, @key.shell_id, @key.key)
@key.destroy
end
end
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index 0db60432ad..8c24082658 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -14,8 +14,8 @@ require 'spec_helper'
describe LabelLink do
let(:label) { create(:label_link) }
- it { label.should be_valid }
+ it { expect(label).to be_valid }
- it { should belong_to(:label) }
- it { should belong_to(:target) }
+ it { is_expected.to belong_to(:label) }
+ it { is_expected.to belong_to(:target) }
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 31634648f0..8644ac4660 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -14,30 +14,30 @@ require 'spec_helper'
describe Label do
let(:label) { create(:label) }
- it { label.should be_valid }
+ it { expect(label).to be_valid }
- it { should belong_to(:project) }
+ it { is_expected.to belong_to(:project) }
describe 'Validation' do
it 'should validate color code' do
- build(:label, color: 'G-ITLAB').should_not be_valid
- build(:label, color: 'AABBCC').should_not be_valid
- build(:label, color: '#AABBCCEE').should_not be_valid
- build(:label, color: '#GGHHII').should_not be_valid
- build(:label, color: '#').should_not be_valid
- build(:label, color: '').should_not be_valid
+ expect(build(:label, color: 'G-ITLAB')).not_to be_valid
+ expect(build(:label, color: 'AABBCC')).not_to be_valid
+ expect(build(:label, color: '#AABBCCEE')).not_to be_valid
+ expect(build(:label, color: '#GGHHII')).not_to be_valid
+ expect(build(:label, color: '#')).not_to be_valid
+ expect(build(:label, color: '')).not_to be_valid
- build(:label, color: '#AABBCC').should be_valid
+ expect(build(:label, color: '#AABBCC')).to be_valid
end
it 'should validate title' do
- build(:label, title: 'G,ITLAB').should_not be_valid
- build(:label, title: 'G?ITLAB').should_not be_valid
- build(:label, title: 'G&ITLAB').should_not be_valid
- build(:label, title: '').should_not be_valid
+ expect(build(:label, title: 'G,ITLAB')).not_to be_valid
+ expect(build(:label, title: 'G?ITLAB')).not_to be_valid
+ expect(build(:label, title: 'G&ITLAB')).not_to be_valid
+ expect(build(:label, title: '')).not_to be_valid
- build(:label, title: 'GITLAB').should be_valid
- build(:label, title: 'gitlab').should be_valid
+ expect(build(:label, title: 'GITLAB')).to be_valid
+ expect(build(:label, title: 'gitlab')).to be_valid
end
end
end
diff --git a/spec/models/group_member_spec.rb b/spec/models/members/group_member_spec.rb
similarity index 67%
rename from spec/models/group_member_spec.rb
rename to spec/models/members/group_member_spec.rb
index 38657de679..e206c11f33 100644
--- a/spec/models/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -21,25 +21,25 @@ describe GroupMember do
it "should send email to user" do
membership = build(:group_member)
membership.stub(notification_service: double('NotificationService').as_null_object)
- membership.should_receive(:notification_service)
+ expect(membership).to receive(:notification_service)
membership.save
end
end
describe "#after_update" do
before do
- @membership = create :group_member
- @membership.stub(notification_service: double('NotificationService').as_null_object)
+ @group_member = create :group_member
+ @group_member.stub(notification_service: double('NotificationService').as_null_object)
end
it "should send email to user" do
- @membership.should_receive(:notification_service)
- @membership.update_attribute(:access_level, GroupMember::MASTER)
+ expect(@group_member).to receive(:notification_service)
+ @group_member.update_attribute(:access_level, GroupMember::MASTER)
end
it "does not send an email when the access level has not changed" do
- @membership.should_not_receive(:notification_service)
- @membership.update_attribute(:access_level, GroupMember::OWNER)
+ expect(@group_member).not_to receive(:notification_service)
+ @group_member.update_attribute(:access_level, GroupMember::OWNER)
end
end
end
diff --git a/spec/models/project_member_spec.rb b/spec/models/members/project_member_spec.rb
similarity index 69%
rename from spec/models/project_member_spec.rb
rename to spec/models/members/project_member_spec.rb
index 9b5f89b6d7..521721f357 100644
--- a/spec/models/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -33,19 +33,19 @@ describe ProjectMember do
@status = @project_2.team.import(@project_1)
end
- it { @status.should be_true }
+ it { expect(@status).to be_truthy }
describe 'project 2 should get user 1 as developer. user_2 should not be changed' do
- it { @project_2.users.should include(@user_1) }
- it { @project_2.users.should include(@user_2) }
+ it { expect(@project_2.users).to include(@user_1) }
+ it { expect(@project_2.users).to include(@user_2) }
- it { @abilities.allowed?(@user_1, :write_project, @project_2).should be_true }
- it { @abilities.allowed?(@user_2, :read_project, @project_2).should be_true }
+ it { expect(@abilities.allowed?(@user_1, :write_project, @project_2)).to be_truthy }
+ it { expect(@abilities.allowed?(@user_2, :read_project, @project_2)).to be_truthy }
end
describe 'project 1 should not be changed' do
- it { @project_1.users.should include(@user_1) }
- it { @project_1.users.should_not include(@user_2) }
+ it { expect(@project_1.users).to include(@user_1) }
+ it { expect(@project_1.users).not_to include(@user_2) }
end
end
@@ -64,12 +64,12 @@ describe ProjectMember do
)
end
- it { @project_1.users.should include(@user_1) }
- it { @project_1.users.should include(@user_2) }
+ it { expect(@project_1.users).to include(@user_1) }
+ it { expect(@project_1.users).to include(@user_2) }
- it { @project_2.users.should include(@user_1) }
- it { @project_2.users.should include(@user_2) }
+ it { expect(@project_2.users).to include(@user_1) }
+ it { expect(@project_2.users).to include(@user_2) }
end
describe :truncate_teams do
@@ -86,7 +86,7 @@ describe ProjectMember do
ProjectMember.truncate_teams([@project_1.id, @project_2.id])
end
- it { @project_1.users.should be_empty }
- it { @project_2.users.should be_empty }
+ it { expect(@project_1.users).to be_empty }
+ it { expect(@project_2.users).to be_empty }
end
end
diff --git a/spec/models/members_spec.rb b/spec/models/members_spec.rb
index 6866c4794c..dfd3f7feb6 100644
--- a/spec/models/members_spec.rb
+++ b/spec/models/members_spec.rb
@@ -2,19 +2,19 @@ require 'spec_helper'
describe Member do
describe "Associations" do
- it { should belong_to(:user) }
+ it { is_expected.to belong_to(:user) }
end
describe "Validation" do
subject { Member.new(access_level: Member::GUEST) }
- it { should validate_presence_of(:user) }
- it { should validate_presence_of(:source) }
- it { should ensure_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
+ it { is_expected.to validate_presence_of(:user) }
+ it { is_expected.to validate_presence_of(:source) }
+ it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
end
describe "Delegate methods" do
- it { should respond_to(:user_name) }
- it { should respond_to(:user_email) }
+ it { is_expected.to respond_to(:user_name) }
+ it { is_expected.to respond_to(:user_email) }
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 7b0d261d72..d40503d791 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -18,41 +18,42 @@
# iid :integer
# description :text
# position :integer default(0)
+# locked_at :datetime
#
require 'spec_helper'
describe MergeRequest do
describe "Validation" do
- it { should validate_presence_of(:target_branch) }
- it { should validate_presence_of(:source_branch) }
+ it { is_expected.to validate_presence_of(:target_branch) }
+ it { is_expected.to validate_presence_of(:source_branch) }
end
describe "Mass assignment" do
end
describe "Respond to" do
- it { should respond_to(:unchecked?) }
- it { should respond_to(:can_be_merged?) }
- it { should respond_to(:cannot_be_merged?) }
+ it { is_expected.to respond_to(:unchecked?) }
+ it { is_expected.to respond_to(:can_be_merged?) }
+ it { is_expected.to respond_to(:cannot_be_merged?) }
end
describe 'modules' do
- it { should include_module(Issuable) }
+ it { is_expected.to include_module(Issuable) }
end
describe "#mr_and_commit_notes" do
let!(:merge_request) { create(:merge_request) }
before do
- merge_request.stub(:commits) { [merge_request.source_project.repository.commit] }
+ allow(merge_request).to receive(:commits) { [merge_request.source_project.repository.commit] }
create(:note, commit_id: merge_request.commits.first.id, noteable_type: 'Commit', project: merge_request.project)
create(:note, noteable: merge_request, project: merge_request.project)
end
it "should include notes for commits" do
- merge_request.commits.should_not be_empty
- merge_request.mr_and_commit_notes.count.should == 2
+ expect(merge_request.commits).not_to be_empty
+ expect(merge_request.mr_and_commit_notes.count).to eq(2)
end
end
@@ -61,10 +62,10 @@ describe MergeRequest do
describe '#is_being_reassigned?' do
it 'returns true if the merge_request assignee has changed' do
subject.assignee = create(:user)
- subject.is_being_reassigned?.should be_true
+ expect(subject.is_being_reassigned?).to be_truthy
end
it 'returns false if the merge request assignee has not changed' do
- subject.is_being_reassigned?.should be_false
+ expect(subject.is_being_reassigned?).to be_falsey
end
end
@@ -73,11 +74,11 @@ describe MergeRequest do
subject.source_project = create(:project, namespace: create(:group))
subject.target_project = create(:project, namespace: create(:group))
- subject.for_fork?.should be_true
+ expect(subject.for_fork?).to be_truthy
end
it 'returns false if is not for a fork' do
- subject.for_fork?.should be_false
+ expect(subject.for_fork?).to be_falsey
end
end
@@ -95,14 +96,14 @@ describe MergeRequest do
it 'accesses the set of issues that will be closed on acceptance' do
subject.project.stub(default_branch: subject.target_branch)
- subject.closes_issues.should == [issue0, issue1].sort_by(&:id)
+ expect(subject.closes_issues).to eq([issue0, issue1].sort_by(&:id))
end
it 'only lists issues as to be closed if it targets the default branch' do
subject.project.stub(default_branch: 'master')
subject.target_branch = 'something-else'
- subject.closes_issues.should be_empty
+ expect(subject.closes_issues).to be_empty
end
it 'detects issues mentioned in the description' do
@@ -110,7 +111,7 @@ describe MergeRequest do
subject.description = "Closes ##{issue2.iid}"
subject.project.stub(default_branch: subject.target_branch)
- subject.closes_issues.should include(issue2)
+ expect(subject.closes_issues).to include(issue2)
end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index a3071c3251..45171e1bf6 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -17,8 +17,8 @@ require 'spec_helper'
describe Milestone do
describe "Associations" do
- it { should belong_to(:project) }
- it { should have_many(:issues) }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:issues) }
end
describe "Mass assignment" do
@@ -26,8 +26,8 @@ describe Milestone do
describe "Validation" do
before { subject.stub(set_iid: false) }
- it { should validate_presence_of(:title) }
- it { should validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:title) }
+ it { is_expected.to validate_presence_of(:project) }
end
let(:milestone) { create(:milestone) }
@@ -36,30 +36,30 @@ describe Milestone do
describe "#percent_complete" do
it "should not count open issues" do
milestone.issues << issue
- milestone.percent_complete.should == 0
+ expect(milestone.percent_complete).to eq(0)
end
it "should count closed issues" do
issue.close
milestone.issues << issue
- milestone.percent_complete.should == 100
+ expect(milestone.percent_complete).to eq(100)
end
it "should recover from dividing by zero" do
- milestone.issues.should_receive(:count).and_return(0)
- milestone.percent_complete.should == 100
+ expect(milestone.issues).to receive(:count).and_return(0)
+ expect(milestone.percent_complete).to eq(100)
end
end
describe "#expires_at" do
it "should be nil when due_date is unset" do
milestone.update_attributes(due_date: nil)
- milestone.expires_at.should be_nil
+ expect(milestone.expires_at).to be_nil
end
it "should not be nil when due_date is set" do
milestone.update_attributes(due_date: Date.tomorrow)
- milestone.expires_at.should be_present
+ expect(milestone.expires_at).to be_present
end
end
@@ -69,7 +69,7 @@ describe Milestone do
milestone.stub(due_date: Date.today.prev_year)
end
- it { milestone.expired?.should be_true }
+ it { expect(milestone.expired?).to be_truthy }
end
context "not expired" do
@@ -77,7 +77,7 @@ describe Milestone do
milestone.stub(due_date: Date.today.next_year)
end
- it { milestone.expired?.should be_false }
+ it { expect(milestone.expired?).to be_falsey }
end
end
@@ -89,7 +89,7 @@ describe Milestone do
)
end
- it { milestone.percent_complete.should == 75 }
+ it { expect(milestone.percent_complete).to eq(75) }
end
describe :items_count do
@@ -99,14 +99,14 @@ describe Milestone do
milestone.merge_requests << create(:merge_request)
end
- it { milestone.closed_items_count.should == 1 }
- it { milestone.open_items_count.should == 2 }
- it { milestone.total_items_count.should == 3 }
- it { milestone.is_empty?.should be_false }
+ it { expect(milestone.closed_items_count).to eq(1) }
+ it { expect(milestone.open_items_count).to eq(2) }
+ it { expect(milestone.total_items_count).to eq(3) }
+ it { expect(milestone.is_empty?).to be_falsey }
end
describe :can_be_closed? do
- it { milestone.can_be_closed?.should be_true }
+ it { expect(milestone.can_be_closed?).to be_truthy }
end
describe :is_empty? do
@@ -116,7 +116,7 @@ describe Milestone do
end
it 'Should return total count of issues and merge requests assigned to milestone' do
- milestone.total_items_count.should eq 2
+ expect(milestone.total_items_count).to eq 2
end
end
@@ -129,14 +129,14 @@ describe Milestone do
end
it 'should be true if milestone active and all nested issues closed' do
- milestone.can_be_closed?.should be_true
+ expect(milestone.can_be_closed?).to be_truthy
end
it 'should be false if milestone active and not all nested issues closed' do
issue.milestone = milestone
issue.save
- milestone.can_be_closed?.should be_false
+ expect(milestone.can_be_closed?).to be_falsey
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 3562ebed1f..ed6845c82c 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -18,29 +18,29 @@ require 'spec_helper'
describe Namespace do
let!(:namespace) { create(:namespace) }
- it { should have_many :projects }
- it { should validate_presence_of :name }
- it { should validate_uniqueness_of(:name) }
- it { should validate_presence_of :path }
- it { should validate_uniqueness_of(:path) }
- it { should validate_presence_of :owner }
+ it { is_expected.to have_many :projects }
+ it { is_expected.to validate_presence_of :name }
+ it { is_expected.to validate_uniqueness_of(:name) }
+ it { is_expected.to validate_presence_of :path }
+ it { is_expected.to validate_uniqueness_of(:path) }
+ it { is_expected.to validate_presence_of :owner }
describe "Mass assignment" do
end
describe "Respond to" do
- it { should respond_to(:human_name) }
- it { should respond_to(:to_param) }
+ it { is_expected.to respond_to(:human_name) }
+ it { is_expected.to respond_to(:to_param) }
end
- it { Namespace.global_id.should == 'GLN' }
+ it { expect(Namespace.global_id).to eq('GLN') }
describe :to_param do
- it { namespace.to_param.should == namespace.path }
+ it { expect(namespace.to_param).to eq(namespace.path) }
end
describe :human_name do
- it { namespace.human_name.should == namespace.owner_name }
+ it { expect(namespace.human_name).to eq(namespace.owner_name) }
end
describe :search do
@@ -48,8 +48,8 @@ describe Namespace do
@namespace = create :namespace
end
- it { Namespace.search(@namespace.path).should == [@namespace] }
- it { Namespace.search('unknown').should == [] }
+ it { expect(Namespace.search(@namespace.path)).to eq([@namespace]) }
+ it { expect(Namespace.search('unknown')).to eq([]) }
end
describe :move_dir do
@@ -66,13 +66,23 @@ describe Namespace do
new_path = @namespace.path + "_new"
@namespace.stub(path_was: @namespace.path)
@namespace.stub(path: new_path)
- @namespace.move_dir.should be_true
+ expect(@namespace.move_dir).to be_truthy
end
end
describe :rm_dir do
it "should remove dir" do
- namespace.rm_dir.should be_true
+ expect(namespace.rm_dir).to be_truthy
end
end
+
+ describe :find_by_path_or_name do
+ before do
+ @namespace = create(:namespace, name: 'WoW', path: 'woW')
+ end
+
+ it { expect(Namespace.find_by_path_or_name('wow')).to eq(@namespace) }
+ it { expect(Namespace.find_by_path_or_name('WOW')).to eq(@namespace) }
+ it { expect(Namespace.find_by_path_or_name('unknown')).to eq(nil) }
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index eeecd714a2..17cb439c90 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -21,17 +21,17 @@ require 'spec_helper'
describe Note do
describe "Associations" do
- it { should belong_to(:project) }
- it { should belong_to(:noteable) }
- it { should belong_to(:author).class_name('User') }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:noteable) }
+ it { is_expected.to belong_to(:author).class_name('User') }
end
describe "Mass assignment" do
end
describe "Validation" do
- it { should validate_presence_of(:note) }
- it { should validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:note) }
+ it { is_expected.to validate_presence_of(:project) }
end
describe "Voting score" do
@@ -39,44 +39,44 @@ describe Note do
it "recognizes a neutral note" do
note = create(:votable_note, note: "This is not a +1 note")
- note.should_not be_upvote
- note.should_not be_downvote
+ expect(note).not_to be_upvote
+ expect(note).not_to be_downvote
end
it "recognizes a neutral emoji note" do
note = build(:votable_note, note: "I would :+1: this, but I don't want to")
- note.should_not be_upvote
- note.should_not be_downvote
+ expect(note).not_to be_upvote
+ expect(note).not_to be_downvote
end
it "recognizes a +1 note" do
note = create(:votable_note, note: "+1 for this")
- note.should be_upvote
+ expect(note).to be_upvote
end
it "recognizes a +1 emoji as a vote" do
note = build(:votable_note, note: ":+1: for this")
- note.should be_upvote
+ expect(note).to be_upvote
end
it "recognizes a thumbsup emoji as a vote" do
note = build(:votable_note, note: ":thumbsup: for this")
- note.should be_upvote
+ expect(note).to be_upvote
end
it "recognizes a -1 note" do
note = create(:votable_note, note: "-1 for this")
- note.should be_downvote
+ expect(note).to be_downvote
end
it "recognizes a -1 emoji as a vote" do
note = build(:votable_note, note: ":-1: for this")
- note.should be_downvote
+ expect(note).to be_downvote
end
it "recognizes a thumbsdown emoji as a vote" do
note = build(:votable_note, note: ":thumbsdown: for this")
- note.should be_downvote
+ expect(note).to be_downvote
end
end
@@ -87,22 +87,22 @@ describe Note do
let!(:commit) { note.noteable }
it "should be accessible through #noteable" do
- note.commit_id.should == commit.id
- note.noteable.should be_a(Commit)
- note.noteable.should == commit
+ expect(note.commit_id).to eq(commit.id)
+ expect(note.noteable).to be_a(Commit)
+ expect(note.noteable).to eq(commit)
end
it "should save a valid note" do
- note.commit_id.should == commit.id
+ expect(note.commit_id).to eq(commit.id)
note.noteable == commit
end
it "should be recognized by #for_commit?" do
- note.should be_for_commit
+ expect(note).to be_for_commit
end
it "should not be votable" do
- note.should_not be_votable
+ expect(note).not_to be_votable
end
end
@@ -111,20 +111,20 @@ describe Note do
let!(:commit) { note.noteable }
it "should save a valid note" do
- note.commit_id.should == commit.id
- note.noteable.id.should == commit.id
+ expect(note.commit_id).to eq(commit.id)
+ expect(note.noteable.id).to eq(commit.id)
end
it "should be recognized by #for_diff_line?" do
- note.should be_for_diff_line
+ expect(note).to be_for_diff_line
end
it "should be recognized by #for_commit_diff_line?" do
- note.should be_for_commit_diff_line
+ expect(note).to be_for_commit_diff_line
end
it "should not be votable" do
- note.should_not be_votable
+ expect(note).not_to be_votable
end
end
@@ -132,7 +132,7 @@ describe Note do
let!(:note) { create(:note_on_issue, note: "+1 from me") }
it "should not be votable" do
- note.should be_votable
+ expect(note).to be_votable
end
end
@@ -140,7 +140,7 @@ describe Note do
let!(:note) { create(:note_on_merge_request, note: "+1 from me") }
it "should be votable" do
- note.should be_votable
+ expect(note).to be_votable
end
end
@@ -148,7 +148,7 @@ describe Note do
let!(:note) { create(:note_on_merge_request_diff, note: "+1 from me") }
it "should not be votable" do
- note.should_not be_votable
+ expect(note).not_to be_votable
end
end
@@ -161,20 +161,35 @@ describe Note do
subject { Note.create_status_change_note(thing, project, author, status, nil) }
it 'creates and saves a Note' do
- should be_a Note
- subject.id.should_not be_nil
+ is_expected.to be_a Note
+ expect(subject.id).not_to be_nil
end
- its(:noteable) { should == thing }
- its(:project) { should == thing.project }
- its(:author) { should == author }
- its(:note) { should =~ /Status changed to #{status}/ }
+ describe '#noteable' do
+ subject { super().noteable }
+ it { is_expected.to eq(thing) }
+ end
+
+ describe '#project' do
+ subject { super().project }
+ it { is_expected.to eq(thing.project) }
+ end
+
+ describe '#author' do
+ subject { super().author }
+ it { is_expected.to eq(author) }
+ end
+
+ describe '#note' do
+ subject { super().note }
+ it { is_expected.to match(/Status changed to #{status}/) }
+ end
it 'appends a back-reference if a closing mentionable is supplied' do
commit = double('commit', gfm_reference: 'commit 123456')
n = Note.create_status_change_note(thing, project, author, status, commit)
- n.note.should =~ /Status changed to #{status} by commit 123456/
+ expect(n.note).to match(/Status changed to #{status} by commit 123456/)
end
end
@@ -187,19 +202,41 @@ describe Note do
subject { Note.create_assignee_change_note(thing, project, author, assignee) }
context 'creates and saves a Note' do
- it { should be_a Note }
- its(:id) { should_not be_nil }
+ it { is_expected.to be_a Note }
+
+ describe '#id' do
+ subject { super().id }
+ it { is_expected.not_to be_nil }
+ end
end
- its(:noteable) { should == thing }
- its(:project) { should == thing.project }
- its(:author) { should == author }
- its(:note) { should =~ /Reassigned to @#{assignee.username}/ }
+ describe '#noteable' do
+ subject { super().noteable }
+ it { is_expected.to eq(thing) }
+ end
+
+ describe '#project' do
+ subject { super().project }
+ it { is_expected.to eq(thing.project) }
+ end
+
+ describe '#author' do
+ subject { super().author }
+ it { is_expected.to eq(author) }
+ end
+
+ describe '#note' do
+ subject { super().note }
+ it { is_expected.to match(/Reassigned to @#{assignee.username}/) }
+ end
context 'assignee is removed' do
let(:assignee) { nil }
- its(:note) { should =~ /Assignee removed/ }
+ describe '#note' do
+ subject { super().note }
+ it { is_expected.to match(/Assignee removed/) }
+ end
end
end
@@ -216,58 +253,144 @@ describe Note do
context 'issue from a merge request' do
subject { Note.create_cross_reference_note(issue, mergereq, author, project) }
- it { should be_valid }
- its(:noteable) { should == issue }
- its(:project) { should == issue.project }
- its(:author) { should == author }
- its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" }
+ it { is_expected.to be_valid }
+
+ describe '#noteable' do
+ subject { super().noteable }
+ it { is_expected.to eq(issue) }
+ end
+
+ describe '#project' do
+ subject { super().project }
+ it { is_expected.to eq(issue.project) }
+ end
+
+ describe '#author' do
+ subject { super().author }
+ it { is_expected.to eq(author) }
+ end
+
+ describe '#note' do
+ subject { super().note }
+ it { is_expected.to eq("_mentioned in merge request !#{mergereq.iid}_") }
+ end
end
context 'issue from a commit' do
subject { Note.create_cross_reference_note(issue, commit, author, project) }
- it { should be_valid }
- its(:noteable) { should == issue }
- its(:note) { should == "_mentioned in commit #{commit.sha[0..5]}_" }
+ it { is_expected.to be_valid }
+
+ describe '#noteable' do
+ subject { super().noteable }
+ it { is_expected.to eq(issue) }
+ end
+
+ describe '#note' do
+ subject { super().note }
+ it { is_expected.to eq("_mentioned in commit #{commit.sha}_") }
+ end
end
context 'merge request from an issue' do
subject { Note.create_cross_reference_note(mergereq, issue, author, project) }
- it { should be_valid }
- its(:noteable) { should == mergereq }
- its(:project) { should == mergereq.project }
- its(:note) { should == "_mentioned in issue ##{issue.iid}_" }
+ it { is_expected.to be_valid }
+
+ describe '#noteable' do
+ subject { super().noteable }
+ it { is_expected.to eq(mergereq) }
+ end
+
+ describe '#project' do
+ subject { super().project }
+ it { is_expected.to eq(mergereq.project) }
+ end
+
+ describe '#note' do
+ subject { super().note }
+ it { is_expected.to eq("_mentioned in issue ##{issue.iid}_") }
+ end
end
context 'commit from a merge request' do
subject { Note.create_cross_reference_note(commit, mergereq, author, project) }
- it { should be_valid }
- its(:noteable) { should == commit }
- its(:project) { should == project }
- its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" }
+ it { is_expected.to be_valid }
+
+ describe '#noteable' do
+ subject { super().noteable }
+ it { is_expected.to eq(commit) }
+ end
+
+ describe '#project' do
+ subject { super().project }
+ it { is_expected.to eq(project) }
+ end
+
+ describe '#note' do
+ subject { super().note }
+ it { is_expected.to eq("_mentioned in merge request !#{mergereq.iid}_") }
+ end
+ end
+
+ context 'commit contained in a merge request' do
+ subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author, project) }
+
+ it { is_expected.to be_nil }
end
context 'commit from issue' do
subject { Note.create_cross_reference_note(commit, issue, author, project) }
- it { should be_valid }
- its(:noteable_type) { should == "Commit" }
- its(:noteable_id) { should be_nil }
- its(:commit_id) { should == commit.id }
- its(:note) { should == "_mentioned in issue ##{issue.iid}_" }
+ it { is_expected.to be_valid }
+
+ describe '#noteable_type' do
+ subject { super().noteable_type }
+ it { is_expected.to eq("Commit") }
+ end
+
+ describe '#noteable_id' do
+ subject { super().noteable_id }
+ it { is_expected.to be_nil }
+ end
+
+ describe '#commit_id' do
+ subject { super().commit_id }
+ it { is_expected.to eq(commit.id) }
+ end
+
+ describe '#note' do
+ subject { super().note }
+ it { is_expected.to eq("_mentioned in issue ##{issue.iid}_") }
+ end
end
context 'commit from commit' do
let(:parent_commit) { commit.parents.first }
subject { Note.create_cross_reference_note(commit, parent_commit, author, project) }
- it { should be_valid }
- its(:noteable_type) { should == "Commit" }
- its(:noteable_id) { should be_nil }
- its(:commit_id) { should == commit.id }
- its(:note) { should == "_mentioned in commit #{parent_commit.id[0...6]}_" }
+ it { is_expected.to be_valid }
+
+ describe '#noteable_type' do
+ subject { super().noteable_type }
+ it { is_expected.to eq("Commit") }
+ end
+
+ describe '#noteable_id' do
+ subject { super().noteable_id }
+ it { is_expected.to be_nil }
+ end
+
+ describe '#commit_id' do
+ subject { super().commit_id }
+ it { is_expected.to eq(commit.id) }
+ end
+
+ describe '#note' do
+ subject { super().note }
+ it { is_expected.to eq("_mentioned in commit #{parent_commit.id}_") }
+ end
end
end
@@ -283,11 +406,11 @@ describe Note do
end
it 'detects if a mentionable has already been mentioned' do
- Note.cross_reference_exists?(issue, commit0).should be_true
+ expect(Note.cross_reference_exists?(issue, commit0)).to be_truthy
end
it 'detects if a mentionable has not already been mentioned' do
- Note.cross_reference_exists?(issue, commit1).should be_false
+ expect(Note.cross_reference_exists?(issue, commit1)).to be_falsey
end
context 'commit on commit' do
@@ -295,8 +418,8 @@ describe Note do
Note.create_cross_reference_note(commit0, commit1, author, project)
end
- it { Note.cross_reference_exists?(commit0, commit1).should be_true }
- it { Note.cross_reference_exists?(commit1, commit0).should be_false }
+ it { expect(Note.cross_reference_exists?(commit0, commit1)).to be_truthy }
+ it { expect(Note.cross_reference_exists?(commit1, commit0)).to be_falsey }
end
end
@@ -309,22 +432,22 @@ describe Note do
it 'should recognize user-supplied notes as non-system' do
@note = create(:note_on_issue)
- @note.should_not be_system
+ expect(@note).not_to be_system
end
it 'should identify status-change notes as system notes' do
@note = Note.create_status_change_note(issue, project, author, 'closed', nil)
- @note.should be_system
+ expect(@note).to be_system
end
it 'should identify cross-reference notes as system notes' do
@note = Note.create_cross_reference_note(issue, other, author, project)
- @note.should be_system
+ expect(@note).to be_system
end
it 'should identify assignee-change notes as system notes' do
@note = Note.create_assignee_change_note(issue, project, author, assignee)
- @note.should be_system
+ expect(@note).to be_system
end
end
@@ -345,9 +468,9 @@ describe Note do
@p2.project_members.create(user: @u3, access_level: ProjectMember::GUEST)
end
- it { @abilities.allowed?(@u1, :read_note, @p1).should be_false }
- it { @abilities.allowed?(@u2, :read_note, @p1).should be_true }
- it { @abilities.allowed?(@u3, :read_note, @p1).should be_false }
+ it { expect(@abilities.allowed?(@u1, :read_note, @p1)).to be_falsey }
+ it { expect(@abilities.allowed?(@u2, :read_note, @p1)).to be_truthy }
+ it { expect(@abilities.allowed?(@u3, :read_note, @p1)).to be_falsey }
end
describe :write do
@@ -356,9 +479,9 @@ describe Note do
@p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER)
end
- it { @abilities.allowed?(@u1, :write_note, @p1).should be_false }
- it { @abilities.allowed?(@u2, :write_note, @p1).should be_true }
- it { @abilities.allowed?(@u3, :write_note, @p1).should be_false }
+ it { expect(@abilities.allowed?(@u1, :write_note, @p1)).to be_falsey }
+ it { expect(@abilities.allowed?(@u2, :write_note, @p1)).to be_truthy }
+ it { expect(@abilities.allowed?(@u3, :write_note, @p1)).to be_falsey }
end
describe :admin do
@@ -368,9 +491,9 @@ describe Note do
@p2.project_members.create(user: @u3, access_level: ProjectMember::MASTER)
end
- it { @abilities.allowed?(@u1, :admin_note, @p1).should be_false }
- it { @abilities.allowed?(@u2, :admin_note, @p1).should be_true }
- it { @abilities.allowed?(@u3, :admin_note, @p1).should be_false }
+ it { expect(@abilities.allowed?(@u1, :admin_note, @p1)).to be_falsey }
+ it { expect(@abilities.allowed?(@u2, :admin_note, @p1)).to be_truthy }
+ it { expect(@abilities.allowed?(@u3, :admin_note, @p1)).to be_falsey }
end
end
diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb
index 5c8d1e7438..1ee1900354 100644
--- a/spec/models/project_security_spec.rb
+++ b/spec/models/project_security_spec.rb
@@ -23,7 +23,7 @@ describe Project do
describe "Non member rules" do
it "should deny for non-project users any actions" do
admin_actions.each do |action|
- @abilities.allowed?(@u1, action, @p1).should be_false
+ expect(@abilities.allowed?(@u1, action, @p1)).to be_falsey
end
end
end
@@ -35,7 +35,7 @@ describe Project do
it "should allow for project user any guest actions" do
guest_actions.each do |action|
- @abilities.allowed?(@u2, action, @p1).should be_true
+ expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
end
end
end
@@ -47,7 +47,7 @@ describe Project do
it "should allow for project user any report actions" do
report_actions.each do |action|
- @abilities.allowed?(@u2, action, @p1).should be_true
+ expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
end
end
end
@@ -60,13 +60,13 @@ describe Project do
it "should deny for developer master-specific actions" do
[dev_actions - report_actions].each do |action|
- @abilities.allowed?(@u2, action, @p1).should be_false
+ expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
it "should allow for project user any dev actions" do
dev_actions.each do |action|
- @abilities.allowed?(@u3, action, @p1).should be_true
+ expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
end
end
end
@@ -79,13 +79,13 @@ describe Project do
it "should deny for developer master-specific actions" do
[master_actions - dev_actions].each do |action|
- @abilities.allowed?(@u2, action, @p1).should be_false
+ expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
it "should allow for project user any master actions" do
master_actions.each do |action|
- @abilities.allowed?(@u3, action, @p1).should be_true
+ expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
end
end
end
@@ -98,13 +98,13 @@ describe Project do
it "should deny for masters admin-specific actions" do
[admin_actions - master_actions].each do |action|
- @abilities.allowed?(@u2, action, @p1).should be_false
+ expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
it "should allow for project owner any admin actions" do
admin_actions.each do |action|
- @abilities.allowed?(@u4, action, @p1).should be_true
+ expect(@abilities.allowed?(@u4, action, @p1)).to be_truthy
end
end
end
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
new file mode 100644
index 0000000000..91730da1ee
--- /dev/null
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -0,0 +1,53 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe AssemblaService, models: true do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe "Execute" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ @assembla_service = AssemblaService.new
+ @assembla_service.stub(
+ project_id: project.id,
+ project: project,
+ service_hook: true,
+ token: 'verySecret',
+ subdomain: 'project_name'
+ )
+ @sample_data = Gitlab::PushDataBuilder.build_sample(project, user)
+ @api_url = 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret'
+ WebMock.stub_request(:post, @api_url)
+ end
+
+ it "should call Assembla API" do
+ @assembla_service.execute(@sample_data)
+ expect(WebMock).to have_requested(:post, @api_url).with(
+ body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/
+ ).once
+ end
+ end
+end
diff --git a/spec/models/buildbox_service_spec.rb b/spec/models/project_services/buildbox_service_spec.rb
similarity index 56%
rename from spec/models/buildbox_service_spec.rb
rename to spec/models/project_services/buildbox_service_spec.rb
index 1d9ca51be1..39d7df54cf 100644
--- a/spec/models/buildbox_service_spec.rb
+++ b/spec/models/project_services/buildbox_service_spec.rb
@@ -2,22 +2,27 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
#
require 'spec_helper'
describe BuildboxService do
describe 'Associations' do
- it { should belong_to :project }
- it { should have_one :service_hook }
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
end
describe 'commits methods' do
@@ -38,35 +43,39 @@ describe BuildboxService do
describe :webhook_url do
it 'returns the webhook url' do
- @service.webhook_url.should ==
+ expect(@service.webhook_url).to eq(
'https://webhook.buildbox.io/deliver/secret-sauce-webhook-token'
+ )
end
end
describe :commit_status_path do
it 'returns the correct status page' do
- @service.commit_status_path('2ab7834c').should ==
+ expect(@service.commit_status_path('2ab7834c')).to eq(
'https://gitlab.buildbox.io/status/secret-sauce-status-token.json?commit=2ab7834c'
+ )
end
end
describe :build_page do
it 'returns the correct build page' do
- @service.build_page('2ab7834c').should ==
+ expect(@service.build_page('2ab7834c')).to eq(
'https://buildbox.io/account-name/example-project/builds?commit=2ab7834c'
+ )
end
end
describe :builds_page do
it 'returns the correct path to the builds page' do
- @service.builds_path.should ==
+ expect(@service.builds_path).to eq(
'https://buildbox.io/account-name/example-project/builds?branch=default-brancho'
+ )
end
end
describe :status_img_path do
it 'returns the correct path to the status image' do
- @service.status_img_path.should == 'https://badge.buildbox.io/secret-sauce-status-token.svg'
+ expect(@service.status_img_path).to eq('https://badge.buildbox.io/secret-sauce-status-token.svg')
end
end
end
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
new file mode 100644
index 0000000000..73f68301a3
--- /dev/null
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -0,0 +1,52 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe FlowdockService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe "Execute" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ @flowdock_service = FlowdockService.new
+ @flowdock_service.stub(
+ project_id: project.id,
+ project: project,
+ service_hook: true,
+ token: 'verySecret'
+ )
+ @sample_data = Gitlab::PushDataBuilder.build_sample(project, user)
+ @api_url = 'https://api.flowdock.com/v1/git/verySecret'
+ WebMock.stub_request(:post, @api_url)
+ end
+
+ it "should call FlowDock API" do
+ @flowdock_service.execute(@sample_data)
+ expect(WebMock).to have_requested(:post, @api_url).with(
+ body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/
+ ).once
+ end
+ end
+end
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
new file mode 100644
index 0000000000..d44064bbe6
--- /dev/null
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -0,0 +1,48 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe GemnasiumService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe "Execute" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ @gemnasium_service = GemnasiumService.new
+ @gemnasium_service.stub(
+ project_id: project.id,
+ project: project,
+ service_hook: true,
+ token: 'verySecret',
+ api_key: 'GemnasiumUserApiKey'
+ )
+ @sample_data = Gitlab::PushDataBuilder.build_sample(project, user)
+ end
+ it "should call Gemnasium service" do
+ expect(Gemnasium::GitlabService).to receive(:execute).with(an_instance_of(Hash)).once
+ @gemnasium_service.execute(@sample_data)
+ end
+ end
+end
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
new file mode 100644
index 0000000000..8bfb19e524
--- /dev/null
+++ b/spec/models/project_services/gitlab_ci_service_spec.rb
@@ -0,0 +1,49 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe GitlabCiService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe "Mass assignment" do
+ end
+
+ describe 'commits methods' do
+ before do
+ @service = GitlabCiService.new
+ @service.stub(
+ service_hook: true,
+ project_url: 'http://ci.gitlab.org/projects/2',
+ token: 'verySecret'
+ )
+ end
+
+ describe :commit_status_path do
+ it { expect(@service.commit_status_path("2ab7834c")).to eq("http://ci.gitlab.org/projects/2/commits/2ab7834c/status.json?token=verySecret")}
+ end
+
+ describe :build_page do
+ it { expect(@service.build_page("2ab7834c")).to eq("http://ci.gitlab.org/projects/2/commits/2ab7834c")}
+ end
+ end
+end
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
new file mode 100644
index 0000000000..959044dc72
--- /dev/null
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -0,0 +1,65 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe GitlabIssueTrackerService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+
+ describe 'project and issue urls' do
+ let(:project) { create(:project) }
+
+ context 'with absolute urls' do
+ before do
+ @service = project.create_gitlab_issue_tracker_service(active: true)
+ end
+
+ after do
+ @service.destroy!
+ end
+
+ it 'should give the correct path' do
+ expect(@service.project_url).to eq("/#{project.path_with_namespace}/issues")
+ expect(@service.new_issue_url).to eq("/#{project.path_with_namespace}/issues/new")
+ expect(@service.issue_url(432)).to eq("/#{project.path_with_namespace}/issues/432")
+ end
+ end
+
+ context 'with enabled relative urls' do
+ before do
+ Settings.gitlab.stub(:relative_url_root).and_return("/gitlab/root")
+ @service = project.create_gitlab_issue_tracker_service(active: true)
+ end
+
+ after do
+ @service.destroy!
+ end
+
+ it 'should give the correct path' do
+ expect(@service.project_url).to eq("/gitlab/root/#{project.path_with_namespace}/issues")
+ expect(@service.new_issue_url).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new")
+ expect(@service.issue_url(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432")
+ end
+ end
+ end
+end
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
new file mode 100644
index 0000000000..8ab847e643
--- /dev/null
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -0,0 +1,217 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe HipchatService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe "Execute" do
+ let(:hipchat) { HipchatService.new }
+ let(:user) { create(:user, username: 'username') }
+ let(:project) { create(:project, name: 'project') }
+ let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' }
+ let(:project_name) { project.name_with_namespace.gsub(/\s/, '') }
+
+ before(:each) do
+ hipchat.stub(
+ project_id: project.id,
+ project: project,
+ room: 123456,
+ server: 'https://hipchat.example.com',
+ token: 'verySecret'
+ )
+ WebMock.stub_request(:post, api_url)
+ end
+
+ context 'push events' do
+ let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+
+ it "should call Hipchat API for push events" do
+ hipchat.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "should create a push message" do
+ message = hipchat.send(:create_push_message, push_sample_data)
+
+ obj_attr = push_sample_data[:object_attributes]
+ branch = push_sample_data[:ref].gsub('refs/heads/', '')
+ expect(message).to include("#{user.name} pushed to branch " \
+ "#{branch} of " \
+ "#{project_name} ")
+ end
+ end
+
+ context 'tag_push events' do
+ let(:push_sample_data) { Gitlab::PushDataBuilder.build(project, user, Gitlab::Git::BLANK_SHA, '1' * 40, 'refs/tags/test', []) }
+
+ it "should call Hipchat API for tag push events" do
+ hipchat.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "should create a tag push message" do
+ message = hipchat.send(:create_push_message, push_sample_data)
+
+ obj_attr = push_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} pushed new tag " \
+ "test to " \
+ "#{project_name} \n")
+ end
+ end
+
+ context 'issue events' do
+ let(:issue) { create(:issue, title: 'Awesome issue', description: 'please fix') }
+ let(:issue_service) { Issues::CreateService.new(project, user) }
+ let(:issues_sample_data) { issue_service.hook_data(issue, 'open') }
+
+ it "should call Hipchat API for issue events" do
+ hipchat.execute(issues_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "should create an issue message" do
+ message = hipchat.send(:create_issue_message, issues_sample_data)
+
+ obj_attr = issues_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} opened " \
+ "issue ##{obj_attr["iid"]} in " \
+ "#{project_name} : " \
+ "Awesome issue " \
+ "please fix ")
+ end
+ end
+
+ context 'merge request events' do
+ let(:merge_request) { create(:merge_request, description: 'please fix', title: 'Awesome merge request', target_project: project, source_project: project) }
+ let(:merge_service) { MergeRequests::CreateService.new(project, user) }
+ let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') }
+
+ it "should call Hipchat API for merge requests events" do
+ hipchat.execute(merge_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "should create a merge request message" do
+ message = hipchat.send(:create_merge_request_message,
+ merge_sample_data)
+
+ obj_attr = merge_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} opened " \
+ "merge request ##{obj_attr["iid"]} in " \
+ "#{project_name} : " \
+ "Awesome merge request " \
+ "please fix ")
+ end
+ end
+
+ context "Note events" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, creator_id: user.id) }
+ let(:issue) { create(:issue, project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:snippet) { create(:project_snippet, project: project) }
+ let(:commit_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
+ let(:merge_request_note) { create(:note_on_merge_request, noteable_id: merge_request.id, note: "merge request note") }
+ let(:issue_note) { create(:note_on_issue, noteable_id: issue.id, note: "issue note")}
+ let(:snippet_note) { create(:note_on_project_snippet, noteable_id: snippet.id, note: "snippet note") }
+
+ it "should call Hipchat API for commit comment events" do
+ data = Gitlab::NoteDataBuilder.build(commit_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ commit_id = Commit.truncate_sha(data[:commit][:id])
+ title = hipchat.send(:format_title, data[:commit][:message])
+
+ expect(message).to eq("#{user.name} commented on " \
+ "commit #{commit_id} in " \
+ "#{project_name} : " \
+ "#{title}" \
+ "a comment on a commit ")
+ end
+
+ it "should call Hipchat API for merge request comment events" do
+ data = Gitlab::NoteDataBuilder.build(merge_request_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ merge_id = data[:merge_request]['iid']
+ title = data[:merge_request]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "merge request ##{merge_id} in " \
+ "#{project_name} : " \
+ "#{title} " \
+ "merge request note ")
+ end
+
+ it "should call Hipchat API for issue comment events" do
+ data = Gitlab::NoteDataBuilder.build(issue_note, user)
+ hipchat.execute(data)
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ issue_id = data[:issue]['iid']
+ title = data[:issue]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "issue ##{issue_id} in " \
+ "#{project_name} : " \
+ "#{title} " \
+ "issue note ")
+ end
+
+ it "should call Hipchat API for snippet comment events" do
+ data = Gitlab::NoteDataBuilder.build(snippet_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ snippet_id = data[:snippet]['id']
+ title = data[:snippet]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "snippet ##{snippet_id} in " \
+ "#{project_name} : " \
+ "#{title} " \
+ "snippet note ")
+ end
+ end
+ end
+end
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
new file mode 100644
index 0000000000..d55399bc36
--- /dev/null
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -0,0 +1,108 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+require 'socket'
+require 'json'
+
+describe IrkerService do
+ describe 'Associations' do
+ it { should belong_to :project }
+ it { should have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ before do
+ subject.active = true
+ subject.properties['recipients'] = _recipients
+ end
+
+ context 'active' do
+ let(:_recipients) { nil }
+ it { should validate_presence_of :recipients }
+ end
+
+ context 'too many recipients' do
+ let(:_recipients) { 'a b c d' }
+ it 'should add an error if there is too many recipients' do
+ subject.send :check_recipients_count
+ subject.errors.should_not be_blank
+ end
+ end
+
+ context '3 recipients' do
+ let(:_recipients) { 'a b c' }
+ it 'should not add an error if there is 3 recipients' do
+ subject.send :check_recipients_count
+ subject.errors.should be_blank
+ end
+ end
+ end
+
+ describe 'Execute' do
+ let(:irker) { IrkerService.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+
+ let(:recipients) { '#commits' }
+ let(:colorize_messages) { '1' }
+
+ before do
+ irker.stub(
+ active: true,
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ properties: {
+ 'recipients' => recipients,
+ 'colorize_messages' => colorize_messages
+ }
+ )
+ irker.settings = {
+ server_ip: 'localhost',
+ server_port: 6659,
+ max_channels: 3,
+ default_irc_uri: 'irc://chat.freenode.net/'
+ }
+ irker.valid?
+ @irker_server = TCPServer.new 'localhost', 6659
+ end
+
+ after do
+ @irker_server.close
+ end
+
+ it 'should send valid JSON messages to an Irker listener' do
+ irker.execute(sample_data)
+
+ conn = @irker_server.accept
+ conn.readlines.each do |line|
+ msg = JSON.load(line.chomp("\n"))
+ msg.keys.should match_array(['to', 'privmsg'])
+ if msg['to'].is_a?(String)
+ msg['to'].should == 'irc://chat.freenode.net/#commits'
+ else
+ msg['to'].should match_array(['irc://chat.freenode.net/#commits'])
+ end
+ end
+ conn.close
+ end
+ end
+end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
new file mode 100644
index 0000000000..355911e637
--- /dev/null
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -0,0 +1,102 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe JiraService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe "Validations" do
+ context "active" do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of :project_url }
+ it { is_expected.to validate_presence_of :issues_url }
+ it { is_expected.to validate_presence_of :new_issue_url }
+ end
+ end
+
+ describe 'description and title' do
+ let(:project) { create(:project) }
+
+ context 'when it is not set' do
+ before do
+ @service = project.create_jira_service(active: true)
+ end
+
+ after do
+ @service.destroy!
+ end
+
+ it 'should be initialized' do
+ expect(@service.title).to eq('JIRA')
+ expect(@service.description).to eq("Jira issue tracker")
+ end
+ end
+
+ context 'when it is set' do
+ before do
+ properties = { 'title' => 'Jira One', 'description' => 'Jira One issue tracker' }
+ @service = project.create_jira_service(active: true, properties: properties)
+ end
+
+ after do
+ @service.destroy!
+ end
+
+ it "should be correct" do
+ expect(@service.title).to eq('Jira One')
+ expect(@service.description).to eq('Jira One issue tracker')
+ end
+ end
+ end
+
+ describe 'project and issue urls' do
+ let(:project) { create(:project) }
+
+ context 'when gitlab.yml was initialized' do
+ before do
+ settings = { "jira" => {
+ "title" => "Jira",
+ "project_url" => "http://jira.sample/projects/project_a",
+ "issues_url" => "http://jira.sample/issues/:id",
+ "new_issue_url" => "http://jira.sample/projects/project_a/issues/new"
+ }
+ }
+ allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
+ @service = project.create_jira_service(active: true)
+ end
+
+ after do
+ @service.destroy!
+ end
+
+ it 'should be prepopulated with the settings' do
+ expect(@service.properties[:project_url]).to eq('http://jira.sample/projects/project_a')
+ expect(@service.properties[:issues_url]).to eq("http://jira.sample/issues/:id")
+ expect(@service.properties[:new_issue_url]).to eq("http://jira.sample/projects/project_a/issues/new")
+ end
+ end
+ end
+end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
new file mode 100644
index 0000000000..5a18fd09bf
--- /dev/null
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -0,0 +1,74 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe PushoverService do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of :api_key }
+ it { is_expected.to validate_presence_of :user_key }
+ it { is_expected.to validate_presence_of :priority }
+ end
+ end
+
+ describe 'Execute' do
+ let(:pushover) { PushoverService.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+
+ let(:api_key) { 'verySecret' }
+ let(:user_key) { 'verySecret' }
+ let(:device) { 'myDevice' }
+ let(:priority) { 0 }
+ let(:sound) { 'bike' }
+ let(:api_url) { 'https://api.pushover.net/1/messages.json' }
+
+ before do
+ pushover.stub(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ api_key: api_key,
+ user_key: user_key,
+ device: device,
+ priority: priority,
+ sound: sound
+ )
+
+ WebMock.stub_request(:post, api_url)
+ end
+
+ it 'should call Pushover API' do
+ pushover.execute(sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+ end
+end
diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb
new file mode 100644
index 0000000000..8bca1fef44
--- /dev/null
+++ b/spec/models/project_services/slack_service/issue_message_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe SlackService::IssueMessage do
+ subject { SlackService::IssueMessage.new(args) }
+
+ let(:args) {
+ {
+ user: {
+ name: 'Test User',
+ username: 'Test User'
+ },
+ project_name: 'project_name',
+ project_url: 'somewhere.com',
+
+ object_attributes: {
+ title: 'Issue title',
+ id: 10,
+ iid: 100,
+ assignee_id: 1,
+ url: 'url',
+ action: 'open',
+ state: 'opened',
+ description: 'issue description'
+ }
+ }
+ }
+
+ let(:color) { '#345' }
+
+ context 'open' do
+ it 'returns a message regarding opening of issues' do
+ expect(subject.pretext).to eq(
+ 'Test User opened in : '\
+ '*Issue title*')
+ expect(subject.attachments).to eq([
+ {
+ text: "issue description",
+ color: color,
+ }
+ ])
+ end
+ end
+
+ context 'close' do
+ before do
+ args[:object_attributes][:action] = 'close'
+ args[:object_attributes][:state] = 'closed'
+ end
+ it 'returns a message regarding closing of issues' do
+ expect(subject.pretext). to eq(
+ 'Test User closed in : '\
+ '*Issue title*')
+ expect(subject.attachments).to be_empty
+ end
+ end
+end
diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb
new file mode 100644
index 0000000000..aeb408aa76
--- /dev/null
+++ b/spec/models/project_services/slack_service/merge_message_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe SlackService::MergeMessage do
+ subject { SlackService::MergeMessage.new(args) }
+
+ let(:args) {
+ {
+ user: {
+ name: 'Test User',
+ username: 'Test User'
+ },
+ project_name: 'project_name',
+ project_url: 'somewhere.com',
+
+ object_attributes: {
+ title: "Issue title\nSecond line",
+ id: 10,
+ iid: 100,
+ assignee_id: 1,
+ url: 'url',
+ state: 'opened',
+ description: 'issue description',
+ source_branch: 'source_branch',
+ target_branch: 'target_branch',
+ }
+ }
+ }
+
+ let(:color) { '#345' }
+
+ context 'open' do
+ it 'returns a message regarding opening of merge requests' do
+ expect(subject.pretext).to eq(
+ 'Test User opened '\
+ 'in : *Issue title*')
+ expect(subject.attachments).to be_empty
+ end
+ end
+
+ context 'close' do
+ before do
+ args[:object_attributes][:state] = 'closed'
+ end
+ it 'returns a message regarding closing of merge requests' do
+ expect(subject.pretext).to eq(
+ 'Test User closed '\
+ 'in : *Issue title*')
+ expect(subject.attachments).to be_empty
+ end
+ end
+end
diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb
new file mode 100644
index 0000000000..21fb575480
--- /dev/null
+++ b/spec/models/project_services/slack_service/note_message_spec.rb
@@ -0,0 +1,129 @@
+require 'spec_helper'
+
+describe SlackService::NoteMessage do
+ let(:color) { '#345' }
+
+ before do
+ @args = {
+ user: {
+ name: 'Test User',
+ username: 'username',
+ avatar_url: 'http://fakeavatar'
+ },
+ project_name: 'project_name',
+ project_url: 'somewhere.com',
+ repository: {
+ name: 'project_name',
+ url: 'somewhere.com',
+ },
+ object_attributes: {
+ id: 10,
+ note: 'comment on a commit',
+ url: 'url',
+ noteable_type: 'Commit'
+ }
+ }
+ end
+
+ context 'commit notes' do
+ before do
+ @args[:object_attributes][:note] = 'comment on a commit'
+ @args[:object_attributes][:noteable_type] = 'Commit'
+ @args[:commit] = {
+ id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23',
+ message: "Added a commit message\ndetails\n123\n"
+ }
+ end
+
+ it 'returns a message regarding notes on commits' do
+ message = SlackService::NoteMessage.new(@args)
+ expect(message.pretext).to eq("Test User commented on " \
+ " in : " \
+ "*Added a commit message*")
+ expected_attachments = [
+ {
+ text: "comment on a commit",
+ color: color,
+ }
+ ]
+ expect(message.attachments).to eq(expected_attachments)
+ end
+ end
+
+ context 'merge request notes' do
+ before do
+ @args[:object_attributes][:note] = 'comment on a merge request'
+ @args[:object_attributes][:noteable_type] = 'MergeRequest'
+ @args[:merge_request] = {
+ id: 1,
+ iid: 30,
+ title: "merge request title\ndetails\n"
+ }
+ end
+ it 'returns a message regarding notes on a merge request' do
+ message = SlackService::NoteMessage.new(@args)
+ expect(message.pretext).to eq("Test User commented on " \
+ " in : " \
+ "*merge request title*")
+ expected_attachments = [
+ {
+ text: "comment on a merge request",
+ color: color,
+ }
+ ]
+ expect(message.attachments).to eq(expected_attachments)
+ end
+ end
+
+ context 'issue notes' do
+ before do
+ @args[:object_attributes][:note] = 'comment on an issue'
+ @args[:object_attributes][:noteable_type] = 'Issue'
+ @args[:issue] = {
+ id: 1,
+ iid: 20,
+ title: "issue title\ndetails\n"
+ }
+ end
+
+ it 'returns a message regarding notes on an issue' do
+ message = SlackService::NoteMessage.new(@args)
+ expect(message.pretext).to eq(
+ "Test User commented on " \
+ " in : " \
+ "*issue title*")
+ expected_attachments = [
+ {
+ text: "comment on an issue",
+ color: color,
+ }
+ ]
+ expect(message.attachments).to eq(expected_attachments)
+ end
+ end
+
+ context 'project snippet notes' do
+ before do
+ @args[:object_attributes][:note] = 'comment on a snippet'
+ @args[:object_attributes][:noteable_type] = 'Snippet'
+ @args[:snippet] = {
+ id: 5,
+ title: "snippet title\ndetails\n"
+ }
+ end
+
+ it 'returns a message regarding notes on a project snippet' do
+ message = SlackService::NoteMessage.new(@args)
+ expect(message.pretext).to eq("Test User commented on " \
+ " in : " \
+ "*snippet title*")
+ expected_attachments = [
+ {
+ text: "comment on a snippet",
+ color: color,
+ }
+ ]
+ expect(message.attachments).to eq(expected_attachments)
+ end
+ end
+end
diff --git a/spec/models/slack_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb
similarity index 50%
rename from spec/models/slack_message_spec.rb
rename to spec/models/project_services/slack_service/push_message_spec.rb
index 1cd5853470..10963481a1 100644
--- a/spec/models/slack_message_spec.rb
+++ b/spec/models/project_services/slack_service/push_message_spec.rb
@@ -1,7 +1,7 @@
-require_relative '../../app/models/project_services/slack_message'
+require 'spec_helper'
-describe SlackMessage do
- subject { SlackMessage.new(args) }
+describe SlackService::PushMessage do
+ subject { SlackService::PushMessage.new(args) }
let(:args) {
{
@@ -25,41 +25,64 @@ describe SlackMessage do
end
it 'returns a message regarding pushes' do
- subject.pretext.should ==
- 'user_name pushed to branch of ' <<
+ expect(subject.pretext).to eq(
+ 'user_name pushed to branch of '\
' ()'
- subject.attachments.should == [
+ )
+ expect(subject.attachments).to eq([
{
- text: ": message1 - author1\n" <<
- ": message2 - author2",
+ text: ": message1 - author1\n"\
+ ": message2 - author2",
color: color,
}
- ]
+ ])
+ end
+ end
+
+ context 'tag push' do
+ let(:args) {
+ {
+ after: 'after',
+ before: Gitlab::Git::BLANK_SHA,
+ project_name: 'project_name',
+ ref: 'refs/tags/new_tag',
+ user_name: 'user_name',
+ project_url: 'url'
+ }
+ }
+
+ it 'returns a message regarding pushes' do
+ expect(subject.pretext).to eq('user_name pushed new tag ' \
+ ' to ' \
+ '')
+ expect(subject.attachments).to be_empty
end
end
context 'new branch' do
before do
- args[:before] = '000000'
+ args[:before] = Gitlab::Git::BLANK_SHA
end
it 'returns a message regarding a new branch' do
- subject.pretext.should ==
- 'user_name pushed new branch to ' <<
+ expect(subject.pretext).to eq(
+ 'user_name pushed new branch to '\
''
- subject.attachments.should be_empty
+ )
+ expect(subject.attachments).to be_empty
end
end
context 'removed branch' do
before do
- args[:after] = '000000'
+ args[:after] = Gitlab::Git::BLANK_SHA
end
it 'returns a message regarding a removed branch' do
- subject.pretext.should ==
+ expect(subject.pretext).to eq(
'user_name removed branch master from '
- subject.attachments.should be_empty
+ )
+ expect(subject.attachments).to be_empty
end
end
end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
new file mode 100644
index 0000000000..c36506644b
--- /dev/null
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -0,0 +1,170 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+#
+
+require 'spec_helper'
+
+describe SlackService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe "Validations" do
+ context "active" do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of :webhook }
+ end
+ end
+
+ describe "Execute" do
+ let(:slack) { SlackService.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+ let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
+ let(:username) { 'slack_username' }
+ let(:channel) { 'slack_channel' }
+
+ before do
+ slack.stub(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ webhook: webhook_url
+ )
+
+ WebMock.stub_request(:post, webhook_url)
+
+ opts = {
+ title: 'Awesome issue',
+ description: 'please fix'
+ }
+
+ issue_service = Issues::CreateService.new(project, user, opts)
+ @issue = issue_service.execute
+ @issues_sample_data = issue_service.hook_data(@issue, 'open')
+
+ opts = {
+ title: 'Awesome merge_request',
+ description: 'please fix',
+ source_branch: 'stable',
+ target_branch: 'master'
+ }
+ merge_service = MergeRequests::CreateService.new(project,
+ user, opts)
+ @merge_request = merge_service.execute
+ @merge_sample_data = merge_service.hook_data(@merge_request,
+ 'open')
+ end
+
+ it "should call Slack API for push events" do
+ slack.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+
+ it "should call Slack API for issue events" do
+ slack.execute(@issues_sample_data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+
+ it "should call Slack API for merge requests events" do
+ slack.execute(@merge_sample_data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+
+ it 'should use the username as an option for slack when configured' do
+ slack.stub(username: username)
+ expect(Slack::Notifier).to receive(:new).
+ with(webhook_url, username: username).
+ and_return(
+ double(:slack_service).as_null_object
+ )
+ slack.execute(push_sample_data)
+ end
+
+ it 'should use the channel as an option when it is configured' do
+ slack.stub(channel: channel)
+ expect(Slack::Notifier).to receive(:new).
+ with(webhook_url, channel: channel).
+ and_return(
+ double(:slack_service).as_null_object
+ )
+ slack.execute(push_sample_data)
+ end
+ end
+
+ describe "Note events" do
+ let(:slack) { SlackService.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project, creator_id: user.id) }
+ let(:issue) { create(:issue, project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:snippet) { create(:project_snippet, project: project) }
+ let(:commit_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
+ let(:merge_request_note) { create(:note_on_merge_request, noteable_id: merge_request.id, note: "merge request note") }
+ let(:issue_note) { create(:note_on_issue, noteable_id: issue.id, note: "issue note")}
+ let(:snippet_note) { create(:note_on_project_snippet, noteable_id: snippet.id, note: "snippet note") }
+ let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
+
+ before do
+ slack.stub(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ webhook: webhook_url
+ )
+
+ WebMock.stub_request(:post, webhook_url)
+ end
+
+ it "should call Slack API for commit comment events" do
+ data = Gitlab::NoteDataBuilder.build(commit_note, user)
+ slack.execute(data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+
+ it "should call Slack API for merge request comment events" do
+ data = Gitlab::NoteDataBuilder.build(merge_request_note, user)
+ slack.execute(data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+
+ it "should call Slack API for issue comment events" do
+ data = Gitlab::NoteDataBuilder.build(issue_note, user)
+ slack.execute(data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+
+ it "should call Slack API for snippet comment events" do
+ data = Gitlab::NoteDataBuilder.build(snippet_note, user)
+ slack.execute(data)
+
+ expect(WebMock).to have_requested(:post, webhook_url).once
+ end
+ end
+end
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index a6e1d9eef5..3e8f106d27 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -19,13 +19,13 @@ require 'spec_helper'
describe ProjectSnippet do
describe "Associations" do
- it { should belong_to(:project) }
+ it { is_expected.to belong_to(:project) }
end
describe "Mass assignment" do
end
describe "Validation" do
- it { should validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:project) }
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 48b58400a1..879a63dd9f 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -24,103 +24,107 @@
# import_status :string(255)
# repository_size :float default(0.0)
# star_count :integer default(0), not null
+# import_type :string(255)
+# import_source :string(255)
+# avatar :string(255)
#
require 'spec_helper'
describe Project do
- describe "Associations" do
- it { should belong_to(:group) }
- it { should belong_to(:namespace) }
- it { should belong_to(:creator).class_name('User') }
- it { should have_many(:users) }
- it { should have_many(:events).dependent(:destroy) }
- it { should have_many(:merge_requests).dependent(:destroy) }
- it { should have_many(:issues).dependent(:destroy) }
- it { should have_many(:milestones).dependent(:destroy) }
- it { should have_many(:project_members).dependent(:destroy) }
- it { should have_many(:notes).dependent(:destroy) }
- it { should have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) }
- it { should have_many(:deploy_keys_projects).dependent(:destroy) }
- it { should have_many(:deploy_keys) }
- it { should have_many(:hooks).dependent(:destroy) }
- it { should have_many(:protected_branches).dependent(:destroy) }
- it { should have_one(:forked_project_link).dependent(:destroy) }
- it { should have_one(:slack_service).dependent(:destroy) }
- it { should have_one(:pushover_service).dependent(:destroy) }
+ describe 'Associations' do
+ it { is_expected.to belong_to(:group) }
+ it { is_expected.to belong_to(:namespace) }
+ it { is_expected.to belong_to(:creator).class_name('User') }
+ it { is_expected.to have_many(:users) }
+ it { is_expected.to have_many(:events).dependent(:destroy) }
+ it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
+ it { is_expected.to have_many(:issues).dependent(:destroy) }
+ it { is_expected.to have_many(:milestones).dependent(:destroy) }
+ it { is_expected.to have_many(:project_members).dependent(:destroy) }
+ it { is_expected.to have_many(:notes).dependent(:destroy) }
+ it { is_expected.to have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) }
+ it { is_expected.to have_many(:deploy_keys_projects).dependent(:destroy) }
+ it { is_expected.to have_many(:deploy_keys) }
+ it { is_expected.to have_many(:hooks).dependent(:destroy) }
+ it { is_expected.to have_many(:protected_branches).dependent(:destroy) }
+ it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
+ it { is_expected.to have_one(:slack_service).dependent(:destroy) }
+ it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
+ it { is_expected.to have_one(:asana_service).dependent(:destroy) }
end
- describe "Mass assignment" do
+ describe 'Mass assignment' do
end
- describe "Validation" do
+ describe 'Validation' do
let!(:project) { create(:project) }
- it { should validate_presence_of(:name) }
- it { should validate_uniqueness_of(:name).scoped_to(:namespace_id) }
- it { should ensure_length_of(:name).is_within(0..255) }
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
+ it { is_expected.to ensure_length_of(:name).is_within(0..255) }
- it { should validate_presence_of(:path) }
- it { should validate_uniqueness_of(:path).scoped_to(:namespace_id) }
- it { should ensure_length_of(:path).is_within(0..255) }
- it { should ensure_length_of(:description).is_within(0..2000) }
- it { should validate_presence_of(:creator) }
- it { should ensure_length_of(:issues_tracker_id).is_within(0..255) }
- it { should validate_presence_of(:namespace) }
+ it { is_expected.to validate_presence_of(:path) }
+ it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
+ it { is_expected.to ensure_length_of(:path).is_within(0..255) }
+ it { is_expected.to ensure_length_of(:description).is_within(0..2000) }
+ it { is_expected.to validate_presence_of(:creator) }
+ it { is_expected.to ensure_length_of(:issues_tracker_id).is_within(0..255) }
+ it { is_expected.to validate_presence_of(:namespace) }
- it "should not allow new projects beyond user limits" do
+ it 'should not allow new projects beyond user limits' do
project2 = build(:project)
- project2.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
- project2.should_not be_valid
- project2.errors[:limit_reached].first.should match(/Your project limit is 0/)
+ allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
+ expect(project2).not_to be_valid
+ expect(project2.errors[:limit_reached].first).to match(/Your project limit is 0/)
end
end
- describe "Respond to" do
- it { should respond_to(:url_to_repo) }
- it { should respond_to(:repo_exists?) }
- it { should respond_to(:satellite) }
- it { should respond_to(:update_merge_requests) }
- it { should respond_to(:execute_hooks) }
- it { should respond_to(:name_with_namespace) }
- it { should respond_to(:owner) }
- it { should respond_to(:path_with_namespace) }
+ describe 'Respond to' do
+ it { is_expected.to respond_to(:url_to_repo) }
+ it { is_expected.to respond_to(:repo_exists?) }
+ it { is_expected.to respond_to(:satellite) }
+ it { is_expected.to respond_to(:update_merge_requests) }
+ it { is_expected.to respond_to(:execute_hooks) }
+ it { is_expected.to respond_to(:name_with_namespace) }
+ it { is_expected.to respond_to(:owner) }
+ it { is_expected.to respond_to(:path_with_namespace) }
end
- it "should return valid url to repo" do
- project = Project.new(path: "somewhere")
- project.url_to_repo.should == Gitlab.config.gitlab_shell.ssh_path_prefix + "somewhere.git"
+ it 'should return valid url to repo' do
+ project = Project.new(path: 'somewhere')
+ expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
end
- it "returns the full web URL for this repo" do
- project = Project.new(path: "somewhere")
- project.web_url.should == "#{Gitlab.config.gitlab.url}/somewhere"
+ it 'returns the full web URL for this repo' do
+ project = Project.new(path: 'somewhere')
+ expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/somewhere")
end
- it "returns the web URL without the protocol for this repo" do
- project = Project.new(path: "somewhere")
- project.web_url_without_protocol.should == "#{Gitlab.config.gitlab.url.split("://")[1]}/somewhere"
+ it 'returns the web URL without the protocol for this repo' do
+ project = Project.new(path: 'somewhere')
+ expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/somewhere")
end
- describe "last_activity methods" do
+ describe 'last_activity methods' do
let(:project) { create(:project) }
let(:last_event) { double(created_at: Time.now) }
- describe "last_activity" do
- it "should alias last_activity to last_event" do
+ describe 'last_activity' do
+ it 'should alias last_activity to last_event' do
project.stub(last_event: last_event)
- project.last_activity.should == last_event
+ expect(project.last_activity).to eq(last_event)
end
end
describe 'last_activity_date' do
it 'returns the creation date of the project\'s last event if present' do
last_activity_event = create(:event, project: project)
- project.last_activity_at.to_i.should == last_event.created_at.to_i
+ expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i)
end
it 'returns the project\'s last update date if it has no events' do
- project.last_activity_date.should == project.updated_at
+ expect(project.last_activity_date).to eq(project.updated_at)
end
end
end
@@ -132,73 +136,16 @@ describe Project do
let(:prev_commit_id) { merge_request.commits.last.id }
let(:commit_id) { merge_request.commits.first.id }
- it "should close merge request if last commit from source branch was pushed to target branch" do
+ it 'should close merge request if last commit from source branch was pushed to target branch' do
project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.target_branch}", key.user)
merge_request.reload
- merge_request.merged?.should be_true
+ expect(merge_request.merged?).to be_truthy
end
- it "should update merge request commits with new one if pushed to source branch" do
+ it 'should update merge request commits with new one if pushed to source branch' do
project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user)
merge_request.reload
- merge_request.last_commit.id.should == commit_id
- end
- end
-
- describe 'comment merge requests with commits' do
- before do
- @user = create(:user)
- group = create(:group)
- group.add_owner(@user)
-
- @project = create(:project, namespace: group)
- @fork_project = Projects::ForkService.new(@project, @user).execute
- @merge_request = create(:merge_request, source_project: @project,
- source_branch: 'master',
- target_branch: 'feature',
- target_project: @project)
- @fork_merge_request = create(:merge_request, source_project: @fork_project,
- source_branch: 'master',
- target_branch: 'feature',
- target_project: @project)
-
- @commits = @merge_request.commits
- end
-
- context 'push to origin repo source branch' do
- before do
- @project.comment_mr_with_commits('master', @commits, @user)
- end
-
- it { @merge_request.notes.should_not be_empty }
- it { @fork_merge_request.notes.should be_empty }
- end
-
- context 'push to origin repo target branch' do
- before do
- @project.comment_mr_with_commits('feature', @commits, @user)
- end
-
- it { @merge_request.notes.should be_empty }
- it { @fork_merge_request.notes.should be_empty }
- end
-
- context 'push to fork repo source branch' do
- before do
- @fork_project.comment_mr_with_commits('master', @commits, @user)
- end
-
- it { @merge_request.notes.should be_empty }
- it { @fork_merge_request.notes.should_not be_empty }
- end
-
- context 'push to fork repo target branch' do
- before do
- @fork_project.comment_mr_with_commits('feature', @commits, @user)
- end
-
- it { @merge_request.notes.should be_empty }
- it { @fork_merge_request.notes.should be_empty }
+ expect(merge_request.last_commit.id).to eq(commit_id)
end
end
@@ -209,8 +156,8 @@ describe Project do
@project = create(:project, name: 'gitlabhq', namespace: @group)
end
- it { Project.find_with_namespace('gitlab/gitlabhq').should == @project }
- it { Project.find_with_namespace('gitlab-ci').should be_nil }
+ it { expect(Project.find_with_namespace('gitlab/gitlabhq')).to eq(@project) }
+ it { expect(Project.find_with_namespace('gitlab-ci')).to be_nil }
end
end
@@ -221,15 +168,15 @@ describe Project do
@project = create(:project, name: 'gitlabhq', namespace: @group)
end
- it { @project.to_param.should == "gitlab/gitlabhq" }
+ it { expect(@project.to_param).to eq('gitlabhq') }
end
end
describe :repository do
let(:project) { create(:project) }
- it "should return valid repo" do
- project.repository.should be_kind_of(Repository)
+ it 'should return valid repo' do
+ expect(project.repository).to be_kind_of(Repository)
end
end
@@ -239,29 +186,29 @@ describe Project do
let(:not_existed_issue) { create(:issue) }
let(:ext_project) { create(:redmine_project) }
- it "should be true or if used internal tracker and issue exists" do
- project.issue_exists?(existed_issue.iid).should be_true
+ it 'should be true or if used internal tracker and issue exists' do
+ expect(project.issue_exists?(existed_issue.iid)).to be_truthy
end
- it "should be false or if used internal tracker and issue not exists" do
- project.issue_exists?(not_existed_issue.iid).should be_false
+ it 'should be false or if used internal tracker and issue not exists' do
+ expect(project.issue_exists?(not_existed_issue.iid)).to be_falsey
end
- it "should always be true if used other tracker" do
- ext_project.issue_exists?(rand(100)).should be_true
+ it 'should always be true if used other tracker' do
+ expect(ext_project.issue_exists?(rand(100))).to be_truthy
end
end
- describe :used_default_issues_tracker? do
+ describe :default_issues_tracker? do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
it "should be true if used internal tracker" do
- project.used_default_issues_tracker?.should be_true
+ expect(project.default_issues_tracker?).to be_truthy
end
it "should be false if used other tracker" do
- ext_project.used_default_issues_tracker?.should be_false
+ expect(ext_project.default_issues_tracker?).to be_falsey
end
end
@@ -269,20 +216,20 @@ describe Project do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
- it "should be true for projects with external issues tracker if issues enabled" do
- ext_project.can_have_issues_tracker_id?.should be_true
+ it 'should be true for projects with external issues tracker if issues enabled' do
+ expect(ext_project.can_have_issues_tracker_id?).to be_truthy
end
- it "should be false for projects with internal issue tracker if issues enabled" do
- project.can_have_issues_tracker_id?.should be_false
+ it 'should be false for projects with internal issue tracker if issues enabled' do
+ expect(project.can_have_issues_tracker_id?).to be_falsey
end
- it "should be always false if issues disabled" do
+ it 'should be always false if issues disabled' do
project.issues_enabled = false
ext_project.issues_enabled = false
- project.can_have_issues_tracker_id?.should be_false
- ext_project.can_have_issues_tracker_id?.should be_false
+ expect(project.can_have_issues_tracker_id?).to be_falsey
+ expect(ext_project.can_have_issues_tracker_id?).to be_falsey
end
end
@@ -293,8 +240,8 @@ describe Project do
project.protected_branches.create(name: 'master')
end
- it { project.open_branches.map(&:name).should include('feature') }
- it { project.open_branches.map(&:name).should_not include('master') }
+ it { expect(project.open_branches.map(&:name)).to include('feature') }
+ it { expect(project.open_branches.map(&:name)).not_to include('master') }
end
describe '#star_count' do
@@ -365,4 +312,49 @@ describe Project do
expect(project.star_count).to eq(0)
end
end
+
+ describe :avatar_type do
+ let(:project) { create(:project) }
+
+ it 'should be true if avatar is image' do
+ project.update_attribute(:avatar, 'uploads/avatar.png')
+ expect(project.avatar_type).to be_truthy
+ end
+
+ it 'should be false if avatar is html page' do
+ project.update_attribute(:avatar, 'uploads/avatar.html')
+ expect(project.avatar_type).to eq(['only images allowed'])
+ end
+ end
+
+ describe :avatar_url do
+ subject { project.avatar_url }
+
+ let(:project) { create(:project) }
+
+ context 'When avatar file is uploaded' do
+ before do
+ project.update_columns(avatar: 'uploads/avatar.png')
+ allow(project.avatar).to receive(:present?) { true }
+ end
+
+ let(:avatar_path) do
+ "/uploads/project/avatar/#{project.id}/uploads/avatar.png"
+ end
+
+ it { should eq "http://localhost#{avatar_path}" }
+ end
+
+ context 'When avatar file in git' do
+ before do
+ allow(project).to receive(:avatar_in_git) { true }
+ end
+
+ let(:avatar_path) do
+ "/#{project.namespace.name}/#{project.path}/avatar"
+ end
+
+ it { should eq "http://localhost#{avatar_path}" }
+ end
+ end
end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index bbf50b654f..19201cc15a 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -16,19 +16,19 @@ describe ProjectTeam do
end
describe 'members collection' do
- it { project.team.masters.should include(master) }
- it { project.team.masters.should_not include(guest) }
- it { project.team.masters.should_not include(reporter) }
- it { project.team.masters.should_not include(nonmember) }
+ it { expect(project.team.masters).to include(master) }
+ it { expect(project.team.masters).not_to include(guest) }
+ it { expect(project.team.masters).not_to include(reporter) }
+ it { expect(project.team.masters).not_to include(nonmember) }
end
describe 'access methods' do
- it { project.team.master?(master).should be_true }
- it { project.team.master?(guest).should be_false }
- it { project.team.master?(reporter).should be_false }
- it { project.team.master?(nonmember).should be_false }
- it { project.team.member?(nonmember).should be_false }
- it { project.team.member?(guest).should be_true }
+ it { expect(project.team.master?(master)).to be_truthy }
+ it { expect(project.team.master?(guest)).to be_falsey }
+ it { expect(project.team.master?(reporter)).to be_falsey }
+ it { expect(project.team.master?(nonmember)).to be_falsey }
+ it { expect(project.team.member?(nonmember)).to be_falsey }
+ it { expect(project.team.member?(guest)).to be_truthy }
end
end
@@ -49,21 +49,21 @@ describe ProjectTeam do
end
describe 'members collection' do
- it { project.team.reporters.should include(reporter) }
- it { project.team.masters.should include(master) }
- it { project.team.masters.should include(guest) }
- it { project.team.masters.should_not include(reporter) }
- it { project.team.masters.should_not include(nonmember) }
+ it { expect(project.team.reporters).to include(reporter) }
+ it { expect(project.team.masters).to include(master) }
+ it { expect(project.team.masters).to include(guest) }
+ it { expect(project.team.masters).not_to include(reporter) }
+ it { expect(project.team.masters).not_to include(nonmember) }
end
describe 'access methods' do
- it { project.team.reporter?(reporter).should be_true }
- it { project.team.master?(master).should be_true }
- it { project.team.master?(guest).should be_true }
- it { project.team.master?(reporter).should be_false }
- it { project.team.master?(nonmember).should be_false }
- it { project.team.member?(nonmember).should be_false }
- it { project.team.member?(guest).should be_true }
+ it { expect(project.team.reporter?(reporter)).to be_truthy }
+ it { expect(project.team.master?(master)).to be_truthy }
+ it { expect(project.team.master?(guest)).to be_truthy }
+ it { expect(project.team.master?(reporter)).to be_falsey }
+ it { expect(project.team.master?(nonmember)).to be_falsey }
+ it { expect(project.team.member?(nonmember)).to be_falsey }
+ it { expect(project.team.member?(guest)).to be_truthy }
end
end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index e4ee2fc5b1..2acdb7dfdd 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -12,19 +12,19 @@ describe ProjectWiki do
describe "#path_with_namespace" do
it "returns the project path with namespace with the .wiki extension" do
- subject.path_with_namespace.should == project.path_with_namespace + ".wiki"
+ expect(subject.path_with_namespace).to eq(project.path_with_namespace + ".wiki")
end
end
describe "#url_to_repo" do
it "returns the correct ssh url to the repo" do
- subject.url_to_repo.should == gitlab_shell.url_to_repo(subject.path_with_namespace)
+ expect(subject.url_to_repo).to eq(gitlab_shell.url_to_repo(subject.path_with_namespace))
end
end
describe "#ssh_url_to_repo" do
it "equals #url_to_repo" do
- subject.ssh_url_to_repo.should == subject.url_to_repo
+ expect(subject.ssh_url_to_repo).to eq(subject.url_to_repo)
end
end
@@ -32,21 +32,21 @@ describe ProjectWiki do
it "provides the full http url to the repo" do
gitlab_url = Gitlab.config.gitlab.url
repo_http_url = "#{gitlab_url}/#{subject.path_with_namespace}.git"
- subject.http_url_to_repo.should == repo_http_url
+ expect(subject.http_url_to_repo).to eq(repo_http_url)
end
end
describe "#wiki" do
it "contains a Gollum::Wiki instance" do
- subject.wiki.should be_a Gollum::Wiki
+ expect(subject.wiki).to be_a Gollum::Wiki
end
it "creates a new wiki repo if one does not yet exist" do
- project_wiki.create_page("index", "test content").should be_true
+ expect(project_wiki.create_page("index", "test content")).to be_truthy
end
it "raises CouldNotCreateWikiError if it can't create the wiki repository" do
- project_wiki.stub(:init_repo).and_return(false)
+ allow(project_wiki).to receive(:init_repo).and_return(false)
expect { project_wiki.send(:create_repo!) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError)
end
end
@@ -54,21 +54,27 @@ describe ProjectWiki do
describe "#empty?" do
context "when the wiki repository is empty" do
before do
- Gitlab::Shell.any_instance.stub(:add_repository) do
+ allow_any_instance_of(Gitlab::Shell).to receive(:add_repository) do
create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git")
end
- project.stub(:path_with_namespace).and_return("non-existant")
+ allow(project).to receive(:path_with_namespace).and_return("non-existant")
end
- its(:empty?) { should be_true }
+ describe '#empty?' do
+ subject { super().empty? }
+ it { is_expected.to be_truthy }
+ end
end
context "when the wiki has pages" do
before do
- create_page("index", "This is an awesome new Gollum Wiki")
+ project_wiki.create_page("index", "This is an awesome new Gollum Wiki")
end
- its(:empty?) { should be_false }
+ describe '#empty?' do
+ subject { super().empty? }
+ it { is_expected.to be_falsey }
+ end
end
end
@@ -83,11 +89,11 @@ describe ProjectWiki do
end
it "returns an array of WikiPage instances" do
- @pages.first.should be_a WikiPage
+ expect(@pages.first).to be_a WikiPage
end
it "returns the correct number of pages" do
- @pages.count.should == 1
+ expect(@pages.count).to eq(1)
end
end
@@ -102,55 +108,55 @@ describe ProjectWiki do
it "returns the latest version of the page if it exists" do
page = subject.find_page("index page")
- page.title.should == "index page"
+ expect(page.title).to eq("index page")
end
it "returns nil if the page does not exist" do
- subject.find_page("non-existant").should == nil
+ expect(subject.find_page("non-existant")).to eq(nil)
end
it "can find a page by slug" do
page = subject.find_page("index-page")
- page.title.should == "index page"
+ expect(page.title).to eq("index page")
end
it "returns a WikiPage instance" do
page = subject.find_page("index page")
- page.should be_a WikiPage
+ expect(page).to be_a WikiPage
end
end
describe '#find_file' do
before do
file = Gollum::File.new(subject.wiki)
- Gollum::Wiki.any_instance.
- stub(:file).with('image.jpg', 'master', true).
+ allow_any_instance_of(Gollum::Wiki).
+ to receive(:file).with('image.jpg', 'master', true).
and_return(file)
- Gollum::File.any_instance.
- stub(:mime_type).
+ allow_any_instance_of(Gollum::File).
+ to receive(:mime_type).
and_return('image/jpeg')
- Gollum::Wiki.any_instance.
- stub(:file).with('non-existant', 'master', true).
+ allow_any_instance_of(Gollum::Wiki).
+ to receive(:file).with('non-existant', 'master', true).
and_return(nil)
end
after do
- Gollum::Wiki.any_instance.unstub(:file)
- Gollum::File.any_instance.unstub(:mime_type)
+ allow_any_instance_of(Gollum::Wiki).to receive(:file).and_call_original
+ allow_any_instance_of(Gollum::File).to receive(:mime_type).and_call_original
end
it 'returns the latest version of the file if it exists' do
file = subject.find_file('image.jpg')
- file.mime_type.should == 'image/jpeg'
+ expect(file.mime_type).to eq('image/jpeg')
end
it 'returns nil if the page does not exist' do
- subject.find_file('non-existant').should == nil
+ expect(subject.find_file('non-existant')).to eq(nil)
end
it 'returns a Gollum::File instance' do
file = subject.find_file('image.jpg')
- file.should be_a Gollum::File
+ expect(file).to be_a Gollum::File
end
end
@@ -160,23 +166,23 @@ describe ProjectWiki do
end
it "creates a new wiki page" do
- subject.create_page("test page", "this is content").should_not == false
- subject.pages.count.should == 1
+ expect(subject.create_page("test page", "this is content")).not_to eq(false)
+ expect(subject.pages.count).to eq(1)
end
it "returns false when a duplicate page exists" do
subject.create_page("test page", "content")
- subject.create_page("test page", "content").should == false
+ expect(subject.create_page("test page", "content")).to eq(false)
end
it "stores an error message when a duplicate page exists" do
2.times { subject.create_page("test page", "content") }
- subject.error_message.should =~ /Duplicate page:/
+ expect(subject.error_message).to match(/Duplicate page:/)
end
it "sets the correct commit message" do
subject.create_page("test page", "some content", :markdown, "commit message")
- subject.pages.first.page.version.message.should == "commit message"
+ expect(subject.pages.first.page.version.message).to eq("commit message")
end
end
@@ -193,11 +199,11 @@ describe ProjectWiki do
end
it "updates the content of the page" do
- @page.raw_data.should == "some other content"
+ expect(@page.raw_data).to eq("some other content")
end
it "sets the correct commit message" do
- @page.version.message.should == "updated page"
+ expect(@page.version.message).to eq("updated page")
end
end
@@ -209,7 +215,7 @@ describe ProjectWiki do
it "deletes the page" do
subject.delete_page(@page)
- subject.pages.count.should == 0
+ expect(subject.pages.count).to eq(0)
end
end
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index af48c2c6d9..1e6937b536 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -2,25 +2,26 @@
#
# Table name: protected_branches
#
-# id :integer not null, primary key
-# project_id :integer not null
-# name :string(255) not null
-# created_at :datetime
-# updated_at :datetime
+# id :integer not null, primary key
+# project_id :integer not null
+# name :string(255) not null
+# created_at :datetime
+# updated_at :datetime
+# developers_can_push :boolean default(FALSE), not null
#
require 'spec_helper'
describe ProtectedBranch do
describe 'Associations' do
- it { should belong_to(:project) }
+ it { is_expected.to belong_to(:project) }
end
describe "Mass assignment" do
end
describe 'Validation' do
- it { should validate_presence_of(:project) }
- it { should validate_presence_of(:name) }
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:name) }
end
end
diff --git a/spec/models/pushover_service_spec.rb b/spec/models/pushover_service_spec.rb
deleted file mode 100644
index 59db69d757..0000000000
--- a/spec/models/pushover_service_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-require 'spec_helper'
-
-describe PushoverService do
- describe 'Associations' do
- it { should belong_to :project }
- it { should have_one :service_hook }
- end
-
- describe 'Validations' do
- context 'active' do
- before do
- subject.active = true
- end
-
- it { should validate_presence_of :api_key }
- it { should validate_presence_of :user_key }
- it { should validate_presence_of :priority }
- end
- end
-
- describe 'Execute' do
- let(:pushover) { PushoverService.new }
- let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:sample_data) { GitPushService.new.sample_data(project, user) }
-
- let(:api_key) { 'verySecret' }
- let(:user_key) { 'verySecret' }
- let(:device) { 'myDevice' }
- let(:priority) { 0 }
- let(:sound) { 'bike' }
- let(:api_url) { 'https://api.pushover.net/1/messages.json' }
-
- before do
- pushover.stub(
- project: project,
- project_id: project.id,
- service_hook: true,
- api_key: api_key,
- user_key: user_key,
- device: device,
- priority: priority,
- sound: sound
- )
-
- WebMock.stub_request(:post, api_url)
- end
-
- it 'should call Pushover API' do
- pushover.execute(sample_data)
-
- WebMock.should have_requested(:post, api_url).once
- end
- end
-end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 6c3e221f34..b3a38f6c5b 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -8,14 +8,37 @@ describe Repository do
describe :branch_names_contains do
subject { repository.branch_names_contains(sample_commit.id) }
- it { should include('master') }
- it { should_not include('feature') }
- it { should_not include('fix') }
+ it { is_expected.to include('master') }
+ it { is_expected.not_to include('feature') }
+ it { is_expected.not_to include('fix') }
end
describe :last_commit_for_path do
subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
- it { should eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
+ it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
+ end
+
+ context :timestamps_by_user_log do
+ before do
+ Date.stub(:today).and_return(Date.new(2015, 03, 01))
+ end
+
+ describe 'single e-mail for user' do
+ let(:user) { create(:user, email: sample_commit.author_email) }
+
+ subject { repository.timestamps_by_user_log(user) }
+
+ it { is_expected.to eq(["2014-08-06", "2014-07-31", "2014-07-31"]) }
+ end
+
+ describe 'multiple emails for user' do
+ let(:email_alias) { create(:email, email: another_sample_commit.author_email) }
+ let(:user) { create(:user, email: sample_commit.author_email, emails: [email_alias]) }
+
+ subject { repository.timestamps_by_user_log(user) }
+
+ it { is_expected.to eq(["2015-01-10", "2014-08-06", "2014-07-31", "2014-07-31"]) }
+ end
end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index c96f2b2052..735652aea7 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -2,14 +2,19 @@
#
# Table name: services
#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
#
require 'spec_helper'
@@ -17,8 +22,8 @@ require 'spec_helper'
describe Service do
describe "Associations" do
- it { should belong_to :project }
- it { should have_one :service_hook }
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
end
describe "Mass assignment" do
@@ -40,7 +45,7 @@ describe Service do
end
describe :can_test do
- it { @testable.should == true }
+ it { expect(@testable).to eq(true) }
end
end
@@ -55,7 +60,32 @@ describe Service do
end
describe :can_test do
- it { @testable.should == true }
+ it { expect(@testable).to eq(true) }
+ end
+ end
+ end
+
+ describe "Template" do
+ describe "for pushover service" do
+ let(:service_template) {
+ PushoverService.create(template: true, properties: {device: 'MyDevice', sound: 'mic', priority: 4, api_key: '123456789'})
+ }
+ let(:project) { create(:project) }
+
+ describe 'should be prefilled for projects pushover service' do
+ before do
+ service_template
+ project.build_missing_services
+ end
+
+ it "should have all fields prefilled" do
+ service = project.pushover_service
+ expect(service.template).to eq(false)
+ expect(service.device).to eq('MyDevice')
+ expect(service.sound).to eq('mic')
+ expect(service.priority).to eq(4)
+ expect(service.api_key).to eq('123456789')
+ end
end
end
end
diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb
deleted file mode 100644
index 95df38d940..0000000000
--- a/spec/models/slack_service_spec.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-# == Schema Information
-#
-# Table name: services
-#
-# id :integer not null, primary key
-# type :string(255)
-# title :string(255)
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
-# active :boolean default(FALSE), not null
-# properties :text
-#
-
-require 'spec_helper'
-
-describe SlackService do
- describe "Associations" do
- it { should belong_to :project }
- it { should have_one :service_hook }
- end
-
- describe "Validations" do
- context "active" do
- before do
- subject.active = true
- end
-
- it { should validate_presence_of :webhook }
- end
- end
-
- describe "Execute" do
- let(:slack) { SlackService.new }
- let(:slack_service) { SlackService.new }
- let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:sample_data) { GitPushService.new.sample_data(project, user) }
- let(:webhook) { 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' }
- let(:new_webhook) { 'https://hooks.gitlabhq.slack.com/services/cdIj4r4LfXUOySDUjp0tk3OI' }
- let(:api_url) {
- 'https://gitlabhq.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI'
- }
-
- before do
- slack.stub(
- project: project,
- project_id: project.id,
- service_hook: true,
- webhook: webhook
- )
-
- WebMock.stub_request(:post, api_url)
- end
-
- it "should call Slack API" do
- slack.execute(sample_data)
-
- WebMock.should have_requested(:post, api_url).once
- end
-
- context 'with new webhook syntax' do
- before do
- slack_service.stub(
- project: project,
- project_id: project.id,
- service_hook: true,
- webhook: new_webhook
- )
-
- WebMock.stub_request(:post, api_url)
- end
-
- it "should call Slack API" do
- slack_service.execute(sample_data)
-
- WebMock.should have_requested(:post, api_url).once
- end
- end
- end
-end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 1ef2c512c1..e37dcc7523 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -19,22 +19,22 @@ require 'spec_helper'
describe Snippet do
describe "Associations" do
- it { should belong_to(:author).class_name('User') }
- it { should have_many(:notes).dependent(:destroy) }
+ it { is_expected.to belong_to(:author).class_name('User') }
+ it { is_expected.to have_many(:notes).dependent(:destroy) }
end
describe "Mass assignment" do
end
describe "Validation" do
- it { should validate_presence_of(:author) }
+ it { is_expected.to validate_presence_of(:author) }
- it { should validate_presence_of(:title) }
- it { should ensure_length_of(:title).is_within(0..255) }
+ it { is_expected.to validate_presence_of(:title) }
+ it { is_expected.to ensure_length_of(:title).is_within(0..255) }
- it { should validate_presence_of(:file_name) }
- it { should ensure_length_of(:title).is_within(0..255) }
+ it { is_expected.to validate_presence_of(:file_name) }
+ it { is_expected.to ensure_length_of(:title).is_within(0..255) }
- it { should validate_presence_of(:content) }
+ it { is_expected.to validate_presence_of(:content) }
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0250014bc2..10e90cae14 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,79 +2,85 @@
#
# Table name: users
#
-# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime
-# updated_at :datetime
-# name :string(255)
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# extern_uid :string(255)
-# provider :string(255)
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# color_scheme_id :integer default(1), not null
-# notification_level :integer default(1), not null
-# password_expires_at :datetime
-# created_by_id :integer
-# last_credential_check_at :datetime
-# avatar :string(255)
-# confirmation_token :string(255)
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
+# id :integer not null, primary key
+# email :string(255) default(""), not null
+# encrypted_password :string(255) default(""), not null
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# created_at :datetime
+# updated_at :datetime
+# name :string(255)
+# admin :boolean default(FALSE), not null
+# projects_limit :integer default(10)
+# skype :string(255) default(""), not null
+# linkedin :string(255) default(""), not null
+# twitter :string(255) default(""), not null
+# authentication_token :string(255)
+# theme_id :integer default(1), not null
+# bio :string(255)
+# failed_attempts :integer default(0)
+# locked_at :datetime
+# username :string(255)
+# can_create_group :boolean default(TRUE), not null
+# can_create_team :boolean default(TRUE), not null
+# state :string(255)
+# color_scheme_id :integer default(1), not null
+# notification_level :integer default(1), not null
+# password_expires_at :datetime
+# created_by_id :integer
+# last_credential_check_at :datetime
+# avatar :string(255)
+# confirmation_token :string(255)
+# confirmed_at :datetime
+# confirmation_sent_at :datetime
+# unconfirmed_email :string(255)
+# hide_no_ssh_key :boolean default(FALSE)
+# website_url :string(255) default(""), not null
+# github_access_token :string(255)
+# gitlab_access_token :string(255)
+# notification_email :string(255)
+# hide_no_password :boolean default(FALSE)
+# password_automatically_set :boolean default(FALSE)
+# bitbucket_access_token :string(255)
+# bitbucket_access_token_secret :string(255)
#
require 'spec_helper'
describe User do
describe "Associations" do
- it { should have_one(:namespace) }
- it { should have_many(:snippets).class_name('Snippet').dependent(:destroy) }
- it { should have_many(:project_members).dependent(:destroy) }
- it { should have_many(:groups) }
- it { should have_many(:keys).dependent(:destroy) }
- it { should have_many(:events).class_name('Event').dependent(:destroy) }
- it { should have_many(:recent_events).class_name('Event') }
- it { should have_many(:issues).dependent(:destroy) }
- it { should have_many(:notes).dependent(:destroy) }
- it { should have_many(:assigned_issues).dependent(:destroy) }
- it { should have_many(:merge_requests).dependent(:destroy) }
- it { should have_many(:assigned_merge_requests).dependent(:destroy) }
+ 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(:project_members).dependent(:destroy) }
+ it { is_expected.to have_many(:groups) }
+ 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(:recent_events).class_name('Event') }
+ it { is_expected.to have_many(:issues).dependent(:destroy) }
+ it { is_expected.to have_many(:notes).dependent(:destroy) }
+ it { is_expected.to have_many(:assigned_issues).dependent(:destroy) }
+ it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
+ it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) }
+ it { is_expected.to have_many(:identities).dependent(:destroy) }
end
describe "Mass assignment" do
end
describe 'validations' do
- it { should validate_presence_of(:username) }
- it { should validate_presence_of(:projects_limit) }
- it { should validate_numericality_of(:projects_limit) }
- it { should allow_value(0).for(:projects_limit) }
- it { should_not allow_value(-1).for(:projects_limit) }
+ it { is_expected.to validate_presence_of(:username) }
+ it { is_expected.to validate_presence_of(:projects_limit) }
+ it { is_expected.to validate_numericality_of(:projects_limit) }
+ it { is_expected.to allow_value(0).for(:projects_limit) }
+ it { is_expected.not_to allow_value(-1).for(:projects_limit) }
- it { should ensure_length_of(:bio).is_within(0..255) }
+ it { is_expected.to ensure_length_of(:bio).is_within(0..255) }
describe 'email' do
it 'accepts info@example.com' do
@@ -110,34 +116,34 @@ describe User do
end
describe "Respond to" do
- it { should respond_to(:is_admin?) }
- it { should respond_to(:name) }
- it { should respond_to(:private_token) }
+ it { is_expected.to respond_to(:is_admin?) }
+ it { is_expected.to respond_to(:name) }
+ it { is_expected.to respond_to(:private_token) }
end
describe '#generate_password' do
it "should execute callback when force_random_password specified" do
user = build(:user, force_random_password: true)
- user.should_receive(:generate_password)
+ expect(user).to receive(:generate_password)
user.save
end
it "should not generate password by default" do
user = create(:user, password: 'abcdefghe')
- user.password.should == 'abcdefghe'
+ expect(user.password).to eq('abcdefghe')
end
it "should generate password when forcing random password" do
- Devise.stub(:friendly_token).and_return('123456789')
+ allow(Devise).to receive(:friendly_token).and_return('123456789')
user = create(:user, password: 'abcdefg', force_random_password: true)
- user.password.should == '12345678'
+ expect(user.password).to eq('12345678')
end
end
describe 'authentication token' do
it "should have authentication token" do
user = create(:user)
- user.authentication_token.should_not be_blank
+ expect(user.authentication_token).not_to be_blank
end
end
@@ -152,15 +158,15 @@ describe User do
@project_3.team << [@user, :developer]
end
- it { @user.authorized_projects.should include(@project) }
- it { @user.authorized_projects.should include(@project_2) }
- it { @user.authorized_projects.should include(@project_3) }
- it { @user.owned_projects.should include(@project) }
- it { @user.owned_projects.should_not include(@project_2) }
- it { @user.owned_projects.should_not include(@project_3) }
- it { @user.personal_projects.should include(@project) }
- it { @user.personal_projects.should_not include(@project_2) }
- it { @user.personal_projects.should_not include(@project_3) }
+ it { expect(@user.authorized_projects).to include(@project) }
+ it { expect(@user.authorized_projects).to include(@project_2) }
+ it { expect(@user.authorized_projects).to include(@project_3) }
+ it { expect(@user.owned_projects).to include(@project) }
+ it { expect(@user.owned_projects).not_to include(@project_2) }
+ it { expect(@user.owned_projects).not_to include(@project_3) }
+ it { expect(@user.personal_projects).to include(@project) }
+ it { expect(@user.personal_projects).not_to include(@project_2) }
+ it { expect(@user.personal_projects).not_to include(@project_3) }
end
describe 'groups' do
@@ -170,9 +176,9 @@ describe User do
@group.add_owner(@user)
end
- it { @user.several_namespaces?.should be_true }
- it { @user.authorized_groups.should == [@group] }
- it { @user.owned_groups.should == [@group] }
+ it { expect(@user.several_namespaces?).to be_truthy }
+ it { expect(@user.authorized_groups).to eq([@group]) }
+ it { expect(@user.owned_groups).to eq([@group]) }
end
describe 'group multiple owners' do
@@ -185,7 +191,7 @@ describe User do
@group.add_user(@user2, GroupMember::OWNER)
end
- it { @user2.several_namespaces?.should be_true }
+ it { expect(@user2.several_namespaces?).to be_truthy }
end
describe 'namespaced' do
@@ -194,7 +200,7 @@ describe User do
@project = create :project, namespace: @user.namespace
end
- it { @user.several_namespaces?.should be_false }
+ it { expect(@user.several_namespaces?).to be_falsey }
end
describe 'blocking user' do
@@ -202,7 +208,7 @@ describe User do
it "should block user" do
user.block
- user.blocked?.should be_true
+ expect(user.blocked?).to be_truthy
end
end
@@ -214,10 +220,10 @@ describe User do
@blocked = create :user, state: :blocked
end
- it { User.filter("admins").should == [@admin] }
- it { User.filter("blocked").should == [@blocked] }
- it { User.filter("wop").should include(@user, @admin, @blocked) }
- it { User.filter(nil).should include(@user, @admin) }
+ it { expect(User.filter("admins")).to eq([@admin]) }
+ it { expect(User.filter("blocked")).to eq([@blocked]) }
+ it { expect(User.filter("wop")).to include(@user, @admin, @blocked) }
+ it { expect(User.filter(nil)).to include(@user, @admin) }
end
describe :not_in_project do
@@ -227,27 +233,27 @@ describe User do
@project = create :project
end
- it { User.not_in_project(@project).should include(@user, @project.owner) }
+ it { expect(User.not_in_project(@project)).to include(@user, @project.owner) }
end
describe 'user creation' do
describe 'normal user' do
let(:user) { create(:user, name: 'John Smith') }
- it { user.is_admin?.should be_false }
- it { user.require_ssh_key?.should be_true }
- it { user.can_create_group?.should be_true }
- it { user.can_create_project?.should be_true }
- it { user.first_name.should == 'John' }
+ it { expect(user.is_admin?).to be_falsey }
+ it { expect(user.require_ssh_key?).to be_truthy }
+ it { expect(user.can_create_group?).to be_truthy }
+ it { expect(user.can_create_project?).to be_truthy }
+ it { expect(user.first_name).to eq('John') }
end
describe 'with defaults' do
let(:user) { User.new }
it "should apply defaults to user" do
- user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit
- user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group
- user.theme_id.should == Gitlab.config.gitlab.default_theme
+ expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
+ expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
+ expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
end
end
@@ -255,9 +261,9 @@ describe User do
let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: Gitlab::Theme::BASIC) }
it "should apply defaults to user" do
- user.projects_limit.should == 123
- user.can_create_group.should be_false
- user.theme_id.should == Gitlab::Theme::BASIC
+ expect(user.projects_limit).to eq(123)
+ expect(user.can_create_group).to be_falsey
+ expect(user.theme_id).to eq(Gitlab::Theme::BASIC)
end
end
end
@@ -267,12 +273,12 @@ describe User do
let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') }
it "should be case insensitive" do
- User.search(user1.username.upcase).to_a.should == [user1]
- User.search(user1.username.downcase).to_a.should == [user1]
- User.search(user2.username.upcase).to_a.should == [user2]
- User.search(user2.username.downcase).to_a.should == [user2]
- User.search(user1.username.downcase).to_a.count.should == 2
- User.search(user2.username.downcase).to_a.count.should == 1
+ expect(User.search(user1.username.upcase).to_a).to eq([user1])
+ expect(User.search(user1.username.downcase).to_a).to eq([user1])
+ expect(User.search(user2.username.upcase).to_a).to eq([user2])
+ expect(User.search(user2.username.downcase).to_a).to eq([user2])
+ expect(User.search(user1.username.downcase).to_a.count).to eq(2)
+ expect(User.search(user2.username.downcase).to_a.count).to eq(1)
end
end
@@ -280,21 +286,45 @@ describe User do
let(:user1) { create(:user, username: 'foo') }
it "should get the correct user" do
- User.by_username_or_id(user1.id).should == user1
- User.by_username_or_id('foo').should == user1
- User.by_username_or_id(-1).should be_nil
- User.by_username_or_id('bar').should be_nil
+ expect(User.by_username_or_id(user1.id)).to eq(user1)
+ expect(User.by_username_or_id('foo')).to eq(user1)
+ expect(User.by_username_or_id(-1)).to be_nil
+ expect(User.by_username_or_id('bar')).to be_nil
+ end
+ end
+
+ describe '.by_login' do
+ let(:username) { 'John' }
+ let!(:user) { create(:user, username: username) }
+
+ it 'should get the correct user' do
+ expect(User.by_login(user.email.upcase)).to eq user
+ expect(User.by_login(user.email)).to eq user
+ expect(User.by_login(username.downcase)).to eq user
+ expect(User.by_login(username)).to eq user
+ expect(User.by_login(nil)).to be_nil
+ expect(User.by_login('')).to be_nil
+ end
+ end
+
+ describe ".clean_username" do
+
+ let!(:user) { create(:user, username: "johngitlab-etc") }
+ let!(:namespace) { create(:namespace, path: "JohnGitLab-etc1") }
+
+ it "cleans a username and makes sure it's available" do
+ expect(User.clean_username("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
end
end
describe 'all_ssh_keys' do
- it { should have_many(:keys).dependent(:destroy) }
+ it { is_expected.to have_many(:keys).dependent(:destroy) }
it "should have all ssh keys" do
user = create :user
key = create :key, key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD33bWLBxu48Sev9Fert1yzEO4WGcWglWF7K/AwblIUFselOt/QdOL9DSjpQGxLagO1s9wl53STIO8qGS4Ms0EJZyIXOEFMjFJ5xmjSy+S37By4sG7SsltQEHMxtbtFOaW5LV2wCrX+rUsRNqLMamZjgjcPO0/EgGCXIGMAYW4O7cwGZdXWYIhQ1Vwy+CsVMDdPkPgBXqK7nR/ey8KMs8ho5fMNgB5hBw/AL9fNGhRw3QTD6Q12Nkhl4VZES2EsZqlpNnJttnPdp847DUsT6yuLRlfiQfz5Cn9ysHFdXObMN5VYIiPFwHeYCZp1X2S4fDZooRE8uOLTfxWHPXwrhqSH", user_id: user.id
- user.all_ssh_keys.should include(key.key)
+ expect(user.all_ssh_keys).to include(key.key)
end
end
@@ -303,12 +333,12 @@ describe User do
it "should be true if avatar is image" do
user.update_attribute(:avatar, 'uploads/avatar.png')
- user.avatar_type.should be_true
+ expect(user.avatar_type).to be_truthy
end
it "should be false if avatar is html page" do
user.update_attribute(:avatar, 'uploads/avatar.html')
- user.avatar_type.should == ["only images allowed"]
+ expect(user.avatar_type).to eq(["only images allowed"])
end
end
@@ -319,7 +349,7 @@ describe User do
# Create a condition which would otherwise cause 'true' to be returned
user.stub(ldap_user?: true)
user.last_credential_check_at = nil
- expect(user.requires_ldap_check?).to be_false
+ expect(user.requires_ldap_check?).to be_falsey
end
context 'when LDAP is enabled' do
@@ -327,7 +357,7 @@ describe User do
it 'is false for non-LDAP users' do
user.stub(ldap_user?: false)
- expect(user.requires_ldap_check?).to be_false
+ expect(user.requires_ldap_check?).to be_falsey
end
context 'and when the user is an LDAP user' do
@@ -335,17 +365,41 @@ describe User do
it 'is true when the user has never had an LDAP check before' do
user.last_credential_check_at = nil
- expect(user.requires_ldap_check?).to be_true
+ expect(user.requires_ldap_check?).to be_truthy
end
it 'is true when the last LDAP check happened over 1 hour ago' do
user.last_credential_check_at = 2.hours.ago
- expect(user.requires_ldap_check?).to be_true
+ expect(user.requires_ldap_check?).to be_truthy
end
end
end
end
+ describe :ldap_user? do
+ it "is true if provider name starts with ldap" do
+ user = create(:omniauth_user, provider: 'ldapmain')
+ expect( user.ldap_user? ).to be_truthy
+ end
+
+ it "is false for other providers" do
+ user = create(:omniauth_user, provider: 'other-provider')
+ expect( user.ldap_user? ).to be_falsey
+ end
+
+ it "is false if no extern_uid is provided" do
+ user = create(:omniauth_user, extern_uid: nil)
+ expect( user.ldap_user? ).to be_falsey
+ end
+ end
+
+ describe :ldap_identity do
+ it "returns ldap identity" do
+ user = create :omniauth_user
+ expect(user.ldap_identity.provider).not_to be_empty
+ end
+ end
+
describe '#full_website_url' do
let(:user) { create(:user) }
@@ -396,24 +450,24 @@ describe User do
project1 = create :project, :public
project2 = create :project, :public
- expect(user.starred?(project1)).to be_false
- expect(user.starred?(project2)).to be_false
+ expect(user.starred?(project1)).to be_falsey
+ expect(user.starred?(project2)).to be_falsey
star1 = UsersStarProject.create!(project: project1, user: user)
- expect(user.starred?(project1)).to be_true
- expect(user.starred?(project2)).to be_false
+ expect(user.starred?(project1)).to be_truthy
+ expect(user.starred?(project2)).to be_falsey
star2 = UsersStarProject.create!(project: project2, user: user)
- expect(user.starred?(project1)).to be_true
- expect(user.starred?(project2)).to be_true
+ expect(user.starred?(project1)).to be_truthy
+ expect(user.starred?(project2)).to be_truthy
star1.destroy
- expect(user.starred?(project1)).to be_false
- expect(user.starred?(project2)).to be_true
+ expect(user.starred?(project1)).to be_falsey
+ expect(user.starred?(project2)).to be_truthy
star2.destroy
- expect(user.starred?(project1)).to be_false
- expect(user.starred?(project2)).to be_false
+ expect(user.starred?(project1)).to be_falsey
+ expect(user.starred?(project2)).to be_falsey
end
end
@@ -422,11 +476,67 @@ describe User do
user = create :user
project = create :project, :public
- expect(user.starred?(project)).to be_false
+ expect(user.starred?(project)).to be_falsey
user.toggle_star(project)
- expect(user.starred?(project)).to be_true
+ expect(user.starred?(project)).to be_truthy
user.toggle_star(project)
- expect(user.starred?(project)).to be_false
+ expect(user.starred?(project)).to be_falsey
+ end
+ end
+
+ describe "#sort" do
+ before do
+ User.delete_all
+ @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha'
+ @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega'
+ end
+
+ it "sorts users as recently_signed_in" do
+ expect(User.sort('recent_sign_in').first).to eq(@user)
+ end
+
+ it "sorts users as late_signed_in" do
+ expect(User.sort('oldest_sign_in').first).to eq(@user1)
+ end
+
+ it "sorts users as recently_created" do
+ expect(User.sort('created_desc').first).to eq(@user)
+ end
+
+ it "sorts users as late_created" do
+ expect(User.sort('created_asc').first).to eq(@user1)
+ end
+
+ it "sorts users by name when nil is passed" do
+ expect(User.sort(nil).first).to eq(@user)
+ end
+ end
+
+ describe "#contributed_projects_ids" do
+
+ subject { create(:user) }
+ let!(:project1) { create(:project) }
+ let!(:project2) { create(:project, forked_from_project: project3) }
+ let!(:project3) { create(:project) }
+ let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) }
+ let!(:push_event) { create(:event, action: Event::PUSHED, project: project1, target: project1, author: subject) }
+ let!(:merge_event) { create(:event, action: Event::CREATED, project: project3, target: merge_request, author: subject) }
+
+ before do
+ project1.team << [subject, :master]
+ project2.team << [subject, :master]
+ end
+
+ it "includes IDs for projects the user has pushed to" do
+ expect(subject.contributed_projects_ids).to include(project1.id)
+ end
+
+ it "includes IDs for projects the user has had merge requests merged into" do
+ expect(subject.contributed_projects_ids).to include(project3.id)
+ end
+
+ it "doesn't include IDs for unrelated projects" do
+ expect(subject.contributed_projects_ids).not_to include(project2.id)
end
end
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index d065431ee3..f3fd805783 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -16,27 +16,27 @@ describe WikiPage do
end
it "sets the slug attribute" do
- @wiki_page.slug.should == "test-page"
+ expect(@wiki_page.slug).to eq("test-page")
end
it "sets the title attribute" do
- @wiki_page.title.should == "test page"
+ expect(@wiki_page.title).to eq("test page")
end
it "sets the formatted content attribute" do
- @wiki_page.content.should == "test content"
+ expect(@wiki_page.content).to eq("test content")
end
it "sets the format attribute" do
- @wiki_page.format.should == :markdown
+ expect(@wiki_page.format).to eq(:markdown)
end
it "sets the message attribute" do
- @wiki_page.message.should == "test commit"
+ expect(@wiki_page.message).to eq("test commit")
end
it "sets the version attribute" do
- @wiki_page.version.should be_a Grit::Commit
+ expect(@wiki_page.version).to be_a Gollum::Git::Commit
end
end
end
@@ -48,12 +48,12 @@ describe WikiPage do
it "validates presence of title" do
subject.attributes.delete(:title)
- subject.valid?.should be_false
+ expect(subject.valid?).to be_falsey
end
it "validates presence of content" do
subject.attributes.delete(:content)
- subject.valid?.should be_false
+ expect(subject.valid?).to be_falsey
end
end
@@ -69,11 +69,11 @@ describe WikiPage do
context "with valid attributes" do
it "saves the wiki page" do
subject.create(@wiki_attr)
- wiki.find_page("Index").should_not be_nil
+ expect(wiki.find_page("Index")).not_to be_nil
end
it "returns true" do
- subject.create(@wiki_attr).should == true
+ expect(subject.create(@wiki_attr)).to eq(true)
end
end
end
@@ -95,7 +95,7 @@ describe WikiPage do
end
it "returns true" do
- @page.update("more content").should be_true
+ expect(@page.update("more content")).to be_truthy
end
end
end
@@ -108,11 +108,11 @@ describe WikiPage do
it "should delete the page" do
@page.delete
- wiki.pages.should be_empty
+ expect(wiki.pages).to be_empty
end
it "should return true" do
- @page.delete.should == true
+ expect(@page.delete).to eq(true)
end
end
@@ -128,7 +128,7 @@ describe WikiPage do
it "returns an array of all commits for the page" do
3.times { |i| @page.update("content #{i}") }
- @page.versions.count.should == 4
+ expect(@page.versions.count).to eq(4)
end
end
@@ -144,7 +144,7 @@ describe WikiPage do
it "should be replace a hyphen to a space" do
@page.title = "Import-existing-repositories-into-GitLab"
- @page.title.should == "Import existing repositories into GitLab"
+ expect(@page.title).to eq("Import existing repositories into GitLab")
end
end
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index e2f222c0d3..20cb30a39b 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -41,32 +41,33 @@ describe API, api: true do
describe ".current_user" do
it "should return nil for an invalid token" do
env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = 'invalid token'
- current_user.should be_nil
+ allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false }
+ expect(current_user).to be_nil
end
it "should return nil for a user without access" do
env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
Gitlab::UserAccess.stub(allowed?: false)
- current_user.should be_nil
+ expect(current_user).to be_nil
end
it "should leave user as is when sudo not specified" do
env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
- current_user.should == user
+ expect(current_user).to eq(user)
clear_env
params[API::APIHelpers::PRIVATE_TOKEN_PARAM] = user.private_token
- current_user.should == user
+ expect(current_user).to eq(user)
end
it "should change current user to sudo when admin" do
set_env(admin, user.id)
- current_user.should == user
+ expect(current_user).to eq(user)
set_param(admin, user.id)
- current_user.should == user
+ expect(current_user).to eq(user)
set_env(admin, user.username)
- current_user.should == user
+ expect(current_user).to eq(user)
set_param(admin, user.username)
- current_user.should == user
+ expect(current_user).to eq(user)
end
it "should throw an error when the current user is not an admin and attempting to sudo" do
@@ -82,8 +83,8 @@ describe API, api: true do
it "should throw an error when the user cannot be found for a given id" do
id = user.id + admin.id
- user.id.should_not == id
- admin.id.should_not == id
+ expect(user.id).not_to eq(id)
+ expect(admin.id).not_to eq(id)
set_env(admin, id)
expect { current_user }.to raise_error
@@ -93,8 +94,8 @@ describe API, api: true do
it "should throw an error when the user cannot be found for a given username" do
username = "#{user.username}#{admin.username}"
- user.username.should_not == username
- admin.username.should_not == username
+ expect(user.username).not_to eq(username)
+ expect(admin.username).not_to eq(username)
set_env(admin, username)
expect { current_user }.to raise_error
@@ -104,69 +105,69 @@ describe API, api: true do
it "should handle sudo's to oneself" do
set_env(admin, admin.id)
- current_user.should == admin
+ expect(current_user).to eq(admin)
set_param(admin, admin.id)
- current_user.should == admin
+ expect(current_user).to eq(admin)
set_env(admin, admin.username)
- current_user.should == admin
+ expect(current_user).to eq(admin)
set_param(admin, admin.username)
- current_user.should == admin
+ expect(current_user).to eq(admin)
end
it "should handle multiple sudo's to oneself" do
set_env(admin, user.id)
- current_user.should == user
- current_user.should == user
+ expect(current_user).to eq(user)
+ expect(current_user).to eq(user)
set_env(admin, user.username)
- current_user.should == user
- current_user.should == user
+ expect(current_user).to eq(user)
+ expect(current_user).to eq(user)
set_param(admin, user.id)
- current_user.should == user
- current_user.should == user
+ expect(current_user).to eq(user)
+ expect(current_user).to eq(user)
set_param(admin, user.username)
- current_user.should == user
- current_user.should == user
+ expect(current_user).to eq(user)
+ expect(current_user).to eq(user)
end
it "should handle multiple sudo's to oneself using string ids" do
set_env(admin, user.id.to_s)
- current_user.should == user
- current_user.should == user
+ expect(current_user).to eq(user)
+ expect(current_user).to eq(user)
set_param(admin, user.id.to_s)
- current_user.should == user
- current_user.should == user
+ expect(current_user).to eq(user)
+ expect(current_user).to eq(user)
end
end
describe '.sudo_identifier' do
it "should return integers when input is an int" do
set_env(admin, '123')
- sudo_identifier.should == 123
+ expect(sudo_identifier).to eq(123)
set_env(admin, '0001234567890')
- sudo_identifier.should == 1234567890
+ expect(sudo_identifier).to eq(1234567890)
set_param(admin, '123')
- sudo_identifier.should == 123
+ expect(sudo_identifier).to eq(123)
set_param(admin, '0001234567890')
- sudo_identifier.should == 1234567890
+ expect(sudo_identifier).to eq(1234567890)
end
it "should return string when input is an is not an int" do
set_env(admin, '12.30')
- sudo_identifier.should == "12.30"
+ expect(sudo_identifier).to eq("12.30")
set_env(admin, 'hello')
- sudo_identifier.should == 'hello'
+ expect(sudo_identifier).to eq('hello')
set_env(admin, ' 123')
- sudo_identifier.should == ' 123'
+ expect(sudo_identifier).to eq(' 123')
set_param(admin, '12.30')
- sudo_identifier.should == "12.30"
+ expect(sudo_identifier).to eq("12.30")
set_param(admin, 'hello')
- sudo_identifier.should == 'hello'
+ expect(sudo_identifier).to eq('hello')
set_param(admin, ' 123')
- sudo_identifier.should == ' 123'
+ expect(sudo_identifier).to eq(' 123')
end
end
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 8834a6cfa8..f40d68b75a 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -15,79 +15,79 @@ describe API::API, api: true do
describe "GET /projects/:id/repository/branches" do
it "should return an array of project branches" do
get api("/projects/#{project.id}/repository/branches", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['name'].should == project.repository.branch_names.first
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(project.repository.branch_names.first)
end
end
describe "GET /projects/:id/repository/branches/:branch" do
it "should return the branch information for a single branch" do
get api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response['name'].should == branch_name
- json_response['commit']['id'].should == branch_sha
- json_response['protected'].should == false
+ expect(json_response['name']).to eq(branch_name)
+ expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(json_response['protected']).to eq(false)
end
it "should return a 403 error if guest" do
get api("/projects/#{project.id}/repository/branches", user2)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
it "should return a 404 error if branch is not available" do
get api("/projects/#{project.id}/repository/branches/unknown", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
describe "PUT /projects/:id/repository/branches/:branch/protect" do
it "should protect a single branch" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response['name'].should == branch_name
- json_response['commit']['id'].should == branch_sha
- json_response['protected'].should == true
+ expect(json_response['name']).to eq(branch_name)
+ expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(json_response['protected']).to eq(true)
end
it "should return a 404 error if branch not found" do
put api("/projects/#{project.id}/repository/branches/unknown/protect", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it "should return a 403 error if guest" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user2)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
it "should return 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)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
end
describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
it "should unprotect a single branch" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response['name'].should == branch_name
- json_response['commit']['id'].should == branch_sha
- json_response['protected'].should == false
+ expect(json_response['name']).to eq(branch_name)
+ expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(json_response['protected']).to eq(false)
end
it "should return success when unprotect branch" do
put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it "should return success when unprotect branch again" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
end
@@ -97,46 +97,46 @@ describe API::API, api: true do
branch_name: 'feature1',
ref: branch_sha
- response.status.should == 201
+ expect(response.status).to eq(201)
- json_response['name'].should == 'feature1'
- json_response['commit']['id'].should == branch_sha
+ expect(json_response['name']).to eq('feature1')
+ expect(json_response['commit']['id']).to eq(branch_sha)
end
it "should deny for user without push access" do
post api("/projects/#{project.id}/repository/branches", user2),
branch_name: branch_name,
ref: branch_sha
- response.status.should == 403
+ expect(response.status).to eq(403)
end
it 'should return 400 if branch name is invalid' do
post api("/projects/#{project.id}/repository/branches", user),
branch_name: 'new design',
ref: branch_sha
- response.status.should == 400
- json_response['message'].should == 'Branch name invalid'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('Branch name invalid')
end
it 'should return 400 if branch already exists' do
post api("/projects/#{project.id}/repository/branches", user),
branch_name: 'new_design1',
ref: branch_sha
- response.status.should == 201
+ expect(response.status).to eq(201)
post api("/projects/#{project.id}/repository/branches", user),
branch_name: 'new_design1',
ref: branch_sha
- response.status.should == 400
- json_response['message'].should == 'Branch already exists'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('Branch already exists')
end
it 'should return 400 if ref name is invalid' do
post api("/projects/#{project.id}/repository/branches", user),
branch_name: 'new_design3',
ref: 'foo'
- response.status.should == 400
- json_response['message'].should == 'Invalid reference name'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('Invalid reference name')
end
end
@@ -145,25 +145,26 @@ describe API::API, api: true do
it "should remove branch" do
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
+ expect(json_response['branch_name']).to eq(branch_name)
end
it 'should return 404 if branch not exists' do
delete api("/projects/#{project.id}/repository/branches/foobar", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it "should remove protected branch" do
project.protected_branches.create(name: branch_name)
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
- response.status.should == 405
- json_response['message'].should == 'Protected branch cant be removed'
+ expect(response.status).to eq(405)
+ expect(json_response['message']).to eq('Protected branch cant be removed')
end
it "should not remove HEAD branch" do
delete api("/projects/#{project.id}/repository/branches/master", user)
- response.status.should == 405
- json_response['message'].should == 'Cannot remove HEAD branch'
+ expect(response.status).to eq(405)
+ expect(json_response['message']).to eq('Cannot remove HEAD branch')
end
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 38e0a284c3..9ea60e1a4a 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -8,6 +8,7 @@ describe API::API, api: true do
let!(:project) { create(:project, creator_id: user.id) }
let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
before { project.team << [user, :reporter] }
@@ -17,17 +18,17 @@ describe API::API, api: true do
it "should return project commits" do
get api("/projects/#{project.id}/repository/commits", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response.should be_an Array
- json_response.first['id'].should == project.repository.commit.id
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(project.repository.commit.id)
end
end
context "unauthorized user" do
it "should not return project commits" do
get api("/projects/#{project.id}/repository/commits")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
end
@@ -36,21 +37,21 @@ describe API::API, api: true do
context "authorized user" do
it "should return a commit by sha" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
- response.status.should == 200
- json_response['id'].should == project.repository.commit.id
- json_response['title'].should == project.repository.commit.title
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(project.repository.commit.id)
+ expect(json_response['title']).to eq(project.repository.commit.title)
end
it "should return a 404 error if not found" do
get api("/projects/#{project.id}/repository/commits/invalid_sha", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
context "unauthorized user" do
it "should not return the selected commit" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
end
@@ -61,23 +62,87 @@ describe API::API, api: true do
it "should return the diff of the selected commit" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response.should be_an Array
- json_response.length.should >= 1
- json_response.first.keys.should include "diff"
+ expect(json_response).to be_an Array
+ expect(json_response.length).to be >= 1
+ expect(json_response.first.keys).to include "diff"
end
it "should return a 404 error if invalid commit" do
get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
context "unauthorized user" do
it "should not return the diff of the selected commit" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff")
- response.status.should == 401
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'GET /projects:id/repository/commits/:sha/comments' do
+ context 'authorized user' do
+ it 'should return merge_request comments' do
+ get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['note']).to eq('a comment on a commit')
+ expect(json_response.first['author']['id']).to eq(user.id)
+ end
+
+ it 'should return a 404 error if merge_request_id not found' do
+ get api("/projects/#{project.id}/repository/commits/1234ab/comments", user)
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return the diff of the selected commit' do
+ get api("/projects/#{project.id}/repository/commits/1234ab/comments")
+ expect(response.status).to eq(401)
+ end
+ end
+ end
+
+ describe 'POST /projects:id/repository/commits/:sha/comments' do
+ context 'authorized user' do
+ it 'should return comment' do
+ post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment'
+ expect(response.status).to eq(201)
+ expect(json_response['note']).to eq('My comment')
+ expect(json_response['path']).to be_nil
+ expect(json_response['line']).to be_nil
+ expect(json_response['line_type']).to be_nil
+ end
+
+ it 'should return the inline comment' do
+ post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new'
+ expect(response.status).to eq(201)
+ expect(json_response['note']).to eq('My comment')
+ expect(json_response['path']).to eq(project.repository.commit.diffs.first.new_path)
+ expect(json_response['line']).to eq(7)
+ expect(json_response['line_type']).to eq('new')
+ end
+
+ it 'should return 400 if note is missing' do
+ post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
+ expect(response.status).to eq(400)
+ end
+
+ it 'should return 404 if note is attached to non existent commit' do
+ post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment'
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return the diff of the selected commit' do
+ post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments")
+ expect(response.status).to eq(401)
end
end
end
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
new file mode 100644
index 0000000000..39949a9042
--- /dev/null
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let!(:user) { create(:user) }
+ let!(:application) { Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "https://app.com", :owner => user) }
+ let!(:token) { Doorkeeper::AccessToken.create! :application_id => application.id, :resource_owner_id => user.id }
+
+
+ describe "when unauthenticated" do
+ it "returns authentication success" do
+ get api("/user"), :access_token => token.token
+ expect(response.status).to eq(200)
+ end
+ end
+
+ describe "when token invalid" do
+ it "returns authentication error" do
+ get api("/user"), :access_token => "123a"
+ expect(response.status).to eq(401)
+ end
+ end
+
+ describe "authorization by private token" do
+ it "returns authentication success" do
+ get api("/user", user)
+ expect(response.status).to eq(200)
+ end
+ end
+end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index b43a202aec..bab8888a63 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -16,15 +16,15 @@ describe API::API, api: true do
}
get api("/projects/#{project.id}/repository/files", user), params
- response.status.should == 200
- json_response['file_path'].should == file_path
- json_response['file_name'].should == 'popen.rb'
- Base64.decode64(json_response['content']).lines.first.should == "require 'fileutils'\n"
+ expect(response.status).to eq(200)
+ expect(json_response['file_path']).to eq(file_path)
+ expect(json_response['file_name']).to eq('popen.rb')
+ expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n")
end
it "should return a 400 bad request if no params given" do
get api("/projects/#{project.id}/repository/files", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 404 if such file does not exist" do
@@ -34,7 +34,7 @@ describe API::API, api: true do
}
get api("/projects/#{project.id}/repository/files", user), params
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
@@ -54,13 +54,13 @@ describe API::API, api: true do
)
post api("/projects/#{project.id}/repository/files", user), valid_params
- response.status.should == 201
- json_response['file_path'].should == 'newfile.rb'
+ expect(response.status).to eq(201)
+ expect(json_response['file_path']).to eq('newfile.rb')
end
it "should return a 400 bad request if no params given" do
post api("/projects/#{project.id}/repository/files", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 400 if satellite fails to create file" do
@@ -69,7 +69,7 @@ describe API::API, api: true do
)
post api("/projects/#{project.id}/repository/files", user), valid_params
- response.status.should == 400
+ expect(response.status).to eq(400)
end
end
@@ -89,22 +89,42 @@ describe API::API, api: true do
)
put api("/projects/#{project.id}/repository/files", user), valid_params
- response.status.should == 200
- json_response['file_path'].should == file_path
+ expect(response.status).to eq(200)
+ expect(json_response['file_path']).to eq(file_path)
end
it "should return a 400 bad request if no params given" do
put api("/projects/#{project.id}/repository/files", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
- it "should return a 400 if satellite fails to create file" do
- Gitlab::Satellite::EditFileAction.any_instance.stub(
- commit!: false,
- )
+ it 'should return a 400 if the checkout fails' do
+ Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!)
+ .and_raise(Gitlab::Satellite::CheckoutFailed)
put api("/projects/#{project.id}/repository/files", user), valid_params
- response.status.should == 400
+ expect(response.status).to eq(400)
+
+ ref = valid_params[:branch_name]
+ expect(response.body).to match("ref '#{ref}' could not be checked out")
+ end
+
+ it 'should return a 409 if the file was not modified' do
+ Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!)
+ .and_raise(Gitlab::Satellite::CommitFailed)
+
+ put api("/projects/#{project.id}/repository/files", user), valid_params
+ expect(response.status).to eq(409)
+ expect(response.body).to match("Maybe there was nothing to commit?")
+ end
+
+ it 'should return a 409 if the push fails' do
+ Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!)
+ .and_raise(Gitlab::Satellite::PushFailed)
+
+ put api("/projects/#{project.id}/repository/files", user), valid_params
+ expect(response.status).to eq(409)
+ expect(response.body).to match("Maybe the file was changed by another process?")
end
end
@@ -123,13 +143,13 @@ describe API::API, api: true do
)
delete api("/projects/#{project.id}/repository/files", user), valid_params
- response.status.should == 200
- json_response['file_path'].should == file_path
+ expect(response.status).to eq(200)
+ expect(json_response['file_path']).to eq(file_path)
end
it "should return a 400 bad request if no params given" do
delete api("/projects/#{project.id}/repository/files", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 400 if satellite fails to create file" do
@@ -138,7 +158,7 @@ describe API::API, api: true do
)
delete api("/projects/#{project.id}/repository/files", user), valid_params
- response.status.should == 400
+ expect(response.status).to eq(400)
end
end
end
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index cbbd1e7de5..fb3ff552c8 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -23,50 +23,50 @@ describe API::API, api: true do
context 'when authenticated' do
it 'should fork if user has sufficient access to project' do
post api("/projects/fork/#{project.id}", user2)
- response.status.should == 201
- json_response['name'].should == project.name
- json_response['path'].should == project.path
- json_response['owner']['id'].should == user2.id
- json_response['namespace']['id'].should == user2.namespace.id
- json_response['forked_from_project']['id'].should == project.id
+ expect(response.status).to eq(201)
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['path']).to eq(project.path)
+ expect(json_response['owner']['id']).to eq(user2.id)
+ expect(json_response['namespace']['id']).to eq(user2.namespace.id)
+ expect(json_response['forked_from_project']['id']).to eq(project.id)
end
it 'should fork if user is admin' do
post api("/projects/fork/#{project.id}", admin)
- response.status.should == 201
- json_response['name'].should == project.name
- json_response['path'].should == project.path
- json_response['owner']['id'].should == admin.id
- json_response['namespace']['id'].should == admin.namespace.id
- json_response['forked_from_project']['id'].should == project.id
+ expect(response.status).to eq(201)
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['path']).to eq(project.path)
+ expect(json_response['owner']['id']).to eq(admin.id)
+ expect(json_response['namespace']['id']).to eq(admin.namespace.id)
+ expect(json_response['forked_from_project']['id']).to eq(project.id)
end
it 'should fail on missing project access for the project to fork' do
post api("/projects/fork/#{project.id}", user3)
- response.status.should == 404
- json_response['message'].should == '404 Not Found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
end
it 'should fail if forked project exists in the user namespace' do
post api("/projects/fork/#{project.id}", user)
- response.status.should == 409
- json_response['message']['base'].should == ['Invalid fork destination']
- json_response['message']['name'].should == ['has already been taken']
- json_response['message']['path'].should == ['has already been taken']
+ expect(response.status).to eq(409)
+ expect(json_response['message']['base']).to eq(['Invalid fork destination'])
+ expect(json_response['message']['name']).to eq(['has already been taken'])
+ expect(json_response['message']['path']).to eq(['has already been taken'])
end
it 'should fail if project to fork from does not exist' do
post api('/projects/fork/424242', user)
- response.status.should == 404
- json_response['message'].should == '404 Not Found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
end
end
context 'when unauthenticated' do
it 'should return authentication error' do
post api("/projects/fork/#{project.id}")
- response.status.should == 401
- json_response['message'].should == '401 Unauthorized'
+ expect(response.status).to eq(401)
+ expect(json_response['message']).to eq('401 Unauthorized')
end
end
end
diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb
new file mode 100644
index 0000000000..8ba6876a95
--- /dev/null
+++ b/spec/requests/api/group_members_spec.rb
@@ -0,0 +1,199 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let(:owner) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:master) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:stranger) { create(:user) }
+
+ let!(:group_with_members) do
+ group = create(:group)
+ group.add_users([reporter.id], GroupMember::REPORTER)
+ group.add_users([developer.id], GroupMember::DEVELOPER)
+ group.add_users([master.id], GroupMember::MASTER)
+ group.add_users([guest.id], GroupMember::GUEST)
+ group
+ end
+
+ let!(:group_no_members) { create(:group) }
+
+ before do
+ group_with_members.add_owner owner
+ group_no_members.add_owner owner
+ end
+
+ describe "GET /groups/:id/members" do
+ context "when authenticated as user that is part or the group" do
+ it "each user: should return an array of members groups of group3" do
+ [owner, master, developer, reporter, guest].each do |user|
+ get api("/groups/#{group_with_members.id}/members", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(5)
+ expect(json_response.find { |e| e['id']==owner.id }['access_level']).to eq(GroupMember::OWNER)
+ expect(json_response.find { |e| e['id']==reporter.id }['access_level']).to eq(GroupMember::REPORTER)
+ expect(json_response.find { |e| e['id']==developer.id }['access_level']).to eq(GroupMember::DEVELOPER)
+ expect(json_response.find { |e| e['id']==master.id }['access_level']).to eq(GroupMember::MASTER)
+ expect(json_response.find { |e| e['id']==guest.id }['access_level']).to eq(GroupMember::GUEST)
+ end
+ end
+
+ it "users not part of the group should get access error" do
+ get api("/groups/#{group_with_members.id}/members", stranger)
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
+ describe "POST /groups/:id/members" do
+ context "when not a member of the group" do
+ it "should not add guest as member of group_no_members when adding being done by person outside the group" do
+ post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context "when a member of the group" do
+ it "should return ok and add new member" do
+ new_user = create(:user)
+
+ expect {
+ post api("/groups/#{group_no_members.id}/members", owner),
+ user_id: new_user.id, access_level: GroupMember::MASTER
+ }.to change { group_no_members.members.count }.by(1)
+
+ expect(response.status).to eq(201)
+ expect(json_response['name']).to eq(new_user.name)
+ expect(json_response['access_level']).to eq(GroupMember::MASTER)
+ end
+
+ it "should not allow guest to modify group members" do
+ new_user = create(:user)
+
+ expect {
+ post api("/groups/#{group_with_members.id}/members", guest),
+ user_id: new_user.id, access_level: GroupMember::MASTER
+ }.not_to change { group_with_members.members.count }
+
+ expect(response.status).to eq(403)
+ end
+
+ it "should return error if member already exists" do
+ post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER
+ expect(response.status).to eq(409)
+ end
+
+ it "should return a 400 error when user id is not given" do
+ post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER
+ expect(response.status).to eq(400)
+ end
+
+ it "should return a 400 error when access level is not given" do
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
+ expect(response.status).to eq(400)
+ end
+
+ it "should return a 422 error when access level is not known" do
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
+ expect(response.status).to eq(422)
+ end
+ end
+ end
+
+ describe 'PUT /groups/:id/members/:user_id' do
+ context 'when not a member of the group' do
+ it 'should return a 409 error if the user is not a group member' do
+ put(
+ api("/groups/#{group_no_members.id}/members/#{developer.id}",
+ owner), access_level: GroupMember::MASTER
+ )
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when a member of the group' do
+ it 'should return ok and update member access level' do
+ put(
+ api("/groups/#{group_with_members.id}/members/#{reporter.id}",
+ owner),
+ access_level: GroupMember::MASTER
+ )
+
+ expect(response.status).to eq(200)
+
+ get api("/groups/#{group_with_members.id}/members", owner)
+ json_reporter = json_response.find do |e|
+ e['id'] == reporter.id
+ end
+
+ expect(json_reporter['access_level']).to eq(GroupMember::MASTER)
+ end
+
+ it 'should not allow guest to modify group members' do
+ put(
+ api("/groups/#{group_with_members.id}/members/#{developer.id}",
+ guest),
+ access_level: GroupMember::MASTER
+ )
+
+ expect(response.status).to eq(403)
+
+ get api("/groups/#{group_with_members.id}/members", owner)
+ json_developer = json_response.find do |e|
+ e['id'] == developer.id
+ end
+
+ expect(json_developer['access_level']).to eq(GroupMember::DEVELOPER)
+ end
+
+ it 'should return a 400 error when access level is not given' do
+ put(
+ api("/groups/#{group_with_members.id}/members/#{master.id}", owner)
+ )
+ expect(response.status).to eq(400)
+ end
+
+ it 'should return a 422 error when access level is not known' do
+ put(
+ api("/groups/#{group_with_members.id}/members/#{master.id}", owner),
+ access_level: 1234
+ )
+ expect(response.status).to eq(422)
+ end
+ end
+ end
+
+ describe "DELETE /groups/:id/members/:user_id" do
+ context "when not a member of the group" do
+ it "should not delete guest's membership of group_with_members" do
+ random_user = create(:user)
+ delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context "when a member of the group" do
+ it "should delete guest's membership of group" do
+ expect {
+ delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
+ }.to change { group_with_members.members.count }.by(-1)
+
+ expect(response.status).to eq(200)
+ end
+
+ it "should return a 404 error when user id is not known" do
+ delete api("/groups/#{group_with_members.id}/members/1328", owner)
+ expect(response.status).to eq(404)
+ end
+
+ it "should not allow guest to modify group members" do
+ delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest)
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 42ccad71aa..d963dbac9f 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -18,26 +18,26 @@ describe API::API, api: true do
context "when unauthenticated" do
it "should return authentication error" do
get api("/groups")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
context "when authenticated as user" do
it "normal user: should return an array of groups of user1" do
get api("/groups", user1)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['name'].should == group1.name
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['name']).to eq(group1.name)
end
end
context "when authenticated as admin" do
it "admin: should return an array of all groups" do
get api("/groups", admin)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 2
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
end
end
end
@@ -46,31 +46,49 @@ describe API::API, api: true do
context "when authenticated as user" do
it "should return one of user1's groups" do
get api("/groups/#{group1.id}", user1)
- response.status.should == 200
+ expect(response.status).to eq(200)
json_response['name'] == group1.name
end
it "should not return a non existing group" do
get api("/groups/1328", user1)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it "should not return a group not attached to user1" do
get api("/groups/#{group2.id}", user1)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
end
context "when authenticated as admin" do
it "should return any existing group" do
get api("/groups/#{group2.id}", admin)
- response.status.should == 200
+ expect(response.status).to eq(200)
json_response['name'] == group2.name
end
it "should not return a non existing group" do
get api("/groups/1328", admin)
- response.status.should == 404
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when using group path in URL' do
+ it 'should return any existing group' do
+ get api("/groups/#{group1.path}", admin)
+ expect(response.status).to eq(200)
+ json_response['name'] == group2.name
+ end
+
+ it 'should not return a non existing group' do
+ get api('/groups/unknown', admin)
+ expect(response.status).to eq(404)
+ end
+
+ it 'should not return a group not attached to user1' do
+ get api("/groups/#{group2.path}", user1)
+ expect(response.status).to eq(403)
end
end
end
@@ -79,29 +97,30 @@ describe API::API, api: true do
context "when authenticated as user" do
it "should not create group" do
post api("/groups", user1), attributes_for(:group)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
end
context "when authenticated as admin" do
it "should create group" do
post api("/groups", admin), attributes_for(:group)
- response.status.should == 201
+ expect(response.status).to eq(201)
end
it "should not create group, duplicate" do
post api("/groups", admin), {name: "Duplicate Test", path: group2.path}
- response.status.should == 404
+ expect(response.status).to eq(400)
+ expect(response.message).to eq("Bad Request")
end
it "should return 400 bad request error if name not given" do
post api("/groups", admin), {path: group2.path}
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return 400 bad request error if path not given" do
post api("/groups", admin), { name: 'test' }
- response.status.should == 400
+ expect(response.status).to eq(400)
end
end
end
@@ -110,36 +129,36 @@ describe API::API, api: true do
context "when authenticated as user" do
it "should remove group" do
delete api("/groups/#{group1.id}", user1)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should not remove a group if not an owner" do
user3 = create(:user)
group1.add_user(user3, Gitlab::Access::MASTER)
delete api("/groups/#{group1.id}", user3)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
it "should not remove a non existing group" do
delete api("/groups/1328", user1)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it "should not remove a group not attached to user1" do
delete api("/groups/#{group2.id}", user1)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
end
context "when authenticated as admin" do
it "should remove any existing group" do
delete api("/groups/#{group2.id}", admin)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should not remove a non existing group" do
delete api("/groups/1328", admin)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
end
@@ -148,130 +167,20 @@ describe API::API, api: true do
let(:project) { create(:project) }
before(:each) do
Projects::TransferService.any_instance.stub(execute: true)
- Project.stub(:find).and_return(project)
+ allow(Project).to receive(:find).and_return(project)
end
context "when authenticated as user" do
it "should not transfer project to group" do
post api("/groups/#{group1.id}/projects/#{project.id}", user2)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
end
context "when authenticated as admin" do
it "should transfer project to group" do
post api("/groups/#{group1.id}/projects/#{project.id}", admin)
- response.status.should == 201
- end
- end
- end
-
- describe "members" do
- let(:owner) { create(:user) }
- let(:reporter) { create(:user) }
- let(:developer) { create(:user) }
- let(:master) { create(:user) }
- let(:guest) { create(:user) }
- let!(:group_with_members) do
- group = create(:group)
- group.add_users([reporter.id], GroupMember::REPORTER)
- group.add_users([developer.id], GroupMember::DEVELOPER)
- group.add_users([master.id], GroupMember::MASTER)
- group.add_users([guest.id], GroupMember::GUEST)
- group
- end
- let!(:group_no_members) { create(:group) }
-
- before do
- group_with_members.add_owner owner
- group_no_members.add_owner owner
- end
-
- describe "GET /groups/:id/members" do
- context "when authenticated as user that is part or the group" do
- it "each user: should return an array of members groups of group3" do
- [owner, master, developer, reporter, guest].each do |user|
- get api("/groups/#{group_with_members.id}/members", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.size.should == 5
- json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER
- json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER
- json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER
- json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER
- json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST
- end
- end
-
- it "users not part of the group should get access error" do
- get api("/groups/#{group_with_members.id}/members", user1)
- response.status.should == 403
- end
- end
- end
-
- describe "POST /groups/:id/members" do
- context "when not a member of the group" do
- it "should not add guest as member of group_no_members when adding being done by person outside the group" do
- post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER
- response.status.should == 403
- end
- end
-
- context "when a member of the group" do
- it "should return ok and add new member" do
- count_before=group_no_members.group_members.count
- new_user = create(:user)
- post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER
- response.status.should == 201
- json_response['name'].should == new_user.name
- json_response['access_level'].should == GroupMember::MASTER
- group_no_members.group_members.count.should == count_before + 1
- end
-
- it "should return error if member already exists" do
- post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER
- response.status.should == 409
- end
-
- it "should return a 400 error when user id is not given" do
- post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER
- response.status.should == 400
- end
-
- it "should return a 400 error when access level is not given" do
- post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
- response.status.should == 400
- end
-
- it "should return a 422 error when access level is not known" do
- post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
- response.status.should == 422
- end
- end
- end
-
- describe "DELETE /groups/:id/members/:user_id" do
- context "when not a member of the group" do
- it "should not delete guest's membership of group_with_members" do
- random_user = create(:user)
- delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
- response.status.should == 403
- end
- end
-
- context "when a member of the group" do
- it "should delete guest's membership of group" do
- count_before=group_with_members.group_members.count
- delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
- response.status.should == 200
- group_with_members.group_members.count.should == count_before - 1
- end
-
- it "should return a 404 error when user id is not known" do
- delete api("/groups/#{group_with_members.id}/members/1328", owner)
- response.status.should == 404
- end
+ expect(response.status).to eq(201)
end
end
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 6df5ef3896..4c7d15d659 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -5,27 +5,50 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:key) { create(:key, user: user) }
let(:project) { create(:project) }
+ let(:secret_token) { File.read Rails.root.join('.gitlab_shell_secret') }
describe "GET /internal/check", no_db: true do
it do
- get api("/internal/check")
+ get api("/internal/check"), secret_token: secret_token
- response.status.should == 200
- json_response['api_version'].should == API::API.version
+ expect(response.status).to eq(200)
+ expect(json_response['api_version']).to eq(API::API.version)
+ end
+ end
+
+ describe "GET /internal/broadcast_message" do
+ context "broadcast message exists" do
+ let!(:broadcast_message) { create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow ) }
+
+ it do
+ get api("/internal/broadcast_message"), secret_token: secret_token
+
+ expect(response.status).to eq(200)
+ expect(json_response["message"]).to eq(broadcast_message.message)
+ end
+ end
+
+ context "broadcast message doesn't exist" do
+ it do
+ get api("/internal/broadcast_message"), secret_token: secret_token
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_empty
+ end
end
end
describe "GET /internal/discover" do
it do
- get(api("/internal/discover"), key_id: key.id)
+ get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response['name'].should == user.name
+ expect(json_response['name']).to eq(user.name)
end
end
- describe "GET /internal/allowed" do
+ describe "POST /internal/allowed" do
context "access granted" do
before do
project.team << [user, :developer]
@@ -35,8 +58,8 @@ describe API::API, api: true do
it do
pull(key, project)
- response.status.should == 200
- response.body.should == 'true'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_truthy
end
end
@@ -44,8 +67,8 @@ describe API::API, api: true do
it do
push(key, project)
- response.status.should == 200
- response.body.should == 'true'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_truthy
end
end
end
@@ -59,8 +82,8 @@ describe API::API, api: true do
it do
pull(key, project)
- response.status.should == 200
- response.body.should == 'false'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_falsey
end
end
@@ -68,8 +91,8 @@ describe API::API, api: true do
it do
push(key, project)
- response.status.should == 200
- response.body.should == 'false'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_falsey
end
end
end
@@ -85,8 +108,8 @@ describe API::API, api: true do
it do
pull(key, personal_project)
- response.status.should == 200
- response.body.should == 'false'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_falsey
end
end
@@ -94,8 +117,8 @@ describe API::API, api: true do
it do
push(key, personal_project)
- response.status.should == 200
- response.body.should == 'false'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_falsey
end
end
end
@@ -112,8 +135,8 @@ describe API::API, api: true do
it do
pull(key, project)
- response.status.should == 200
- response.body.should == 'true'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_truthy
end
end
@@ -121,8 +144,8 @@ describe API::API, api: true do
it do
push(key, project)
- response.status.should == 200
- response.body.should == 'false'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_falsey
end
end
end
@@ -138,8 +161,8 @@ describe API::API, api: true do
it do
archive(key, project)
- response.status.should == 200
- response.body.should == 'true'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_truthy
end
end
@@ -147,11 +170,29 @@ describe API::API, api: true do
it do
archive(key, project)
- response.status.should == 200
- response.body.should == 'false'
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_falsey
end
end
end
+
+ context 'project does not exist' do
+ it do
+ pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists'))
+
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_falsey
+ end
+ end
+
+ context 'user does not exist' do
+ it do
+ pull(OpenStruct.new(id: 0), project)
+
+ expect(response.status).to eq(200)
+ expect(json_response["status"]).to be_falsey
+ end
+ end
end
def pull(key, project)
@@ -159,7 +200,8 @@ describe API::API, api: true do
api("/internal/allowed"),
key_id: key.id,
project: project.path_with_namespace,
- action: 'git-upload-pack'
+ action: 'git-upload-pack',
+ secret_token: secret_token
)
end
@@ -169,7 +211,8 @@ describe API::API, api: true do
changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master',
key_id: key.id,
project: project.path_with_namespace,
- action: 'git-receive-pack'
+ action: 'git-receive-pack',
+ secret_token: secret_token
)
end
@@ -179,7 +222,8 @@ describe API::API, api: true do
ref: 'master',
key_id: key.id,
project: project.path_with_namespace,
- action: 'git-upload-archive'
+ action: 'git-upload-archive',
+ secret_token: secret_token
)
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 775d7b4e18..b6b0427deb 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -34,86 +34,87 @@ describe API::API, api: true do
context "when unauthenticated" do
it "should return authentication error" do
get api("/issues")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
context "when authenticated" do
it "should return an array of issues" do
get api("/issues", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['title'].should == issue.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['title']).to eq(issue.title)
end
it "should add pagination headers" do
get api("/issues?per_page=3", user)
- response.headers['Link'].should ==
+ expect(response.headers['Link']).to eq(
'; rel="first", ; rel="last"'
+ )
end
it 'should return an array of closed issues' do
get api('/issues?state=closed', user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['id'].should == closed_issue.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'should return an array of opened issues' do
get api('/issues?state=opened', user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['id'].should == issue.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(issue.id)
end
it 'should return an array of all issues' do
get api('/issues?state=all', user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 2
- json_response.first['id'].should == issue.id
- json_response.second['id'].should == closed_issue.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['id']).to eq(issue.id)
+ expect(json_response.second['id']).to eq(closed_issue.id)
end
it 'should return an array of labeled issues' do
get api("/issues?labels=#{label.title}", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['labels'].should == [label.title]
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label.title])
end
it 'should return an array of labeled issues when at least one label matches' do
get api("/issues?labels=#{label.title},foo,bar", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['labels'].should == [label.title]
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label.title])
end
it 'should return an empty array if no issue matches labels' do
get api('/issues?labels=foo,bar', user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 0
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
end
it 'should return an array of labeled issues matching given state' do
get api("/issues?labels=#{label.title}&state=opened", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['labels'].should == [label.title]
- json_response.first['state'].should == 'opened'
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label.title])
+ expect(json_response.first['state']).to eq('opened')
end
it 'should return an empty array if no issue matches labels and state filters' do
get api("/issues?labels=#{label.title}&state=closed", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 0
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
end
end
end
@@ -124,78 +125,78 @@ describe API::API, api: true do
it "should return project issues" do
get api("#{base_url}/issues", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['title'].should == issue.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['title']).to eq(issue.title)
end
it 'should return an array of labeled project issues' do
get api("#{base_url}/issues?labels=#{label.title}", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['labels'].should == [label.title]
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label.title])
end
it 'should return an array of labeled project issues when at least one label matches' do
get api("#{base_url}/issues?labels=#{label.title},foo,bar", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['labels'].should == [label.title]
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label.title])
end
it 'should return an empty array if no project issue matches labels' do
get api("#{base_url}/issues?labels=foo,bar", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 0
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
end
it 'should return an empty array if no issue matches milestone' do
get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 0
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
end
it 'should return an empty array if milestone does not exist' do
get api("#{base_url}/issues?milestone=foo", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 0
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
end
it 'should return an array of issues in given milestone' do
get api("#{base_url}/issues?milestone=#{title}", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 2
- json_response.first['id'].should == issue.id
- json_response.second['id'].should == closed_issue.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['id']).to eq(issue.id)
+ expect(json_response.second['id']).to eq(closed_issue.id)
end
it 'should return an array of issues matching state in milestone' do
get api("#{base_url}/issues?milestone=#{milestone.title}"\
'&state=closed', user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['id'].should == closed_issue.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(closed_issue.id)
end
end
describe "GET /projects/:id/issues/:issue_id" do
it "should return a project issue by id" do
get api("/projects/#{project.id}/issues/#{issue.id}", user)
- response.status.should == 200
- json_response['title'].should == issue.title
- json_response['iid'].should == issue.iid
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(issue.title)
+ expect(json_response['iid']).to eq(issue.iid)
end
it "should return 404 if issue id not found" do
get api("/projects/#{project.id}/issues/54321", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
@@ -203,32 +204,32 @@ describe API::API, api: true do
it "should create a new project issue" do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', labels: 'label, label2'
- response.status.should == 201
- json_response['title'].should == 'new issue'
- json_response['description'].should be_nil
- json_response['labels'].should == ['label', 'label2']
+ expect(response.status).to eq(201)
+ expect(json_response['title']).to eq('new issue')
+ expect(json_response['description']).to be_nil
+ expect(json_response['labels']).to eq(['label', 'label2'])
end
it "should return a 400 bad request if title not given" do
post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it 'should return 400 on invalid label names' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue',
labels: 'label, ?'
- response.status.should == 400
- json_response['message']['labels']['?']['title'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
end
it 'should return 400 if title is too long' do
post api("/projects/#{project.id}/issues", user),
title: 'g' * 256
- response.status.should == 400
- json_response['message']['title'].should == [
+ expect(response.status).to eq(400)
+ expect(json_response['message']['title']).to eq([
'is too long (maximum is 255 characters)'
- ]
+ ])
end
end
@@ -236,23 +237,23 @@ describe API::API, api: true do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title'
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response['title'].should == 'updated title'
+ expect(json_response['title']).to eq('updated title')
end
it "should return 404 error if issue id not found" do
put api("/projects/#{project.id}/issues/44444", user),
title: 'updated title'
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it 'should return 400 on invalid label names' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title',
labels: 'label, ?'
- response.status.should == 400
- json_response['message']['labels']['?']['title'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
end
end
@@ -263,49 +264,49 @@ describe API::API, api: true do
it 'should not update labels if not present' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title'
- response.status.should == 200
- json_response['labels'].should == [label.title]
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to eq([label.title])
end
it 'should remove all labels' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: ''
- response.status.should == 200
- json_response['labels'].should == []
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to eq([])
end
it 'should update labels' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: 'foo,bar'
- response.status.should == 200
- json_response['labels'].should include 'foo'
- json_response['labels'].should include 'bar'
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to include 'foo'
+ expect(json_response['labels']).to include 'bar'
end
it 'should return 400 on invalid label names' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: 'label, ?'
- response.status.should == 400
- json_response['message']['labels']['?']['title'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
end
it 'should allow special label names' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: 'label:foo, label-bar,label_bar,label/bar'
- response.status.should == 200
- json_response['labels'].should include 'label:foo'
- json_response['labels'].should include 'label-bar'
- json_response['labels'].should include 'label_bar'
- json_response['labels'].should include 'label/bar'
+ expect(response.status).to eq(200)
+ expect(json_response['labels']).to include 'label:foo'
+ expect(json_response['labels']).to include 'label-bar'
+ expect(json_response['labels']).to include 'label_bar'
+ expect(json_response['labels']).to include 'label/bar'
end
it 'should return 400 if title is too long' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'g' * 256
- response.status.should == 400
- json_response['message']['title'].should == [
+ expect(response.status).to eq(400)
+ expect(json_response['message']['title']).to eq([
'is too long (maximum is 255 characters)'
- ]
+ ])
end
end
@@ -313,17 +314,17 @@ describe API::API, api: true do
it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: 'label2', state_event: "close"
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response['labels'].should include 'label2'
- json_response['state'].should eq "closed"
+ expect(json_response['labels']).to include 'label2'
+ expect(json_response['state']).to eq "closed"
end
end
describe "DELETE /projects/:id/issues/:issue_id" do
it "should delete a project issue" do
delete api("/projects/#{project.id}/issues/#{issue.id}", user)
- response.status.should == 405
+ expect(response.status).to eq(405)
end
end
end
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index dbddc8a7da..aff109a942 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -15,10 +15,10 @@ describe API::API, api: true do
describe 'GET /projects/:id/labels' do
it 'should return project labels' do
get api("/projects/#{project.id}/labels", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.size.should == 1
- json_response.first['name'].should == label1.name
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['name']).to eq(label1.name)
end
end
@@ -27,69 +27,69 @@ describe API::API, api: true do
post api("/projects/#{project.id}/labels", user),
name: 'Foo',
color: '#FFAABB'
- response.status.should == 201
- json_response['name'].should == 'Foo'
- json_response['color'].should == '#FFAABB'
+ expect(response.status).to eq(201)
+ expect(json_response['name']).to eq('Foo')
+ expect(json_response['color']).to eq('#FFAABB')
end
it 'should return a 400 bad request if name not given' do
post api("/projects/#{project.id}/labels", user), color: '#FFAABB'
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it 'should return a 400 bad request if color not given' do
post api("/projects/#{project.id}/labels", user), name: 'Foobar'
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it 'should return 400 for invalid color' do
post api("/projects/#{project.id}/labels", user),
name: 'Foo',
color: '#FFAA'
- response.status.should == 400
- json_response['message']['color'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['color']).to eq(['is invalid'])
end
it 'should return 400 for too long color code' do
post api("/projects/#{project.id}/labels", user),
name: 'Foo',
color: '#FFAAFFFF'
- response.status.should == 400
- json_response['message']['color'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['color']).to eq(['is invalid'])
end
it 'should return 400 for invalid name' do
post api("/projects/#{project.id}/labels", user),
name: '?',
color: '#FFAABB'
- response.status.should == 400
- json_response['message']['title'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['title']).to eq(['is invalid'])
end
it 'should return 409 if label already exists' do
post api("/projects/#{project.id}/labels", user),
name: 'label1',
color: '#FFAABB'
- response.status.should == 409
- json_response['message'].should == 'Label already exists'
+ expect(response.status).to eq(409)
+ expect(json_response['message']).to eq('Label already exists')
end
end
describe 'DELETE /projects/:id/labels' do
it 'should return 200 for existing label' do
delete api("/projects/#{project.id}/labels", user), name: 'label1'
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it 'should return 404 for non existing label' do
delete api("/projects/#{project.id}/labels", user), name: 'label2'
- response.status.should == 404
- json_response['message'].should == '404 Label Not Found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Label Not Found')
end
it 'should return 400 for wrong parameters' do
delete api("/projects/#{project.id}/labels", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
end
@@ -99,47 +99,47 @@ describe API::API, api: true do
name: 'label1',
new_name: 'New Label',
color: '#FFFFFF'
- response.status.should == 200
- json_response['name'].should == 'New Label'
- json_response['color'].should == '#FFFFFF'
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq('New Label')
+ expect(json_response['color']).to eq('#FFFFFF')
end
it 'should return 200 if name is changed' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
new_name: 'New Label'
- response.status.should == 200
- json_response['name'].should == 'New Label'
- json_response['color'].should == label1.color
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq('New Label')
+ expect(json_response['color']).to eq(label1.color)
end
it 'should return 200 if colors is changed' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
color: '#FFFFFF'
- response.status.should == 200
- json_response['name'].should == label1.name
- json_response['color'].should == '#FFFFFF'
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['color']).to eq('#FFFFFF')
end
it 'should return 404 if label does not exist' do
put api("/projects/#{project.id}/labels", user),
name: 'label2',
new_name: 'label3'
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it 'should return 400 if no label name given' do
put api("/projects/#{project.id}/labels", user), new_name: 'label2'
- response.status.should == 400
- json_response['message'].should == '400 (Bad request) "name" not given'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('400 (Bad request) "name" not given')
end
it 'should return 400 if no new parameters given' do
put api("/projects/#{project.id}/labels", user), name: 'label1'
- response.status.should == 400
- json_response['message'].should == 'Required parameters '\
- '"new_name" or "color" missing'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('Required parameters '\
+ '"new_name" or "color" missing')
end
it 'should return 400 for invalid name' do
@@ -147,24 +147,24 @@ describe API::API, api: true do
name: 'label1',
new_name: '?',
color: '#FFFFFF'
- response.status.should == 400
- json_response['message']['title'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['title']).to eq(['is invalid'])
end
it 'should return 400 for invalid name' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
color: '#FF'
- response.status.should == 400
- json_response['message']['color'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['color']).to eq(['is invalid'])
end
it 'should return 400 for too long color code' do
post api("/projects/#{project.id}/labels", user),
name: 'Foo',
color: '#FFAAFFFF'
- response.status.should == 400
- json_response['message']['color'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['color']).to eq(['is invalid'])
end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 5ba3a33099..9e252441a4 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -16,46 +16,50 @@ describe API::API, api: true do
context "when unauthenticated" do
it "should return authentication error" do
get api("/projects/#{project.id}/merge_requests")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
context "when authenticated" do
it "should return an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 3
- json_response.first['title'].should == merge_request.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.last['title']).to eq(merge_request.title)
end
+
it "should return an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests?state", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 3
- json_response.first['title'].should == merge_request.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.last['title']).to eq(merge_request.title)
end
+
it "should return an array of open merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=opened", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['title'].should == merge_request.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.last['title']).to eq(merge_request.title)
end
+
it "should return an array of closed merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=closed", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 2
- json_response.first['title'].should == merge_request_closed.title
- json_response.second['title'].should == merge_request_merged.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.second['title']).to eq(merge_request_closed.title)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
end
+
it "should return an array of merged merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=merged", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['title'].should == merge_request_merged.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
end
context "with ordering" do
@@ -66,35 +70,38 @@ describe API::API, api: true do
it "should return an array of merge_requests in ascending order" do
get api("/projects/#{project.id}/merge_requests?sort=asc", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 3
- json_response.first['id'].should == @mr_earlier.id
- json_response.last['id'].should == @mr_later.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.last['id']).to eq(@mr_earlier.id)
+ expect(json_response.first['id']).to eq(@mr_later.id)
end
+
it "should return an array of merge_requests in descending order" do
get api("/projects/#{project.id}/merge_requests?sort=desc", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 3
- json_response.first['id'].should == @mr_later.id
- json_response.last['id'].should == @mr_earlier.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.first['id']).to eq(@mr_later.id)
+ expect(json_response.last['id']).to eq(@mr_earlier.id)
end
+
it "should return an array of merge_requests ordered by updated_at" do
get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 3
- json_response.first['id'].should == @mr_earlier.id
- json_response.last['id'].should == @mr_later.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.last['id']).to eq(@mr_earlier.id)
+ expect(json_response.first['id']).to eq(@mr_later.id)
end
+
it "should return an array of merge_requests ordered by created_at" do
get api("/projects/#{project.id}/merge_requests?sort=created_at", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 3
- json_response.first['id'].should == @mr_earlier.id
- json_response.last['id'].should == @mr_later.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.last['id']).to eq(@mr_earlier.id)
+ expect(json_response.first['id']).to eq(@mr_later.id)
end
end
end
@@ -103,14 +110,27 @@ describe API::API, api: true do
describe "GET /projects/:id/merge_request/:merge_request_id" do
it "should return merge_request" do
get api("/projects/#{project.id}/merge_request/#{merge_request.id}", user)
- response.status.should == 200
- json_response['title'].should == merge_request.title
- json_response['iid'].should == merge_request.iid
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(merge_request.title)
+ expect(json_response['iid']).to eq(merge_request.iid)
end
it "should return a 404 error if merge_request_id not found" do
get api("/projects/#{project.id}/merge_request/999", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
+ end
+ end
+
+ describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do
+ it 'should return the change information of the merge_request' do
+ get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user)
+ expect(response.status).to eq 200
+ expect(json_response['changes'].size).to eq(merge_request.diffs.size)
+ end
+
+ it 'returns a 404 when merge_request_id not found' do
+ get api("/projects/#{project.id}/merge_request/999/changes", user)
+ expect(response.status).to eq(404)
end
end
@@ -123,33 +143,33 @@ describe API::API, api: true do
target_branch: 'master',
author: user,
labels: 'label, label2'
- response.status.should == 201
- json_response['title'].should == 'Test merge_request'
- json_response['labels'].should == ['label', 'label2']
+ expect(response.status).to eq(201)
+ expect(json_response['title']).to eq('Test merge_request')
+ expect(json_response['labels']).to eq(['label', 'label2'])
end
it "should return 422 when source_branch equals target_branch" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
- response.status.should == 422
+ expect(response.status).to eq(422)
end
it "should return 400 when source_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", target_branch: "master", author: user
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return 400 when target_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "stable", author: user
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return 400 when title is missing" do
post api("/projects/#{project.id}/merge_requests", user),
target_branch: 'master', source_branch: 'stable'
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it 'should return 400 on invalid label names' do
@@ -159,9 +179,10 @@ describe API::API, api: true do
target_branch: 'master',
author: user,
labels: 'label, ?'
- response.status.should == 400
- json_response['message']['labels']['?']['title'].should ==
+ expect(response.status).to eq(400)
+ expect(json_response['message']['labels']['?']['title']).to eq(
['is invalid']
+ )
end
context 'with existing MR' do
@@ -182,7 +203,7 @@ describe API::API, api: true do
target_branch: 'master',
author: user
end.to change { MergeRequest.count }.by(0)
- response.status.should == 409
+ expect(response.status).to eq(409)
end
end
end
@@ -199,37 +220,37 @@ describe API::API, api: true do
it "should return merge_request" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
- response.status.should == 201
- json_response['title'].should == 'Test merge_request'
- json_response['description'].should == 'Test description for Test merge_request'
+ expect(response.status).to eq(201)
+ expect(json_response['title']).to eq('Test merge_request')
+ expect(json_response['description']).to eq('Test description for Test merge_request')
end
it "should not return 422 when source_branch equals target_branch" do
- project.id.should_not == fork_project.id
- fork_project.forked?.should be_true
- fork_project.forked_from_project.should == project
+ expect(project.id).not_to eq(fork_project.id)
+ expect(fork_project.forked?).to be_truthy
+ expect(fork_project.forked_from_project).to eq(project)
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
- response.status.should == 201
- json_response['title'].should == 'Test merge_request'
+ expect(response.status).to eq(201)
+ expect(json_response['title']).to eq('Test merge_request')
end
it "should return 400 when source_branch is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return 400 when target_branch is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return 400 when title is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id
- response.status.should == 400
+ expect(response.status).to eq(400)
end
context 'when target_branch is specified' do
@@ -240,7 +261,7 @@ describe API::API, api: true do
source_branch: 'stable',
author: user,
target_project_id: fork_project.id
- response.status.should == 422
+ expect(response.status).to eq(422)
end
it 'should return 422 if targeting a different fork' do
@@ -250,14 +271,14 @@ describe API::API, api: true do
source_branch: 'stable',
author: user2,
target_project_id: unrelated_project.id
- response.status.should == 422
+ expect(response.status).to eq(422)
end
end
it "should return 201 when target_branch is specified and for the same project" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id
- response.status.should == 201
+ expect(response.status).to eq(201)
end
end
end
@@ -265,8 +286,8 @@ describe API::API, api: true do
describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close"
- response.status.should == 200
- json_response['state'].should == 'closed'
+ expect(response.status).to eq(200)
+ expect(json_response['state']).to eq('closed')
end
end
@@ -274,55 +295,55 @@ describe API::API, api: true do
it "should return merge_request in case of success" do
MergeRequest.any_instance.stub(can_be_merged?: true, automerge!: true)
put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should return 405 if branch can't be merged" do
MergeRequest.any_instance.stub(can_be_merged?: false)
put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
- response.status.should == 405
- json_response['message'].should == 'Branch cannot be merged'
+ expect(response.status).to eq(405)
+ expect(json_response['message']).to eq('Branch cannot be merged')
end
it "should return 405 if merge_request is not open" do
merge_request.close
put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
- response.status.should == 405
- json_response['message'].should == '405 Method Not Allowed'
+ expect(response.status).to eq(405)
+ expect(json_response['message']).to eq('405 Method Not Allowed')
end
it "should return 401 if user has no permissions to merge" do
user2 = create(:user)
project.team << [user2, :reporter]
put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user2)
- response.status.should == 401
- json_response['message'].should == '401 Unauthorized'
+ expect(response.status).to eq(401)
+ expect(json_response['message']).to eq('401 Unauthorized')
end
end
describe "PUT /projects/:id/merge_request/:merge_request_id" do
it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title"
- response.status.should == 200
- json_response['title'].should == 'New title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('New title')
end
it "should return merge_request" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), description: "New description"
- response.status.should == 200
- json_response['description'].should == 'New description'
+ expect(response.status).to eq(200)
+ expect(json_response['description']).to eq('New description')
end
it "should return 422 when source_branch and target_branch are renamed the same" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
- response.status.should == 422
+ expect(response.status).to eq(422)
end
it "should return merge_request with renamed target_branch" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki"
- response.status.should == 200
- json_response['target_branch'].should == 'wiki'
+ expect(response.status).to eq(200)
+ expect(json_response['target_branch']).to eq('wiki')
end
it 'should return 400 on invalid label names' do
@@ -330,43 +351,43 @@ describe API::API, api: true do
user),
title: 'new issue',
labels: 'label, ?'
- response.status.should == 400
- json_response['message']['labels']['?']['title'].should == ['is invalid']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
end
end
describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
it "should return comment" do
post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
- response.status.should == 201
- json_response['note'].should == 'My comment'
+ expect(response.status).to eq(201)
+ expect(json_response['note']).to eq('My comment')
end
it "should return 400 if note is missing" do
post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return 404 if note is attached to non existent merge request" do
post api("/projects/#{project.id}/merge_request/404/comments", user),
note: 'My comment'
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
describe "GET :id/merge_request/:merge_request_id/comments" do
it "should return merge_request comments" do
get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.length.should == 1
- json_response.first['note'].should == "a comment on a MR"
- json_response.first['author']['id'].should == user.id
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['note']).to eq("a comment on a MR")
+ expect(json_response.first['author']['id']).to eq(user.id)
end
it "should return a 404 error if merge_request_id not found" do
get api("/projects/#{project.id}/merge_request/999/comments", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index f0619a1c80..effb072347 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -8,92 +8,109 @@ describe API::API, api: true do
before { project.team << [user, :developer] }
- describe "GET /projects/:id/milestones" do
- it "should return project milestones" do
+ describe 'GET /projects/:id/milestones' do
+ it 'should return project milestones' do
get api("/projects/#{project.id}/milestones", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['title'].should == milestone.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['title']).to eq(milestone.title)
end
- it "should return a 401 error if user not authenticated" do
+ it 'should return a 401 error if user not authenticated' do
get api("/projects/#{project.id}/milestones")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
- describe "GET /projects/:id/milestones/:milestone_id" do
- it "should return a project milestone by id" do
+ describe 'GET /projects/:id/milestones/:milestone_id' do
+ it 'should return a project milestone by id' do
get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
- response.status.should == 200
- json_response['title'].should == milestone.title
- json_response['iid'].should == milestone.iid
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(milestone.title)
+ expect(json_response['iid']).to eq(milestone.iid)
end
- it "should return 401 error if user not authenticated" do
+ it 'should return 401 error if user not authenticated' do
get api("/projects/#{project.id}/milestones/#{milestone.id}")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
- it "should return a 404 error if milestone id not found" do
+ it 'should return a 404 error if milestone id not found' do
get api("/projects/#{project.id}/milestones/1234", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
- describe "POST /projects/:id/milestones" do
- it "should create a new project milestone" do
+ describe 'POST /projects/:id/milestones' do
+ it 'should create a new project milestone' do
post api("/projects/#{project.id}/milestones", user), title: 'new milestone'
- response.status.should == 201
- json_response['title'].should == 'new milestone'
- json_response['description'].should be_nil
+ expect(response.status).to eq(201)
+ expect(json_response['title']).to eq('new milestone')
+ expect(json_response['description']).to be_nil
end
- it "should create a new project milestone with description and due date" do
+ it 'should create a new project milestone with description and due date' do
post api("/projects/#{project.id}/milestones", user),
title: 'new milestone', description: 'release', due_date: '2013-03-02'
- response.status.should == 201
- json_response['description'].should == 'release'
- json_response['due_date'].should == '2013-03-02'
+ expect(response.status).to eq(201)
+ expect(json_response['description']).to eq('release')
+ expect(json_response['due_date']).to eq('2013-03-02')
end
- it "should return a 400 error if title is missing" do
+ it 'should return a 400 error if title is missing' do
post api("/projects/#{project.id}/milestones", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
end
- describe "PUT /projects/:id/milestones/:milestone_id" do
- it "should update a project milestone" do
+ describe 'PUT /projects/:id/milestones/:milestone_id' do
+ it 'should update a project milestone' do
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
title: 'updated title'
- response.status.should == 200
- json_response['title'].should == 'updated title'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('updated title')
end
- it "should return a 404 error if milestone id not found" do
+ it 'should return a 404 error if milestone id not found' do
put api("/projects/#{project.id}/milestones/1234", user),
title: 'updated title'
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
- describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do
- it "should update a project milestone" do
+ describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do
+ it 'should update a project milestone' do
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
state_event: 'close'
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response['state'].should == 'closed'
+ expect(json_response['state']).to eq('closed')
end
end
- describe "PUT /projects/:id/milestones/:milestone_id to test observer on close" do
- it "should create an activity event when an milestone is closed" do
- Event.should_receive(:create)
+ describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
+ it 'should create an activity event when an milestone is closed' do
+ expect(Event).to receive(:create)
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
state_event: 'close'
end
end
+
+ describe 'GET /projects/:id/milestones/:milestone_id/issues' do
+ before do
+ milestone.issues << create(:issue)
+ end
+ it 'should return project issues for a particular milestone' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['milestone']['title']).to eq(milestone.title)
+ end
+
+ it 'should return a 401 error if user not authenticated' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
+ expect(response.status).to eq(401)
+ end
+ end
end
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index b8943ea076..6ddaaa0a6d 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -10,17 +10,17 @@ describe API::API, api: true do
context "when unauthenticated" do
it "should return authentication error" do
get api("/namespaces")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
context "when authenticated as admin" do
it "admin: should return an array of all namespaces" do
get api("/namespaces", admin)
- response.status.should == 200
- json_response.should be_an Array
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
- json_response.length.should == Namespace.count
+ expect(json_response.length).to eq(Namespace.count)
end
end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 7aa53787ae..8b177af468 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -16,42 +16,42 @@ describe API::API, api: true do
context "when noteable is an Issue" do
it "should return an array of issue notes" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['body'].should == issue_note.note
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['body']).to eq(issue_note.note)
end
it "should return a 404 error when issue id not found" do
get api("/projects/#{project.id}/issues/123/notes", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
context "when noteable is a Snippet" do
it "should return an array of snippet notes" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['body'].should == snippet_note.note
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['body']).to eq(snippet_note.note)
end
it "should return a 404 error when snippet id not found" do
get api("/projects/#{project.id}/snippets/42/notes", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
context "when noteable is a Merge Request" do
it "should return an array of merge_requests notes" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['body'].should == merge_request_note.note
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['body']).to eq(merge_request_note.note)
end
it "should return a 404 error if merge request id not found" do
get api("/projects/#{project.id}/merge_requests/4444/notes", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
end
@@ -60,26 +60,26 @@ describe API::API, api: true do
context "when noteable is an Issue" do
it "should return an issue note by id" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user)
- response.status.should == 200
- json_response['body'].should == issue_note.note
+ expect(response.status).to eq(200)
+ expect(json_response['body']).to eq(issue_note.note)
end
it "should return a 404 error if issue note not found" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
context "when noteable is a Snippet" do
it "should return a snippet note by id" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user)
- response.status.should == 200
- json_response['body'].should == snippet_note.note
+ expect(response.status).to eq(200)
+ expect(json_response['body']).to eq(snippet_note.note)
end
it "should return a 404 error if snippet note not found" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
end
@@ -88,47 +88,101 @@ describe API::API, api: true do
context "when noteable is an Issue" do
it "should create a new issue note" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
- response.status.should == 201
- json_response['body'].should == 'hi!'
- json_response['author']['username'].should == user.username
+ expect(response.status).to eq(201)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['author']['username']).to eq(user.username)
end
it "should return a 400 bad request error if body not given" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 401 unauthorized error if user not authenticated" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!'
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
context "when noteable is a Snippet" do
it "should create a new snippet note" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!'
- response.status.should == 201
- json_response['body'].should == 'hi!'
- json_response['author']['username'].should == user.username
+ expect(response.status).to eq(201)
+ expect(json_response['body']).to eq('hi!')
+ expect(json_response['author']['username']).to eq(user.username)
end
it "should return a 400 bad request error if body not given" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 401 unauthorized error if user not authenticated" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!'
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
end
describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do
it "should create an activity event when an issue note is created" do
- Event.should_receive(:create)
+ expect(Event).to receive(:create)
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
end
end
+
+ describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do
+ context 'when noteable is an Issue' do
+ it 'should return modified note' do
+ put api("/projects/#{project.id}/issues/#{issue.id}/"\
+ "notes/#{issue_note.id}", user), body: 'Hello!'
+ expect(response.status).to eq(200)
+ expect(json_response['body']).to eq('Hello!')
+ end
+
+ it 'should return a 404 error when note id not found' do
+ put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user),
+ body: 'Hello!'
+ expect(response.status).to eq(404)
+ end
+
+ it 'should return a 400 bad request error if body not given' do
+ put api("/projects/#{project.id}/issues/#{issue.id}/"\
+ "notes/#{issue_note.id}", user)
+ expect(response.status).to eq(400)
+ end
+ end
+
+ context 'when noteable is a Snippet' do
+ it 'should return modified note' do
+ put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
+ "notes/#{snippet_note.id}", user), body: 'Hello!'
+ expect(response.status).to eq(200)
+ expect(json_response['body']).to eq('Hello!')
+ end
+
+ it 'should return a 404 error when note id not found' do
+ put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
+ "notes/123", user), body: "Hello!"
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when noteable is a Merge Request' do
+ it 'should return modified note' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
+ "notes/#{merge_request_note.id}", user), body: 'Hello!'
+ expect(response.status).to eq(200)
+ expect(json_response['body']).to eq('Hello!')
+ end
+
+ it 'should return a 404 error when note id not found' do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
+ "notes/123", user), body: "Hello!"
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index cdb5e3d061..81fe68de66 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -16,18 +16,18 @@ describe API::API, 'ProjectHooks', api: true do
context "authorized user" do
it "should return project hooks" do
get api("/projects/#{project.id}/hooks", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response.should be_an Array
- json_response.count.should == 1
- json_response.first['url'].should == "http://example.com"
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['url']).to eq("http://example.com")
end
end
context "unauthorized user" do
it "should not access project hooks" do
get api("/projects/#{project.id}/hooks", user3)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
end
end
@@ -36,26 +36,26 @@ describe API::API, 'ProjectHooks', api: true do
context "authorized user" do
it "should return a project hook" do
get api("/projects/#{project.id}/hooks/#{hook.id}", user)
- response.status.should == 200
- json_response['url'].should == hook.url
+ expect(response.status).to eq(200)
+ expect(json_response['url']).to eq(hook.url)
end
it "should return a 404 error if hook id is not available" do
get api("/projects/#{project.id}/hooks/1234", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
context "unauthorized user" do
it "should not access an existing hook" do
get api("/projects/#{project.id}/hooks/#{hook.id}", user3)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
end
it "should return a 404 error if hook id is not available" do
get api("/projects/#{project.id}/hooks/1234", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
@@ -65,17 +65,17 @@ describe API::API, 'ProjectHooks', api: true do
post api("/projects/#{project.id}/hooks", user),
url: "http://example.com", issues_events: true
}.to change {project.hooks.count}.by(1)
- response.status.should == 201
+ expect(response.status).to eq(201)
end
it "should return a 400 error if url not given" do
post api("/projects/#{project.id}/hooks", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 422 error if url not valid" do
post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com"
- response.status.should == 422
+ expect(response.status).to eq(422)
end
end
@@ -83,23 +83,23 @@ describe API::API, 'ProjectHooks', api: true do
it "should update an existing project hook" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user),
url: 'http://example.org', push_events: false
- response.status.should == 200
- json_response['url'].should == 'http://example.org'
+ expect(response.status).to eq(200)
+ expect(json_response['url']).to eq('http://example.org')
end
it "should return 404 error if hook id not found" do
put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org'
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it "should return 400 error if url is not given" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 422 error if url is not valid" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com'
- response.status.should == 422
+ expect(response.status).to eq(422)
end
end
@@ -108,22 +108,22 @@ describe API::API, 'ProjectHooks', api: true do
expect {
delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
}.to change {project.hooks.count}.by(-1)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should return success when deleting hook" do
delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should return success when deleting non existent hook" do
delete api("/projects/#{project.id}/hooks/42", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should return a 405 error if hook id not given" do
delete api("/projects/#{project.id}/hooks", user)
- response.status.should == 405
+ expect(response.status).to eq(405)
end
end
end
diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb
index 836f21f3e0..8419a364ed 100644
--- a/spec/requests/api/project_members_spec.rb
+++ b/spec/requests/api/project_members_spec.rb
@@ -15,23 +15,23 @@ describe API::API, api: true do
it "should return project team members" do
get api("/projects/#{project.id}/members", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.count.should == 2
- json_response.map { |u| u['username'] }.should include user.username
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(2)
+ expect(json_response.map { |u| u['username'] }).to include user.username
end
it "finds team members with query string" do
get api("/projects/#{project.id}/members", user), query: user.username
- response.status.should == 200
- json_response.should be_an Array
- json_response.count.should == 1
- json_response.first['username'].should == user.username
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['username']).to eq(user.username)
end
it "should return a 404 error if id not found" do
get api("/projects/9999/members", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
@@ -40,14 +40,14 @@ describe API::API, api: true do
it "should return project team member" do
get api("/projects/#{project.id}/members/#{user.id}", user)
- response.status.should == 200
- json_response['username'].should == user.username
- json_response['access_level'].should == ProjectMember::MASTER
+ expect(response.status).to eq(200)
+ expect(json_response['username']).to eq(user.username)
+ expect(json_response['access_level']).to eq(ProjectMember::MASTER)
end
it "should return a 404 error if user id not found" do
get api("/projects/#{project.id}/members/1234", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
@@ -58,9 +58,9 @@ describe API::API, api: true do
access_level: ProjectMember::DEVELOPER
}.to change { ProjectMember.count }.by(1)
- response.status.should == 201
- json_response['username'].should == user2.username
- json_response['access_level'].should == ProjectMember::DEVELOPER
+ expect(response.status).to eq(201)
+ expect(json_response['username']).to eq(user2.username)
+ expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER)
end
it "should return a 201 status if user is already project member" do
@@ -69,26 +69,26 @@ describe API::API, api: true do
expect {
post api("/projects/#{project.id}/members", user), user_id: user2.id,
access_level: ProjectMember::DEVELOPER
- }.not_to change { ProjectMember.count }.by(1)
+ }.not_to change { ProjectMember.count }
- response.status.should == 201
- json_response['username'].should == user2.username
- json_response['access_level'].should == ProjectMember::DEVELOPER
+ expect(response.status).to eq(201)
+ expect(json_response['username']).to eq(user2.username)
+ expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER)
end
it "should return a 400 error when user id is not given" do
post api("/projects/#{project.id}/members", user), access_level: ProjectMember::MASTER
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 400 error when access level is not given" do
post api("/projects/#{project.id}/members", user), user_id: user2.id
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 422 error when access level is not known" do
post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234
- response.status.should == 422
+ expect(response.status).to eq(422)
end
end
@@ -97,24 +97,24 @@ describe API::API, api: true do
it "should update project team member" do
put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: ProjectMember::MASTER
- response.status.should == 200
- json_response['username'].should == user3.username
- json_response['access_level'].should == ProjectMember::MASTER
+ expect(response.status).to eq(200)
+ expect(json_response['username']).to eq(user3.username)
+ expect(json_response['access_level']).to eq(ProjectMember::MASTER)
end
it "should return a 404 error if user_id is not found" do
put api("/projects/#{project.id}/members/1234", user), access_level: ProjectMember::MASTER
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it "should return a 400 error when access level is not given" do
put api("/projects/#{project.id}/members/#{user3.id}", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return a 422 error when access level is not known" do
put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: 123
- response.status.should == 422
+ expect(response.status).to eq(422)
end
end
@@ -132,22 +132,22 @@ describe API::API, api: true do
delete api("/projects/#{project.id}/members/#{user3.id}", user)
expect {
delete api("/projects/#{project.id}/members/#{user3.id}", user)
- }.to_not change { ProjectMember.count }.by(1)
+ }.to_not change { ProjectMember.count }
end
it "should return 200 if team member already removed" do
delete api("/projects/#{project.id}/members/#{user3.id}", user)
delete api("/projects/#{project.id}/members/#{user3.id}", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should return 200 OK when the user was not member" do
expect {
delete api("/projects/#{project.id}/members/1000000", user)
}.to change { ProjectMember.count }.by(0)
- response.status.should == 200
- json_response['message'].should == "Access revoked"
- json_response['id'].should == 1000000
+ expect(response.status).to eq(200)
+ expect(json_response['message']).to eq("Access revoked")
+ expect(json_response['id']).to eq(1000000)
end
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index aa1437c71a..f28dfea3cc 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1,122 +1,149 @@
+# -*- coding: utf-8 -*-
require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
+ include Gitlab::CurrentSettings
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
+ let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
+ let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) }
-
- describe "GET /projects" do
- before { project }
-
- context "when unauthenticated" do
- it "should return authentication error" do
- get api("/projects")
- response.status.should == 401
- end
- end
-
- context "when authenticated" do
- it "should return an array of projects" do
- get api("/projects", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['name'].should == project.name
- json_response.first['owner']['username'].should == user.username
- end
- end
+ let(:user4) { create(:user) }
+ let(:project3) do
+ create(:project,
+ name: 'second_project',
+ path: 'second_project',
+ creator_id: user.id,
+ namespace: user.namespace,
+ merge_requests_enabled: false,
+ issues_enabled: false, wiki_enabled: false,
+ snippets_enabled: false, visibility_level: 0)
+ end
+ let(:project_member3) do
+ create(:project_member,
+ user: user4,
+ project: project3,
+ access_level: ProjectMember::MASTER)
+ end
+ let(:project4) do
+ create(:project,
+ name: 'third_project',
+ path: 'third_project',
+ creator_id: user4.id,
+ namespace: user4.namespace)
end
- describe "GET /projects/all" do
+ describe 'GET /projects' do
before { project }
- context "when unauthenticated" do
- it "should return authentication error" do
- get api("/projects/all")
- response.status.should == 401
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
+ get api('/projects')
+ expect(response.status).to eq(401)
end
end
- context "when authenticated as regular user" do
- it "should return authentication error" do
- get api("/projects/all", user)
- response.status.should == 403
+ context 'when authenticated' do
+ it 'should return an array of projects' do
+ get api('/projects', user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(project.name)
+ expect(json_response.first['owner']['username']).to eq(user.username)
end
- end
- context "when authenticated as admin" do
- it "should return an array of all projects" do
- get api("/projects/all", admin)
- response.status.should == 200
- json_response.should be_an Array
- project_name = project.name
-
- json_response.detect {
- |project| project['name'] == project_name
- }['name'].should == project_name
-
- json_response.detect {
- |project| project['owner']['username'] == user.username
- }['owner']['username'].should == user.username
- end
- end
- end
-
- describe "POST /projects" do
- context "maximum number of projects reached" do
- before do
- (1..user2.projects_limit).each do |project|
- post api("/projects", user2), name: "foo#{project}"
+ context 'and using search' do
+ it 'should return searched project' do
+ get api('/projects', user), { search: project.name }
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
end
end
- it "should not create new project" do
+ context 'and using sorting' do
+ before do
+ project2
+ project3
+ end
+
+ it 'should return the correct order when sorted by id' do
+ get api('/projects', user), { order_by: 'id', sort: 'desc'}
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(project3.id)
+ end
+ end
+ end
+ end
+
+ describe 'GET /projects/all' do
+ before { project }
+
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
+ get api('/projects/all')
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated as regular user' do
+ it 'should return authentication error' do
+ get api('/projects/all', user)
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'when authenticated as admin' do
+ it 'should return an array of all projects' do
+ get api('/projects/all', admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ project_name = project.name
+
+ expect(json_response.detect {
+ |project| project['name'] == project_name
+ }['name']).to eq(project_name)
+
+ expect(json_response.detect {
+ |project| project['owner']['username'] == user.username
+ }['owner']['username']).to eq(user.username)
+ end
+ end
+ end
+
+ describe 'POST /projects' do
+ context 'maximum number of projects reached' do
+ it 'should not create new project and respond with 403' do
+ allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
expect {
- post api("/projects", user2), name: 'foo'
+ post api('/projects', user2), name: 'foo'
}.to change {Project.count}.by(0)
+ expect(response.status).to eq(403)
end
end
- it "should create new project without path" do
- expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1)
+ it 'should create new project without path and return 201' do
+ expect { post api('/projects', user), name: 'foo' }.
+ to change { Project.count }.by(1)
+ expect(response.status).to eq(201)
end
- it "should not create new project without name" do
- expect { post api("/projects", user) }.to_not change {Project.count}
+ it 'should create last project before reaching project limit' do
+ allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1)
+ post api('/projects', user2), name: 'foo'
+ expect(response.status).to eq(201)
end
- it "should return a 400 error if name not given" do
- post api("/projects", user)
- response.status.should == 400
- end
-
- it "should create last project before reaching project limit" do
- (1..user2.projects_limit-1).each { |p| post api("/projects", user2), name: "foo#{p}" }
- post api("/projects", user2), name: "foo"
- response.status.should == 201
- end
-
- it "should respond with 201 on success" do
- post api("/projects", user), name: 'foo'
- response.status.should == 201
- end
-
- it "should respond with 400 if name is not given" do
- post api("/projects", user)
- response.status.should == 400
- end
-
- it "should return a 403 error if project limit reached" do
- (1..user.projects_limit).each do |p|
- post api("/projects", user), name: "foo#{p}"
- end
- post api("/projects", user), name: 'bar'
- response.status.should == 403
+ it 'should not create new project without name and return 400' do
+ expect { post api('/projects', user) }.to_not change { Project.count }
+ expect(response.status).to eq(400)
end
it "should assign attributes to project" do
@@ -128,94 +155,108 @@ describe API::API, api: true do
wiki_enabled: false
})
- post api("/projects", user), project
+ post api('/projects', user), project
project.each_pair do |k,v|
- json_response[k.to_s].should == v
+ expect(json_response[k.to_s]).to eq(v)
end
end
- it "should set a project as public" do
+ it 'should set a project as public' do
project = attributes_for(:project, :public)
- post api("/projects", user), project
- json_response['public'].should be_true
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
+ post api('/projects', user), project
+ expect(json_response['public']).to be_truthy
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it "should set a project as public using :public" do
+ it 'should set a project as public using :public' do
project = attributes_for(:project, { public: true })
- post api("/projects", user), project
- json_response['public'].should be_true
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
+ post api('/projects', user), project
+ expect(json_response['public']).to be_truthy
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it "should set a project as internal" do
+ it 'should set a project as internal' do
project = attributes_for(:project, :internal)
- post api("/projects", user), project
- json_response['public'].should be_false
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
+ post api('/projects', user), project
+ expect(json_response['public']).to be_falsey
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it "should set a project as internal overriding :public" do
+ it 'should set a project as internal overriding :public' do
project = attributes_for(:project, :internal, { public: true })
- post api("/projects", user), project
- json_response['public'].should be_false
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
+ post api('/projects', user), project
+ expect(json_response['public']).to be_falsey
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it "should set a project as private" do
+ it 'should set a project as private' do
project = attributes_for(:project, :private)
- post api("/projects", user), project
- json_response['public'].should be_false
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
+ post api('/projects', user), project
+ expect(json_response['public']).to be_falsey
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
- it "should set a project as private using :public" do
+ it 'should set a project as private using :public' do
project = attributes_for(:project, { public: false })
- post api("/projects", user), project
- json_response['public'].should be_false
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
+ post api('/projects', user), project
+ expect(json_response['public']).to be_falsey
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ context 'when a visibility level is restricted' do
+ before do
+ @project = attributes_for(:project, { public: true })
+ allow_any_instance_of(ApplicationSetting).to(
+ receive(:restricted_visibility_levels).and_return([20])
+ )
+ end
+
+ it 'should not allow a non-admin to use a restricted visibility level' do
+ post api('/projects', user), @project
+ expect(response.status).to eq(400)
+ expect(json_response['message']['visibility_level'].first).to(
+ match('restricted by your GitLab administrator')
+ )
+ end
+
+ it 'should allow an admin to override restricted visibility settings' do
+ post api('/projects', admin), @project
+ expect(json_response['public']).to be_truthy
+ expect(json_response['visibility_level']).to(
+ eq(Gitlab::VisibilityLevel::PUBLIC)
+ )
+ end
end
end
- describe "POST /projects/user/:id" do
+ describe 'POST /projects/user/:id' do
before { project }
before { admin }
- it "should create new project without path" do
+ it 'should create new project without path and return 201' do
expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1)
+ expect(response.status).to eq(201)
end
- it "should not create new project without name" do
- expect { post api("/projects/user/#{user.id}", admin) }.to_not change {Project.count}
- end
+ it 'should respond with 400 on failure and not project' do
+ expect { post api("/projects/user/#{user.id}", admin) }.
+ to_not change { Project.count }
- it "should respond with 201 on success" do
- post api("/projects/user/#{user.id}", admin), name: 'foo'
- response.status.should == 201
- end
-
- it 'should respond with 400 on failure' do
- post api("/projects/user/#{user.id}", admin)
- response.status.should == 400
- json_response['message']['creator'].should == ['can\'t be blank']
- json_response['message']['namespace'].should == ['can\'t be blank']
- json_response['message']['name'].should == [
+ expect(response.status).to eq(400)
+ expect(json_response['message']['name']).to eq([
'can\'t be blank',
'is too short (minimum is 0 characters)',
- 'can contain only letters, digits, \'_\', \'-\' and \'.\' and '\
- 'space. It must start with letter, digit or \'_\'.'
- ]
- json_response['message']['path'].should == [
+ Gitlab::Regex.project_regex_message
+ ])
+ expect(json_response['message']['path']).to eq([
'can\'t be blank',
'is too short (minimum is 0 characters)',
- 'can contain only letters, digits, \'_\', \'-\' and \'.\'. It must '\
- 'start with letter, digit or \'_\', optionally preceeded by \'.\'. '\
- 'It must not end in \'.git\'.'
- ]
+ Gitlab::Regex.send(:default_regex_message)
+ ])
end
- it "should assign attributes to project" do
+ it 'should assign attributes to project' do
project = attributes_for(:project, {
description: Faker::Lorem.sentence,
issues_enabled: false,
@@ -227,226 +268,217 @@ describe API::API, api: true do
project.each_pair do |k,v|
next if k == :path
- json_response[k.to_s].should == v
+ expect(json_response[k.to_s]).to eq(v)
end
end
- it "should set a project as public" do
+ it 'should set a project as public' do
project = attributes_for(:project, :public)
post api("/projects/user/#{user.id}", admin), project
- json_response['public'].should be_true
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
+ expect(json_response['public']).to be_truthy
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it "should set a project as public using :public" do
+ it 'should set a project as public using :public' do
project = attributes_for(:project, { public: true })
post api("/projects/user/#{user.id}", admin), project
- json_response['public'].should be_true
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
+ expect(json_response['public']).to be_truthy
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it "should set a project as internal" do
+ it 'should set a project as internal' do
project = attributes_for(:project, :internal)
post api("/projects/user/#{user.id}", admin), project
- json_response['public'].should be_false
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
+ expect(json_response['public']).to be_falsey
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it "should set a project as internal overriding :public" do
+ it 'should set a project as internal overriding :public' do
project = attributes_for(:project, :internal, { public: true })
post api("/projects/user/#{user.id}", admin), project
- json_response['public'].should be_false
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
+ expect(json_response['public']).to be_falsey
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it "should set a project as private" do
+ it 'should set a project as private' do
project = attributes_for(:project, :private)
post api("/projects/user/#{user.id}", admin), project
- json_response['public'].should be_false
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
+ expect(json_response['public']).to be_falsey
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
- it "should set a project as private using :public" do
+ it 'should set a project as private using :public' do
project = attributes_for(:project, { public: false })
post api("/projects/user/#{user.id}", admin), project
- json_response['public'].should be_false
- json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
+ expect(json_response['public']).to be_falsey
+ expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
- describe "GET /projects/:id" do
+ describe 'GET /projects/:id' do
before { project }
before { project_member }
- it "should return a project by id" do
+ it 'should return a project by id' do
get api("/projects/#{project.id}", user)
- response.status.should == 200
- json_response['name'].should == project.name
- json_response['owner']['username'].should == user.username
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq(project.name)
+ expect(json_response['owner']['username']).to eq(user.username)
end
- it "should return a project by path name" do
+ it 'should return a project by path name' do
get api("/projects/#{project.id}", user)
- response.status.should == 200
- json_response['name'].should == project.name
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq(project.name)
end
- it "should return a 404 error if not found" do
- get api("/projects/42", user)
- response.status.should == 404
- json_response['message'].should == '404 Not Found'
+ it 'should return a 404 error if not found' do
+ get api('/projects/42', user)
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
end
- it "should return a 404 error if user is not a member" do
+ it 'should return a 404 error if user is not a member' do
other_user = create(:user)
get api("/projects/#{project.id}", other_user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
describe 'permissions' do
context 'personal project' do
- before do
+ it 'Sets project access and returns 200' do
project.team << [user, :master]
get api("/projects/#{project.id}", user)
- end
- it { response.status.should == 200 }
- it { json_response['permissions']["project_access"]["access_level"].should == Gitlab::Access::MASTER }
- it { json_response['permissions']["group_access"].should be_nil }
+ expect(response.status).to eq(200)
+ expect(json_response['permissions']['project_access']['access_level']).
+ to eq(Gitlab::Access::MASTER)
+ expect(json_response['permissions']['group_access']).to be_nil
+ end
end
context 'group project' do
- before do
+ it 'should set the owner and return 200' do
project2 = create(:project, group: create(:group))
project2.group.add_owner(user)
get api("/projects/#{project2.id}", user)
- end
- it { response.status.should == 200 }
- it { json_response['permissions']["project_access"].should be_nil }
- it { json_response['permissions']["group_access"]["access_level"].should == Gitlab::Access::OWNER }
+ expect(response.status).to eq(200)
+ expect(json_response['permissions']['project_access']).to be_nil
+ expect(json_response['permissions']['group_access']['access_level']).
+ to eq(Gitlab::Access::OWNER)
+ end
end
end
end
- describe "GET /projects/:id/events" do
- before { project_member }
+ describe 'GET /projects/:id/events' do
+ before { project_member2 }
- it "should return a project events" do
+ it 'should return a project events' do
get api("/projects/#{project.id}/events", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
json_event = json_response.first
- json_event['action_name'].should == 'joined'
- json_event['project_id'].to_i.should == project.id
+ expect(json_event['action_name']).to eq('joined')
+ expect(json_event['project_id'].to_i).to eq(project.id)
+ expect(json_event['author_username']).to eq(user3.username)
end
- it "should return a 404 error if not found" do
- get api("/projects/42/events", user)
- response.status.should == 404
- json_response['message'].should == '404 Not Found'
+ it 'should return a 404 error if not found' do
+ get api('/projects/42/events', user)
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Project Not Found')
end
- it "should return a 404 error if user is not a member" do
+ it 'should return a 404 error if user is not a member' do
other_user = create(:user)
get api("/projects/#{project.id}/events", other_user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
- describe "GET /projects/:id/snippets" do
+ describe 'GET /projects/:id/snippets' do
before { snippet }
- it "should return an array of project snippets" do
+ it 'should return an array of project snippets' do
get api("/projects/#{project.id}/snippets", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['title'].should == snippet.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['title']).to eq(snippet.title)
end
end
- describe "GET /projects/:id/snippets/:snippet_id" do
- it "should return a project snippet" do
+ describe 'GET /projects/:id/snippets/:snippet_id' do
+ it 'should return a project snippet' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
- response.status.should == 200
- json_response['title'].should == snippet.title
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(snippet.title)
end
- it "should return a 404 error if snippet id not found" do
+ it 'should return a 404 error if snippet id not found' do
get api("/projects/#{project.id}/snippets/1234", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
- describe "POST /projects/:id/snippets" do
- it "should create a new project snippet" do
+ describe 'POST /projects/:id/snippets' do
+ it 'should create a new project snippet' do
post api("/projects/#{project.id}/snippets", user),
- title: 'api test', file_name: 'sample.rb', code: 'test'
- response.status.should == 201
- json_response['title'].should == 'api test'
+ title: 'api test', file_name: 'sample.rb', code: 'test',
+ visibility_level: '0'
+ expect(response.status).to eq(201)
+ expect(json_response['title']).to eq('api test')
end
- it "should return a 400 error if title is not given" do
- post api("/projects/#{project.id}/snippets", user),
- file_name: 'sample.rb', code: 'test'
- response.status.should == 400
- end
-
- it "should return a 400 error if file_name not given" do
- post api("/projects/#{project.id}/snippets", user),
- title: 'api test', code: 'test'
- response.status.should == 400
- end
-
- it "should return a 400 error if code not given" do
- post api("/projects/#{project.id}/snippets", user),
- title: 'api test', file_name: 'sample.rb'
- response.status.should == 400
+ it 'should return a 400 error if invalid snippet is given' do
+ post api("/projects/#{project.id}/snippets", user)
+ expect(status).to eq(400)
end
end
- describe "PUT /projects/:id/snippets/:shippet_id" do
- it "should update an existing project snippet" do
+ describe 'PUT /projects/:id/snippets/:shippet_id' do
+ it 'should update an existing project snippet' do
put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
code: 'updated code'
- response.status.should == 200
- json_response['title'].should == 'example'
- snippet.reload.content.should == 'updated code'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('example')
+ expect(snippet.reload.content).to eq('updated code')
end
- it "should update an existing project snippet with new title" do
+ it 'should update an existing project snippet with new title' do
put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
title: 'other api test'
- response.status.should == 200
- json_response['title'].should == 'other api test'
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq('other api test')
end
end
- describe "DELETE /projects/:id/snippets/:snippet_id" do
+ describe 'DELETE /projects/:id/snippets/:snippet_id' do
before { snippet }
- it "should delete existing project snippet" do
+ it 'should delete existing project snippet' do
expect {
delete api("/projects/#{project.id}/snippets/#{snippet.id}", user)
}.to change { Snippet.count }.by(-1)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it 'should return 404 when deleting unknown snippet id' do
delete api("/projects/#{project.id}/snippets/1234", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
- describe "GET /projects/:id/snippets/:snippet_id/raw" do
- it "should get a raw project snippet" do
+ describe 'GET /projects/:id/snippets/:snippet_id/raw' do
+ it 'should get a raw project snippet' do
get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
- it "should return a 404 error if raw project snippet not found" do
+ it 'should return a 404 error if raw project snippet not found' do
get api("/projects/#{project.id}/snippets/5555/raw", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
@@ -454,51 +486,51 @@ describe API::API, api: true do
let(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
let(:deploy_key) { deploy_keys_project.deploy_key }
- describe "GET /projects/:id/keys" do
+ describe 'GET /projects/:id/keys' do
before { deploy_key }
- it "should return array of ssh keys" do
+ it 'should return array of ssh keys' do
get api("/projects/#{project.id}/keys", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['title'].should == deploy_key.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['title']).to eq(deploy_key.title)
end
end
- describe "GET /projects/:id/keys/:key_id" do
- it "should return a single key" do
+ describe 'GET /projects/:id/keys/:key_id' do
+ it 'should return a single key' do
get api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
- response.status.should == 200
- json_response['title'].should == deploy_key.title
+ expect(response.status).to eq(200)
+ expect(json_response['title']).to eq(deploy_key.title)
end
- it "should return 404 Not Found with invalid ID" do
+ it 'should return 404 Not Found with invalid ID' do
get api("/projects/#{project.id}/keys/404", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
- describe "POST /projects/:id/keys" do
- it "should not create an invalid ssh key" do
- post api("/projects/#{project.id}/keys", user), { title: "invalid key" }
- response.status.should == 400
- json_response['message']['key'].should == [
+ describe 'POST /projects/:id/keys' do
+ it 'should not create an invalid ssh key' do
+ post api("/projects/#{project.id}/keys", user), { title: 'invalid key' }
+ expect(response.status).to eq(400)
+ expect(json_response['message']['key']).to eq([
'can\'t be blank',
'is too short (minimum is 0 characters)',
'is invalid'
- ]
+ ])
end
it 'should not create a key without title' do
post api("/projects/#{project.id}/keys", user), key: 'some key'
- response.status.should == 400
- json_response['message']['title'].should == [
+ expect(response.status).to eq(400)
+ expect(json_response['message']['title']).to eq([
'can\'t be blank',
'is too short (minimum is 0 characters)'
- ]
+ ])
end
- it "should create new ssh key" do
+ it 'should create new ssh key' do
key_attrs = attributes_for :key
expect {
post api("/projects/#{project.id}/keys", user), key_attrs
@@ -506,18 +538,18 @@ describe API::API, api: true do
end
end
- describe "DELETE /projects/:id/keys/:key_id" do
+ describe 'DELETE /projects/:id/keys/:key_id' do
before { deploy_key }
- it "should delete existing key" do
+ it 'should delete existing key' do
expect {
delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
}.to change{ project.deploy_keys.count }.by(-1)
end
- it "should return 404 Not Found with invalid ID" do
+ it 'should return 404 Not Found with invalid ID' do
delete api("/projects/#{project.id}/keys/404", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
end
@@ -526,70 +558,70 @@ describe API::API, api: true do
let(:project_fork_target) { create(:project) }
let(:project_fork_source) { create(:project, :public) }
- describe "POST /projects/:id/fork/:forked_from_id" do
+ describe 'POST /projects/:id/fork/:forked_from_id' do
let(:new_project_fork_source) { create(:project, :public) }
it "shouldn't available for non admin users" do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
- it "should allow project to be forked from an existing project" do
- project_fork_target.forked?.should_not be_true
+ it 'should allow project to be forked from an existing project' do
+ expect(project_fork_target.forked?).not_to be_truthy
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
- response.status.should == 201
+ expect(response.status).to eq(201)
project_fork_target.reload
- project_fork_target.forked_from_project.id.should == project_fork_source.id
- project_fork_target.forked_project_link.should_not be_nil
- project_fork_target.forked?.should be_true
+ expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
+ expect(project_fork_target.forked_project_link).not_to be_nil
+ expect(project_fork_target.forked?).to be_truthy
end
- it "should fail if forked_from project which does not exist" do
+ it 'should fail if forked_from project which does not exist' do
post api("/projects/#{project_fork_target.id}/fork/9999", admin)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
- it "should fail with 409 if already forked" do
+ it 'should fail with 409 if already forked' do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
project_fork_target.reload
- project_fork_target.forked_from_project.id.should == project_fork_source.id
+ expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin)
- response.status.should == 409
+ expect(response.status).to eq(409)
project_fork_target.reload
- project_fork_target.forked_from_project.id.should == project_fork_source.id
- project_fork_target.forked?.should be_true
+ expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
+ expect(project_fork_target.forked?).to be_truthy
end
end
- describe "DELETE /projects/:id/fork" do
+ describe 'DELETE /projects/:id/fork' do
it "shouldn't available for non admin users" do
delete api("/projects/#{project_fork_target.id}/fork", user)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
- it "should make forked project unforked" do
+ it 'should make forked project unforked' do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
project_fork_target.reload
- project_fork_target.forked_from_project.should_not be_nil
- project_fork_target.forked?.should be_true
+ expect(project_fork_target.forked_from_project).not_to be_nil
+ expect(project_fork_target.forked?).to be_truthy
delete api("/projects/#{project_fork_target.id}/fork", admin)
- response.status.should == 200
+ expect(response.status).to eq(200)
project_fork_target.reload
- project_fork_target.forked_from_project.should be_nil
- project_fork_target.forked?.should_not be_true
+ expect(project_fork_target.forked_from_project).to be_nil
+ expect(project_fork_target.forked?).not_to be_truthy
end
- it "should be idempotent if not forked" do
- project_fork_target.forked_from_project.should be_nil
+ it 'should be idempotent if not forked' do
+ expect(project_fork_target.forked_from_project).to be_nil
delete api("/projects/#{project_fork_target.id}/fork", admin)
- response.status.should == 200
- project_fork_target.reload.forked_from_project.should be_nil
+ expect(response.status).to eq(200)
+ expect(project_fork_target.reload.forked_from_project).to be_nil
end
end
end
- describe "GET /projects/search/:query" do
+ describe 'GET /projects/search/:query' do
let!(:query) { 'query'}
let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) }
let!(:pre) { create(:empty_project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) }
@@ -601,68 +633,185 @@ describe API::API, api: true do
let!(:public) { create(:empty_project, :public, name: "public #{query}") }
let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') }
- context "when unauthenticated" do
- it "should return authentication error" do
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
get api("/projects/search/#{query}")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
- context "when authenticated" do
- it "should return an array of projects" do
+ context 'when authenticated' do
+ it 'should return an array of projects' do
get api("/projects/search/#{query}",user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.size.should == 6
- json_response.each {|project| project['name'].should =~ /.*query.*/}
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(6)
+ json_response.each {|project| expect(project['name']).to match(/.*query.*/)}
end
end
- context "when authenticated as a different user" do
- it "should return matching public projects" do
+ context 'when authenticated as a different user' do
+ it 'should return matching public projects' do
get api("/projects/search/#{query}", user2)
- response.status.should == 200
- json_response.should be_an Array
- json_response.size.should == 2
- json_response.each {|project| project['name'].should =~ /(internal|public) query/}
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(2)
+ json_response.each {|project| expect(project['name']).to match(/(internal|public) query/)}
end
end
end
- describe "DELETE /projects/:id" do
- context "when authenticated as user" do
- it "should remove project" do
- delete api("/projects/#{project.id}", user)
- response.status.should == 200
- end
+ describe 'PUT /projects/:id̈́' do
+ before { project }
+ before { user }
+ before { user3 }
+ before { user4 }
+ before { project3 }
+ before { project4 }
+ before { project_member3 }
+ before { project_member2 }
- it "should not remove a project if not an owner" do
- user3 = create(:user)
- project.team << [user3, :developer]
- delete api("/projects/#{project.id}", user3)
- response.status.should == 403
- end
-
- it "should not remove a non existing project" do
- delete api("/projects/1328", user)
- response.status.should == 404
- end
-
- it "should not remove a project not attached to user" do
- delete api("/projects/#{project.id}", user2)
- response.status.should == 404
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
+ project_param = { name: 'bar' }
+ put api("/projects/#{project.id}"), project_param
+ expect(response.status).to eq(401)
end
end
- context "when authenticated as admin" do
- it "should remove any existing project" do
- delete api("/projects/#{project.id}", admin)
- response.status.should == 200
+ context 'when authenticated as project owner' do
+ it 'should update name' do
+ project_param = { name: 'bar' }
+ put api("/projects/#{project.id}", user), project_param
+ expect(response.status).to eq(200)
+ project_param.each_pair do |k, v|
+ expect(json_response[k.to_s]).to eq(v)
+ end
end
- it "should not remove a non existing project" do
- delete api("/projects/1328", admin)
- response.status.should == 404
+ it 'should update visibility_level' do
+ project_param = { visibility_level: 20 }
+ put api("/projects/#{project3.id}", user), project_param
+ expect(response.status).to eq(200)
+ project_param.each_pair do |k, v|
+ expect(json_response[k.to_s]).to eq(v)
+ end
+ end
+
+ it 'should not update name to existing name' do
+ project_param = { name: project3.name }
+ put api("/projects/#{project.id}", user), project_param
+ expect(response.status).to eq(400)
+ expect(json_response['message']['name']).to eq(['has already been taken'])
+ end
+
+ it 'should update path & name to existing path & name in different namespace' do
+ project_param = { path: project4.path, name: project4.name }
+ put api("/projects/#{project3.id}", user), project_param
+ expect(response.status).to eq(200)
+ project_param.each_pair do |k, v|
+ expect(json_response[k.to_s]).to eq(v)
+ end
+ end
+ end
+
+ context 'when authenticated as project master' do
+ it 'should update path' do
+ project_param = { path: 'bar' }
+ put api("/projects/#{project3.id}", user4), project_param
+ expect(response.status).to eq(200)
+ project_param.each_pair do |k, v|
+ expect(json_response[k.to_s]).to eq(v)
+ end
+ end
+
+ it 'should update other attributes' do
+ project_param = { issues_enabled: true,
+ wiki_enabled: true,
+ snippets_enabled: true,
+ merge_requests_enabled: true,
+ description: 'new description' }
+
+ put api("/projects/#{project3.id}", user4), project_param
+ expect(response.status).to eq(200)
+ project_param.each_pair do |k, v|
+ expect(json_response[k.to_s]).to eq(v)
+ end
+ end
+
+ it 'should not update path to existing path' do
+ project_param = { path: project.path }
+ put api("/projects/#{project3.id}", user4), project_param
+ expect(response.status).to eq(400)
+ expect(json_response['message']['path']).to eq(['has already been taken'])
+ end
+
+ it 'should not update name' do
+ project_param = { name: 'bar' }
+ put api("/projects/#{project3.id}", user4), project_param
+ expect(response.status).to eq(403)
+ end
+
+ it 'should not update visibility_level' do
+ project_param = { visibility_level: 20 }
+ put api("/projects/#{project3.id}", user4), project_param
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'when authenticated as project developer' do
+ it 'should not update other attributes' do
+ project_param = { path: 'bar',
+ issues_enabled: true,
+ wiki_enabled: true,
+ snippets_enabled: true,
+ merge_requests_enabled: true,
+ description: 'new description' }
+ put api("/projects/#{project.id}", user3), project_param
+ expect(response.status).to eq(403)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id' do
+ context 'when authenticated as user' do
+ it 'should remove project' do
+ expect(GitlabShellWorker).to(
+ receive(:perform_async).with(:remove_repository,
+ /#{project.path_with_namespace}/)
+ ).twice
+
+ delete api("/projects/#{project.id}", user)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should not remove a project if not an owner' do
+ user3 = create(:user)
+ project.team << [user3, :developer]
+ delete api("/projects/#{project.id}", user3)
+ expect(response.status).to eq(403)
+ end
+
+ it 'should not remove a non existing project' do
+ delete api('/projects/1328', user)
+ expect(response.status).to eq(404)
+ end
+
+ it 'should not remove a project not attached to user' do
+ delete api("/projects/#{project.id}", user2)
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when authenticated as admin' do
+ it 'should remove any existing project' do
+ delete api("/projects/#{project.id}", admin)
+ expect(response.status).to eq(200)
+ end
+
+ it 'should not remove a non existing project' do
+ delete api('/projects/1328', admin)
+ expect(response.status).to eq(404)
end
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 6e54839b67..729970153d 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -16,9 +16,9 @@ describe API::API, api: true do
describe "GET /projects/:id/repository/tags" do
it "should return an array of project tags" do
get api("/projects/#{project.id}/repository/tags", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name)
end
end
@@ -29,60 +29,62 @@ describe API::API, api: true do
tag_name: 'v7.0.1',
ref: 'master'
- response.status.should == 201
- json_response['name'].should == 'v7.0.1'
+ expect(response.status).to eq(201)
+ expect(json_response['name']).to eq('v7.0.1')
end
end
- # TODO: fix this test for CI
- #context 'annotated tag' do
- #it 'should create a new annotated tag' do
- #post api("/projects/#{project.id}/repository/tags", user),
- #tag_name: 'v7.1.0',
- #ref: 'master',
- #message: 'tag message'
+ context 'annotated tag' do
+ it 'should create a new annotated tag' do
+ # Identity must be set in .gitconfig to create annotated tag.
+ repo_path = project.repository.path_to_repo
+ system(*%W(git --git-dir=#{repo_path} config user.name #{user.name}))
+ system(*%W(git --git-dir=#{repo_path} config user.email #{user.email}))
- #response.status.should == 201
- #json_response['name'].should == 'v7.1.0'
- # The message is not part of the JSON response.
- # Additional changes to the gitlab_git gem may be required.
- # json_response['message'].should == 'tag message'
- #end
- #end
+ post api("/projects/#{project.id}/repository/tags", user),
+ tag_name: 'v7.1.0',
+ ref: 'master',
+ message: 'Release 7.1.0'
+
+ expect(response.status).to eq(201)
+ expect(json_response['name']).to eq('v7.1.0')
+ expect(json_response['message']).to eq('Release 7.1.0')
+ end
+ end
it 'should deny for user without push access' do
post api("/projects/#{project.id}/repository/tags", user2),
tag_name: 'v1.9.0',
ref: '621491c677087aa243f165eab467bfdfbee00be1'
- response.status.should == 403
+ expect(response.status).to eq(403)
end
it 'should return 400 if tag name is invalid' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v 1.0.0',
ref: 'master'
- response.status.should == 400
- json_response['message'].should == 'Tag name invalid'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('Tag name invalid')
end
it 'should return 400 if tag already exists' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v8.0.0',
ref: 'master'
- response.status.should == 201
+ expect(response.status).to eq(201)
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v8.0.0',
ref: 'master'
- response.status.should == 400
- json_response['message'].should == 'Tag already exists'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('Tag already exists')
end
it 'should return 400 if ref name is invalid' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'mytag',
ref: 'foo'
- response.status.should == 400
- json_response['message'].should == 'Invalid reference name'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('Invalid reference name')
end
end
@@ -92,19 +94,27 @@ describe API::API, api: true do
it "should return project commits" do
get api("/projects/#{project.id}/repository/tree", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
- json_response.should be_an Array
- json_response.first['name'].should == 'encoding'
- json_response.first['type'].should == 'tree'
- json_response.first['mode'].should == '040000'
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).to eq('encoding')
+ expect(json_response.first['type']).to eq('tree')
+ expect(json_response.first['mode']).to eq('040000')
+ end
+
+ it 'should return a 404 for unknown ref' do
+ get api("/projects/#{project.id}/repository/tree?ref_name=foo", user)
+ expect(response.status).to eq(404)
+
+ expect(json_response).to be_an Object
+ json_response['message'] == '404 Tree Not Found'
end
end
context "unauthorized user" do
it "should not return project commits" do
get api("/projects/#{project.id}/repository/tree")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
end
@@ -112,36 +122,44 @@ describe API::API, api: true do
describe "GET /projects/:id/repository/blobs/:sha" do
it "should get the raw file contents" do
get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should return 404 for invalid branch_name" do
get api("/projects/#{project.id}/repository/blobs/invalid_branch_name?filepath=README.md", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it "should return 404 for invalid file" do
get api("/projects/#{project.id}/repository/blobs/master?filepath=README.invalid", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
it "should return a 400 error if filepath is missing" do
get api("/projects/#{project.id}/repository/blobs/master", user)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
end
describe "GET /projects/:id/repository/commits/:sha/blob" do
it "should get the raw file contents" do
get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
end
describe "GET /projects/:id/repository/raw_blobs/:sha" do
it "should get the raw file contents" do
get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
+ end
+
+ it 'should return a 404 for unknown blob' do
+ get api("/projects/#{project.id}/repository/raw_blobs/123456", user)
+ expect(response.status).to eq(404)
+
+ expect(json_response).to be_an Object
+ json_response['message'] == '404 Blob Not Found'
end
end
@@ -149,83 +167,83 @@ describe API::API, api: true do
it "should get the archive" do
get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "")
- response.status.should == 200
- response.headers['Content-Disposition'].should =~ /filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/
- response.content_type.should == MIME::Types.type_for('file.tar.gz').first.content_type
+ expect(response.status).to eq(200)
+ expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/)
+ expect(response.content_type).to eq(MIME::Types.type_for('file.tar.gz').first.content_type)
end
it "should get the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "")
- response.status.should == 200
- response.headers['Content-Disposition'].should =~ /filename\=\"#{repo_name}\-[^\.]+\.zip\"/
- response.content_type.should == MIME::Types.type_for('file.zip').first.content_type
+ expect(response.status).to eq(200)
+ expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.zip\"/)
+ expect(response.content_type).to eq(MIME::Types.type_for('file.zip').first.content_type)
end
it "should get the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "")
- response.status.should == 200
- response.headers['Content-Disposition'].should =~ /filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/
- response.content_type.should == MIME::Types.type_for('file.tar.bz2').first.content_type
+ expect(response.status).to eq(200)
+ expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/)
+ expect(response.content_type).to eq(MIME::Types.type_for('file.tar.bz2').first.content_type)
end
it "should return 404 for invalid sha" do
get api("/projects/#{project.id}/repository/archive/?sha=xxx", user)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
describe 'GET /projects/:id/repository/compare' do
it "should compare branches" do
get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'feature'
- response.status.should == 200
- json_response['commits'].should be_present
- json_response['diffs'].should be_present
+ expect(response.status).to eq(200)
+ expect(json_response['commits']).to be_present
+ expect(json_response['diffs']).to be_present
end
it "should compare tags" do
get api("/projects/#{project.id}/repository/compare", user), from: 'v1.0.0', to: 'v1.1.0'
- response.status.should == 200
- json_response['commits'].should be_present
- json_response['diffs'].should be_present
+ expect(response.status).to eq(200)
+ expect(json_response['commits']).to be_present
+ expect(json_response['diffs']).to be_present
end
it "should compare commits" do
get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.id, to: sample_commit.parent_id
- response.status.should == 200
- json_response['commits'].should be_empty
- json_response['diffs'].should be_empty
- json_response['compare_same_ref'].should be_false
+ expect(response.status).to eq(200)
+ expect(json_response['commits']).to be_empty
+ expect(json_response['diffs']).to be_empty
+ expect(json_response['compare_same_ref']).to be_falsey
end
it "should compare commits in reverse order" do
get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.parent_id, to: sample_commit.id
- response.status.should == 200
- json_response['commits'].should be_present
- json_response['diffs'].should be_present
+ expect(response.status).to eq(200)
+ expect(json_response['commits']).to be_present
+ expect(json_response['diffs']).to be_present
end
it "should compare same refs" do
get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'master'
- response.status.should == 200
- json_response['commits'].should be_empty
- json_response['diffs'].should be_empty
- json_response['compare_same_ref'].should be_true
+ expect(response.status).to eq(200)
+ expect(json_response['commits']).to be_empty
+ expect(json_response['diffs']).to be_empty
+ expect(json_response['compare_same_ref']).to be_truthy
end
end
describe 'GET /projects/:id/repository/contributors' do
it 'should return valid data' do
get api("/projects/#{project.id}/repository/contributors", user)
- response.status.should == 200
- json_response.should be_an Array
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
contributor = json_response.first
- contributor['email'].should == 'dmitriy.zaporozhets@gmail.com'
- contributor['name'].should == 'Dmitriy Zaporozhets'
- contributor['commits'].should == 13
- contributor['additions'].should == 0
- contributor['deletions'].should == 0
+ expect(contributor['email']).to eq('dmitriy.zaporozhets@gmail.com')
+ expect(contributor['name']).to eq('Dmitriy Zaporozhets')
+ expect(contributor['commits']).to eq(13)
+ expect(contributor['additions']).to eq(0)
+ expect(contributor['deletions']).to eq(0)
end
end
end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index f883c9e028..51c543578d 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -9,13 +9,13 @@ describe API::API, api: true do
it "should update gitlab-ci settings" do
put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secret-token', project_url: "http://ci.example.com/projects/1"
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should return if required fields missing" do
put api("/projects/#{project.id}/services/gitlab-ci", user), project_url: "http://ci.example.com/projects/1", active: true
- response.status.should == 400
+ expect(response.status).to eq(400)
end
end
@@ -23,8 +23,34 @@ describe API::API, api: true do
it "should update gitlab-ci settings" do
delete api("/projects/#{project.id}/services/gitlab-ci", user)
- response.status.should == 200
- project.gitlab_ci_service.should be_nil
+ expect(response.status).to eq(200)
+ expect(project.gitlab_ci_service).to be_nil
+ end
+ end
+
+ describe 'PUT /projects/:id/services/hipchat' do
+ it 'should update hipchat settings' do
+ put api("/projects/#{project.id}/services/hipchat", user),
+ token: 'secret-token', room: 'test'
+
+ expect(response.status).to eq(200)
+ expect(project.hipchat_service).not_to be_nil
+ end
+
+ it 'should return if required fields missing' do
+ put api("/projects/#{project.id}/services/gitlab-ci", user),
+ token: 'secret-token', active: true
+
+ expect(response.status).to eq(400)
+ end
+ end
+
+ describe 'DELETE /projects/:id/services/hipchat' do
+ it 'should delete hipchat settings' do
+ delete api("/projects/#{project.id}/services/hipchat", user)
+
+ expect(response.status).to eq(200)
+ expect(project.hipchat_service).to be_nil
end
end
end
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index 013f425d6c..fbd57b34a5 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -9,43 +9,69 @@ describe API::API, api: true do
context "when valid password" do
it "should return private token" do
post api("/session"), email: user.email, password: '12345678'
- response.status.should == 201
+ expect(response.status).to eq(201)
- json_response['email'].should == user.email
- json_response['private_token'].should == user.private_token
- json_response['is_admin'].should == user.is_admin?
- json_response['can_create_project'].should == user.can_create_project?
- json_response['can_create_group'].should == user.can_create_group?
+ expect(json_response['email']).to eq(user.email)
+ expect(json_response['private_token']).to eq(user.private_token)
+ expect(json_response['is_admin']).to eq(user.is_admin?)
+ expect(json_response['can_create_project']).to eq(user.can_create_project?)
+ expect(json_response['can_create_group']).to eq(user.can_create_group?)
+ end
+ end
+
+ context 'when email has case-typo and password is valid' do
+ it 'should return private token' do
+ post api('/session'), email: user.email.upcase, password: '12345678'
+ expect(response.status).to eq 201
+
+ expect(json_response['email']).to eq user.email
+ expect(json_response['private_token']).to eq user.private_token
+ expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['can_create_project']).to eq user.can_create_project?
+ expect(json_response['can_create_group']).to eq user.can_create_group?
+ end
+ end
+
+ context 'when login has case-typo and password is valid' do
+ it 'should return private token' do
+ post api('/session'), login: user.username.upcase, password: '12345678'
+ expect(response.status).to eq 201
+
+ expect(json_response['email']).to eq user.email
+ expect(json_response['private_token']).to eq user.private_token
+ expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['can_create_project']).to eq user.can_create_project?
+ expect(json_response['can_create_group']).to eq user.can_create_group?
end
end
context "when invalid password" do
it "should return authentication error" do
post api("/session"), email: user.email, password: '123'
- response.status.should == 401
+ expect(response.status).to eq(401)
- json_response['email'].should be_nil
- json_response['private_token'].should be_nil
+ expect(json_response['email']).to be_nil
+ expect(json_response['private_token']).to be_nil
end
end
context "when empty password" do
it "should return authentication error" do
post api("/session"), email: user.email
- response.status.should == 401
+ expect(response.status).to eq(401)
- json_response['email'].should be_nil
- json_response['private_token'].should be_nil
+ expect(json_response['email']).to be_nil
+ expect(json_response['private_token']).to be_nil
end
end
context "when empty name" do
it "should return authentication error" do
post api("/session"), password: user.password
- response.status.should == 401
+ expect(response.status).to eq(401)
- json_response['email'].should be_nil
- json_response['private_token'].should be_nil
+ expect(json_response['email']).to be_nil
+ expect(json_response['private_token']).to be_nil
end
end
end
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 5784ae8c23..a9d86bbce6 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -13,23 +13,23 @@ describe API::API, api: true do
context "when no user" do
it "should return authentication error" do
get api("/hooks")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
context "when not an admin" do
it "should return forbidden error" do
get api("/hooks", user)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
end
context "when authenticated as admin" do
it "should return an array of hooks" do
get api("/hooks", admin)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['url'].should == hook.url
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['url']).to eq(hook.url)
end
end
end
@@ -43,7 +43,7 @@ describe API::API, api: true do
it "should respond with 400 if url not given" do
post api("/hooks", admin)
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should not create new hook without url" do
@@ -56,13 +56,13 @@ describe API::API, api: true do
describe "GET /hooks/:id" do
it "should return hook by id" do
get api("/hooks/#{hook.id}", admin)
- response.status.should == 200
- json_response['event_name'].should == 'project_create'
+ expect(response.status).to eq(200)
+ expect(json_response['event_name']).to eq('project_create')
end
it "should return 404 on failure" do
get api("/hooks/404", admin)
- response.status.should == 404
+ expect(response.status).to eq(404)
end
end
@@ -75,7 +75,7 @@ describe API::API, api: true do
it "should return success if hook id not found" do
delete api("/hooks/12345", admin)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index bc1598273b..081400cded 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -11,30 +11,30 @@ describe API::API, api: true do
context "when unauthenticated" do
it "should return authentication error" do
get api("/users")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
context "when authenticated" do
it "should return an array of users" do
get api("/users", user)
- response.status.should == 200
- json_response.should be_an Array
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
username = user.username
- json_response.detect {
+ expect(json_response.detect {
|user| user['username'] == username
- }['username'].should == username
+ }['username']).to eq(username)
end
end
context "when admin" do
it "should return an array of users" do
get api("/users", admin)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first.keys.should include 'email'
- json_response.first.keys.should include 'extern_uid'
- json_response.first.keys.should include 'can_create_project'
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first.keys).to include 'email'
+ expect(json_response.first.keys).to include 'identities'
+ expect(json_response.first.keys).to include 'can_create_project'
end
end
end
@@ -42,19 +42,19 @@ describe API::API, api: true do
describe "GET /users/:id" do
it "should return a user by id" do
get api("/users/#{user.id}", user)
- response.status.should == 200
- json_response['username'].should == user.username
+ expect(response.status).to eq(200)
+ expect(json_response['username']).to eq(user.username)
end
it "should return a 401 if unauthenticated" do
get api("/users/9998")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
it "should return a 404 error if user id not found" do
get api("/users/9999", user)
- response.status.should == 404
- json_response['message'].should == '404 Not found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Not found')
end
end
@@ -69,36 +69,36 @@ describe API::API, api: true do
it "should create user with correct attributes" do
post api('/users', admin), attributes_for(:user, admin: true, can_create_group: true)
- response.status.should == 201
+ expect(response.status).to eq(201)
user_id = json_response['id']
new_user = User.find(user_id)
- new_user.should_not == nil
- new_user.admin.should == true
- new_user.can_create_group.should == true
+ expect(new_user).not_to eq(nil)
+ expect(new_user.admin).to eq(true)
+ expect(new_user.can_create_group).to eq(true)
end
it "should create non-admin user" do
post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false)
- response.status.should == 201
+ expect(response.status).to eq(201)
user_id = json_response['id']
new_user = User.find(user_id)
- new_user.should_not == nil
- new_user.admin.should == false
- new_user.can_create_group.should == false
+ expect(new_user).not_to eq(nil)
+ expect(new_user.admin).to eq(false)
+ expect(new_user.can_create_group).to eq(false)
end
it "should create non-admin users by default" do
post api('/users', admin), attributes_for(:user)
- response.status.should == 201
+ expect(response.status).to eq(201)
user_id = json_response['id']
new_user = User.find(user_id)
- new_user.should_not == nil
- new_user.admin.should == false
+ expect(new_user).not_to eq(nil)
+ expect(new_user.admin).to eq(false)
end
it "should return 201 Created on success" do
post api("/users", admin), attributes_for(:user, projects_limit: 3)
- response.status.should == 201
+ expect(response.status).to eq(201)
end
it "should not create user with invalid email" do
@@ -106,22 +106,22 @@ describe API::API, api: true do
email: 'invalid email',
password: 'password',
name: 'test'
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it 'should return 400 error if name not given' do
post api('/users', admin), email: 'test@example.com', password: 'pass1234'
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it 'should return 400 error if password not given' do
post api('/users', admin), email: 'test@example.com', name: 'test'
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it "should return 400 error if email not given" do
post api('/users', admin), password: 'pass1234', name: 'test'
- response.status.should == 400
+ expect(response.status).to eq(400)
end
it 'should return 400 error if user does not validate' do
@@ -132,22 +132,20 @@ describe API::API, api: true do
name: 'test',
bio: 'g' * 256,
projects_limit: -1
- response.status.should == 400
- json_response['message']['password'].
- should == ['is too short (minimum is 8 characters)']
- json_response['message']['bio'].
- should == ['is too long (maximum is 255 characters)']
- json_response['message']['projects_limit'].
- should == ['must be greater than or equal to 0']
- json_response['message']['username'].
- should == ['can contain only letters, digits, '\
- '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\
- '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['password']).
+ to eq(['is too short (minimum is 8 characters)'])
+ expect(json_response['message']['bio']).
+ to eq(['is too long (maximum is 255 characters)'])
+ expect(json_response['message']['projects_limit']).
+ to eq(['must be greater than or equal to 0'])
+ expect(json_response['message']['username']).
+ to eq([Gitlab::Regex.send(:default_regex_message)])
end
it "shouldn't available for non admin users" do
post api("/users", user), attributes_for(:user)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
context 'with existing user' do
@@ -167,8 +165,8 @@ describe API::API, api: true do
password: 'password',
username: 'foo'
}.to change { User.count }.by(0)
- response.status.should == 409
- json_response['message'].should == 'Email has already been taken'
+ expect(response.status).to eq(409)
+ expect(json_response['message']).to eq('Email has already been taken')
end
it 'should return 409 conflict error if same username exists' do
@@ -179,34 +177,18 @@ describe API::API, api: true do
password: 'password',
username: 'test'
end.to change { User.count }.by(0)
- response.status.should == 409
- json_response['message'].should == 'Username has already been taken'
+ expect(response.status).to eq(409)
+ expect(json_response['message']).to eq('Username has already been taken')
end
end
end
describe "GET /users/sign_up" do
- context 'enabled' do
- before do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(true)
- end
- it "should return sign up page if signup is enabled" do
- get "/users/sign_up"
- response.status.should == 200
- end
- end
-
- context 'disabled' do
- before do
- Gitlab.config.gitlab.stub(:signup_enabled).and_return(false)
- end
-
- it "should redirect to sign in page if signup is disabled" do
- get "/users/sign_up"
- response.status.should == 302
- response.should redirect_to(new_user_session_path)
- end
+ it "should redirect to sign in page" do
+ get "/users/sign_up"
+ expect(response.status).to eq(302)
+ expect(response).to redirect_to(new_user_session_path)
end
end
@@ -217,55 +199,55 @@ describe API::API, api: true do
it "should update user with new bio" do
put api("/users/#{user.id}", admin), {bio: 'new test bio'}
- response.status.should == 200
- json_response['bio'].should == 'new test bio'
- user.reload.bio.should == 'new test bio'
+ expect(response.status).to eq(200)
+ expect(json_response['bio']).to eq('new test bio')
+ expect(user.reload.bio).to eq('new test bio')
end
it 'should update user with his own email' do
put api("/users/#{user.id}", admin), email: user.email
- response.status.should == 200
- json_response['email'].should == user.email
- user.reload.email.should == user.email
+ expect(response.status).to eq(200)
+ expect(json_response['email']).to eq(user.email)
+ expect(user.reload.email).to eq(user.email)
end
it 'should update user with his own username' do
put api("/users/#{user.id}", admin), username: user.username
- response.status.should == 200
- json_response['username'].should == user.username
- user.reload.username.should == user.username
+ expect(response.status).to eq(200)
+ expect(json_response['username']).to eq(user.username)
+ expect(user.reload.username).to eq(user.username)
end
it "should update admin status" do
put api("/users/#{user.id}", admin), {admin: true}
- response.status.should == 200
- json_response['is_admin'].should == true
- user.reload.admin.should == true
+ expect(response.status).to eq(200)
+ expect(json_response['is_admin']).to eq(true)
+ expect(user.reload.admin).to eq(true)
end
it "should not update admin status" do
put api("/users/#{admin_user.id}", admin), {can_create_group: false}
- response.status.should == 200
- json_response['is_admin'].should == true
- admin_user.reload.admin.should == true
- admin_user.can_create_group.should == false
+ expect(response.status).to eq(200)
+ expect(json_response['is_admin']).to eq(true)
+ expect(admin_user.reload.admin).to eq(true)
+ expect(admin_user.can_create_group).to eq(false)
end
it "should not allow invalid update" do
put api("/users/#{user.id}", admin), {email: 'invalid email'}
- response.status.should == 400
- user.reload.email.should_not == 'invalid email'
+ expect(response.status).to eq(400)
+ expect(user.reload.email).not_to eq('invalid email')
end
it "shouldn't available for non admin users" do
put api("/users/#{user.id}", user), attributes_for(:user)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
it "should return 404 for non-existing user" do
put api("/users/999999", admin), {bio: 'update should fail'}
- response.status.should == 404
- json_response['message'].should == '404 Not found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Not found')
end
it 'should return 400 error if user does not validate' do
@@ -276,17 +258,15 @@ describe API::API, api: true do
name: 'test',
bio: 'g' * 256,
projects_limit: -1
- response.status.should == 400
- json_response['message']['password'].
- should == ['is too short (minimum is 8 characters)']
- json_response['message']['bio'].
- should == ['is too long (maximum is 255 characters)']
- json_response['message']['projects_limit'].
- should == ['must be greater than or equal to 0']
- json_response['message']['username'].
- should == ['can contain only letters, digits, '\
- '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\
- '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.']
+ expect(response.status).to eq(400)
+ expect(json_response['message']['password']).
+ to eq(['is too short (minimum is 8 characters)'])
+ expect(json_response['message']['bio']).
+ to eq(['is too long (maximum is 255 characters)'])
+ expect(json_response['message']['projects_limit']).
+ to eq(['must be greater than or equal to 0'])
+ expect(json_response['message']['username']).
+ to eq([Gitlab::Regex.send(:default_regex_message)])
end
context "with existing user" do
@@ -298,15 +278,15 @@ describe API::API, api: true do
it 'should return 409 conflict error if email address exists' do
put api("/users/#{@user.id}", admin), email: 'test@example.com'
- response.status.should == 409
- @user.reload.email.should == @user.email
+ expect(response.status).to eq(409)
+ expect(@user.reload.email).to eq(@user.email)
end
it 'should return 409 conflict error if username taken' do
@user_id = User.all.last.id
put api("/users/#{@user.id}", admin), username: 'test'
- response.status.should == 409
- @user.reload.username.should == @user.username
+ expect(response.status).to eq(409)
+ expect(@user.reload.username).to eq(@user.username)
end
end
end
@@ -316,14 +296,14 @@ describe API::API, api: true do
it "should not create invalid ssh key" do
post api("/users/#{user.id}/keys", admin), { title: "invalid key" }
- response.status.should == 400
- json_response['message'].should == '400 (Bad request) "key" not given'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('400 (Bad request) "key" not given')
end
it 'should not create key without title' do
post api("/users/#{user.id}/keys", admin), key: 'some key'
- response.status.should == 400
- json_response['message'].should == '400 (Bad request) "title" not given'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('400 (Bad request) "title" not given')
end
it "should create ssh key" do
@@ -340,24 +320,24 @@ describe API::API, api: true do
context 'when unauthenticated' do
it 'should return authentication error' do
get api("/users/#{user.id}/keys")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
context 'when authenticated' do
it 'should return 404 for non-existing user' do
get api('/users/999999/keys', admin)
- response.status.should == 404
- json_response['message'].should == '404 User Not Found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 User Not Found')
end
it 'should return array of ssh keys' do
user.keys << key
user.save
get api("/users/#{user.id}/keys", admin)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first['title'].should == key.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['title']).to eq(key.title)
end
end
end
@@ -368,7 +348,7 @@ describe API::API, api: true do
context 'when unauthenticated' do
it 'should return authentication error' do
delete api("/users/#{user.id}/keys/42")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
@@ -379,21 +359,21 @@ describe API::API, api: true do
expect {
delete api("/users/#{user.id}/keys/#{key.id}", admin)
}.to change { user.keys.count }.by(-1)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it 'should return 404 error if user not found' do
user.keys << key
user.save
delete api("/users/999999/keys/#{key.id}", admin)
- response.status.should == 404
- json_response['message'].should == '404 User Not Found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 User Not Found')
end
it 'should return 404 error if key not foud' do
delete api("/users/#{user.id}/keys/42", admin)
- response.status.should == 404
- json_response['message'].should == '404 Key Not Found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Key Not Found')
end
end
end
@@ -403,41 +383,42 @@ describe API::API, api: true do
it "should delete user" do
delete api("/users/#{user.id}", admin)
- response.status.should == 200
+ expect(response.status).to eq(200)
expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound
- json_response['email'].should == user.email
+ expect(json_response['email']).to eq(user.email)
end
it "should not delete for unauthenticated user" do
delete api("/users/#{user.id}")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
it "shouldn't available for non admin users" do
delete api("/users/#{user.id}", user)
- response.status.should == 403
+ expect(response.status).to eq(403)
end
it "should return 404 for non-existing user" do
delete api("/users/999999", admin)
- response.status.should == 404
- json_response['message'].should == '404 User Not Found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 User Not Found')
end
end
describe "GET /user" do
it "should return current user" do
get api("/user", user)
- response.status.should == 200
- json_response['email'].should == user.email
- json_response['is_admin'].should == user.is_admin?
- json_response['can_create_project'].should == user.can_create_project?
- json_response['can_create_group'].should == user.can_create_group?
+ expect(response.status).to eq(200)
+ expect(json_response['email']).to eq(user.email)
+ expect(json_response['is_admin']).to eq(user.is_admin?)
+ expect(json_response['can_create_project']).to eq(user.can_create_project?)
+ expect(json_response['can_create_group']).to eq(user.can_create_group?)
+ expect(json_response['projects_limit']).to eq(user.projects_limit)
end
it "should return 401 error if user is unauthenticated" do
get api("/user")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
@@ -445,7 +426,7 @@ describe API::API, api: true do
context "when unauthenticated" do
it "should return authentication error" do
get api("/user/keys")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
@@ -454,9 +435,9 @@ describe API::API, api: true do
user.keys << key
user.save
get api("/user/keys", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.first["title"].should == key.title
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first["title"]).to eq(key.title)
end
end
end
@@ -466,14 +447,14 @@ describe API::API, api: true do
user.keys << key
user.save
get api("/user/keys/#{key.id}", user)
- response.status.should == 200
- json_response["title"].should == key.title
+ expect(response.status).to eq(200)
+ expect(json_response["title"]).to eq(key.title)
end
it "should return 404 Not Found within invalid ID" do
get api("/user/keys/42", user)
- response.status.should == 404
- json_response['message'].should == '404 Not found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Not found')
end
it "should return 404 error if admin accesses user's ssh key" do
@@ -481,8 +462,8 @@ describe API::API, api: true do
user.save
admin
get api("/user/keys/#{key.id}", admin)
- response.status.should == 404
- json_response['message'].should == '404 Not found'
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 Not found')
end
end
@@ -492,29 +473,29 @@ describe API::API, api: true do
expect {
post api("/user/keys", user), key_attrs
}.to change{ user.keys.count }.by(1)
- response.status.should == 201
+ expect(response.status).to eq(201)
end
it "should return a 401 error if unauthorized" do
post api("/user/keys"), title: 'some title', key: 'some key'
- response.status.should == 401
+ expect(response.status).to eq(401)
end
it "should not create ssh key without key" do
post api("/user/keys", user), title: 'title'
- response.status.should == 400
- json_response['message'].should == '400 (Bad request) "key" not given'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('400 (Bad request) "key" not given')
end
it 'should not create ssh key without title' do
post api('/user/keys', user), key: 'some key'
- response.status.should == 400
- json_response['message'].should == '400 (Bad request) "title" not given'
+ expect(response.status).to eq(400)
+ expect(json_response['message']).to eq('400 (Bad request) "title" not given')
end
it "should not create ssh key without title" do
post api("/user/keys", user), key: "somekey"
- response.status.should == 400
+ expect(response.status).to eq(400)
end
end
@@ -525,19 +506,19 @@ describe API::API, api: true do
expect {
delete api("/user/keys/#{key.id}", user)
}.to change{user.keys.count}.by(-1)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should return success if key ID not found" do
delete api("/user/keys/42", user)
- response.status.should == 200
+ expect(response.status).to eq(200)
end
it "should return 401 error if unauthorized" do
user.keys << key
user.save
delete api("/user/keys/#{key.id}")
- response.status.should == 401
+ expect(response.status).to eq(401)
end
end
end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 7fe18ff47c..bf8abcfb00 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -12,47 +12,47 @@ require 'spec_helper'
# DELETE /admin/users/:id(.:format) admin/users#destroy
describe Admin::UsersController, "routing" do
it "to #team_update" do
- put("/admin/users/1/team_update").should route_to('admin/users#team_update', id: '1')
+ expect(put("/admin/users/1/team_update")).to route_to('admin/users#team_update', id: '1')
end
it "to #block" do
- put("/admin/users/1/block").should route_to('admin/users#block', id: '1')
+ expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1')
end
it "to #unblock" do
- put("/admin/users/1/unblock").should route_to('admin/users#unblock', id: '1')
+ expect(put("/admin/users/1/unblock")).to route_to('admin/users#unblock', id: '1')
end
it "to #index" do
- get("/admin/users").should route_to('admin/users#index')
+ expect(get("/admin/users")).to route_to('admin/users#index')
end
it "to #show" do
- get("/admin/users/1").should route_to('admin/users#show', id: '1')
+ expect(get("/admin/users/1")).to route_to('admin/users#show', id: '1')
end
it "to #create" do
- post("/admin/users").should route_to('admin/users#create')
+ expect(post("/admin/users")).to route_to('admin/users#create')
end
it "to #new" do
- get("/admin/users/new").should route_to('admin/users#new')
+ expect(get("/admin/users/new")).to route_to('admin/users#new')
end
it "to #edit" do
- get("/admin/users/1/edit").should route_to('admin/users#edit', id: '1')
+ expect(get("/admin/users/1/edit")).to route_to('admin/users#edit', id: '1')
end
it "to #show" do
- get("/admin/users/1").should route_to('admin/users#show', id: '1')
+ expect(get("/admin/users/1")).to route_to('admin/users#show', id: '1')
end
it "to #update" do
- put("/admin/users/1").should route_to('admin/users#update', id: '1')
+ expect(put("/admin/users/1")).to route_to('admin/users#update', id: '1')
end
it "to #destroy" do
- delete("/admin/users/1").should route_to('admin/users#destroy', id: '1')
+ expect(delete("/admin/users/1")).to route_to('admin/users#destroy', id: '1')
end
end
@@ -67,11 +67,11 @@ end
# DELETE /admin/projects/:id(.:format) admin/projects#destroy {id: /[^\/]+/}
describe Admin::ProjectsController, "routing" do
it "to #index" do
- get("/admin/projects").should route_to('admin/projects#index')
+ expect(get("/admin/projects")).to route_to('admin/projects#index')
end
it "to #show" do
- get("/admin/projects/gitlab").should route_to('admin/projects#show', id: 'gitlab')
+ expect(get("/admin/projects/gitlab")).to route_to('admin/projects#show', namespace_id: 'gitlab')
end
end
@@ -81,19 +81,19 @@ end
# admin_hook DELETE /admin/hooks/:id(.:format) admin/hooks#destroy
describe Admin::HooksController, "routing" do
it "to #test" do
- get("/admin/hooks/1/test").should route_to('admin/hooks#test', hook_id: '1')
+ expect(get("/admin/hooks/1/test")).to route_to('admin/hooks#test', hook_id: '1')
end
it "to #index" do
- get("/admin/hooks").should route_to('admin/hooks#index')
+ expect(get("/admin/hooks")).to route_to('admin/hooks#index')
end
it "to #create" do
- post("/admin/hooks").should route_to('admin/hooks#create')
+ expect(post("/admin/hooks")).to route_to('admin/hooks#create')
end
it "to #destroy" do
- delete("/admin/hooks/1").should route_to('admin/hooks#destroy', id: '1')
+ expect(delete("/admin/hooks/1")).to route_to('admin/hooks#destroy', id: '1')
end
end
@@ -101,21 +101,21 @@ end
# admin_logs GET /admin/logs(.:format) admin/logs#show
describe Admin::LogsController, "routing" do
it "to #show" do
- get("/admin/logs").should route_to('admin/logs#show')
+ expect(get("/admin/logs")).to route_to('admin/logs#show')
end
end
# admin_background_jobs GET /admin/background_jobs(.:format) admin/background_jobs#show
describe Admin::BackgroundJobsController, "routing" do
it "to #show" do
- get("/admin/background_jobs").should route_to('admin/background_jobs#show')
+ expect(get("/admin/background_jobs")).to route_to('admin/background_jobs#show')
end
end
# admin_root /admin(.:format) admin/dashboard#index
describe Admin::DashboardController, "routing" do
it "to #index" do
- get("/admin").should route_to('admin/dashboard#index')
+ expect(get("/admin")).to route_to('admin/dashboard#index')
end
end
diff --git a/spec/routing/notifications_routing_spec.rb b/spec/routing/notifications_routing_spec.rb
index 112b825e02..24592942a9 100644
--- a/spec/routing/notifications_routing_spec.rb
+++ b/spec/routing/notifications_routing_spec.rb
@@ -3,11 +3,11 @@ require "spec_helper"
describe Profiles::NotificationsController do
describe "routing" do
it "routes to #show" do
- get("/profile/notifications").should route_to("profiles/notifications#show")
+ expect(get("/profile/notifications")).to route_to("profiles/notifications#show")
end
it "routes to #update" do
- put("/profile/notifications").should route_to("profiles/notifications#update")
+ expect(put("/profile/notifications")).to route_to("profiles/notifications#update")
end
end
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 4b2eb42c70..d9bd91f599 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -12,86 +12,88 @@ require 'spec_helper'
# Examples
#
# # Default behavior
-# it_behaves_like "RESTful project resources" do
+# it_behaves_like 'RESTful project resources' do
# let(:controller) { 'issues' }
# end
#
# # Customizing actions
-# it_behaves_like "RESTful project resources" do
+# it_behaves_like 'RESTful project resources' do
# let(:actions) { [:index] }
# let(:controller) { 'issues' }
# end
-shared_examples "RESTful project resources" do
+shared_examples 'RESTful project resources' do
let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] }
- it "to #index" do
- get("/gitlab/gitlabhq/#{controller}").should route_to("projects/#{controller}#index", project_id: 'gitlab/gitlabhq') if actions.include?(:index)
+ it 'to #index' do
+ expect(get("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index)
end
- it "to #create" do
- post("/gitlab/gitlabhq/#{controller}").should route_to("projects/#{controller}#create", project_id: 'gitlab/gitlabhq') if actions.include?(:create)
+ it 'to #create' do
+ expect(post("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create)
end
- it "to #new" do
- get("/gitlab/gitlabhq/#{controller}/new").should route_to("projects/#{controller}#new", project_id: 'gitlab/gitlabhq') if actions.include?(:new)
+ it 'to #new' do
+ expect(get("/gitlab/gitlabhq/#{controller}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new)
end
- it "to #edit" do
- get("/gitlab/gitlabhq/#{controller}/1/edit").should route_to("projects/#{controller}#edit", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:edit)
+ it 'to #edit' do
+ expect(get("/gitlab/gitlabhq/#{controller}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit)
end
- it "to #show" do
- get("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#show", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:show)
+ it 'to #show' do
+ expect(get("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show)
end
- it "to #update" do
- put("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#update", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:update)
+ it 'to #update' do
+ expect(put("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update)
end
- it "to #destroy" do
- delete("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#destroy", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:destroy)
+ it 'to #destroy' do
+ expect(delete("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy)
end
end
-# projects POST /projects(.:format) projects#create
-# new_project GET /projects/new(.:format) projects#new
-# fork_project POST /:id/fork(.:format) projects#fork
-# files_project GET /:id/files(.:format) projects#files
-# edit_project GET /:id/edit(.:format) projects#edit
-# project GET /:id(.:format) projects#show
-# PUT /:id(.:format) projects#update
-# DELETE /:id(.:format) projects#destroy
-describe ProjectsController, "routing" do
- it "to #create" do
- post("/projects").should route_to('projects#create')
+# projects POST /projects(.:format) projects#create
+# new_project GET /projects/new(.:format) projects#new
+# files_project GET /:id/files(.:format) projects#files
+# edit_project GET /:id/edit(.:format) projects#edit
+# project GET /:id(.:format) projects#show
+# PUT /:id(.:format) projects#update
+# DELETE /:id(.:format) projects#destroy
+# markdown_preview_project POST /:id/markdown_preview(.:format) projects#markdown_preview
+describe ProjectsController, 'routing' do
+ it 'to #create' do
+ expect(post('/projects')).to route_to('projects#create')
end
- it "to #new" do
- get("/projects/new").should route_to('projects#new')
+ it 'to #new' do
+ expect(get('/projects/new')).to route_to('projects#new')
end
- it "to #fork" do
- post("/gitlab/gitlabhq/fork").should route_to('projects#fork', id: 'gitlab/gitlabhq')
+ it 'to #edit' do
+ expect(get('/gitlab/gitlabhq/edit')).to route_to('projects#edit', namespace_id: 'gitlab', id: 'gitlabhq')
end
- it "to #edit" do
- get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq')
+ it 'to #autocomplete_sources' do
+ expect(get('/gitlab/gitlabhq/autocomplete_sources')).to route_to('projects#autocomplete_sources', namespace_id: 'gitlab', id: 'gitlabhq')
end
- it "to #autocomplete_sources" do
- get('/gitlab/gitlabhq/autocomplete_sources').should route_to('projects#autocomplete_sources', id: "gitlab/gitlabhq")
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq')
end
- it "to #show" do
- get("/gitlab/gitlabhq").should route_to('projects#show', id: 'gitlab/gitlabhq')
+ it 'to #update' do
+ expect(put('/gitlab/gitlabhq')).to route_to('projects#update', namespace_id: 'gitlab', id: 'gitlabhq')
end
- it "to #update" do
- put("/gitlab/gitlabhq").should route_to('projects#update', id: 'gitlab/gitlabhq')
+ it 'to #destroy' do
+ expect(delete('/gitlab/gitlabhq')).to route_to('projects#destroy', namespace_id: 'gitlab', id: 'gitlabhq')
end
- it "to #destroy" do
- delete("/gitlab/gitlabhq").should route_to('projects#destroy', id: 'gitlab/gitlabhq')
+ it 'to #markdown_preview' do
+ expect(post('/gitlab/gitlabhq/markdown_preview')).to(
+ route_to('projects#markdown_preview', namespace_id: 'gitlab', id: 'gitlabhq')
+ )
end
end
@@ -101,16 +103,16 @@ end
# edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit
# project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show
# DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy
-describe Projects::WikisController, "routing" do
- it "to #pages" do
- get("/gitlab/gitlabhq/wikis/pages").should route_to('projects/wikis#pages', project_id: 'gitlab/gitlabhq')
+describe Projects::WikisController, 'routing' do
+ it 'to #pages' do
+ expect(get('/gitlab/gitlabhq/wikis/pages')).to route_to('projects/wikis#pages', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #history" do
- get("/gitlab/gitlabhq/wikis/1/history").should route_to('projects/wikis#history', project_id: 'gitlab/gitlabhq', id: '1')
+ it 'to #history' do
+ expect(get('/gitlab/gitlabhq/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it_behaves_like "RESTful project resources" do
+ it_behaves_like 'RESTful project resources' do
let(:actions) { [:create, :edit, :show, :destroy] }
let(:controller) { 'wikis' }
end
@@ -120,45 +122,45 @@ end
# tags_project_repository GET /:project_id/repository/tags(.:format) projects/repositories#tags
# archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive
# edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit
-describe Projects::RepositoriesController, "routing" do
- it "to #archive" do
- get("/gitlab/gitlabhq/repository/archive").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq')
+describe Projects::RepositoriesController, 'routing' do
+ it 'to #archive' do
+ expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #archive format:zip" do
- get("/gitlab/gitlabhq/repository/archive.zip").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'zip')
+ it 'to #archive format:zip' do
+ expect(get('/gitlab/gitlabhq/repository/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip')
end
- it "to #archive format:tar.bz2" do
- get("/gitlab/gitlabhq/repository/archive.tar.bz2").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'tar.bz2')
+ it 'to #archive format:tar.bz2' do
+ expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2')
end
- it "to #show" do
- get("/gitlab/gitlabhq/repository").should route_to('projects/repositories#show', project_id: 'gitlab/gitlabhq')
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/repository')).to route_to('projects/repositories#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end
-describe Projects::BranchesController, "routing" do
- it "to #branches" do
- get("/gitlab/gitlabhq/branches").should route_to('projects/branches#index', project_id: 'gitlab/gitlabhq')
- delete("/gitlab/gitlabhq/branches/feature%2345").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45')
- delete("/gitlab/gitlabhq/branches/feature%2B45").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45')
- delete("/gitlab/gitlabhq/branches/feature@45").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45')
- delete("/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz')
- delete("/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz')
- delete("/gitlab/gitlabhq/branches/feature@45/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz')
+describe Projects::BranchesController, 'routing' do
+ it 'to #branches' do
+ expect(get('/gitlab/gitlabhq/branches')).to route_to('projects/branches#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(delete('/gitlab/gitlabhq/branches/feature%2345')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45')
+ expect(delete('/gitlab/gitlabhq/branches/feature%2B45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45')
+ expect(delete('/gitlab/gitlabhq/branches/feature@45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45')
+ expect(delete('/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz')
+ expect(delete('/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz')
+ expect(delete('/gitlab/gitlabhq/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz')
end
end
-describe Projects::TagsController, "routing" do
- it "to #tags" do
- get("/gitlab/gitlabhq/tags").should route_to('projects/tags#index', project_id: 'gitlab/gitlabhq')
- delete("/gitlab/gitlabhq/tags/feature%2345").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45')
- delete("/gitlab/gitlabhq/tags/feature%2B45").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45')
- delete("/gitlab/gitlabhq/tags/feature@45").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45')
- delete("/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz')
- delete("/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz')
- delete("/gitlab/gitlabhq/tags/feature@45/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz')
+describe Projects::TagsController, 'routing' do
+ it 'to #tags' do
+ expect(get('/gitlab/gitlabhq/tags')).to route_to('projects/tags#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ expect(delete('/gitlab/gitlabhq/tags/feature%2345')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45')
+ expect(delete('/gitlab/gitlabhq/tags/feature%2B45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45')
+ expect(delete('/gitlab/gitlabhq/tags/feature@45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45')
+ expect(delete('/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz')
+ expect(delete('/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz')
+ expect(delete('/gitlab/gitlabhq/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz')
end
end
@@ -170,8 +172,8 @@ end
# project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show
# PUT /:project_id/deploy_keys/:id(.:format) deploy_keys#update
# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy
-describe Projects::DeployKeysController, "routing" do
- it_behaves_like "RESTful project resources" do
+describe Projects::DeployKeysController, 'routing' do
+ it_behaves_like 'RESTful project resources' do
let(:controller) { 'deploy_keys' }
end
end
@@ -179,8 +181,8 @@ end
# project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index
# POST /:project_id/protected_branches(.:format) protected_branches#create
# project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy
-describe Projects::ProtectedBranchesController, "routing" do
- it_behaves_like "RESTful project resources" do
+describe Projects::ProtectedBranchesController, 'routing' do
+ it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :create, :destroy] }
let(:controller) { 'protected_branches' }
end
@@ -189,21 +191,21 @@ end
# switch_project_refs GET /:project_id/refs/switch(.:format) refs#switch
# logs_tree_project_ref GET /:project_id/refs/:id/logs_tree(.:format) refs#logs_tree
# logs_file_project_ref GET /:project_id/refs/:id/logs_tree/:path(.:format) refs#logs_tree
-describe Projects::RefsController, "routing" do
- it "to #switch" do
- get("/gitlab/gitlabhq/refs/switch").should route_to('projects/refs#switch', project_id: 'gitlab/gitlabhq')
+describe Projects::RefsController, 'routing' do
+ it 'to #switch' do
+ expect(get('/gitlab/gitlabhq/refs/switch')).to route_to('projects/refs#switch', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #logs_tree" do
- get("/gitlab/gitlabhq/refs/stable/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable')
- get("/gitlab/gitlabhq/refs/feature%2345/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45')
- get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45')
- get("/gitlab/gitlabhq/refs/feature@45/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45')
- get("/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'foo/bar/baz')
- get("/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45', path: 'foo/bar/baz')
- get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45', path: 'foo/bar/baz')
- get("/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45', path: 'foo/bar/baz')
- get("/gitlab/gitlabhq/refs/stable/logs_tree/files.scss").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'files.scss')
+ it 'to #logs_tree' do
+ expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable')
+ expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45')
+ expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45')
+ expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45')
+ expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz')
+ expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz')
+ expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz')
+ expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45', path: 'foo/bar/baz')
+ expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/files.scss')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'files.scss')
end
end
@@ -219,36 +221,36 @@ end
# project_merge_request GET /:project_id/merge_requests/:id(.:format) projects/merge_requests#show
# PUT /:project_id/merge_requests/:id(.:format) projects/merge_requests#update
# DELETE /:project_id/merge_requests/:id(.:format) projects/merge_requests#destroy
-describe Projects::MergeRequestsController, "routing" do
- it "to #diffs" do
- get("/gitlab/gitlabhq/merge_requests/1/diffs").should route_to('projects/merge_requests#diffs', project_id: 'gitlab/gitlabhq', id: '1')
+describe Projects::MergeRequestsController, 'routing' do
+ it 'to #diffs' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it "to #automerge" do
- post('/gitlab/gitlabhq/merge_requests/1/automerge').should route_to(
+ it 'to #automerge' do
+ expect(post('/gitlab/gitlabhq/merge_requests/1/automerge')).to route_to(
'projects/merge_requests#automerge',
- project_id: 'gitlab/gitlabhq', id: '1'
+ namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1'
)
end
- it "to #automerge_check" do
- get("/gitlab/gitlabhq/merge_requests/1/automerge_check").should route_to('projects/merge_requests#automerge_check', project_id: 'gitlab/gitlabhq', id: '1')
+ it 'to #automerge_check' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1/automerge_check')).to route_to('projects/merge_requests#automerge_check', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it "to #branch_from" do
- get("/gitlab/gitlabhq/merge_requests/branch_from").should route_to('projects/merge_requests#branch_from', project_id: 'gitlab/gitlabhq')
+ it 'to #branch_from' do
+ expect(get('/gitlab/gitlabhq/merge_requests/branch_from')).to route_to('projects/merge_requests#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #branch_to" do
- get("/gitlab/gitlabhq/merge_requests/branch_to").should route_to('projects/merge_requests#branch_to', project_id: 'gitlab/gitlabhq')
+ it 'to #branch_to' do
+ expect(get('/gitlab/gitlabhq/merge_requests/branch_to')).to route_to('projects/merge_requests#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #show" do
- get("/gitlab/gitlabhq/merge_requests/1.diff").should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'diff')
- get("/gitlab/gitlabhq/merge_requests/1.patch").should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'patch')
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff')
+ expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch')
end
- it_behaves_like "RESTful project resources" do
+ it_behaves_like 'RESTful project resources' do
let(:controller) { 'merge_requests' }
let(:actions) { [:index, :create, :new, :edit, :show, :update] }
end
@@ -262,37 +264,37 @@ end
# project_snippet GET /:project_id/snippets/:id(.:format) snippets#show
# PUT /:project_id/snippets/:id(.:format) snippets#update
# DELETE /:project_id/snippets/:id(.:format) snippets#destroy
-describe SnippetsController, "routing" do
- it "to #raw" do
- get("/gitlab/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlab/gitlabhq', id: '1')
+describe SnippetsController, 'routing' do
+ it 'to #raw' do
+ expect(get('/gitlab/gitlabhq/snippets/1/raw')).to route_to('projects/snippets#raw', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it "to #index" do
- get("/gitlab/gitlabhq/snippets").should route_to("projects/snippets#index", project_id: 'gitlab/gitlabhq')
+ it 'to #index' do
+ expect(get('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #create" do
- post("/gitlab/gitlabhq/snippets").should route_to("projects/snippets#create", project_id: 'gitlab/gitlabhq')
+ it 'to #create' do
+ expect(post('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #new" do
- get("/gitlab/gitlabhq/snippets/new").should route_to("projects/snippets#new", project_id: 'gitlab/gitlabhq')
+ it 'to #new' do
+ expect(get('/gitlab/gitlabhq/snippets/new')).to route_to('projects/snippets#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #edit" do
- get("/gitlab/gitlabhq/snippets/1/edit").should route_to("projects/snippets#edit", project_id: 'gitlab/gitlabhq', id: '1')
+ it 'to #edit' do
+ expect(get('/gitlab/gitlabhq/snippets/1/edit')).to route_to('projects/snippets#edit', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it "to #show" do
- get("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#show", project_id: 'gitlab/gitlabhq', id: '1')
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it "to #update" do
- put("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#update", project_id: 'gitlab/gitlabhq', id: '1')
+ it 'to #update' do
+ expect(put('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#update', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it "to #destroy" do
- delete("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#destroy", project_id: 'gitlab/gitlabhq', id: '1')
+ it 'to #destroy' do
+ expect(delete('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
end
@@ -300,24 +302,24 @@ end
# project_hooks GET /:project_id/hooks(.:format) hooks#index
# POST /:project_id/hooks(.:format) hooks#create
# project_hook DELETE /:project_id/hooks/:id(.:format) hooks#destroy
-describe Projects::HooksController, "routing" do
- it "to #test" do
- get("/gitlab/gitlabhq/hooks/1/test").should route_to('projects/hooks#test', project_id: 'gitlab/gitlabhq', id: '1')
+describe Projects::HooksController, 'routing' do
+ it 'to #test' do
+ expect(get('/gitlab/gitlabhq/hooks/1/test')).to route_to('projects/hooks#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
- it_behaves_like "RESTful project resources" do
+ it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :create, :destroy] }
let(:controller) { 'hooks' }
end
end
# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /[[:alnum:]]{6,40}/, project_id: /[^\/]+/}
-describe Projects::CommitController, "routing" do
- it "to #show" do
- get("/gitlab/gitlabhq/commit/4246fb").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb')
- get("/gitlab/gitlabhq/commit/4246fb.diff").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'diff')
- get("/gitlab/gitlabhq/commit/4246fb.patch").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'patch')
- get("/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
+describe Projects::CommitController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/commit/4246fb')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb')
+ expect(get('/gitlab/gitlabhq/commit/4246fb.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'diff')
+ expect(get('/gitlab/gitlabhq/commit/4246fb.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'patch')
+ expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
end
end
@@ -325,28 +327,25 @@ end
# project_commits GET /:project_id/commits(.:format) commits#index
# POST /:project_id/commits(.:format) commits#create
# project_commit GET /:project_id/commits/:id(.:format) commits#show
-describe Projects::CommitsController, "routing" do
- it_behaves_like "RESTful project resources" do
+describe Projects::CommitsController, 'routing' do
+ it_behaves_like 'RESTful project resources' do
let(:actions) { [:show] }
let(:controller) { 'commits' }
end
- it "to #show" do
- get("/gitlab/gitlabhq/commits/master.atom").should route_to('projects/commits#show', project_id: 'gitlab/gitlabhq', id: "master", format: "atom")
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'atom')
end
end
-# project_team_members GET /:project_id/team_members(.:format) team_members#index
-# POST /:project_id/team_members(.:format) team_members#create
-# new_project_team_member GET /:project_id/team_members/new(.:format) team_members#new
-# edit_project_team_member GET /:project_id/team_members/:id/edit(.:format) team_members#edit
-# project_team_member GET /:project_id/team_members/:id(.:format) team_members#show
-# PUT /:project_id/team_members/:id(.:format) team_members#update
-# DELETE /:project_id/team_members/:id(.:format) team_members#destroy
-describe Projects::TeamMembersController, "routing" do
- it_behaves_like "RESTful project resources" do
- let(:actions) { [:new, :create, :update, :destroy] }
- let(:controller) { 'team_members' }
+# project_project_members GET /:project_id/project_members(.:format) project_members#index
+# POST /:project_id/project_members(.:format) project_members#create
+# PUT /:project_id/project_members/:id(.:format) project_members#update
+# DELETE /:project_id/project_members/:id(.:format) project_members#destroy
+describe Projects::ProjectMembersController, 'routing' do
+ it_behaves_like 'RESTful project resources' do
+ let(:actions) { [:index, :create, :update, :destroy] }
+ let(:controller) { 'project_members' }
end
end
@@ -357,17 +356,17 @@ end
# project_milestone GET /:project_id/milestones/:id(.:format) milestones#show
# PUT /:project_id/milestones/:id(.:format) milestones#update
# DELETE /:project_id/milestones/:id(.:format) milestones#destroy
-describe Projects::MilestonesController, "routing" do
- it_behaves_like "RESTful project resources" do
+describe Projects::MilestonesController, 'routing' do
+ it_behaves_like 'RESTful project resources' do
let(:controller) { 'milestones' }
let(:actions) { [:index, :create, :new, :edit, :show, :update] }
end
end
# project_labels GET /:project_id/labels(.:format) labels#index
-describe Projects::LabelsController, "routing" do
- it "to #index" do
- get("/gitlab/gitlabhq/labels").should route_to('projects/labels#index', project_id: 'gitlab/gitlabhq')
+describe Projects::LabelsController, 'routing' do
+ it 'to #index' do
+ expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end
@@ -381,84 +380,114 @@ end
# project_issue GET /:project_id/issues/:id(.:format) issues#show
# PUT /:project_id/issues/:id(.:format) issues#update
# DELETE /:project_id/issues/:id(.:format) issues#destroy
-describe Projects::IssuesController, "routing" do
- it "to #bulk_update" do
- post("/gitlab/gitlabhq/issues/bulk_update").should route_to('projects/issues#bulk_update', project_id: 'gitlab/gitlabhq')
+describe Projects::IssuesController, 'routing' do
+ it 'to #bulk_update' do
+ expect(post('/gitlab/gitlabhq/issues/bulk_update')).to route_to('projects/issues#bulk_update', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it_behaves_like "RESTful project resources" do
+ it_behaves_like 'RESTful project resources' do
let(:controller) { 'issues' }
let(:actions) { [:index, :create, :new, :edit, :show, :update] }
end
end
-# preview_project_notes POST /:project_id/notes/preview(.:format) notes#preview
# project_notes GET /:project_id/notes(.:format) notes#index
# POST /:project_id/notes(.:format) notes#create
# project_note DELETE /:project_id/notes/:id(.:format) notes#destroy
-describe Projects::NotesController, "routing" do
- it "to #preview" do
- post("/gitlab/gitlabhq/notes/preview").should route_to('projects/notes#preview', project_id: 'gitlab/gitlabhq')
- end
-
- it_behaves_like "RESTful project resources" do
+describe Projects::NotesController, 'routing' do
+ it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :create, :destroy] }
let(:controller) { 'notes' }
end
end
# project_blame GET /:project_id/blame/:id(.:format) blame#show {id: /.+/, project_id: /[^\/]+/}
-describe Projects::BlameController, "routing" do
- it "to #show" do
- get("/gitlab/gitlabhq/blame/master/app/models/project.rb").should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')
- get("/gitlab/gitlabhq/blame/master/files.scss").should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
+describe Projects::BlameController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/blame/master/app/models/project.rb')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
+ expect(get('/gitlab/gitlabhq/blame/master/files.scss')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss')
end
end
# project_blob GET /:project_id/blob/:id(.:format) blob#show {id: /.+/, project_id: /[^\/]+/}
-describe Projects::BlobController, "routing" do
- it "to #show" do
- get("/gitlab/gitlabhq/blob/master/app/models/project.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')
- get("/gitlab/gitlabhq/blob/master/app/models/compare.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/compare.rb')
- get("/gitlab/gitlabhq/blob/master/files.scss").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
+describe Projects::BlobController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/blob/master/app/models/project.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
+ expect(get('/gitlab/gitlabhq/blob/master/app/models/compare.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/compare.rb')
+ expect(get('/gitlab/gitlabhq/blob/master/app/models/diff.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/diff.js')
+ expect(get('/gitlab/gitlabhq/blob/master/files.scss')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss')
end
end
# project_tree GET /:project_id/tree/:id(.:format) tree#show {id: /.+/, project_id: /[^\/]+/}
-describe Projects::TreeController, "routing" do
- it "to #show" do
- get("/gitlab/gitlabhq/tree/master/app/models/project.rb").should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb')
- get("/gitlab/gitlabhq/tree/master/files.scss").should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss')
+describe Projects::TreeController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/tree/master/app/models/project.rb')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
+ expect(get('/gitlab/gitlabhq/tree/master/files.scss')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss')
+ end
+end
+
+describe Projects::BlobController, 'routing' do
+ it 'to #edit' do
+ expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to(
+ route_to('projects/blob#edit',
+ namespace_id: 'gitlab', project_id: 'gitlabhq',
+ id: 'master/app/models/project.rb'))
+ end
+
+ it 'to #preview' do
+ expect(post('/gitlab/gitlabhq/preview/master/app/models/project.rb')).to(
+ route_to('projects/blob#preview',
+ namespace_id: 'gitlab', project_id: 'gitlabhq',
+ id: 'master/app/models/project.rb'))
end
end
# project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/}
# POST /:project_id/compare(.:format) compare#create {id: /[^\/]+/, project_id: /[^\/]+/}
# project_compare /:project_id/compare/:from...:to(.:format) compare#show {from: /.+/, to: /.+/, id: /[^\/]+/, project_id: /[^\/]+/}
-describe Projects::CompareController, "routing" do
- it "to #index" do
- get("/gitlab/gitlabhq/compare").should route_to('projects/compare#index', project_id: 'gitlab/gitlabhq')
+describe Projects::CompareController, 'routing' do
+ it 'to #index' do
+ expect(get('/gitlab/gitlabhq/compare')).to route_to('projects/compare#index', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #compare" do
- post("/gitlab/gitlabhq/compare").should route_to('projects/compare#create', project_id: 'gitlab/gitlabhq')
+ it 'to #compare' do
+ expect(post('/gitlab/gitlabhq/compare')).to route_to('projects/compare#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
- it "to #show" do
- get("/gitlab/gitlabhq/compare/master...stable").should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'master', to: 'stable')
- get("/gitlab/gitlabhq/compare/issue/1234...stable").should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'issue/1234', to: 'stable')
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/compare/master...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'master', to: 'stable')
+ expect(get('/gitlab/gitlabhq/compare/issue/1234...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable')
end
end
-describe Projects::NetworkController, "routing" do
- it "to #show" do
- get("/gitlab/gitlabhq/network/master").should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master')
- get("/gitlab/gitlabhq/network/master.json").should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master', format: "json")
+describe Projects::NetworkController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
+ expect(get('/gitlab/gitlabhq/network/master.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
end
end
-describe Projects::GraphsController, "routing" do
- it "to #show" do
- get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master')
+describe Projects::GraphsController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
+ end
+end
+
+describe Projects::ForksController, 'routing' do
+ it 'to #new' do
+ expect(get('/gitlab/gitlabhq/fork/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ end
+
+ it 'to #create' do
+ expect(post('/gitlab/gitlabhq/fork')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq')
+ end
+end
+
+# project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy
+describe Projects::AvatarsController, 'routing' do
+ it 'to #destroy' do
+ expect(delete('/gitlab/gitlabhq/avatar')).to route_to(
+ 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 1e92cf62dd..d4915b5195 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
# search GET /search(.:format) search#show
describe SearchController, "routing" do
it "to #show" do
- get("/search").should route_to('search#show')
+ expect(get("/search")).to route_to('search#show')
end
end
@@ -11,11 +11,11 @@ end
# /:path Grack
describe "Mounted Apps", "routing" do
it "to API" do
- get("/api/issues").should be_routable
+ expect(get("/api/issues")).to be_routable
end
it "to Grack" do
- get("/gitlab/gitlabhq.git").should be_routable
+ expect(get("/gitlab/gitlabhq.git")).to be_routable
end
end
@@ -28,39 +28,39 @@ end
# DELETE /snippets/:id(.:format) snippets#destroy
describe SnippetsController, "routing" do
it "to #user_index" do
- get("/s/User").should route_to('snippets#user_index', username: 'User')
+ expect(get("/s/User")).to route_to('snippets#user_index', username: 'User')
end
it "to #raw" do
- get("/snippets/1/raw").should route_to('snippets#raw', id: '1')
+ expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1')
end
it "to #index" do
- get("/snippets").should route_to('snippets#index')
+ expect(get("/snippets")).to route_to('snippets#index')
end
it "to #create" do
- post("/snippets").should route_to('snippets#create')
+ expect(post("/snippets")).to route_to('snippets#create')
end
it "to #new" do
- get("/snippets/new").should route_to('snippets#new')
+ expect(get("/snippets/new")).to route_to('snippets#new')
end
it "to #edit" do
- get("/snippets/1/edit").should route_to('snippets#edit', id: '1')
+ expect(get("/snippets/1/edit")).to route_to('snippets#edit', id: '1')
end
it "to #show" do
- get("/snippets/1").should route_to('snippets#show', id: '1')
+ expect(get("/snippets/1")).to route_to('snippets#show', id: '1')
end
it "to #update" do
- put("/snippets/1").should route_to('snippets#update', id: '1')
+ expect(put("/snippets/1")).to route_to('snippets#update', id: '1')
end
it "to #destroy" do
- delete("/snippets/1").should route_to('snippets#destroy', id: '1')
+ expect(delete("/snippets/1")).to route_to('snippets#destroy', id: '1')
end
end
@@ -75,39 +75,39 @@ end
# help_raketasks GET /help/raketasks(.:format) help#raketasks
describe HelpController, "routing" do
it "to #index" do
- get("/help").should route_to('help#index')
+ expect(get("/help")).to route_to('help#index')
end
it "to #permissions" do
- get("/help/permissions/permissions").should route_to('help#show', category: "permissions", file: "permissions")
+ expect(get("/help/permissions/permissions")).to route_to('help#show', category: "permissions", file: "permissions")
end
it "to #workflow" do
- get("/help/workflow/README").should route_to('help#show', category: "workflow", file: "README")
+ expect(get("/help/workflow/README")).to route_to('help#show', category: "workflow", file: "README")
end
it "to #api" do
- get("/help/api/README").should route_to('help#show', category: "api", file: "README")
+ expect(get("/help/api/README")).to route_to('help#show', category: "api", file: "README")
end
it "to #web_hooks" do
- get("/help/web_hooks/web_hooks").should route_to('help#show', category: "web_hooks", file: "web_hooks")
+ expect(get("/help/web_hooks/web_hooks")).to route_to('help#show', category: "web_hooks", file: "web_hooks")
end
it "to #system_hooks" do
- get("/help/system_hooks/system_hooks").should route_to('help#show', category: "system_hooks", file: "system_hooks")
+ expect(get("/help/system_hooks/system_hooks")).to route_to('help#show', category: "system_hooks", file: "system_hooks")
end
it "to #markdown" do
- get("/help/markdown/markdown").should route_to('help#show',category: "markdown", file: "markdown")
+ expect(get("/help/markdown/markdown")).to route_to('help#show',category: "markdown", file: "markdown")
end
it "to #ssh" do
- get("/help/ssh/README").should route_to('help#show', category: "ssh", file: "README")
+ expect(get("/help/ssh/README")).to route_to('help#show', category: "ssh", file: "README")
end
it "to #raketasks" do
- get("/help/raketasks/README").should route_to('help#show', category: "raketasks", file: "README")
+ expect(get("/help/raketasks/README")).to route_to('help#show', category: "raketasks", file: "README")
end
end
@@ -121,23 +121,23 @@ end
# profile_update PUT /profile/update(.:format) profile#update
describe ProfilesController, "routing" do
it "to #account" do
- get("/profile/account").should route_to('profiles/accounts#show')
+ expect(get("/profile/account")).to route_to('profiles/accounts#show')
end
it "to #history" do
- get("/profile/history").should route_to('profiles#history')
+ expect(get("/profile/history")).to route_to('profiles#history')
end
it "to #reset_private_token" do
- put("/profile/reset_private_token").should route_to('profiles#reset_private_token')
+ expect(put("/profile/reset_private_token")).to route_to('profiles#reset_private_token')
end
it "to #show" do
- get("/profile").should route_to('profiles#show')
+ expect(get("/profile")).to route_to('profiles#show')
end
it "to #design" do
- get("/profile/design").should route_to('profiles#design')
+ expect(get("/profile/design")).to route_to('profiles#design')
end
end
@@ -150,36 +150,36 @@ end
# DELETE /keys/:id(.:format) keys#destroy
describe Profiles::KeysController, "routing" do
it "to #index" do
- get("/profile/keys").should route_to('profiles/keys#index')
+ expect(get("/profile/keys")).to route_to('profiles/keys#index')
end
it "to #create" do
- post("/profile/keys").should route_to('profiles/keys#create')
+ expect(post("/profile/keys")).to route_to('profiles/keys#create')
end
it "to #new" do
- get("/profile/keys/new").should route_to('profiles/keys#new')
+ expect(get("/profile/keys/new")).to route_to('profiles/keys#new')
end
it "to #edit" do
- get("/profile/keys/1/edit").should route_to('profiles/keys#edit', id: '1')
+ expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1')
end
it "to #show" do
- get("/profile/keys/1").should route_to('profiles/keys#show', id: '1')
+ expect(get("/profile/keys/1")).to route_to('profiles/keys#show', id: '1')
end
it "to #update" do
- put("/profile/keys/1").should route_to('profiles/keys#update', id: '1')
+ expect(put("/profile/keys/1")).to route_to('profiles/keys#update', id: '1')
end
it "to #destroy" do
- delete("/profile/keys/1").should route_to('profiles/keys#destroy', id: '1')
+ expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1')
end
# get all the ssh-keys of a user
it "to #get_keys" do
- get("/foo.keys").should route_to('profiles/keys#get_keys', username: 'foo')
+ expect(get("/foo.keys")).to route_to('profiles/keys#get_keys', username: 'foo')
end
end
@@ -188,22 +188,22 @@ end
# DELETE /keys/:id(.:format) keys#destroy
describe Profiles::EmailsController, "routing" do
it "to #index" do
- get("/profile/emails").should route_to('profiles/emails#index')
+ expect(get("/profile/emails")).to route_to('profiles/emails#index')
end
it "to #create" do
- post("/profile/emails").should route_to('profiles/emails#create')
+ expect(post("/profile/emails")).to route_to('profiles/emails#create')
end
it "to #destroy" do
- delete("/profile/emails/1").should route_to('profiles/emails#destroy', id: '1')
+ expect(delete("/profile/emails/1")).to route_to('profiles/emails#destroy', id: '1')
end
end
# profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy
describe Profiles::AvatarsController, "routing" do
it "to #destroy" do
- delete("/profile/avatar").should route_to('profiles/avatars#destroy')
+ expect(delete("/profile/avatar")).to route_to('profiles/avatars#destroy')
end
end
@@ -213,16 +213,16 @@ end
# root / dashboard#show
describe DashboardController, "routing" do
it "to #index" do
- get("/dashboard").should route_to('dashboard#show')
- get("/").should route_to('dashboard#show')
+ expect(get("/dashboard")).to route_to('dashboard#show')
+ expect(get("/")).to route_to('dashboard#show')
end
it "to #issues" do
- get("/dashboard/issues").should route_to('dashboard#issues')
+ expect(get("/dashboard/issues")).to route_to('dashboard#issues')
end
it "to #merge_requests" do
- get("/dashboard/merge_requests").should route_to('dashboard#merge_requests')
+ expect(get("/dashboard/merge_requests")).to route_to('dashboard#merge_requests')
end
end
@@ -241,11 +241,11 @@ end
describe "Groups", "routing" do
it "to #show" do
- get("/groups/1").should route_to('groups#show', id: '1')
+ expect(get("/groups/1")).to route_to('groups#show', id: '1')
end
it "also display group#show on the short path" do
- get('/1').should route_to('namespaces#show', id: '1')
+ expect(get('/1')).to route_to('namespaces#show', id: '1')
end
end
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
new file mode 100644
index 0000000000..08689c15ca
--- /dev/null
+++ b/spec/services/create_snippet_service_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe CreateSnippetService do
+ before do
+ @user = create :user
+ @admin = create :user, admin: true
+ @opts = {
+ title: 'Test snippet',
+ file_name: 'snippet.rb',
+ content: 'puts "hello world"',
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE
+ }
+ end
+
+ context 'When public visibility is restricted' do
+ before do
+ allow_any_instance_of(ApplicationSetting).to(
+ receive(:restricted_visibility_levels).and_return(
+ [Gitlab::VisibilityLevel::PUBLIC]
+ )
+ )
+
+ @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it 'non-admins should not be able to create a public snippet' do
+ snippet = create_snippet(nil, @user, @opts)
+ expect(snippet.errors.messages).to have_key(:visibility_level)
+ expect(snippet.errors.messages[:visibility_level].first).to(
+ match('Public visibility has been restricted')
+ )
+ end
+
+ it 'admins should be able to create a public snippet' do
+ snippet = create_snippet(nil, @admin, @opts)
+ expect(snippet.errors.any?).to be_falsey
+ expect(snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
+ def create_snippet(project, user, opts)
+ CreateSnippetService.new(project, user, opts).execute
+ end
+end
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 713aa3e7e7..007a9eed19 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -7,7 +7,7 @@ describe EventCreateService do
describe :open_issue do
let(:issue) { create(:issue) }
- it { service.open_issue(issue, issue.author).should be_true }
+ it { expect(service.open_issue(issue, issue.author)).to be_truthy }
it "should create new event" do
expect { service.open_issue(issue, issue.author) }.to change { Event.count }
@@ -17,7 +17,7 @@ describe EventCreateService do
describe :close_issue do
let(:issue) { create(:issue) }
- it { service.close_issue(issue, issue.author).should be_true }
+ it { expect(service.close_issue(issue, issue.author)).to be_truthy }
it "should create new event" do
expect { service.close_issue(issue, issue.author) }.to change { Event.count }
@@ -27,7 +27,7 @@ describe EventCreateService do
describe :reopen_issue do
let(:issue) { create(:issue) }
- it { service.reopen_issue(issue, issue.author).should be_true }
+ it { expect(service.reopen_issue(issue, issue.author)).to be_truthy }
it "should create new event" do
expect { service.reopen_issue(issue, issue.author) }.to change { Event.count }
@@ -39,7 +39,7 @@ describe EventCreateService do
describe :open_mr do
let(:merge_request) { create(:merge_request) }
- it { service.open_mr(merge_request, merge_request.author).should be_true }
+ it { expect(service.open_mr(merge_request, merge_request.author)).to be_truthy }
it "should create new event" do
expect { service.open_mr(merge_request, merge_request.author) }.to change { Event.count }
@@ -49,7 +49,7 @@ describe EventCreateService do
describe :close_mr do
let(:merge_request) { create(:merge_request) }
- it { service.close_mr(merge_request, merge_request.author).should be_true }
+ it { expect(service.close_mr(merge_request, merge_request.author)).to be_truthy }
it "should create new event" do
expect { service.close_mr(merge_request, merge_request.author) }.to change { Event.count }
@@ -59,7 +59,7 @@ describe EventCreateService do
describe :merge_mr do
let(:merge_request) { create(:merge_request) }
- it { service.merge_mr(merge_request, merge_request.author).should be_true }
+ it { expect(service.merge_mr(merge_request, merge_request.author)).to be_truthy }
it "should create new event" do
expect { service.merge_mr(merge_request, merge_request.author) }.to change { Event.count }
@@ -69,7 +69,7 @@ describe EventCreateService do
describe :reopen_mr do
let(:merge_request) { create(:merge_request) }
- it { service.reopen_mr(merge_request, merge_request.author).should be_true }
+ it { expect(service.reopen_mr(merge_request, merge_request.author)).to be_truthy }
it "should create new event" do
expect { service.reopen_mr(merge_request, merge_request.author) }.to change { Event.count }
@@ -83,7 +83,7 @@ describe EventCreateService do
describe :open_milestone do
let(:milestone) { create(:milestone) }
- it { service.open_milestone(milestone, user).should be_true }
+ it { expect(service.open_milestone(milestone, user)).to be_truthy }
it "should create new event" do
expect { service.open_milestone(milestone, user) }.to change { Event.count }
@@ -93,7 +93,7 @@ describe EventCreateService do
describe :close_mr do
let(:milestone) { create(:milestone) }
- it { service.close_milestone(milestone, user).should be_true }
+ it { expect(service.close_milestone(milestone, user)).to be_truthy }
it "should create new event" do
expect { service.close_milestone(milestone, user) }.to change { Event.count }
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 4ef053a767..1b1e3ca5f8 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -8,7 +8,7 @@ describe GitPushService do
let (:service) { GitPushService.new }
before do
- @blankrev = '0000000000000000000000000000000000000000'
+ @blankrev = Gitlab::Git::BLANK_SHA
@oldrev = sample_commit.parent_id
@newrev = sample_commit.id
@ref = 'refs/heads/master'
@@ -20,7 +20,7 @@ describe GitPushService do
service.execute(project, user, @blankrev, @newrev, @ref)
end
- it { should be_true }
+ it { is_expected.to be_truthy }
end
context 'existing branch' do
@@ -28,7 +28,7 @@ describe GitPushService do
service.execute(project, user, @oldrev, @newrev, @ref)
end
- it { should be_true }
+ it { is_expected.to be_truthy }
end
context 'rm branch' do
@@ -36,7 +36,7 @@ describe GitPushService do
service.execute(project, user, @oldrev, @blankrev, @ref)
end
- it { should be_true }
+ it { is_expected.to be_truthy }
end
end
@@ -49,41 +49,54 @@ describe GitPushService do
subject { @push_data }
- it { should include(before: @oldrev) }
- it { should include(after: @newrev) }
- it { should include(ref: @ref) }
- it { should include(user_id: user.id) }
- it { should include(user_name: user.name) }
- it { should include(project_id: project.id) }
+ it { is_expected.to include(object_kind: 'push') }
+ it { is_expected.to include(before: @oldrev) }
+ it { is_expected.to include(after: @newrev) }
+ it { is_expected.to include(ref: @ref) }
+ it { is_expected.to include(user_id: user.id) }
+ it { is_expected.to include(user_name: user.name) }
+ it { is_expected.to include(project_id: project.id) }
context "with repository data" do
subject { @push_data[:repository] }
- it { should include(name: project.name) }
- it { should include(url: project.url_to_repo) }
- it { should include(description: project.description) }
- it { should include(homepage: project.web_url) }
+ it { is_expected.to include(name: project.name) }
+ it { is_expected.to include(url: project.url_to_repo) }
+ it { is_expected.to include(description: project.description) }
+ it { is_expected.to include(homepage: project.web_url) }
end
context "with commits" do
subject { @push_data[:commits] }
- it { should be_an(Array) }
- it { should have(1).element }
+ it { is_expected.to be_an(Array) }
+ it 'has 1 element' do
+ expect(subject.size).to eq(1)
+ end
context "the commit" do
subject { @push_data[:commits].first }
- it { should include(id: @commit.id) }
- it { should include(message: @commit.safe_message) }
- it { should include(timestamp: @commit.date.xmlschema) }
- it { should include(url: "#{Gitlab.config.gitlab.url}/#{project.to_param}/commit/#{@commit.id}") }
+ it { is_expected.to include(id: @commit.id) }
+ it { is_expected.to include(message: @commit.safe_message) }
+ it { is_expected.to include(timestamp: @commit.date.xmlschema) }
+ it do
+ is_expected.to include(
+ url: [
+ Gitlab.config.gitlab.url,
+ project.namespace.to_param,
+ project.to_param,
+ 'commit',
+ @commit.id
+ ].join('/')
+ )
+ end
context "with a author" do
subject { @push_data[:commits].first[:author] }
- it { should include(name: @commit.author_name) }
- it { should include(email: @commit.author_email) }
+ it { is_expected.to include(name: @commit.author_name) }
+ it { is_expected.to include(email: @commit.author_email) }
end
end
end
@@ -95,28 +108,46 @@ describe GitPushService do
@event = Event.last
end
- it { @event.should_not be_nil }
- it { @event.project.should == project }
- it { @event.action.should == Event::PUSHED }
- it { @event.data.should == service.push_data }
+ it { expect(@event).not_to be_nil }
+ it { expect(@event.project).to eq(project) }
+ it { expect(@event.action).to eq(Event::PUSHED) }
+ it { expect(@event.data).to eq(service.push_data) }
end
describe "Web Hooks" do
context "execute web hooks" do
it "when pushing a branch for the first time" do
- project.should_receive(:execute_hooks)
- project.default_branch.should == "master"
- project.protected_branches.should_receive(:create).with({ name: "master" })
+ expect(project).to receive(:execute_hooks)
+ expect(project.default_branch).to eq("master")
+ expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false })
+ service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ end
+
+ it "when pushing a branch for the first time with default branch protection disabled" do
+ ApplicationSetting.any_instance.stub(default_branch_protection: 0)
+
+ expect(project).to receive(:execute_hooks)
+ expect(project.default_branch).to eq("master")
+ expect(project.protected_branches).not_to receive(:create)
+ service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ end
+
+ it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
+ ApplicationSetting.any_instance.stub(default_branch_protection: 1)
+
+ expect(project).to receive(:execute_hooks)
+ expect(project.default_branch).to eq("master")
+ expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true })
service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
end
it "when pushing new commits to existing branch" do
- project.should_receive(:execute_hooks)
+ expect(project).to receive(:execute_hooks)
service.execute(project, user, 'oldrev', 'newrev', 'refs/heads/master')
end
it "when pushing tags" do
- project.should_not_receive(:execute_hooks)
+ expect(project).not_to receive(:execute_hooks)
service.execute(project, user, 'newrev', 'newrev', 'refs/tags/v1.0.0')
end
end
@@ -138,7 +169,7 @@ describe GitPushService do
end
it "creates a note if a pushed commit mentions an issue" do
- Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
+ expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
service.execute(project, user, @oldrev, @newrev, @ref)
end
@@ -146,35 +177,26 @@ describe GitPushService do
it "only creates a cross-reference note if one doesn't already exist" do
Note.create_cross_reference_note(issue, commit, user, project)
- Note.should_not_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
+ expect(Note).not_to receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
service.execute(project, user, @oldrev, @newrev, @ref)
end
it "defaults to the pushing user if the commit's author is not known" do
commit.stub(author_name: 'unknown name', author_email: 'unknown@email.com')
- Note.should_receive(:create_cross_reference_note).with(issue, commit, user, project)
+ expect(Note).to receive(:create_cross_reference_note).with(issue, commit, user, project)
service.execute(project, user, @oldrev, @newrev, @ref)
end
it "finds references in the first push to a non-default branch" do
- project.repository.stub(:commits_between).with(@blankrev, @newrev).and_return([])
- project.repository.stub(:commits_between).with("master", @newrev).and_return([commit])
+ allow(project.repository).to receive(:commits_between).with(@blankrev, @newrev).and_return([])
+ allow(project.repository).to receive(:commits_between).with("master", @newrev).and_return([commit])
- Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
+ expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
service.execute(project, user, @blankrev, @newrev, 'refs/heads/other')
end
-
- it "finds references in the first push to a default branch" do
- project.repository.stub(:commits_between).with(@blankrev, @newrev).and_return([])
- project.repository.stub(:commits).with(@newrev).and_return([commit])
-
- Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project)
-
- service.execute(project, user, @blankrev, @newrev, 'refs/heads/master')
- end
end
describe "closing issues from pushed commits" do
@@ -197,7 +219,7 @@ describe GitPushService do
it "closes issues with commit messages" do
service.execute(project, user, @oldrev, @newrev, @ref)
- Issue.find(issue.id).should be_closed
+ expect(Issue.find(issue.id)).to be_closed
end
it "doesn't create cross-reference notes for a closing reference" do
@@ -214,7 +236,7 @@ describe GitPushService do
service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf')
}.not_to change { Note.where(project_id: project.id, system: true).count }
- Issue.find(issue.id).should be_opened
+ expect(Issue.find(issue.id)).to be_opened
end
end
end
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index e65a8204c5..fcf462edbf 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -19,27 +19,27 @@ describe GitTagPushService do
subject { @push_data }
- it { should include(ref: @ref) }
- it { should include(before: @oldrev) }
- it { should include(after: @newrev) }
- it { should include(user_id: user.id) }
- it { should include(user_name: user.name) }
- it { should include(project_id: project.id) }
+ it { is_expected.to include(ref: @ref) }
+ it { is_expected.to include(before: @oldrev) }
+ it { is_expected.to include(after: @newrev) }
+ it { is_expected.to include(user_id: user.id) }
+ it { is_expected.to include(user_name: user.name) }
+ it { is_expected.to include(project_id: project.id) }
context 'With repository data' do
subject { @push_data[:repository] }
- it { should include(name: project.name) }
- it { should include(url: project.url_to_repo) }
- it { should include(description: project.description) }
- it { should include(homepage: project.web_url) }
+ it { is_expected.to include(name: project.name) }
+ it { is_expected.to include(url: project.url_to_repo) }
+ it { is_expected.to include(description: project.description) }
+ it { is_expected.to include(homepage: project.web_url) }
end
end
describe "Web Hooks" do
context "execute web hooks" do
it "when pushing tags" do
- project.should_receive(:execute_hooks)
+ expect(project).to receive(:execute_hooks)
service.execute(project, user, 'oldrev', 'newrev', 'refs/tags/v1.0.0')
end
end
diff --git a/spec/services/issues/bulk_update_context_spec.rb b/spec/services/issues/bulk_update_context_spec.rb
deleted file mode 100644
index f4c9148f1a..0000000000
--- a/spec/services/issues/bulk_update_context_spec.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-require 'spec_helper'
-
-describe Issues::BulkUpdateService do
- let(:issue) {
- create(:issue, project: @project)
- }
-
- before do
- @user = create :user
- opts = {
- name: "GitLab",
- namespace: @user.namespace
- }
- @project = Projects::CreateService.new(@user, opts).execute
- end
-
- describe :close_issue do
-
- before do
- @issues = 5.times.collect do
- create(:issue, project: @project)
- end
- @params = {
- update: {
- status: 'closed',
- issues_ids: @issues.map(&:id)
- }
- }
- end
-
- it {
- result = Issues::BulkUpdateService.new(@project, @user, @params).execute
- result[:success].should be_true
- result[:count].should == @issues.count
-
- @project.issues.opened.should be_empty
- @project.issues.closed.should_not be_empty
- }
-
- end
-
- describe :reopen_issues do
-
- before do
- @issues = 5.times.collect do
- create(:closed_issue, project: @project)
- end
- @params = {
- update: {
- status: 'reopen',
- issues_ids: @issues.map(&:id)
- }
- }
- end
-
- it {
- result = Issues::BulkUpdateService.new(@project, @user, @params).execute
- result[:success].should be_true
- result[:count].should == @issues.count
-
- @project.issues.closed.should be_empty
- @project.issues.opened.should_not be_empty
- }
-
- end
-
- describe :update_assignee do
-
- before do
- @new_assignee = create :user
- @params = {
- update: {
- issues_ids: [issue.id],
- assignee_id: @new_assignee.id
- }
- }
- end
-
- it {
- result = Issues::BulkUpdateService.new(@project, @user, @params).execute
- result[:success].should be_true
- result[:count].should == 1
-
- @project.issues.first.assignee.should == @new_assignee
- }
-
- end
-
- describe :update_milestone do
-
- before do
- @milestone = create :milestone
- @params = {
- update: {
- issues_ids: [issue.id],
- milestone_id: @milestone.id
- }
- }
- end
-
- it {
- result = Issues::BulkUpdateService.new(@project, @user, @params).execute
- result[:success].should be_true
- result[:count].should == 1
-
- @project.issues.first.milestone.should == @milestone
- }
- end
-
-end
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
new file mode 100644
index 0000000000..a97c55011c
--- /dev/null
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -0,0 +1,121 @@
+require 'spec_helper'
+
+describe Issues::BulkUpdateService do
+ let(:issue) {
+ create(:issue, project: @project)
+ }
+
+ before do
+ @user = create :user
+ opts = {
+ name: "GitLab",
+ namespace: @user.namespace
+ }
+ @project = Projects::CreateService.new(@user, opts).execute
+ end
+
+ describe :close_issue do
+
+ before do
+ @issues = 5.times.collect do
+ create(:issue, project: @project)
+ end
+ @params = {
+ state_event: 'close',
+ issues_ids: @issues.map(&:id)
+ }
+ end
+
+ it {
+ result = Issues::BulkUpdateService.new(@project, @user, @params).execute
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(@issues.count)
+
+ expect(@project.issues.opened).to be_empty
+ expect(@project.issues.closed).not_to be_empty
+ }
+
+ end
+
+ describe :reopen_issues do
+
+ before do
+ @issues = 5.times.collect do
+ create(:closed_issue, project: @project)
+ end
+ @params = {
+ state_event: 'reopen',
+ issues_ids: @issues.map(&:id)
+ }
+ end
+
+ it {
+ result = Issues::BulkUpdateService.new(@project, @user, @params).execute
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(@issues.count)
+
+ expect(@project.issues.closed).to be_empty
+ expect(@project.issues.opened).not_to be_empty
+ }
+
+ end
+
+ describe :update_assignee do
+
+ before do
+ @new_assignee = create :user
+ @params = {
+ issues_ids: [issue.id],
+ assignee_id: @new_assignee.id
+ }
+ end
+
+ it {
+ result = Issues::BulkUpdateService.new(@project, @user, @params).execute
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(1)
+
+ expect(@project.issues.first.assignee).to eq(@new_assignee)
+ }
+
+ it 'allows mass-unassigning' do
+ @project.issues.first.update_attribute(:assignee, @new_assignee)
+ expect(@project.issues.first.assignee).not_to be_nil
+
+ @params[:assignee_id] = -1
+
+ Issues::BulkUpdateService.new(@project, @user, @params).execute
+ expect(@project.issues.first.assignee).to be_nil
+ end
+
+ it 'does not unassign when assignee_id is not present' do
+ @project.issues.first.update_attribute(:assignee, @new_assignee)
+ expect(@project.issues.first.assignee).not_to be_nil
+
+ @params[:assignee_id] = ''
+
+ Issues::BulkUpdateService.new(@project, @user, @params).execute
+ expect(@project.issues.first.assignee).not_to be_nil
+ end
+ end
+
+ describe :update_milestone do
+
+ before do
+ @milestone = create :milestone
+ @params = {
+ issues_ids: [issue.id],
+ milestone_id: @milestone.id
+ }
+ end
+
+ it {
+ result = Issues::BulkUpdateService.new(@project, @user, @params).execute
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(1)
+
+ expect(@project.issues.first.milestone).to eq(@milestone)
+ }
+ end
+
+end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index d4f2cc1339..d15dff1b52 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -17,18 +17,18 @@ describe Issues::CloseService do
@issue = Issues::CloseService.new(project, user, {}).execute(issue)
end
- it { @issue.should be_valid }
- it { @issue.should be_closed }
+ it { expect(@issue).to be_valid }
+ it { expect(@issue).to be_closed }
it 'should send email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
- email.to.first.should == user2.email
- email.subject.should include(issue.title)
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
end
it 'should create system note about issue reassign' do
note = @issue.notes.last
- note.note.should include "Status changed to closed"
+ expect(note.note).to include "Status changed to closed"
end
end
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 90720be5de..7f1ebcb319 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -16,8 +16,8 @@ describe Issues::CreateService do
@issue = Issues::CreateService.new(project, user, opts).execute
end
- it { @issue.should be_valid }
- it { @issue.title.should == 'Awesome issue' }
+ it { expect(@issue).to be_valid }
+ it { expect(@issue.title).to eq('Awesome issue') }
end
end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 347560414e..22b89bec96 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -5,6 +5,7 @@ describe Issues::UpdateService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:issue) { create(:issue) }
+ let(:label) { create(:label) }
before do
project.team << [user, :master]
@@ -18,26 +19,35 @@ describe Issues::UpdateService do
title: 'New title',
description: 'Also please fix',
assignee_id: user2.id,
- state_event: 'close'
+ state_event: 'close',
+ label_ids: [label.id]
}
@issue = Issues::UpdateService.new(project, user, opts).execute(issue)
+ @issue.reload
end
- it { @issue.should be_valid }
- it { @issue.title.should == 'New title' }
- it { @issue.assignee.should == user2 }
- it { @issue.should be_closed }
+ it { expect(@issue).to be_valid }
+ it { expect(@issue.title).to eq('New title') }
+ it { expect(@issue.assignee).to eq(user2) }
+ it { expect(@issue).to be_closed }
+ it { expect(@issue.labels.count).to eq(1) }
+ it { expect(@issue.labels.first.title).to eq('Bug') }
it 'should send email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
- email.to.first.should == user2.email
- email.subject.should include(issue.title)
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
end
it 'should create system note about issue reassign' do
note = @issue.notes.last
- note.note.should include "Reassigned to \@#{user2.username}"
+ expect(note.note).to include "Reassigned to \@#{user2.username}"
+ end
+
+ it 'should create system note about issue label edit' do
+ note = @issue.notes[1]
+ expect(note.note).to include "Added ~#{label.id} label"
end
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index a504f916b0..b3cbfd4b5b 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -12,23 +12,32 @@ describe MergeRequests::CloseService do
end
describe :execute do
- context "valid params" do
+ context 'valid params' do
+ let(:service) { MergeRequests::CloseService.new(project, user, {}) }
+
before do
- @merge_request = MergeRequests::CloseService.new(project, user, {}).execute(merge_request)
+ allow(service).to receive(:execute_hooks)
+
+ @merge_request = service.execute(merge_request)
end
- it { @merge_request.should be_valid }
- it { @merge_request.should be_closed }
+ it { expect(@merge_request).to be_valid }
+ it { expect(@merge_request).to be_closed }
+
+ it 'should execute hooks with close action' do
+ expect(service).to have_received(:execute_hooks).
+ with(@merge_request, 'close')
+ end
it 'should send email to user2 about assign of new merge_request' do
email = ActionMailer::Base.deliveries.last
- email.to.first.should == user2.email
- email.subject.should include(merge_request.title)
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(merge_request.title)
end
it 'should create system note about merge_request reassign' do
note = @merge_request.notes.last
- note.note.should include "Status changed to closed"
+ expect(note.note).to include 'Status changed to closed'
end
end
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index cebeb0644d..d9bfdf6430 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -5,21 +5,30 @@ describe MergeRequests::CreateService do
let(:user) { create(:user) }
describe :execute do
- context "valid params" do
- before do
- project.team << [user, :master]
- opts = {
+ context 'valid params' do
+ let(:opts) do
+ {
title: 'Awesome merge_request',
description: 'please fix',
source_branch: 'stable',
target_branch: 'master'
}
+ end
+ let(:service) { MergeRequests::CreateService.new(project, user, opts) }
- @merge_request = MergeRequests::CreateService.new(project, user, opts).execute
+ before do
+ project.team << [user, :master]
+ allow(service).to receive(:execute_hooks)
+
+ @merge_request = service.execute
end
- it { @merge_request.should be_valid }
- it { @merge_request.title.should == 'Awesome merge_request' }
+ it { expect(@merge_request).to be_valid }
+ it { expect(@merge_request.title).to eq('Awesome merge_request') }
+
+ it 'should execute hooks with default action' do
+ expect(service).to have_received(:execute_hooks).with(@merge_request)
+ end
end
end
end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
new file mode 100644
index 0000000000..0a25fb12f4
--- /dev/null
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe MergeRequests::MergeService do
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:merge_request) { create(:merge_request, assignee: user2) }
+ let(:project) { merge_request.project }
+
+ before do
+ project.team << [user, :master]
+ project.team << [user2, :developer]
+ end
+
+ describe :execute do
+ context 'valid params' do
+ let(:service) { MergeRequests::MergeService.new(project, user, {}) }
+
+ before do
+ allow(service).to receive(:execute_hooks)
+
+ service.execute(merge_request, 'Awesome message')
+ end
+
+ it { expect(merge_request).to be_valid }
+ it { expect(merge_request).to be_merged }
+
+ it 'should execute hooks with merge action' do
+ expect(service).to have_received(:execute_hooks).
+ with(merge_request, 'merge')
+ end
+
+ it 'should send email to user2 about merge of new merge_request' do
+ email = ActionMailer::Base.deliveries.last
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(merge_request.title)
+ end
+
+ it 'should create system note about merge_request merge' do
+ note = merge_request.notes.last
+ expect(note.note).to include 'Status changed to merged'
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
new file mode 100644
index 0000000000..879df0c9c6
--- /dev/null
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+
+describe MergeRequests::RefreshService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:service) { MergeRequests::RefreshService }
+
+ describe :execute do
+ before do
+ @user = create(:user)
+ group = create(:group)
+ group.add_owner(@user)
+
+ @project = create(:project, namespace: group)
+ @fork_project = Projects::ForkService.new(@project, @user).execute
+ @merge_request = create(:merge_request, source_project: @project,
+ source_branch: 'master',
+ target_branch: 'feature',
+ target_project: @project)
+
+ @fork_merge_request = create(:merge_request, source_project: @fork_project,
+ source_branch: 'master',
+ target_branch: 'feature',
+ target_project: @project)
+
+ @commits = @merge_request.commits
+
+ @oldrev = @commits.last.id
+ @newrev = @commits.first.id
+ end
+
+ context 'push to origin repo source branch' do
+ before do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+
+ it { expect(@merge_request.notes).not_to be_empty }
+ it { expect(@merge_request).to be_open }
+ it { expect(@fork_merge_request).to be_open }
+ it { expect(@fork_merge_request.notes).to be_empty }
+ end
+
+ context 'push to origin repo target branch' do
+ before do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it { expect(@merge_request.notes.last.note).to include('changed to merged') }
+ it { expect(@merge_request).to be_merged }
+ it { expect(@fork_merge_request).to be_merged }
+ it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') }
+ end
+
+ context 'push to fork repo source branch' do
+ before do
+ service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+
+ it { expect(@merge_request.notes).to be_empty }
+ it { expect(@merge_request).to be_open }
+ it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') }
+ it { expect(@fork_merge_request).to be_open }
+ end
+
+ context 'push to fork repo target branch' do
+ before do
+ service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it { expect(@merge_request.notes).to be_empty }
+ it { expect(@merge_request).to be_open }
+ it { expect(@fork_merge_request.notes).to be_empty }
+ it { expect(@fork_merge_request).to be_open }
+ end
+
+ context 'push to origin repo target branch after fork project was removed' do
+ before do
+ @fork_project.destroy
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it { expect(@merge_request.notes.last.note).to include('changed to merged') }
+ it { expect(@merge_request).to be_merged }
+ it { expect(@fork_merge_request).to be_open }
+ it { expect(@fork_merge_request.notes).to be_empty }
+ end
+
+ def reload_mrs
+ @merge_request.reload
+ @fork_merge_request.reload
+ end
+ end
+end
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
new file mode 100644
index 0000000000..9401bc3b55
--- /dev/null
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe MergeRequests::ReopenService do
+ let(:user) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:merge_request) { create(:merge_request, assignee: user2) }
+ let(:project) { merge_request.project }
+
+ before do
+ project.team << [user, :master]
+ project.team << [user2, :developer]
+ end
+
+ describe :execute do
+ context 'valid params' do
+ let(:service) { MergeRequests::ReopenService.new(project, user, {}) }
+
+ before do
+ allow(service).to receive(:execute_hooks)
+
+ merge_request.state = :closed
+ service.execute(merge_request)
+ end
+
+ it { expect(merge_request).to be_valid }
+ it { expect(merge_request).to be_reopened }
+
+ it 'should execute hooks with reopen action' do
+ expect(service).to have_received(:execute_hooks).
+ with(merge_request, 'reopen')
+ end
+
+ it 'should send email to user2 about reopen of merge_request' do
+ email = ActionMailer::Base.deliveries.last
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(merge_request.title)
+ end
+
+ it 'should create system note about merge_request reopen' do
+ note = merge_request.notes.last
+ expect(note.note).to include 'Status changed to reopened'
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index af5d3a3dc8..916b01e1c4 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -5,6 +5,7 @@ describe MergeRequests::UpdateService do
let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, :simple) }
let(:project) { merge_request.project }
+ let(:label) { create(:label) }
before do
project.team << [user, :master]
@@ -12,32 +13,52 @@ describe MergeRequests::UpdateService do
end
describe :execute do
- context "valid params" do
- before do
- opts = {
+ context 'valid params' do
+ let(:opts) do
+ {
title: 'New title',
description: 'Also please fix',
assignee_id: user2.id,
- state_event: 'close'
+ state_event: 'close',
+ label_ids: [label.id]
}
-
- @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
end
- it { @merge_request.should be_valid }
- it { @merge_request.title.should == 'New title' }
- it { @merge_request.assignee.should == user2 }
- it { @merge_request.should be_closed }
+ let(:service) { MergeRequests::UpdateService.new(project, user, opts) }
+
+ before do
+ allow(service).to receive(:execute_hooks)
+
+ @merge_request = service.execute(merge_request)
+ @merge_request.reload
+ end
+
+ it { expect(@merge_request).to be_valid }
+ it { expect(@merge_request.title).to eq('New title') }
+ it { expect(@merge_request.assignee).to eq(user2) }
+ it { expect(@merge_request).to be_closed }
+ it { expect(@merge_request.labels.count).to eq(1) }
+ it { expect(@merge_request.labels.first.title).to eq('Bug') }
+
+ it 'should execute hooks with update action' do
+ expect(service).to have_received(:execute_hooks).
+ with(@merge_request, 'update')
+ end
it 'should send email to user2 about assign of new merge_request' do
email = ActionMailer::Base.deliveries.last
- email.to.first.should == user2.email
- email.subject.should include(merge_request.title)
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(merge_request.title)
end
it 'should create system note about merge_request reassign' do
note = @merge_request.notes.last
- note.note.should include "Reassigned to \@#{user2.username}"
+ expect(note.note).to include "Reassigned to \@#{user2.username}"
+ end
+
+ it 'should create system note about merge_request label edit' do
+ note = @merge_request.notes[1]
+ expect(note.note).to include "Added ~#{label.id} label"
end
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index f59786efcf..1a02299bf1 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -18,8 +18,8 @@ describe Notes::CreateService do
@note = Notes::CreateService.new(project, user, opts).execute
end
- it { @note.should be_valid }
- it { @note.note.should == 'Awesome comment' }
+ it { expect(@note).to be_valid }
+ it { expect(@note.note).to eq('Awesome comment') }
end
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index f8377650e0..bfca2c8826 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -7,10 +7,10 @@ describe NotificationService do
describe :new_key do
let!(:key) { create(:personal_key) }
- it { notification.new_key(key).should be_true }
+ it { expect(notification.new_key(key)).to be_truthy }
it 'should sent email to key owner' do
- Notify.should_receive(:new_ssh_key_email).with(key.id)
+ expect(Notify).to receive(:new_ssh_key_email).with(key.id)
notification.new_key(key)
end
end
@@ -20,10 +20,10 @@ describe NotificationService do
describe :new_email do
let!(:email) { create(:email) }
- it { notification.new_email(email).should be_true }
+ it { expect(notification.new_email(email)).to be_truthy }
it 'should send email to email owner' do
- Notify.should_receive(:new_email_email).with(email.id)
+ expect(Notify).to receive(:new_email_email).with(email.id)
notification.new_email(email)
end
end
@@ -41,20 +41,25 @@ describe NotificationService do
describe :new_note do
it do
+ add_users_with_subscription(note.project, issue)
+
should_email(@u_watcher.id)
should_email(note.noteable.author_id)
should_email(note.noteable.assignee_id)
should_email(@u_mentioned.id)
+ should_email(@subscriber.id)
should_not_email(note.author_id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
+ should_not_email(@unsubscriber.id)
+
notification.new_note(note)
end
it 'filters out "mentioned in" notes' do
mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author, issue.project)
- Notify.should_not_receive(:note_issue_email)
+ expect(Notify).not_to receive(:note_issue_email)
notification.new_note(mentioned_note)
end
end
@@ -69,9 +74,9 @@ describe NotificationService do
user_project = note.project.project_members.find_by_user_id(@u_watcher.id)
user_project.notification_level = Notification::N_PARTICIPATING
user_project.save
- user_group = note.project.group.group_members.find_by_user_id(@u_watcher.id)
- user_group.notification_level = Notification::N_GLOBAL
- user_group.save
+ group_member = note.project.group.group_members.find_by_user_id(@u_watcher.id)
+ group_member.notification_level = Notification::N_GLOBAL
+ group_member.save
end
it do
@@ -87,11 +92,11 @@ describe NotificationService do
end
def should_email(user_id)
- Notify.should_receive(:note_issue_email).with(user_id, note.id)
+ expect(Notify).to receive(:note_issue_email).with(user_id, note.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:note_issue_email).with(user_id, note.id)
+ expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id)
end
end
@@ -116,6 +121,7 @@ describe NotificationService do
should_email(note.noteable.assignee_id)
should_not_email(note.author_id)
+ should_not_email(@u_mentioned.id)
should_not_email(@u_disabled.id)
should_not_email(@u_not_mentioned.id)
notification.new_note(note)
@@ -124,17 +130,17 @@ describe NotificationService do
it 'filters out "mentioned in" notes' do
mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author, issue.project)
- Notify.should_not_receive(:note_issue_email)
+ expect(Notify).not_to receive(:note_issue_email)
notification.new_note(mentioned_note)
end
end
def should_email(user_id)
- Notify.should_receive(:note_issue_email).with(user_id, note.id)
+ expect(Notify).to receive(:note_issue_email).with(user_id, note.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:note_issue_email).with(user_id, note.id)
+ expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id)
end
end
@@ -168,39 +174,54 @@ describe NotificationService do
notification.new_note(note)
end
+ it do
+ @u_committer.update_attributes(notification_level: Notification::N_MENTION)
+ should_not_email(@u_committer.id, note)
+ notification.new_note(note)
+ end
+
def should_email(user_id, n)
- Notify.should_receive(:note_commit_email).with(user_id, n.id)
+ expect(Notify).to receive(:note_commit_email).with(user_id, n.id)
end
def should_not_email(user_id, n)
- Notify.should_not_receive(:note_commit_email).with(user_id, n.id)
+ expect(Notify).not_to receive(:note_commit_email).with(user_id, n.id)
end
end
end
end
describe 'Issues' do
- let(:issue) { create :issue, assignee: create(:user) }
+ let(:issue) { create :issue, assignee: create(:user), description: 'cc @participant' }
before do
build_team(issue.project)
+ add_users_with_subscription(issue.project, issue)
end
describe :new_issue do
it do
should_email(issue.assignee_id)
should_email(@u_watcher.id)
+ should_email(@u_participant_mentioned.id)
+ should_not_email(@u_mentioned.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
notification.new_issue(issue, @u_disabled)
end
+ it do
+ issue.assignee.update_attributes(notification_level: Notification::N_MENTION)
+ should_not_email(issue.assignee_id)
+ notification.new_issue(issue, @u_disabled)
+ end
+
def should_email(user_id)
- Notify.should_receive(:new_issue_email).with(user_id, issue.id)
+ expect(Notify).to receive(:new_issue_email).with(user_id, issue.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:new_issue_email).with(user_id, issue.id)
+ expect(Notify).not_to receive(:new_issue_email).with(user_id, issue.id)
end
end
@@ -208,6 +229,9 @@ describe NotificationService do
it 'should email new assignee' do
should_email(issue.assignee_id)
should_email(@u_watcher.id)
+ should_email(@u_participant_mentioned.id)
+ should_email(@subscriber.id)
+ should_not_email(@unsubscriber.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
@@ -215,11 +239,11 @@ describe NotificationService do
end
def should_email(user_id)
- Notify.should_receive(:reassigned_issue_email).with(user_id, issue.id, nil, @u_disabled.id)
+ expect(Notify).to receive(:reassigned_issue_email).with(user_id, issue.id, nil, @u_disabled.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id)
+ expect(Notify).not_to receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id)
end
end
@@ -228,6 +252,9 @@ describe NotificationService do
should_email(issue.assignee_id)
should_email(issue.author_id)
should_email(@u_watcher.id)
+ should_email(@u_participant_mentioned.id)
+ should_email(@subscriber.id)
+ should_not_email(@unsubscriber.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
@@ -235,11 +262,11 @@ describe NotificationService do
end
def should_email(user_id)
- Notify.should_receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
+ expect(Notify).to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
+ expect(Notify).not_to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id)
end
end
@@ -248,6 +275,9 @@ describe NotificationService do
should_email(issue.assignee_id)
should_email(issue.author_id)
should_email(@u_watcher.id)
+ should_email(@u_participant_mentioned.id)
+ should_email(@subscriber.id)
+ should_not_email(@unsubscriber.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
@@ -255,11 +285,11 @@ describe NotificationService do
end
def should_email(user_id)
- Notify.should_receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id)
+ expect(Notify).to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id)
+ expect(Notify).not_to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id)
end
end
end
@@ -269,6 +299,7 @@ describe NotificationService do
before do
build_team(merge_request.target_project)
+ add_users_with_subscription(merge_request.target_project, merge_request)
end
describe :new_merge_request do
@@ -281,11 +312,11 @@ describe NotificationService do
end
def should_email(user_id)
- Notify.should_receive(:new_merge_request_email).with(user_id, merge_request.id)
+ expect(Notify).to receive(:new_merge_request_email).with(user_id, merge_request.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:new_merge_request_email).with(user_id, merge_request.id)
+ expect(Notify).not_to receive(:new_merge_request_email).with(user_id, merge_request.id)
end
end
@@ -293,17 +324,19 @@ describe NotificationService do
it do
should_email(merge_request.assignee_id)
should_email(@u_watcher.id)
+ should_email(@subscriber.id)
+ should_not_email(@unsubscriber.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
notification.reassigned_merge_request(merge_request, merge_request.author)
end
def should_email(user_id)
- Notify.should_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, nil, merge_request.author_id)
+ expect(Notify).to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, nil, merge_request.author_id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id)
+ expect(Notify).not_to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id)
end
end
@@ -311,17 +344,19 @@ describe NotificationService do
it do
should_email(merge_request.assignee_id)
should_email(@u_watcher.id)
+ should_email(@subscriber.id)
+ should_not_email(@unsubscriber.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
notification.close_mr(merge_request, @u_disabled)
end
def should_email(user_id)
- Notify.should_receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+ expect(Notify).to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+ expect(Notify).not_to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
end
end
@@ -329,17 +364,19 @@ describe NotificationService do
it do
should_email(merge_request.assignee_id)
should_email(@u_watcher.id)
+ should_email(@subscriber.id)
+ should_not_email(@unsubscriber.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
notification.merge_mr(merge_request, @u_disabled)
end
def should_email(user_id)
- Notify.should_receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+ expect(Notify).to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
+ expect(Notify).not_to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id)
end
end
@@ -347,17 +384,19 @@ describe NotificationService do
it do
should_email(merge_request.assignee_id)
should_email(@u_watcher.id)
+ should_email(@subscriber.id)
+ should_not_email(@unsubscriber.id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
notification.reopen_mr(merge_request, @u_disabled)
end
def should_email(user_id)
- Notify.should_receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id)
+ expect(Notify).to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id)
+ expect(Notify).not_to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id)
end
end
end
@@ -378,11 +417,11 @@ describe NotificationService do
end
def should_email(user_id)
- Notify.should_receive(:project_was_moved_email).with(project.id, user_id)
+ expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id)
end
def should_not_email(user_id)
- Notify.should_not_receive(:project_was_moved_email).with(project.id, user_id)
+ expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id)
end
end
end
@@ -390,8 +429,9 @@ describe NotificationService do
def build_team(project)
@u_watcher = create(:user, notification_level: Notification::N_WATCH)
@u_participating = create(:user, notification_level: Notification::N_PARTICIPATING)
+ @u_participant_mentioned = create(:user, username: 'participant', notification_level: Notification::N_PARTICIPATING)
@u_disabled = create(:user, notification_level: Notification::N_DISABLED)
- @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_PARTICIPATING)
+ @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION)
@u_committer = create(:user, username: 'committer')
@u_not_mentioned = create(:user, username: 'regular', notification_level: Notification::N_PARTICIPATING)
@@ -401,4 +441,15 @@ describe NotificationService do
project.team << [@u_mentioned, :master]
project.team << [@u_committer, :master]
end
+
+ def add_users_with_subscription(project, issuable)
+ @subscriber = create :user
+ @unsubscriber = create :user
+
+ project.team << [@subscriber, :master]
+ project.team << [@unsubscriber, :master]
+
+ issuable.subscriptions.create(user: @subscriber, subscribed: true)
+ issuable.subscriptions.create(user: @unsubscriber, subscribed: false)
+ end
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 9c97dad2ff..337dae592d 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -16,9 +16,9 @@ describe Projects::CreateService do
@project = create_project(@user, @opts)
end
- it { @project.should be_valid }
- it { @project.owner.should == @user }
- it { @project.namespace.should == @user.namespace }
+ it { expect(@project).to be_valid }
+ it { expect(@project.owner).to eq(@user) }
+ it { expect(@project.namespace).to eq(@user.namespace) }
end
context 'group namespace' do
@@ -30,9 +30,9 @@ describe Projects::CreateService do
@project = create_project(@user, @opts)
end
- it { @project.should be_valid }
- it { @project.owner.should == @group }
- it { @project.namespace.should == @group }
+ it { expect(@project).to be_valid }
+ it { expect(@project.owner).to eq(@group) }
+ it { expect(@project.namespace).to eq(@group) }
end
context 'wiki_enabled creates repository directory' do
@@ -42,7 +42,7 @@ describe Projects::CreateService do
@path = ProjectWiki.new(@project, @user).send(:path_to_repo)
end
- it { File.exists?(@path).should be_true }
+ it { expect(File.exists?(@path)).to be_truthy }
end
context 'wiki_enabled false does not create wiki repository directory' do
@@ -52,7 +52,34 @@ describe Projects::CreateService do
@path = ProjectWiki.new(@project, @user).send(:path_to_repo)
end
- it { File.exists?(@path).should be_false }
+ it { expect(File.exists?(@path)).to be_falsey }
+ end
+ end
+
+ context 'restricted visibility level' do
+ before do
+ allow_any_instance_of(ApplicationSetting).to(
+ receive(:restricted_visibility_levels).and_return([20])
+ )
+
+ @opts.merge!(
+ visibility_level: Gitlab::VisibilityLevel.options['Public']
+ )
+ end
+
+ it 'should not allow a restricted visibility level for non-admins' do
+ project = create_project(@user, @opts)
+ expect(project).to respond_to(:errors)
+ expect(project.errors.messages).to have_key(:visibility_level)
+ expect(project.errors.messages[:visibility_level].first).to(
+ match('restricted by your GitLab administrator')
+ )
+ end
+
+ it 'should allow a restricted visibility level for admins' do
+ project = create_project(@admin, @opts)
+ expect(project.errors.any?).to be(false)
+ expect(project.saved?).to be(true)
end
end
end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 0edc3a8e80..e55a2e3f8a 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -16,18 +16,18 @@ describe Projects::ForkService do
describe "successfully creates project in the user namespace" do
let(:to_project) { fork_project(@from_project, @to_user) }
- it { to_project.owner.should == @to_user }
- it { to_project.namespace.should == @to_user.namespace }
- it { to_project.star_count.should be_zero }
- it { to_project.description.should == @from_project.description }
+ it { expect(to_project.owner).to eq(@to_user) }
+ it { expect(to_project.namespace).to eq(@to_user.namespace) }
+ it { expect(to_project.star_count).to be_zero }
+ it { expect(to_project.description).to eq(@from_project.description) }
end
end
context 'fork project failure' do
it "fails due to transaction failure" do
@to_project = fork_project(@from_project, @to_user, false)
- @to_project.errors.should_not be_empty
- @to_project.errors[:base].should include("Fork transaction failed.")
+ expect(@to_project.errors).not_to be_empty
+ expect(@to_project.errors[:base]).to include("Fork transaction failed.")
end
end
@@ -35,17 +35,61 @@ describe Projects::ForkService do
it "should fail due to validation, not transaction failure" do
@existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace)
@to_project = fork_project(@from_project, @to_user)
- @existing_project.persisted?.should be_true
- @to_project.errors[:base].should include("Invalid fork destination")
- @to_project.errors[:base].should_not include("Fork transaction failed.")
+ expect(@existing_project.persisted?).to be_truthy
+ expect(@to_project.errors[:base]).to include("Invalid fork destination")
+ expect(@to_project.errors[:base]).not_to include("Fork transaction failed.")
end
end
end
- def fork_project(from_project, user, fork_success = true)
- context = Projects::ForkService.new(from_project, user)
- shell = double("gitlab_shell")
- shell.stub(fork_repository: fork_success)
+ describe :fork_to_namespace do
+ before do
+ @group_owner = create(:user)
+ @developer = create(:user)
+ @project = create(:project, creator_id: @group_owner.id,
+ star_count: 777,
+ description: 'Wow, such a cool project!')
+ @group = create(:group)
+ @group.add_user(@group_owner, GroupMember::OWNER)
+ @group.add_user(@developer, GroupMember::DEVELOPER)
+ @opts = { namespace: @group }
+ end
+
+ context 'fork project for group' do
+ it 'group owner successfully forks project into the group' do
+ to_project = fork_project(@project, @group_owner, true, @opts)
+ expect(to_project.owner).to eq(@group)
+ expect(to_project.namespace).to eq(@group)
+ expect(to_project.name).to eq(@project.name)
+ expect(to_project.path).to eq(@project.path)
+ expect(to_project.description).to eq(@project.description)
+ expect(to_project.star_count).to be_zero
+ end
+ end
+
+ context 'fork project for group when user not owner' do
+ it 'group developer should fail to fork project into the group' do
+ to_project = fork_project(@project, @developer, true, @opts)
+ expect(to_project.errors[:namespace]).to eq(['insufficient access rights'])
+ end
+ end
+
+ context 'project already exists in group' do
+ it 'should fail due to validation, not transaction failure' do
+ existing_project = create(:project, name: @project.name,
+ namespace: @group)
+ to_project = fork_project(@project, @group_owner, true, @opts)
+ expect(existing_project.persisted?).to be_truthy
+ expect(to_project.errors[:base]).to eq(['Invalid fork destination'])
+ expect(to_project.errors[:name]).to eq(['has already been taken'])
+ expect(to_project.errors[:path]).to eq(['has already been taken'])
+ end
+ end
+ end
+
+ def fork_project(from_project, user, fork_success = true, params = {})
+ context = Projects::ForkService.new(from_project, user, params)
+ shell = double('gitlab_shell').stub(fork_repository: fork_success)
context.stub(gitlab_shell: shell)
context.execute
end
diff --git a/spec/services/projects/image_service_spec.rb b/spec/services/projects/image_service_spec.rb
deleted file mode 100644
index 23c4e227ae..0000000000
--- a/spec/services/projects/image_service_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'spec_helper'
-
-describe Projects::ImageService do
- describe 'Image service' do
- before do
- @user = create :user
- @project = create :project, creator_id: @user.id, namespace: @user.namespace
- end
-
- context 'for valid gif file' do
- before do
- gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif')
- @link_to_image = upload_image(@project.repository, { 'markdown_img' => gif }, "http://test.example/")
- end
-
- it { expect(@link_to_image).to have_key("alt") }
- it { expect(@link_to_image).to have_key("url") }
- it { expect(@link_to_image).to have_value("banana_sample") }
- it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") }
- it { expect(@link_to_image["url"]).to match("banana_sample.gif") }
- end
-
- context 'for valid png file' do
- before do
- png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png')
- @link_to_image = upload_image(@project.repository, { 'markdown_img' => png }, "http://test.example/")
- end
-
- it { expect(@link_to_image).to have_key("alt") }
- it { expect(@link_to_image).to have_key("url") }
- it { expect(@link_to_image).to have_value("dk") }
- it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") }
- it { expect(@link_to_image["url"]).to match("dk.png") }
- end
-
- context 'for valid jpg file' do
- before do
- jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg')
- @link_to_image = upload_image(@project.repository, { 'markdown_img' => jpg }, "http://test.example/")
- end
-
- it { expect(@link_to_image).to have_key("alt") }
- it { expect(@link_to_image).to have_key("url") }
- it { expect(@link_to_image).to have_value("rails_sample") }
- it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") }
- it { expect(@link_to_image["url"]).to match("rails_sample.jpg") }
- end
-
- context 'for txt file' do
- before do
- txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain')
- @link_to_image = upload_image(@project.repository, { 'markdown_img' => txt }, "http://test.example/")
- end
-
- it { expect(@link_to_image).to be_nil }
- end
- end
-
- def upload_image(repository, params, root_url)
- Projects::ImageService.new(repository, params, root_url).execute
- end
-end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 2508dfc456..5650626fb1 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -3,41 +3,39 @@ require 'spec_helper'
describe Projects::TransferService do
let(:user) { create(:user) }
let(:group) { create(:group) }
- let(:group2) { create(:group) }
let(:project) { create(:project, namespace: user.namespace) }
context 'namespace -> namespace' do
before do
group.add_owner(user)
- @service = Projects::TransferService.new(project, user, namespace_id: group.id)
- @service.gitlab_shell.stub(mv_repository: true)
- @result = @service.execute
+ @result = transfer_project(project, user, new_namespace_id: group.id)
end
- it { @result.should be_true }
- it { project.namespace.should == group }
+ it { expect(@result).to be_truthy }
+ it { expect(project.namespace).to eq(group) }
end
context 'namespace -> no namespace' do
before do
- group.add_owner(user)
- @service = Projects::TransferService.new(project, user, namespace_id: nil)
- @service.gitlab_shell.stub(mv_repository: true)
- @result = @service.execute
+ @result = transfer_project(project, user, new_namespace_id: nil)
end
- it { @result.should be_false }
- it { project.namespace.should == user.namespace }
+ it { expect(@result).not_to be_nil } # { result.should be_false } passes on nil
+ it { expect(@result).to be_falsey }
+ it { expect(project.namespace).to eq(user.namespace) }
end
context 'namespace -> not allowed namespace' do
before do
- @service = Projects::TransferService.new(project, user, namespace_id: group2.id)
- @service.gitlab_shell.stub(mv_repository: true)
- @result = @service.execute
+ @result = transfer_project(project, user, new_namespace_id: group.id)
end
- it { @result.should be_false }
- it { project.namespace.should == user.namespace }
+ it { expect(@result).not_to be_nil } # { result.should be_false } passes on nil
+ it { expect(@result).to be_falsey }
+ it { expect(project.namespace).to eq(user.namespace) }
+ end
+
+ def transfer_project(project, user, params)
+ Projects::TransferService.new(project, user, params).execute
end
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 5a10174eb3..ea5b881310 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -17,8 +17,8 @@ describe Projects::UpdateService do
update_project(@project, @user, @opts)
end
- it { @created_private.should be_true }
- it { @project.private?.should be_true }
+ it { expect(@created_private).to be_truthy }
+ it { expect(@project.private?).to be_truthy }
end
context 'should be internal when updated to internal' do
@@ -29,8 +29,8 @@ describe Projects::UpdateService do
update_project(@project, @user, @opts)
end
- it { @created_private.should be_true }
- it { @project.internal?.should be_true }
+ it { expect(@created_private).to be_truthy }
+ it { expect(@project.internal?).to be_truthy }
end
context 'should be public when updated to public' do
@@ -41,15 +41,15 @@ describe Projects::UpdateService do
update_project(@project, @user, @opts)
end
- it { @created_private.should be_true }
- it { @project.public?.should be_true }
+ it { expect(@created_private).to be_truthy }
+ it { expect(@project.public?).to be_truthy }
end
context 'respect configured visibility restrictions setting' do
before(:each) do
- @restrictions = double("restrictions")
- @restrictions.stub(:restricted_visibility_levels) { [ "public" ] }
- Settings.stub_chain(:gitlab).and_return(@restrictions)
+ allow_any_instance_of(ApplicationSetting).to(
+ receive(:restricted_visibility_levels).and_return([20])
+ )
end
context 'should be private when updated to private' do
@@ -60,8 +60,8 @@ describe Projects::UpdateService do
update_project(@project, @user, @opts)
end
- it { @created_private.should be_true }
- it { @project.private?.should be_true }
+ it { expect(@created_private).to be_truthy }
+ it { expect(@project.private?).to be_truthy }
end
context 'should be internal when updated to internal' do
@@ -72,8 +72,8 @@ describe Projects::UpdateService do
update_project(@project, @user, @opts)
end
- it { @created_private.should be_true }
- it { @project.internal?.should be_true }
+ it { expect(@created_private).to be_truthy }
+ it { expect(@project.internal?).to be_truthy }
end
context 'should be private when updated to public' do
@@ -84,8 +84,8 @@ describe Projects::UpdateService do
update_project(@project, @user, @opts)
end
- it { @created_private.should be_true }
- it { @project.private?.should be_true }
+ it { expect(@created_private).to be_truthy }
+ it { expect(@project.private?).to be_truthy }
end
context 'should be public when updated to public by admin' do
@@ -96,8 +96,8 @@ describe Projects::UpdateService do
update_project(@project, @admin, @opts)
end
- it { @created_private.should be_true }
- it { @project.public?.should be_true }
+ it { expect(@created_private).to be_truthy }
+ it { expect(@project.public?).to be_truthy }
end
end
end
diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb
new file mode 100644
index 0000000000..fc34b45648
--- /dev/null
+++ b/spec/services/projects/upload_service_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Projects::UploadService do
+ describe 'File service' do
+ before do
+ @user = create :user
+ @project = create :project, creator_id: @user.id, namespace: @user.namespace
+ end
+
+ context 'for valid gif file' do
+ before do
+ gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif')
+ @link_to_file = upload_file(@project.repository, gif)
+ end
+
+ it { expect(@link_to_file).to have_key('alt') }
+ it { expect(@link_to_file).to have_key('url') }
+ it { expect(@link_to_file).to have_key('is_image') }
+ it { expect(@link_to_file).to have_value('banana_sample') }
+ it { expect(@link_to_file['is_image']).to equal(true) }
+ it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
+ it { expect(@link_to_file['url']).to match('banana_sample.gif') }
+ end
+
+ context 'for valid png file' do
+ before do
+ png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png',
+ 'image/png')
+ @link_to_file = upload_file(@project.repository, png)
+ end
+
+ it { expect(@link_to_file).to have_key('alt') }
+ it { expect(@link_to_file).to have_key('url') }
+ it { expect(@link_to_file).to have_value('dk') }
+ it { expect(@link_to_file).to have_key('is_image') }
+ it { expect(@link_to_file['is_image']).to equal(true) }
+ it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
+ it { expect(@link_to_file['url']).to match('dk.png') }
+ end
+
+ context 'for valid jpg file' do
+ before do
+ jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg')
+ @link_to_file = upload_file(@project.repository, jpg)
+ end
+
+ it { expect(@link_to_file).to have_key('alt') }
+ it { expect(@link_to_file).to have_key('url') }
+ it { expect(@link_to_file).to have_key('is_image') }
+ it { expect(@link_to_file).to have_value('rails_sample') }
+ it { expect(@link_to_file['is_image']).to equal(true) }
+ it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
+ it { expect(@link_to_file['url']).to match('rails_sample.jpg') }
+ end
+
+ context 'for txt file' do
+ before do
+ txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain')
+ @link_to_file = upload_file(@project.repository, txt)
+ end
+
+ it { expect(@link_to_file).to have_key('alt') }
+ it { expect(@link_to_file).to have_key('url') }
+ it { expect(@link_to_file).to have_key('is_image') }
+ it { expect(@link_to_file).to have_value('doc_sample.txt') }
+ it { expect(@link_to_file['is_image']).to equal(false) }
+ it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
+ it { expect(@link_to_file['url']).to match('doc_sample.txt') }
+ end
+ end
+
+ def upload_file(repository, file)
+ Projects::UploadService.new(repository, file).execute
+ end
+end
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 3217c571e6..f57bfaea87 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -19,7 +19,7 @@ describe 'Search::GlobalService' do
it 'should return public projects only' do
context = Search::GlobalService.new(nil, search: "searchable")
results = context.execute
- results.objects('projects').should match_array [public_project]
+ expect(results.objects('projects')).to match_array [public_project]
end
end
@@ -27,19 +27,19 @@ describe 'Search::GlobalService' do
it 'should return public, internal and private projects' do
context = Search::GlobalService.new(user, search: "searchable")
results = context.execute
- results.objects('projects').should match_array [public_project, found_project, internal_project]
+ expect(results.objects('projects')).to match_array [public_project, found_project, internal_project]
end
it 'should return only public & internal projects' do
context = Search::GlobalService.new(internal_user, search: "searchable")
results = context.execute
- results.objects('projects').should match_array [internal_project, public_project]
+ expect(results.objects('projects')).to match_array [internal_project, public_project]
end
it 'namespace name should be searchable' do
context = Search::GlobalService.new(user, search: found_project.namespace.path)
results = context.execute
- results.objects('projects').should match_array [found_project]
+ expect(results.objects('projects')).to match_array [found_project]
end
end
end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index 573446d3a1..199ac99660 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -5,27 +5,58 @@ describe SystemHooksService do
let (:project) { create :project }
let (:project_member) { create :project_member }
let (:key) { create(:key, user: user) }
+ let (:group) { create(:group) }
+ let (:group_member) { create(:group_member) }
context 'event data' do
- it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) }
- it { event_data(user, :destroy).should include(:event_name, :name, :created_at, :email, :user_id) }
- it { event_data(project, :create).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { event_data(project, :destroy).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { event_data(project_member, :create).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
- it { event_data(project_member, :destroy).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
- it { event_data(key, :create).should include(:username, :key, :id) }
- it { event_data(key, :destroy).should include(:username, :key, :id) }
+ it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :email, :user_id) }
+ it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :email, :user_id) }
+ it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
+ it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
+ it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
+ it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
+ it { expect(event_data(key, :create)).to include(:username, :key, :id) }
+ it { expect(event_data(key, :destroy)).to include(:username, :key, :id) }
+
+ it do
+ expect(event_data(group, :create)).to include(
+ :event_name, :name, :created_at, :path, :group_id, :owner_name,
+ :owner_email
+ )
+ end
+ it do
+ expect(event_data(group, :destroy)).to include(
+ :event_name, :name, :created_at, :path, :group_id, :owner_name,
+ :owner_email
+ )
+ end
+ it do
+ expect(event_data(group_member, :create)).to include(
+ :event_name, :created_at, :group_name, :group_path, :group_id, :user_id,
+ :user_name, :user_email, :group_access
+ )
+ end
+ it do
+ expect(event_data(group_member, :destroy)).to include(
+ :event_name, :created_at, :group_name, :group_path, :group_id, :user_id,
+ :user_name, :user_email, :group_access
+ )
+ end
end
context 'event names' do
- it { event_name(user, :create).should eq "user_create" }
- it { event_name(user, :destroy).should eq "user_destroy" }
- it { event_name(project, :create).should eq "project_create" }
- it { event_name(project, :destroy).should eq "project_destroy" }
- it { event_name(project_member, :create).should eq "user_add_to_team" }
- it { event_name(project_member, :destroy).should eq "user_remove_from_team" }
- it { event_name(key, :create).should eq 'key_create' }
- it { event_name(key, :destroy).should eq 'key_destroy' }
+ it { expect(event_name(user, :create)).to eq "user_create" }
+ it { expect(event_name(user, :destroy)).to eq "user_destroy" }
+ it { expect(event_name(project, :create)).to eq "project_create" }
+ it { expect(event_name(project, :destroy)).to eq "project_destroy" }
+ it { expect(event_name(project_member, :create)).to eq "user_add_to_team" }
+ it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" }
+ it { expect(event_name(key, :create)).to eq 'key_create' }
+ it { expect(event_name(key, :destroy)).to eq 'key_destroy' }
+ it { expect(event_name(group, :create)).to eq 'group_create' }
+ it { expect(event_name(group, :destroy)).to eq 'group_destroy' }
+ it { expect(event_name(group_member, :create)).to eq 'user_add_to_group' }
+ it { expect(event_name(group_member, :destroy)).to eq 'user_remove_from_group' }
end
def event_data(*args)
diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb
index 76af5bf7b8..d2b505f55a 100644
--- a/spec/services/test_hook_service_spec.rb
+++ b/spec/services/test_hook_service_spec.rb
@@ -8,7 +8,7 @@ describe TestHookService do
describe :execute do
it "should execute successfully" do
stub_request(:post, hook.url).to_return(status: 200)
- TestHookService.new.execute(hook, user).should be_true
+ expect(TestHookService.new.execute(hook, user)).to be_truthy
end
end
end
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
new file mode 100644
index 0000000000..841ef9bfed
--- /dev/null
+++ b/spec/services/update_snippet_service_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe UpdateSnippetService do
+ before do
+ @user = create :user
+ @admin = create :user, admin: true
+ @opts = {
+ title: 'Test snippet',
+ file_name: 'snippet.rb',
+ content: 'puts "hello world"',
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE
+ }
+ end
+
+ context 'When public visibility is restricted' do
+ before do
+ allow_any_instance_of(ApplicationSetting).to(
+ receive(:restricted_visibility_levels).and_return(
+ [Gitlab::VisibilityLevel::PUBLIC]
+ )
+ )
+
+ @snippet = create_snippet(@project, @user, @opts)
+ @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it 'non-admins should not be able to update to public visibility' do
+ old_visibility = @snippet.visibility_level
+ update_snippet(@project, @user, @snippet, @opts)
+ expect(@snippet.errors.messages).to have_key(:visibility_level)
+ expect(@snippet.errors.messages[:visibility_level].first).to(
+ match('Public visibility has been restricted')
+ )
+ expect(@snippet.visibility_level).to eq(old_visibility)
+ end
+
+ it 'admins should be able to update to pubic visibility' do
+ old_visibility = @snippet.visibility_level
+ update_snippet(@project, @admin, @snippet, @opts)
+ expect(@snippet.visibility_level).not_to eq(old_visibility)
+ expect(@snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
+ def create_snippet(project, user, opts)
+ CreateSnippetService.new(project, user, opts).execute
+ end
+
+ def update_snippet(project = nil, user, snippet, opts)
+ UpdateSnippetService.new(project, user, snippet, opts).execute
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 773de6628b..eaec2198dc 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -37,6 +37,8 @@ RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.include TestEnv
+ config.infer_spec_type_from_file_location!
+ config.raise_errors_for_deprecations!
config.before(:suite) do
TestEnv.init
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index d2d532d973..cca7652093 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -36,4 +36,15 @@ RSpec.configure do |config|
config.after(:each) do
DatabaseCleaner.clean
end
+
+ # rspec-rails 3 will no longer automatically infer an example group's spec type
+ # from the file location. You can explicitly opt-in to the feature using this
+ # config option.
+ # To explicitly tag specs without using automatic inference, set the `:type`
+ # metadata manually:
+ #
+ # describe ThingsController, :type => :controller do
+ # # Equivalent to being in spec/controllers
+ # end
+ config.infer_spec_type_from_file_location!
end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 692834c9f2..305592fa5a 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -30,16 +30,16 @@ def common_mentionable_setup
"!#{mentioned_mr.iid}, " +
"#{ext_proj.path_with_namespace}##{ext_issue.iid}, " +
"#{ext_proj.path_with_namespace}!#{ext_mr.iid}, " +
- "#{ext_proj.path_with_namespace}@#{ext_commit.id[0..5]}, " +
- "#{mentioned_commit.sha[0..5]} and itself as #{backref_text}"
+ "#{ext_proj.path_with_namespace}@#{ext_commit.short_id}, " +
+ "#{mentioned_commit.sha[0..10]} and itself as #{backref_text}"
end
before do
# Wire the project's repository to return the mentioned commit, and +nil+ for any
# unrecognized commits.
- commitmap = { '123456' => mentioned_commit }
- extra_commits.each { |c| commitmap[c.sha[0..5]] = c }
- mproject.repository.stub(:commit) { |sha| commitmap[sha] }
+ commitmap = { '1234567890a' => mentioned_commit }
+ extra_commits.each { |c| commitmap[c.short_id] = c }
+ allow(mproject.repository).to receive(:commit) { |sha| commitmap[sha] }
set_mentionable_text.call(ref_string)
end
end
@@ -48,20 +48,19 @@ shared_examples 'a mentionable' do
common_mentionable_setup
it 'generates a descriptive back-reference' do
- subject.gfm_reference.should == backref_text
+ expect(subject.gfm_reference).to eq(backref_text)
end
it "extracts references from its reference property" do
# De-duplicate and omit itself
refs = subject.references(mproject)
-
- refs.should have(6).items
- refs.should include(mentioned_issue)
- refs.should include(mentioned_mr)
- refs.should include(mentioned_commit)
- refs.should include(ext_issue)
- refs.should include(ext_mr)
- refs.should include(ext_commit)
+ expect(refs.size).to eq(6)
+ expect(refs).to include(mentioned_issue)
+ expect(refs).to include(mentioned_mr)
+ expect(refs).to include(mentioned_commit)
+ expect(refs).to include(ext_issue)
+ expect(refs).to include(ext_mr)
+ expect(refs).to include(ext_commit)
end
it 'creates cross-reference notes' do
@@ -69,7 +68,7 @@ shared_examples 'a mentionable' do
ext_issue, ext_mr, ext_commit]
mentioned_objects.each do |referenced|
- Note.should_receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject)
+ expect(Note).to receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject)
end
subject.create_cross_references!(mproject, mauthor)
@@ -78,8 +77,8 @@ shared_examples 'a mentionable' do
it 'detects existing cross-references' do
Note.create_cross_reference_note(mentioned_issue, subject.local_reference, mauthor, mproject)
- subject.has_mentioned?(mentioned_issue).should be_true
- subject.has_mentioned?(mentioned_mr).should be_false
+ expect(subject.has_mentioned?(mentioned_issue)).to be_truthy
+ expect(subject.has_mentioned?(mentioned_mr)).to be_falsey
end
end
@@ -90,18 +89,18 @@ shared_examples 'an editable mentionable' do
it 'creates new cross-reference notes when the mentionable text is edited' do
new_text = "still mentions ##{mentioned_issue.iid}, " +
- "#{mentioned_commit.sha[0..5]}, " +
+ "#{mentioned_commit.sha[0..10]}, " +
"#{ext_issue.iid}, " +
"new refs: ##{other_issue.iid}, " +
"#{ext_proj.path_with_namespace}##{other_ext_issue.iid}"
[mentioned_issue, mentioned_commit, ext_issue].each do |oldref|
- Note.should_not_receive(:create_cross_reference_note).with(oldref, subject.local_reference,
+ expect(Note).not_to receive(:create_cross_reference_note).with(oldref, subject.local_reference,
mauthor, mproject)
end
[other_issue, other_ext_issue].each do |newref|
- Note.should_receive(:create_cross_reference_note).with(
+ expect(Note).to receive(:create_cross_reference_note).with(
newref,
subject.local_reference,
mauthor,
diff --git a/spec/support/repo_helpers.rb b/spec/support/repo_helpers.rb
index 4c4775da69..aadf791bf3 100644
--- a/spec/support/repo_helpers.rb
+++ b/spec/support/repo_helpers.rb
@@ -43,6 +43,25 @@ eos
)
end
+ def another_sample_commit
+ OpenStruct.new(
+ id: "e56497bb5f03a90a51293fc6d516788730953899",
+ parent_id: '4cd80ccab63c82b4bad16faa5193fbd2aa06df40',
+ author_full_name: "Sytse Sijbrandij",
+ author_email: "sytse@gitlab.com",
+ files_changed_count: 1,
+ message: < 'e56497b',
+ 'feature' => '0b4bc9a',
+ 'feature_conflict' => 'bb5206f',
+ 'fix' => '12d65c8',
+ 'improve/awesome' => '5937ac0',
+ 'markdown' => '0ed8c6c',
+ 'master' => '5937ac0'
+ }
+
# Test environment
#
# See gitlab.yml.example test section for paths
#
def init(opts = {})
- RSpec::Mocks::setup(self)
-
# Disable mailer for spinach tests
disable_mailer if opts[:mailer] == false
- # Clean /tmp/tests
- tmp_test_path = Rails.root.join('tmp', 'tests')
+ clean_test_path
- if File.directory?(tmp_test_path)
- Dir.entries(tmp_test_path).each do |entry|
- unless ['.', '..', 'gitlab-shell'].include?(entry)
- FileUtils.rm_r(File.join(tmp_test_path, entry))
- end
- end
- end
-
- FileUtils.mkdir_p(tmp_test_path)
+ FileUtils.mkdir_p(repos_path)
# Setup GitLab shell for test instance
setup_gitlab_shell
@@ -38,24 +38,58 @@ module TestEnv
end
def enable_mailer
- NotificationService.any_instance.unstub(:mailer)
+ allow_any_instance_of(NotificationService).to receive(:mailer).and_call_original
+ end
+
+ # Clean /tmp/tests
+ #
+ # Keeps gitlab-shell and gitlab-test
+ def clean_test_path
+ tmp_test_path = Rails.root.join('tmp', 'tests', '**')
+
+ Dir[tmp_test_path].each do |entry|
+ unless File.basename(entry) =~ /\Agitlab-(shell|test)\z/
+ FileUtils.rm_rf(entry)
+ end
+ end
end
def setup_gitlab_shell
- `rake gitlab:shell:install`
+ unless File.directory?(Rails.root.join(*%w(tmp tests gitlab-shell)))
+ `rake gitlab:shell:install`
+ end
end
def setup_factory_repo
clone_url = "https://gitlab.com/gitlab-org/#{factory_repo_name}.git"
unless File.directory?(factory_repo_path)
- git_cmd = %W(git clone --bare #{clone_url} #{factory_repo_path})
- system(*git_cmd)
+ system(*%W(git clone -q #{clone_url} #{factory_repo_path}))
end
+
+ Dir.chdir(factory_repo_path) do
+ BRANCH_SHA.each do |branch, sha|
+ # Try to reset without fetching to avoid using the network.
+ reset = %W(git update-ref refs/heads/#{branch} #{sha})
+ unless system(*reset)
+ if system(*%w(git fetch origin))
+ unless system(*reset)
+ raise 'The fetched test seed '\
+ 'does not contain the required revision.'
+ end
+ else
+ raise 'Could not fetch test seed repository.'
+ end
+ end
+ end
+ end
+
+ # We must copy bare repositories because we will push to them.
+ system(*%W(git clone -q --bare #{factory_repo_path} #{factory_repo_path_bare}))
end
def copy_repo(project)
- base_repo_path = File.expand_path(factory_repo_path)
+ base_repo_path = File.expand_path(factory_repo_path_bare)
target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git")
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
@@ -69,7 +103,11 @@ module TestEnv
private
def factory_repo_path
- @factory_repo_path ||= repos_path + "/root/#{factory_repo_name}.git"
+ @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
+ end
+
+ def factory_repo_path_bare
+ "#{factory_repo_path}_bare"
end
def factory_repo_name
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 71a45eb2fa..60942cc95f 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -13,7 +13,7 @@ describe 'gitlab:app namespace rake task' do
describe 'backup_restore' do
before do
# avoid writing task output to spec progress
- $stdout.stub :write
+ allow($stdout).to receive :write
end
let :run_rake_task do
@@ -24,7 +24,7 @@ describe 'gitlab:app namespace rake task' do
context 'gitlab version' do
before do
Dir.stub glob: []
- Dir.stub :chdir
+ allow(Dir).to receive :chdir
File.stub exists?: true
Kernel.stub system: true
FileUtils.stub cp_r: true
@@ -41,9 +41,9 @@ describe 'gitlab:app namespace rake task' do
it 'should invoke restoration on mach' do
YAML.stub load_file: {gitlab_version: gitlab_version}
- Rake::Task["gitlab:backup:db:restore"].should_receive :invoke
- Rake::Task["gitlab:backup:repo:restore"].should_receive :invoke
- Rake::Task["gitlab:shell:setup"].should_receive :invoke
+ expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke
+ expect(Rake::Task["gitlab:backup:repo:restore"]).to receive :invoke
+ expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke
expect { run_rake_task }.to_not raise_error
end
end
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
new file mode 100644
index 0000000000..22e746870d
--- /dev/null
+++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+require 'rake'
+
+describe 'gitlab:mail_google_schema_whitelisting rake task' do
+ before :all do
+ Rake.application.rake_require "tasks/gitlab/task_helpers"
+ Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting"
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ describe 'call' do
+ before do
+ # avoid writing task output to spec progress
+ allow($stdout).to receive :write
+ end
+
+ let :run_rake_task do
+ Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable
+ Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
+ end
+
+ it 'should run the task without errors' do
+ expect { run_rake_task }.to_not raise_error
+ end
+ end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 4273fd1019..df1a2b84a5 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -1,9 +1,13 @@
require 'spec_helper'
describe PostReceive do
+ let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
+ let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
+ let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
+
context "as a resque worker" do
it "reponds to #perform" do
- PostReceive.new.should respond_to(:perform)
+ expect(PostReceive.new).to respond_to(:perform)
end
end
@@ -13,33 +17,29 @@ describe PostReceive do
let(:key_id) { key.shell_id }
it "fetches the correct project" do
- Project.should_receive(:find_with_namespace).with(project.path_with_namespace).and_return(project)
- PostReceive.new.perform(pwd(project), key_id, changes)
+ expect(Project).to receive(:find_with_namespace).with(project.path_with_namespace).and_return(project)
+ PostReceive.new.perform(pwd(project), key_id, base64_changes)
end
it "does not run if the author is not in the project" do
- Key.stub(:find_by).with(hash_including(id: anything())) { nil }
+ allow(Key).to receive(:find_by).with(hash_including(id: anything())) { nil }
- project.should_not_receive(:execute_hooks)
+ expect(project).not_to receive(:execute_hooks)
- PostReceive.new.perform(pwd(project), key_id, changes).should be_false
+ expect(PostReceive.new.perform(pwd(project), key_id, base64_changes)).to be_falsey
end
it "asks the project to trigger all hooks" do
Project.stub(find_with_namespace: project)
- project.should_receive(:execute_hooks)
- project.should_receive(:execute_services)
- project.should_receive(:update_merge_requests)
+ expect(project).to receive(:execute_hooks).twice
+ expect(project).to receive(:execute_services).twice
+ expect(project).to receive(:update_merge_requests)
- PostReceive.new.perform(pwd(project), key_id, changes)
+ PostReceive.new.perform(pwd(project), key_id, base64_changes)
end
end
def pwd(project)
File.join(Gitlab.config.gitlab_shell.repos_path, project.path_with_namespace)
end
-
- def changes
- 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master'
- end
end
diff --git a/vendor/assets/javascripts/chart-lib.min.js b/vendor/assets/javascripts/chart-lib.min.js
old mode 100755
new mode 100644
diff --git a/vendor/assets/javascripts/highlight.pack.js b/vendor/assets/javascripts/highlight.pack.js
deleted file mode 100644
index c09eac02df..0000000000
--- a/vendor/assets/javascripts/highlight.pack.js
+++ /dev/null
@@ -1 +0,0 @@
-var hljs=new function(){function j(v){return v.replace(/&/gm,"&").replace(//gm,">")}function t(v){return v.nodeName.toLowerCase()}function h(w,x){var v=w&&w.exec(x);return v&&v.index==0}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^lang(uage)?-/,"")});return v.filter(function(x){return i(x)||/no(-?)highlight/.test(x)})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);if(!t(A).match(/br|hr|img|input/)){v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset"}function E(G){F+=""+t(G)+">"}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=j(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+j(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};var E=function(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})};if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b="\\b("+D.bK.split(" ").join("|")+")\\b"}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?("+F.b+")\\.?":F.b}).concat([D.tE,D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}}}x(y)}function c(T,L,J,R){function v(V,W){for(var U=0;U";V+=aa+'">';return V+Y+Z}function N(){if(!I.k){return j(C)}var U="";var X=0;I.lR.lastIndex=0;var V=I.lR.exec(C);while(V){U+=j(C.substr(X,V.index-X));var W=E(I,V);if(W){H+=W[1];U+=w(W[0],j(V[0]))}else{U+=j(V[0])}X=I.lR.lastIndex;V=I.lR.exec(C)}return U+j(C.substr(X))}function F(){if(I.sL&&!f[I.sL]){return j(C)}var U=I.sL?c(I.sL,C,true,S):e(C);if(I.r>0){H+=U.r}if(I.subLanguageMode=="continuous"){S=U.top}return w(U.language,U.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(W,V){var U=W.cN?w(W.cN,"",true):"";if(W.rB){D+=U;C=""}else{if(W.eB){D+=j(V)+U;C=""}else{D+=U;C=V}}I=Object.create(W,{parent:{value:I}})}function G(U,Y){C+=U;if(Y===undefined){D+=Q();return 0}var W=v(Y,I);if(W){D+=Q();P(W,Y);return W.rB?0:Y.length}var X=z(I,Y);if(X){var V=I;if(!(V.rE||V.eE)){C+=Y}D+=Q();do{if(I.cN){D+=""}H+=I.r;I=I.parent}while(I!=X.parent);if(V.eE){D+=j(Y)}C="";if(X.starts){P(X.starts,"")}return V.rE?0:Y.length}if(A(Y,I)){throw new Error('Illegal lexeme "'+Y+'" for mode "'+(I.cN||"")+'"')}C+=Y;return Y.length||1}var M=i(T);if(!M){throw new Error('Unknown language: "'+T+'"')}m(M);var I=R||M;var S;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D=w(K.cN,"",true)+D}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+=""}}return{r:H,value:D,language:T,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:j(L)}}else{throw O}}}function e(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:j(y)};var w=v;x.forEach(function(z){if(!i(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function g(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g," ")}return v}function p(A){var B=r(A);if(/no(-?)highlight/.test(B)){return}var y;if(b.useBR){y=document.createElementNS("http://www.w3.org/1999/xhtml","div");y.innerHTML=A.innerHTML.replace(/\n/g,"").replace(/ /g,"\n")}else{y=A}var z=y.textContent;var v=B?c(B,z,true):e(z);var x=u(y);if(x.length){var w=document.createElementNS("http://www.w3.org/1999/xhtml","div");w.innerHTML=v.value;v.value=q(x,u(w),z)}v.value=g(v.value);A.innerHTML=v.value;A.className+=" hljs "+(!B&&v.language||"");A.result={language:v.language,re:v.r};if(v.second_best){A.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function d(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function k(){return Object.keys(f)}function i(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=e;this.fixMarkup=g;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=d;this.listLanguages=k;this.getLanguage=i;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/};this.CLCM={cN:"comment",b:"//",e:"$",c:[this.PWM]};this.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[this.PWM]};this.HCM={cN:"comment",b:"#",e:"$",c:[this.PWM]};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.CSSNM={cN:"number",b:this.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0};this.RM={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("1c",function(b){var f="[a-zA-Zа-яА-Я][a-zA-Z0-9_а-яА-Я]*";var c="возврат дата для если и или иначе иначеесли исключение конецесли конецпопытки конецпроцедуры конецфункции конеццикла константа не перейти перем перечисление по пока попытка прервать продолжить процедура строка тогда фс функция цикл число экспорт";var e="ansitooem oemtoansi ввестивидсубконто ввестидату ввестизначение ввестиперечисление ввестипериод ввестиплансчетов ввестистроку ввестичисло вопрос восстановитьзначение врег выбранныйплансчетов вызватьисключение датагод датамесяц датачисло добавитьмесяц завершитьработусистемы заголовоксистемы записьжурналарегистрации запуститьприложение зафиксироватьтранзакцию значениевстроку значениевстрокувнутр значениевфайл значениеизстроки значениеизстрокивнутр значениеизфайла имякомпьютера имяпользователя каталогвременныхфайлов каталогиб каталогпользователя каталогпрограммы кодсимв командасистемы конгода конецпериодаби конецрассчитанногопериодаби конецстандартногоинтервала конквартала конмесяца коннедели лев лог лог10 макс максимальноеколичествосубконто мин монопольныйрежим названиеинтерфейса названиенабораправ назначитьвид назначитьсчет найти найтипомеченныенаудаление найтиссылки началопериодаби началостандартногоинтервала начатьтранзакцию начгода начквартала начмесяца начнедели номерднягода номерднянедели номернеделигода нрег обработкаожидания окр описаниеошибки основнойжурналрасчетов основнойплансчетов основнойязык открытьформу открытьформумодально отменитьтранзакцию очиститьокносообщений периодстр полноеимяпользователя получитьвремята получитьдатута получитьдокументта получитьзначенияотбора получитьпозициюта получитьпустоезначение получитьта прав праводоступа предупреждение префиксавтонумерации пустаястрока пустоезначение рабочаядаттьпустоезначение рабочаядата разделительстраниц разделительстрок разм разобратьпозициюдокумента рассчитатьрегистрына рассчитатьрегистрыпо сигнал симв символтабуляции создатьобъект сокрл сокрлп сокрп сообщить состояние сохранитьзначение сред статусвозврата стрдлина стрзаменить стрколичествострок стрполучитьстроку стрчисловхождений сформироватьпозициюдокумента счетпокоду текущаядата текущеевремя типзначения типзначениястр удалитьобъекты установитьтана установитьтапо фиксшаблон формат цел шаблон";var a={cN:"dquote",b:'""'};var d={cN:"string",b:'"',e:'"|$',c:[a]};var g={cN:"string",b:"\\|",e:'"|$',c:[a]};return{cI:true,l:f,k:{keyword:c,built_in:e},c:[b.CLCM,b.NM,d,g,{cN:"function",b:"(процедура|функция)",e:"$",l:f,k:"процедура функция",c:[b.inherit(b.TM,{b:f}),{cN:"tail",eW:true,c:[{cN:"params",b:"\\(",e:"\\)",l:f,k:"знач",c:[d,g]},{cN:"export",b:"экспорт",eW:true,l:f,k:"экспорт",c:[b.CLCM]}]},b.CLCM]},{cN:"preprocessor",b:"#",e:"$"},{cN:"date",b:"'\\d{2}\\.\\d{2}\\.(\\d{2}|\\d{4})'"}]}});hljs.registerLanguage("actionscript",function(a){var c="[a-zA-Z_$][a-zA-Z0-9_$]*";var b="([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)";var d={cN:"rest_arg",b:"[.]{3}",e:c,r:10};return{aliases:["as"],k:{keyword:"as break case catch class const continue default delete do dynamic each else extends final finally for function get if implements import in include instanceof interface internal is namespace native new override package private protected public return set static super switch this throw try typeof use var void while with",literal:"true false null undefined"},c:[a.ASM,a.QSM,a.CLCM,a.CBCM,a.CNM,{cN:"package",bK:"package",e:"{",c:[a.TM]},{cN:"class",bK:"class interface",e:"{",eE:true,c:[{bK:"extends implements"},a.TM]},{cN:"preprocessor",bK:"import include",e:";"},{cN:"function",bK:"function",e:"[{;]",eE:true,i:"\\S",c:[a.TM,{cN:"params",b:"\\(",e:"\\)",c:[a.ASM,a.QSM,a.CLCM,a.CBCM,d]},{cN:"type",b:":",e:b,r:10}]}]}});hljs.registerLanguage("apache",function(a){var b={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:true,c:[a.HCM,{cN:"tag",b:"?",e:">"},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",b]},b,a.QSM]}}],i:/\S/}});hljs.registerLanguage("applescript",function(a){var b=a.inherit(a.QSM,{i:""});var d={cN:"params",b:"\\(",e:"\\)",c:["self",a.CNM,b]};var c=[{cN:"comment",b:"--",e:"$"},{cN:"comment",b:"\\(\\*",e:"\\*\\)",c:["self",{b:"--",e:"$"}]},a.HCM];return{aliases:["osascript"],k:{keyword:"about above after against and around as at back before beginning behind below beneath beside between but by considering contain contains continue copy div does eighth else end equal equals error every exit fifth first for fourth from front get given global if ignoring in into is it its last local me middle mod my ninth not of on onto or over prop property put ref reference repeat returning script second set seventh since sixth some tell tenth that the|0 then third through thru timeout times to transaction try until where while whose with without",constant:"AppleScript false linefeed return pi quote result space tab true",type:"alias application boolean class constant date file integer list number real record string text",command:"activate beep count delay launch log offset read round run say summarize write",property:"character characters contents day frontmost id item length month name paragraph paragraphs rest reverse running time version weekday word words year"},c:[b,a.CNM,{cN:"type",b:"\\bPOSIX file\\b"},{cN:"command",b:"\\b(clipboard info|the clipboard|info for|list (disks|folder)|mount volume|path to|(close|open for) access|(get|set) eof|current date|do shell script|get volume settings|random number|set volume|system attribute|system info|time to GMT|(load|run|store) script|scripting components|ASCII (character|number)|localized string|choose (application|color|file|file name|folder|from list|remote application|URL)|display (alert|dialog))\\b|^\\s*return\\b"},{cN:"constant",b:"\\b(text item delimiters|current application|missing value)\\b"},{cN:"keyword",b:"\\b(apart from|aside from|instead of|out of|greater than|isn't|(doesn't|does not) (equal|come before|come after|contain)|(greater|less) than( or equal)?|(starts?|ends|begins?) with|contained by|comes (before|after)|a (ref|reference))\\b"},{cN:"property",b:"\\b(POSIX path|(date|time) string|quoted form)\\b"},{cN:"function_start",bK:"on",i:"[${=;\\n]",c:[a.UTM,d]}].concat(c),i:"//"}});hljs.registerLanguage("xml",function(a){var c="[A-Za-z0-9\\._:-]+";var d={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"};var b={eW:true,i:/,r:0,c:[d,{cN:"attribute",b:c,r:0},{b:"=",r:0,c:[{cN:"value",v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s\/>]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:true,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"",rE:true,sL:"css"}},{cN:"tag",b:"