`, 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: #ddd
+// $panel-default-heading-bg: #f5f5f5
+
+// $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 90%
rename from app/assets/stylesheets/main/layout.scss
rename to app/assets/stylesheets/base/layout.scss
index 1085e68b7d..62c11b0636 100644
--- a/app/assets/stylesheets/main/layout.scss
+++ b/app/assets/stylesheets/base/layout.scss
@@ -4,7 +4,7 @@ html {
&.touch .tooltip { display: none !important; }
body {
- padding-top: 47px;
+ padding-top: 46px;
}
}
diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/base/mixins.scss
similarity index 87%
rename from app/assets/stylesheets/main/mixins.scss
rename to app/assets/stylesheets/base/mixins.scss
index e54482d14c..ccba65e3fd 100644
--- a/app/assets/stylesheets/main/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -121,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;
@@ -137,14 +129,3 @@
white-space: nowrap;
max-width: $max_width;
}
-
-@mixin panel-colored {
- border: 1px solid #EEE;
- 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..54af78ee08
--- /dev/null
+++ b/app/assets/stylesheets/base/variables.scss
@@ -0,0 +1,37 @@
+$style_color: #474D57;
+$hover: #FFF3EB;
+$box_bg: #F9F9F9;
+$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;
+
+/*
+ * 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/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss
index 3b36027506..0224484d82 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,11 +21,6 @@
float: right;
}
- &.wide {
- padding-left: 20px;
- padding-right: 20px;
- }
-
&.btn-small {
padding: 2px 10px;
font-size: 12px;
@@ -151,16 +34,16 @@
}
&.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;
}
@@ -174,9 +57,12 @@
}
}
- &.btn-lg {
- font-size: 15px;
- line-height: 1.4;
+ &.btn-save {
+ @extend .btn-primary;
+ }
+
+ &.btn-new, &.btn-create {
+ @extend .btn-success;
}
}
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index aca5c84524..df0f150f40 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;
@@ -61,7 +61,7 @@ pre {
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
- background: $bg_primary;
+ background: $gl-primary;
color: #FFF
}
@@ -71,7 +71,7 @@ pre {
/** FLASH message **/
.author_link {
- color: $link_color;
+ color: $gl-link-color;
}
.help li { color:$style_color; }
@@ -315,20 +315,8 @@ table {
width: 100%;
}
-.broadcast-message {
- padding: 10px;
- text-align: center;
- background: #555;
- color: #BBB;
-}
-
-.broadcast-message-preview {
- @extend .broadcast-message;
- margin-bottom: 20px;
-}
-
.btn-sign-in {
- margin-top: 7px;
+ margin-top: 5px;
text-shadow: none;
}
@@ -346,8 +334,11 @@ table {
overflow-x: auto;
}
-.footer-links a {
- margin-right: 15px;
+.footer-links {
+ margin-bottom: 20px;
+ a {
+ margin-right: 15px;
+ }
}
.search_box {
diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss
index c8982cdc00..19bc11086e 100644
--- a/app/assets/stylesheets/generic/forms.scss
+++ b/app/assets/stylesheets/generic/forms.scss
@@ -29,7 +29,7 @@ fieldset legend {
padding: 17px 20px 18px;
margin-top: 18px;
margin-bottom: 18px;
- background-color: whitesmoke;
+ background-color: #ecf0f1;
border-top: 1px solid #e5e5e5;
}
diff --git a/app/assets/stylesheets/generic/gfm.scss b/app/assets/stylesheets/generic/gfm.scss
index 1427b6a5ae..8fac5e534f 100644
--- a/app/assets/stylesheets/generic/gfm.scss
+++ b/app/assets/stylesheets/generic/gfm.scss
@@ -3,7 +3,7 @@
*/
.issue-form, .merge-request-form, .wiki-form {
.description {
- height: 20em;
+ height: 16em;
border-top-left-radius: 0;
}
}
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss
index 2563ab516e..9558f241b7 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/generic/issue_box.scss
@@ -11,17 +11,17 @@
margin-right: 5px;
&.issue-box-closed {
- background-color: $bg_danger;
+ background-color: $gl-danger;
color: #FFF;
}
&.issue-box-merged {
- background-color: $bg_primary;
+ background-color: $gl-primary;
color: #FFF;
}
&.issue-box-open {
- background-color: $bg_success;
+ background-color: $gl-success;
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/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss
index 5a87cc6c61..eb39b6bb7e 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/generic/markdown_area.scss
@@ -57,7 +57,6 @@
border: 1px solid #ddd;
min-height: 100px;
padding: 5px;
- font-size: 14px;
box-shadow: none;
}
@@ -77,3 +76,12 @@
}
}
}
+
+.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
index b3727c3367..1b0e056216 100644
--- a/app/assets/stylesheets/generic/mobile.scss
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -43,8 +43,10 @@
}
}
- .page-title .new-issue-link {
- display: none;
+ .page-title {
+ .note_created_ago, .new-issue-link {
+ display: none;
+ }
}
.issue_edited_ago, .note_edited_ago {
diff --git a/app/assets/stylesheets/sections/nav_sidebar.scss b/app/assets/stylesheets/generic/nav_sidebar.scss
similarity index 98%
rename from app/assets/stylesheets/sections/nav_sidebar.scss
rename to app/assets/stylesheets/generic/nav_sidebar.scss
index 335f137966..4bf2c609be 100644
--- a/app/assets/stylesheets/sections/nav_sidebar.scss
+++ b/app/assets/stylesheets/generic/nav_sidebar.scss
@@ -154,9 +154,10 @@
.collapse-nav a {
position: fixed;
- top: 47px;
+ top: 46px;
padding: 5px 13px 3px 13px;
left: 197px;
+ font-size: 13px;
background: #EEE;
color: black;
border: 1px solid rgba(0,0,0,0.035);
diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss
index 5d2f054d8f..ff08641263 100644
--- a/app/assets/stylesheets/generic/selects.scss
+++ b/app/assets/stylesheets/generic/selects.scss
@@ -3,9 +3,8 @@
.select2-choice {
background: #FFF;
border-color: #BBB;
- padding: 6px 12px;
- font-size: 13px;
- line-height: 18px;
+ 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;
}
}
}
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index c547ebb3aa..4d940ee6b2 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -2,28 +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;
-}
-
-h4.page-title {
+.page-title {
margin-top: 0px;
-}
-
-h6 {
- color: #888;
- text-transform: uppercase;
+ color: #333;
+ line-height: 1.5;
+ font-weight: normal;
+ margin-bottom: 5px;
}
/** CODE **/
@@ -36,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;
}
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index fcd4d47bac..c8cb18ec35 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -1,6 +1,10 @@
/* https://github.com/MozMorris/tomorrow-pygments */
+pre.code.highlight.dark,
.code.dark {
+ background-color: #1d1f21;
+ color: #c5c8c6;
+
pre.code,
.line-numbers,
.line-numbers a {
@@ -13,8 +17,8 @@
}
// highlight line via anchor
- pre.hll {
- background-color: #fff !important;
+ pre .hll {
+ background-color: #557 !important;
}
.hll { background-color: #373b41 }
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index bcd2e71665..001e8b3102 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -1,6 +1,10 @@
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */
+pre.code.monokai,
.code.monokai {
+ background: #272822;
+ color: #f8f8f2;
+
pre.highlight,
.line-numbers,
.line-numbers a {
@@ -13,7 +17,7 @@
}
// highlight line via anchor
- pre.hll {
+ pre .hll {
background-color: #49483e !important;
}
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 4a6b759bd2..f5b827e7c0 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -1,6 +1,10 @@
/* https://gist.github.com/qguv/7936275 */
+pre.code.highlight.solarized-dark,
.code.solarized-dark {
+ background-color: #002b36;
+ color: #93a1a1;
+
pre.code,
.line-numbers,
.line-numbers a {
@@ -13,8 +17,8 @@
}
// highlight line via anchor
- pre.hll {
- background-color: #073642 !important;
+ pre .hll {
+ background-color: #174652 !important;
}
/* Solarized Dark
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 7254f4d7ac..6b44c00c30 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -1,6 +1,10 @@
/* 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 {
@@ -13,8 +17,8 @@
}
// highlight line via anchor
- pre.hll {
- background-color: #eee8d5 !important;
+ pre .hll {
+ background-color: #ddd8c5 !important;
}
/* Solarized Light
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 4d6f5dfd91..a52ffc971d 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,6 +1,10 @@
/* https://github.com/aahan/pygments-github-style */
+pre.code.highlight.white,
.code.white {
+ background-color: #fff;
+ color: #333;
+
pre.highlight,
.line-numbers,
.line-numbers a {
@@ -13,7 +17,7 @@
}
// highlight line via anchor
- pre.hll {
+ pre .hll {
background-color: #f8eec7 !important;
}
diff --git a/app/assets/stylesheets/main/fonts.scss b/app/assets/stylesheets/main/fonts.scss
deleted file mode 100644
index f945aaca84..0000000000
--- a/app/assets/stylesheets/main/fonts.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-/** Typo **/
-$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/main/variables.scss b/app/assets/stylesheets/main/variables.scss
deleted file mode 100644
index acbf5be94a..0000000000
--- a/app/assets/stylesheets/main/variables.scss
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * General Colors
- */
-$style_color: #474D57;
-$hover: #FFF3EB;
-$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 customize
- */
-$nprogress-color: #c0392b;
-
-/**
- * Font sizes
- */
-$list-font-size: 15px;
-
-/**
- * Sidebar navigation width
- */
-$sidebar_width: 230px;
-
-$avatar_radius: 50%;
-$code_font_size: 13px;
-$code_line_height: 1.5;
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/sections/commit.scss b/app/assets/stylesheets/pages/commit.scss
similarity index 94%
rename from app/assets/stylesheets/sections/commit.scss
rename to app/assets/stylesheets/pages/commit.scss
index 0e2d9571a4..f46d6542c0 100644
--- a/app/assets/stylesheets/sections/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -45,15 +45,6 @@
}
}
-.commit-committer-link,
-.commit-author-link {
- font-size: 13px;
- color: #555;
- &:hover {
- color: #999;
- }
-}
-
.commit-box {
margin: 10px 0;
border-top: 1px solid #ddd;
diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/pages/commits.scss
similarity index 98%
rename from app/assets/stylesheets/sections/commits.scss
rename to app/assets/stylesheets/pages/commits.scss
index 683aca7359..e167d044e4 100644
--- a/app/assets/stylesheets/sections/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -100,6 +100,7 @@ li.commit {
.commit-row-info {
color: #777;
line-height: 24px;
+ font-size: 13px;
a {
color: #777;
diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
similarity index 71%
rename from app/assets/stylesheets/sections/dashboard.scss
rename to app/assets/stylesheets/pages/dashboard.scss
index d8fd83d44b..5a543a852c 100644
--- a/app/assets/stylesheets/sections/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -23,28 +23,6 @@
}
}
-.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;
@@ -111,17 +89,8 @@
}
.dash-new-project {
- background: $bg_success;
- border: 1px solid $border_success;
-
- a {
- color: #FFF;
- }
-}
-
-.dash-new-group {
- background: $bg_success;
- border: 1px solid $border_success;
+ background: $gl-success;
+ border: 1px solid $gl-success;
a {
color: #FFF;
diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/pages/diff.scss
similarity index 100%
rename from app/assets/stylesheets/sections/diff.scss
rename to app/assets/stylesheets/pages/diff.scss
diff --git a/app/assets/stylesheets/sections/editor.scss b/app/assets/stylesheets/pages/editor.scss
similarity index 100%
rename from app/assets/stylesheets/sections/editor.scss
rename to app/assets/stylesheets/pages/editor.scss
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 98%
rename from app/assets/stylesheets/sections/events.scss
rename to app/assets/stylesheets/pages/events.scss
index a477359dc8..3e9e36e477 100644
--- a/app/assets/stylesheets/sections/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -46,7 +46,6 @@
border-bottom: 1px solid #eee;
.event-title {
@include str-truncated(72%);
- color: #333;
font-weight: 500;
font-size: 14px;
.author_name {
@@ -54,6 +53,7 @@
}
}
.event-body {
+ font-size: 13px;
margin-left: 35px;
margin-right: 80px;
color: #777;
@@ -185,11 +185,10 @@
}
.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 100%
rename from app/assets/stylesheets/sections/graph.scss
rename to app/assets/stylesheets/pages/graph.scss
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 100%
rename from app/assets/stylesheets/sections/header.scss
rename to app/assets/stylesheets/pages/header.scss
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/sections/import.scss b/app/assets/stylesheets/pages/import.scss
similarity index 100%
rename from app/assets/stylesheets/sections/import.scss
rename to app/assets/stylesheets/pages/import.scss
diff --git a/app/assets/stylesheets/sections/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
similarity index 100%
rename from app/assets/stylesheets/sections/issuable.scss
rename to app/assets/stylesheets/pages/issuable.scss
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/pages/issues.scss
similarity index 98%
rename from app/assets/stylesheets/sections/issues.scss
rename to app/assets/stylesheets/pages/issues.scss
index b909725bff..4ea34cc1da 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -11,6 +11,7 @@
.issue-info {
color: #999;
+ font-size: 13px;
}
.issue-check {
@@ -40,7 +41,7 @@
}
.check-all-holder {
- height: 32px;
+ height: 36px;
float: left;
margin-right: 12px;
padding: 6px 15px;
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/sections/login.scss b/app/assets/stylesheets/pages/login.scss
similarity index 100%
rename from app/assets/stylesheets/sections/login.scss
rename to app/assets/stylesheets/pages/login.scss
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
similarity index 92%
rename from app/assets/stylesheets/sections/merge_requests.scss
rename to app/assets/stylesheets/pages/merge_requests.scss
index 0d2d8b0173..9bd34b7376 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -96,6 +96,7 @@
.merge-request-info {
color: #999;
+ font-size: 13px;
.merge-request-labels {
display: inline-block;
@@ -136,8 +137,8 @@
background-color: #F5F5F5;
&.ci-success {
- color: $bg_success;
- border-color: $border_success;
+ color: $gl-success;
+ border-color: $gl-success;
background-color: #F1FAF1;
}
@@ -148,20 +149,20 @@
}
&.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;
}
}
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/sections/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
similarity index 100%
rename from app/assets/stylesheets/sections/note_form.scss
rename to app/assets/stylesheets/pages/note_form.scss
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/pages/notes.scss
similarity index 93%
rename from app/assets/stylesheets/sections/notes.scss
rename to app/assets/stylesheets/pages/notes.scss
index 40adc8b3ba..384ff6d740 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -38,13 +38,11 @@ ul.notes {
.author {
color: #333;
font-weight: bold;
- font-size: 14px;
&:hover {
- color: $link_color;
+ color: $gl-link-color;
}
}
.author-username {
- font-size: 14px;
}
}
@@ -57,9 +55,6 @@ ul.notes {
.note {
display: block;
position:relative;
- .attachment {
- font-size: 14px;
- }
.note-body {
overflow: auto;
.note-text {
@@ -70,7 +65,7 @@ ul.notes {
a[href*="/uploads/"] {
&:before {
margin-right: 4px;
-
+
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
@@ -153,7 +148,6 @@ ul.notes {
@extend .cgray;
&:hover {
- color: $link_hover_color;
&.danger { @extend .cred; }
}
}
@@ -181,10 +175,11 @@ ul.notes {
background: #FFF;
padding: 4px;
font-size: 16px;
- color: $link_color;
+ color: $gl-link-color;
margin-left: -60px;
position: absolute;
z-index: 10;
+ width: 32px;
transition: all 0.2s ease;
@@ -193,8 +188,9 @@ ul.notes {
filter: alpha(opacity=0);
&:hover {
- font-size: 24px;
- background: $bg_primary;
+ width: 38px;
+ font-size: 20px;
+ background: $gl-info;
color: #FFF;
@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 100%
rename from app/assets/stylesheets/sections/profile.scss
rename to app/assets/stylesheets/pages/profile.scss
diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/pages/projects.scss
similarity index 91%
rename from app/assets/stylesheets/sections/projects.scss
rename to app/assets/stylesheets/pages/projects.scss
index 8bad9b139f..bfd05973d7 100644
--- a/app/assets/stylesheets/sections/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -99,25 +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 {
- background-color: #f5f5f5;
- border: 1px solid rgba(0,0,0,0.195);
- color: #333;
- font-weight: bold;
- }
- }
-
.form-control {
cursor: auto;
@extend .monospace;
@@ -267,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 {
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 94%
rename from app/assets/stylesheets/sections/tree.scss
rename to app/assets/stylesheets/pages/tree.scss
index 60a1c00b04..ce02cdb165 100644
--- a/app/assets/stylesheets/sections/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -39,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 {
@@ -66,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;
}
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 100%
rename from app/assets/stylesheets/sections/votes.scss
rename to app/assets/stylesheets/pages/votes.scss
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/markdown_area.scss b/app/assets/stylesheets/sections/markdown_area.scss
deleted file mode 100644
index 8ee8eaa4ee..0000000000
--- a/app/assets/stylesheets/sections/markdown_area.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-.markdown-area {
- background: #FFF;
- border: 1px solid #ddd;
- min-height: 100px;
- padding: 5px;
- font-size: 14px;
- box-shadow: none;
- width: 100%;
-}
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/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb
index e80cabd6e1..44a3f1379d 100644
--- a/app/controllers/admin/services_controller.rb
+++ b/app/controllers/admin/services_controller.rb
@@ -45,7 +45,8 @@ class Admin::ServicesController < Admin::ApplicationController
: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
+ :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 ecedb31a7f..693970e534 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -24,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
@@ -32,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
diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb
similarity index 71%
rename from app/controllers/profiles/groups_controller.rb
rename to app/controllers/dashboard/groups_controller.rb
index ce9dd50df6..61d691e636 100644
--- a/app/controllers/profiles/groups_controller.rb
+++ b/app/controllers/dashboard/groups_controller.rb
@@ -1,6 +1,4 @@
-class Profiles::GroupsController < ApplicationController
- layout "profile"
-
+class Dashboard::GroupsController < ApplicationController
def index
@user_groups = current_user.group_members.page(params[:page]).per(20)
end
@@ -9,7 +7,7 @@ class Profiles::GroupsController < ApplicationController
@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.")
+ redirect_to(dashboard_groups_path, info: "You left #{group.name} group.")
else
return render_403
end
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
new file mode 100644
index 0000000000..386e283f3a
--- /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(30)
+ 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 4930029e16..0500619909 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -5,19 +5,9 @@ class DashboardController < ApplicationController
before_filter :event_filter, only: :show
def show
- # Fetch only 30 projects.
- # If user needs more - point to Dashboard#projects page
- @projects_limit = 30
-
- @groups = current_user.authorized_groups.order_name_asc
- @has_authorized_projects = @projects.count > 0
- @projects_count = @projects.count
- @projects = @projects.includes(:namespace).limit(@projects_limit)
-
+ @projects = @projects.includes(:namespace)
@last_push = current_user.recent_push
- @publicish_project_count = Project.publicish(current_user).count
-
respond_to do |format|
format.html
@@ -33,29 +23,6 @@ class DashboardController < ApplicationController
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, :forked_from_project, :tags)
- @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 = get_merge_requests_collection
@merge_requests = @merge_requests.page(params[:page]).per(20)
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 0e5891ae80..d664624fa6 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -6,6 +6,9 @@ class Explore::ProjectsController < ApplicationController
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)
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 860d8e0392..6802e529b5 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -4,10 +4,10 @@ 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)
@@ -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/help_controller.rb b/app/controllers/help_controller.rb
index 0e1b0b6bd2..968823ac81 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -19,4 +19,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
index 7dc0cac8d4..edb8bd4160 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -3,19 +3,17 @@ class Import::BaseController < ApplicationController
private
def get_or_create_namespace
- existing_namespace = Namespace.find_by_path_or_name(@target_namespace)
-
- if existing_namespace
- if existing_namespace.owner == current_user
- namespace = existing_namespace
- else
+ 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
- else
- namespace = Group.create(name: @target_namespace, path: @target_namespace, owner: current_user)
- namespace.add_owner(current_user)
- namespace
end
+
+ namespace
end
end
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 79d9910ce8..b64491b466 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -37,7 +37,7 @@ class Projects::ImportsController < Projects::ApplicationController
private
def require_no_repo
- if @project.repository_exists?
+ if @project.repository_exists? && !@project.import_in_progress?
redirect_to(namespace_project_path(@project.namespace, @project)) and return
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 6a2af08a19..1f1a9b4d43 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -93,7 +93,7 @@ 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
@@ -141,4 +141,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/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 26d4c51773..57c017e799 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -78,10 +78,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
if @merge_request.valid?
- redirect_to(
- merge_request_path(@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
@@ -97,8 +94,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.js
format.html do
redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
- @merge_request.target_project, @merge_request],
- notice: 'Merge request was successfully updated.')
+ @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
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 82aad329c1..570447c746 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -51,7 +51,9 @@ class Projects::ServicesController < Projects::ApplicationController
: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
+ :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/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 08c7ce3f37..03fface2d2 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -27,7 +27,7 @@ class Projects::TagsController < Projects::ApplicationController
tag = @repository.find_tag(params[:id])
if tag && @repository.rm_tag(tag.name)
- EventCreateService.new.push_ref(@project, current_user, tag, 'rm', 'refs/tags')
+ EventCreateService.new.push_ref(@project, current_user, tag, 'rm', Gitlab::Git::TAG_REF_PREFIX)
end
respond_to do |format|
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 95f3ba0294..9b1c33d6c7 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -105,7 +105,7 @@ class ProjectsController < ApplicationController
if request.referer.include?('/admin')
redirect_to admin_namespaces_projects_path
else
- redirect_to projects_dashboard_path
+ redirect_to dashboard_path
end
end
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 5fb763aab9..938d3db286 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -1,24 +1,15 @@
class UploadsController < ApplicationController
- skip_before_filter :authenticate_user!, :reject_blocked!
- before_filter :authorize_access
+ skip_before_filter :authenticate_user!
+ before_filter :find_model, :authorize_access!
def show
- unless upload_model && upload_mount
- return not_found!
- end
-
- model = upload_model.find(params[:id])
- uploader = model.send(upload_mount)
-
- if model.respond_to?(:project) && !can?(current_user, :read_project, model.project)
- return not_found!
- end
+ uploader = @model.send(upload_mount)
unless uploader.file_storage?
return redirect_to uploader.url
end
- unless uploader.file.exists?
+ unless uploader.file && uploader.file.exists?
return not_found!
end
@@ -28,9 +19,34 @@ class UploadsController < ApplicationController
private
- def authorize_access
- unless %w(avatar logo light_logo).include?(params[:mounted_as])
- authenticate_user! && reject_blocked!
+ 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
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 4dae96644c..c25b54eadc 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -1,20 +1,4 @@
module DashboardHelper
- def projects_dashboard_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
-
def assigned_issues_dashboard_path
issues_dashboard_path(assignee_id: current_user.id)
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index d38b546e1b..779cebc013 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -166,7 +166,7 @@ module EventsHelper
def event_note(text)
text = first_line_in_markdown(text, 150)
- sanitize(text, tags: %w(a img b pre code p))
+ sanitize(text, tags: %w(a img b pre code p span))
end
def event_commit_title(message)
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/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index ab30f498c0..daaefe90f1 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-
filter_html: true,
with_toc_data: true,
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index ac37f909ce..8518a47a3a 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -44,4 +44,8 @@ module GitlabRoutingHelper
def merge_request_url(entity, *args)
namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args)
end
+
+ def snippet_url(entity, *args)
+ namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args)
+ end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 47fa147dcc..59fdc0d49c 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -4,6 +4,19 @@ module MilestonesHelper
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/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 4bc40b35f2..b55129de29 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -16,28 +16,38 @@ module Emails
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 = namespace_project_compare_url(@project.namespace,
@project,
- from: @commits.first,
- to: @commits.last)
- @subject = "#{@commits.length} new commits pushed to repository"
+ 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 = namespace_project_commit_url(@project.namespace,
@project, @commits.first)
- @subject = @commits.first.title
+
+ @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 cd2da68262..b212ea1dd2 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -35,21 +35,41 @@ class Notify < ActionMailer::Base
)
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
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 87882ef0ea..676111053d 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -13,6 +13,8 @@
# created_at :datetime
# updated_at :datetime
# home_page_url :string(255)
+# default_branch_protection :integer default(2)
+# twitter_sharing_enabled :boolean default(TRUE)
# help_text :text
#
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index f75b6e5e64..c9eeec731f 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -100,5 +100,4 @@ module Mentionable
preexisting = references(p, original)
create_cross_references!(p, a, preexisting)
end
-
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 5579ab1dbb..8d20d7ef25 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -190,19 +190,19 @@ class Event < ActiveRecord::Base
end
def tag?
- data[:ref]["refs/tags"]
+ Gitlab::Git.tag_ref?(data[:ref])
end
def branch?
- data[:ref]["refs/heads"]
+ 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?
@@ -226,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
diff --git a/app/models/identity.rb b/app/models/identity.rb
index b2c3792d1c..440fcd0d05 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -6,6 +6,8 @@
# extern_uid :string(255)
# provider :string(255)
# user_id :integer
+# created_at :datetime
+# updated_at :datetime
#
class Identity < ActiveRecord::Base
diff --git a/app/models/note.rb b/app/models/note.rb
index 89beebab96..be3c8a5dd0 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -156,18 +156,41 @@ 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_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,
@@ -313,6 +336,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
@@ -471,6 +498,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?
diff --git a/app/models/project.rb b/app/models/project.rb
index 573c3384bf..233ec048dc 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -493,8 +493,9 @@ class Project < ActiveRecord::Base
end
end
- def execute_services(data)
- services.select(&:active).each do |service|
+ 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
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 66b72572b9..d52214cdd6 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -2,17 +2,21 @@
#
# 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)
+# 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
@@ -61,13 +65,19 @@ automatically inspected. Leave blank to include all branches.'
]
end
- def execute(push)
+ 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 = push[:user_name]
- branch = push[:ref].gsub('refs/heads/', '')
+ user = data[:user_name]
+ branch = Gitlab::Git.ref_name(data[:ref])
branch_restriction = restrict_to_branch.to_s
@@ -79,7 +89,7 @@ automatically inspected. Leave blank to include all branches.'
project_name = project.name_with_namespace
push_msg = user + ' pushed to branch ' + branch + ' of ' + project_name
- push[:commits].each do |commit|
+ data[:commits].each do |commit|
check_commit(' ( ' + commit[:url] + ' ): ' + commit[:message], push_msg)
end
end
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index cf7598f35e..fb7e0c0fb0 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
@@ -38,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
index df68803152..0100f1e4a1 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
@@ -69,6 +74,10 @@ class BambooService < CiService
]
end
+ def supported_events
+ %w(push)
+ end
+
def build_info(sha)
url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
@@ -118,7 +127,9 @@ class BambooService < CiService
end
end
- def execute(_data)
+ 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)
diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb
index 058c890ae4..270863c157 100644
--- a/app/models/project_services/buildbox_service.rb
+++ b/app/models/project_services/buildbox_service.rb
@@ -2,16 +2,22 @@
#
# 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)
+# 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
@@ -32,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 14b6b87a0b..e591afdda6 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
@@ -37,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
@@ -53,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]
@@ -61,9 +72,9 @@ class CampfireService < Service
message << "[#{project.name_with_namespace}] "
message << "#{push[:user_name]} "
- if before.include?('000000')
+ if Gitlab::Git.blank_ref?(before)
message << "pushed new branch #{ref} \n"
- elsif after.include?('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 5a26c25b3c..c6f6b4952c 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -2,15 +2,19 @@
#
# 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)
+# 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
@@ -21,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
index b29d1c8688..8d25f62787 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -2,15 +2,19 @@
#
# 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)
+# 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
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index 86693ad0c7..acb5e7f1af 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -2,18 +2,24 @@
#
# 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)
+# 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?
@@ -29,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 13e2dfceb1..99e361dd6e 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -2,15 +2,19 @@
#
# 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)
+# 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"
@@ -37,11 +41,17 @@ class FlowdockService < Service
]
end
- def execute(push_data)
+ 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: project.repository.path_to_repo,
repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index a2c87ae88f..4e75bdfc95 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -2,15 +2,19 @@
#
# 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)
+# 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"
@@ -38,11 +42,17 @@ class GemnasiumService < Service
]
end
- def execute(push_data)
+ 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: project.repository.path_to_repo
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index f4b463e819..d81623625c 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -2,15 +2,19 @@
#
# 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)
+# 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
@@ -18,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
@@ -28,6 +30,16 @@ 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 + "/commits/#{sha}/status.json?token=#{token}"
end
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 05c048e4e4..84346350a6 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 003e06a4c8..d264a56ebd 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -2,15 +2,19 @@
#
# 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)
+# 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
@@ -40,8 +44,14 @@ class HipchatService < Service
]
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
@@ -52,22 +62,39 @@ class HipchatService < Service
@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.include?('000000')
- message << "pushed new branch #{ref} "\
- " to "\
- "#{project.name_with_namespace.gsub!(/\s/, "")} \n"
- elsif after.include?('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 )"
@@ -83,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
index a0203a5bb1..deb210c61e 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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'
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index ad28e87af2..7afd16d2d7 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
@@ -25,18 +30,6 @@ class IssueTrackerService < Service
false
end
- def project_url
- # implement inside child
- end
-
- def issues_url
- # implement inside child
- end
-
- def new_issue_url
- # implement inside child
- end
-
def create_cross_reference_note
# implement inside child
end
@@ -59,9 +52,9 @@ class IssueTrackerService < Service
if enabled_in_gitlab_config
self.properties = {
title: issues_tracker['title'],
- project_url: set_project_url,
- issues_url: issues_tracker['issues_url'],
- new_issue_url: issues_tracker['new_issue_url']
+ 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 = {}
@@ -69,7 +62,13 @@ class IssueTrackerService < Service
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
@@ -104,15 +103,15 @@ class IssueTrackerService < Service
Gitlab.config.issues_tracker[to_param]
end
- def set_project_url
+ def add_issues_tracker_id(url)
if self.project
id = self.project.issues_tracker_id
if id
- issues_tracker['project_url'].gsub(":issues_tracker_id", id)
+ url = url.gsub(":issues_tracker_id", id)
end
end
- issues_tracker['project_url']
+ url
end
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 94bf15ef1e..e71db234ef 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 287812c57a..ade9ee9787 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
@@ -37,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 3a3af59390..53edf522e9 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
@@ -76,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.include?('000000')
- message = "#{push_data[:user_name]} pushed new branch \"#{ref}\"."
- elsif after.include?('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 = {
@@ -100,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
index e1dc10415e..dd9ba97ee1 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb
deleted file mode 100644
index 6c6446db45..0000000000
--- a/app/models/project_services/slack_message.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-require 'slack-notifier'
-
-class SlackMessage
- attr_reader :after
- attr_reader :before
- attr_reader :commits
- attr_reader :project_name
- attr_reader :project_url
- attr_reader :ref
- attr_reader :username
-
- 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
-
- 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.include?('000000')
- end
-
- def removed_branch?
- after.include?('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 c7cbff63fe..36d9874edd 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
@@ -38,20 +43,45 @@ class SlackService < Service
]
end
- def execute(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])
return unless webhook.present?
- message = SlackMessage.new(push_data.merge(
+ object_kind = data[:object_kind]
+
+ data = data.merge(
project_url: project_url,
project_name: project_name
- ))
+ )
+
+ # 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
- notifier = Slack::Notifier.new(webhook, opt)
- notifier.ping(message.pretext, attachments: message.attachments)
+ if message
+ notifier = Slack::Notifier.new(webhook, opt)
+ notifier.ping(message.pretext, attachments: message.attachments)
+ end
end
private
@@ -63,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
index b6932f1c77..7403e19da9 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
@@ -57,6 +62,10 @@ class TeamcityService < CiService
'teamcity'
end
+ def supported_events
+ %w(push)
+ end
+
def fields
[
{ type: 'text', name: 'teamcity_url',
@@ -115,13 +124,15 @@ class TeamcityService < CiService
end
end
- def execute(push)
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
auth = {
username: username,
password: password,
}
- branch = push[:ref].gsub('refs/heads/', '')
+ branch = Gitlab::Git.ref_name(data[:ref])
self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
body: ""\
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 5b52739df2..6117db418a 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -146,7 +146,8 @@ class Repository
end
def timestamps_by_user_log(user)
- args = %W(git log --author=#{user.email} --since=#{(Date.today - 1.year).to_s} --branches --pretty=format:%cd --date=short)
+ 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?
diff --git a/app/models/service.rb b/app/models/service.rb
index adaff1c54f..39d1719838 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -2,15 +2,20 @@
#
# 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)
+# 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
@@ -19,6 +24,11 @@ class Service < ActiveRecord::Base
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
@@ -29,6 +39,12 @@ class Service < ActiveRecord::Base
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
@@ -66,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
@@ -91,6 +111,8 @@ class Service < ActiveRecord::Base
end
def async_execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
Sidekiq::Client.enqueue(ProjectServiceWorker, id, data)
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 82c1ab9444..3fb2ec1d66 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -59,6 +59,10 @@ class Snippet < ActiveRecord::Base
content
end
+ def hook_attrs
+ attributes
+ end
+
def size
0
end
diff --git a/app/models/user.rb b/app/models/user.rb
index f766ea1100..a92095e582 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2,51 +2,53 @@
#
# 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
-# 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
-# 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)
-# hide_no_password :boolean default(FALSE)
-# website_url :string(255) default(""), not null
-# last_credential_check_at :datetime
-# github_access_token :string(255)
-# notification_email :string(255)
-# password_automatically_set :boolean default(FALSE)
-# bitbucket_access_token :string(255)
+# 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'
@@ -152,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
@@ -640,7 +624,7 @@ class User < ActiveRecord::Base
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)",
+ where("action = :pushed OR (target_type = 'MergeRequest' AND action = :created)",
pushed: Event::PUSHED, created: Event::CREATED).
reorder(project_id: :desc).
select(:project_id).
diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb
index a735d3f7f2..dfc5677c9d 100644
--- a/app/services/create_tag_service.rb
+++ b/app/services/create_tag_service.rb
@@ -21,12 +21,12 @@ class CreateTagService < BaseService
new_tag = repository.find_tag(tag_name)
if new_tag
- if project.gitlab_ci?
- push_data = create_push_data(project, current_user, new_tag)
- project.gitlab_ci_service.async_execute(push_data)
- end
+ EventCreateService.new.push_ref(project, current_user, new_tag, 'add', Gitlab::Git::TAG_REF_PREFIX)
+
+ push_data = create_push_data(project, current_user, new_tag)
+ project.execute_hooks(push_data.dup, :tag_push_hooks)
+ project.execute_services(push_data.dup, :tag_push_hooks)
- EventCreateService.new.push_ref(project, current_user, new_tag, 'add', 'refs/tags')
success(new_tag)
else
error('Invalid reference name')
@@ -40,7 +40,9 @@ class CreateTagService < BaseService
end
def create_push_data(project, user, tag)
- Gitlab::PushDataBuilder.
- build(project, user, Gitlab::Git::BLANK_SHA, tag.target, 'refs/tags/' + tag.name, [])
+ data = Gitlab::PushDataBuilder.
+ build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
+ data[:object_kind] = "tag_push"
+ data
end
end
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index ba9547b924..dc52d6d89d 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -62,19 +62,19 @@ class EventCreateService
create_event(project, current_user, Event::CREATED)
end
- def push_ref(project, current_user, ref, action = 'add', prefix = 'refs/heads')
+ def push_ref(project, current_user, ref, action = 'add', prefix = Gitlab::Git::BRANCH_REF_PREFIX)
commit = project.repository.commit(ref.target)
if action.to_s == 'add'
- before = '00000000'
+ before = Gitlab::Git::BLANK_SHA
after = commit.id
else
before = commit.id
- after = '00000000'
+ after = Gitlab::Git::BLANK_SHA
end
data = {
- ref: "#{prefix}/#{ref.name}",
+ ref: "#{prefix}#{ref.name}",
before: before,
after: after
}
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index b3796d1c81..742781f631 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -42,8 +42,10 @@ class GitPushService
# 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)
@@ -54,7 +56,7 @@ class GitPushService
@push_data = post_receive_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
@@ -109,30 +111,24 @@ class GitPushService
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].include?('heads') && oldrev != Gitlab::Git::BLANK_SHA
+ 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].include?('heads') && oldrev == Gitlab::Git::BLANK_SHA
+ Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(oldrev)
end
def push_remove_branch?(ref, newrev)
- ref_parts = ref.split('/')
-
- ref_parts[1].include?('heads') && newrev == Gitlab::Git::BLANK_SHA
+ Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(newrev)
end
def push_to_branch?(ref)
- ref.include?('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 46d8987f12..cd92f50b02 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -8,10 +8,7 @@ class GitTagPushService
EventCreateService.new.push(project, user, @push_data)
project.repository.expire_cache
project.execute_hooks(@push_data.dup, :tag_push_hooks)
-
- if project.gitlab_ci?
- project.gitlab_ci_service.async_execute(@push_data)
- end
+ project.execute_services(@push_data.dup, :tag_push_hooks)
true
end
@@ -19,7 +16,8 @@ class GitTagPushService
private
def create_push_data(oldrev, newrev, ref)
- Gitlab::PushDataBuilder.
- build(project, user, oldrev, newrev, ref, [])
+ data = Gitlab::PushDataBuilder.build(project, user, oldrev, newrev, ref, [])
+ data[:object_kind] = "tag_push"
+ data
end
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 755c0ef45a..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(current_user)
- 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/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index b4199d1c80..f6e1ae6f28 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -5,13 +5,19 @@ module MergeRequests
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end
+ 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
- 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
- merge_request.project.execute_hooks(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/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 96761bec99..cab8a1e880 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -1,10 +1,10 @@
module MergeRequests
class RefreshService < MergeRequests::BaseService
def execute(oldrev, newrev, ref)
- return true unless ref =~ /heads/
+ return true unless Gitlab::Git.branch_ref?(ref)
@oldrev, @newrev = oldrev, newrev
- @branch_name = ref.gsub("refs/heads/", "")
+ @branch_name = Gitlab::Git.ref_name(ref)
@fork_merge_requests = @project.fork_merge_requests.opened
@commits = @project.repository.commits_between(oldrev, newrev)
@@ -82,8 +82,14 @@ module MergeRequests
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, @commits)
+ @current_user, new_commits, existing_commits)
end
end
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/notification_service.rb b/app/services/notification_service.rb
index 2fc63b9f4b..0063b7ce40 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -268,6 +268,7 @@ 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
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index 46f6e91e80..c5d0b08845 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -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
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 13648c0d20..46c5908f1b 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -8,39 +8,47 @@
%fieldset
%legend Features
.form-group
- = f.label :signup_enabled, class: 'control-label'
- .col-sm-10
- = f.check_box :signup_enabled, class: 'checkbox'
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :signup_enabled do
+ = f.check_box :signup_enabled
+ Signin enabled
.form-group
- = f.label :signin_enabled, class: 'control-label'
- .col-sm-10
- = f.check_box :signin_enabled, class: 'checkbox'
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :signin_enabled do
+ = f.check_box :signin_enabled
+ Signup enabled
.form-group
- = f.label :gravatar_enabled, class: 'control-label'
- .col-sm-10
- = f.check_box :gravatar_enabled, class: 'checkbox'
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :gravatar_enabled do
+ = f.check_box :gravatar_enabled
+ Gravatar enabled
.form-group
- = f.label :twitter_sharing_enabled, "Twitter enabled", class: 'control-label'
- .col-sm-10
- = f.check_box :twitter_sharing_enabled, class: 'checkbox'
- %span.help-block Show users button to share their newly created public or internal projects on twitter
+ .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'
+ = 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'
+ = 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 :home_page_url, class: 'control-label'
+ = 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'
- %span.help-block We will redirect non-logged in users to this page
+ = 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'
+ = 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
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 931b0c5c10..d1c586328a 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -32,9 +32,9 @@
%span.light.pull-right
= Milestone.count
%p
- Users who signed in during 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
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 34421edc06..19080089b8 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -14,7 +14,7 @@
.form-group
.col-sm-2
.col-sm-10
- .bs-callout.bs-callout-info
+ .alert.alert-info
= render 'shared/group_tips'
.form-actions
= f.submit 'Create group', class: "btn btn-create"
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index b984188eb9..3a1e61d5d8 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -44,7 +44,7 @@
Projects (#{@projects.total_count})
.panel-head-actions
.dropdown.inline
- %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort:
- if @sort.present?
= sort_options_hash[@sort]
@@ -63,7 +63,7 @@
= 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-new"
+ = link_to 'New Project', new_project_path, class: "btn btn-sm btn-success"
%ul.well-list
- @projects.each do |project|
%li
diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml
index 5df8849317..291e48efc1 100644
--- a/app/views/admin/services/_form.html.haml
+++ b/app/views/admin/services/_form.html.haml
@@ -10,20 +10,59 @@
- @service.errors.full_messages.each do |msg|
%p= msg
- if @service.help.present?
- .bs-callout
+ .alert.alert-info
= preserve do
= markdown @service.help
+ .form-group
+ = f.label :url, "Trigger", class: 'control-label'
+ - if @service.supported_events.length > 1
+ .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, 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
@@ -35,6 +74,8 @@
= 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/users/index.html.haml b/app/views/admin/users/index.html.haml
index cd96bb4477..680bd9ceb9 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -36,7 +36,7 @@
.panel-head-actions
= link_to 'Send email to users', admin_email_path, class: 'btn btn-info'
.dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"}
%span.light sort:
- if @sort.present?
= sort_options_hash[@sort]
@@ -60,7 +60,7 @@
= 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"
+ = link_to 'New User', new_admin_user_path, class: "btn btn-new btn-sm"
%ul.well-list
- @users.each do |user|
%li
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 9026789750..90c9f8c2f9 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -108,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
diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml
deleted file mode 100644
index e3df43d889..0000000000
--- a/app/views/dashboard/_groups.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-.panel.panel-default
- .panel-heading.clearfix
- .input-group
- = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
- - if current_user.can_create_group?
- .input-group-addon.dash-new-group
- = link_to new_group_path, class: "" do
- %strong 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
- .dash-project-avatar
- = image_tag group_icon(group.path), class: "avatar s40"
- %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 fa9179cb24..0000000000
--- a/app/views/dashboard/_project.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-= link_to project_path(project), class: dom_class(project) do
- .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
- %span.arrow
- %i.fa.fa-angle-right
diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml
index 0596738342..3634b2bfd7 100644
--- a/app/views/dashboard/_projects.html.haml
+++ b/app/views/dashboard/_projects.html.haml
@@ -1,25 +1,10 @@
.panel.panel-default
.panel-heading.clearfix
.input-group
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control'
+ = 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 d87ca861ae..0000000000
--- a/app/views/dashboard/_projects_filter.html.haml
+++ /dev/null
@@ -1,100 +0,0 @@
-.dash-projects-filters.append-bottom-20
- .append-right-20
- %ul.nav.nav-tabs
- = nav_tab :scope, nil do
- = link_to projects_dashboard_filter_path(scope: nil) do
- All
- = nav_tab :scope, 'personal' do
- = link_to projects_dashboard_filter_path(scope: 'personal') do
- Personal
- = nav_tab :scope, 'joined' do
- = link_to projects_dashboard_filter_path(scope: 'joined') do
- Joined
- = nav_tab :scope, 'owned' do
- = link_to projects_dashboard_filter_path(scope: 'owned') do
- Owned
-
- .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 projects_dashboard_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 projects_dashboard_filter_path(visibility_level: level) do
- = visibility_level_icon(level)
- = visibility_level_label(level)
-
- - if @groups.present?
- .dropdown.inline.append-right-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-group
- %span.light Group:
- - if params[:group].present?
- = Group.find_by(name: params[:group]).name
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to projects_dashboard_filter_path(group: nil) do
- Any
- - @groups.each do |group|
- %li{ class: (group.name == params[:group]) ? 'active' : 'light' }
- = link_to projects_dashboard_filter_path(group: group.name) do
- = group.name
- %small.pull-right
- = group.projects.count
-
-
-
- - 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 projects_dashboard_filter_path(tag: nil) do
- Any
-
- - @tags.each do |tag|
- %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
- = link_to projects_dashboard_filter_path(tag: tag.name) do
- %i.fa.fa-tag
- = tag.name
-
- .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 projects_dashboard_filter_path(sort: sort_value_recently_created) do
- = sort_title_recently_created
- = link_to projects_dashboard_filter_path(sort: sort_value_oldest_created) do
- = sort_title_oldest_created
- = link_to projects_dashboard_filter_path(sort: sort_value_recently_updated) do
- = sort_title_recently_updated
- = link_to projects_dashboard_filter_path(sort: sort_value_oldest_updated) do
- = sort_title_oldest_updated
- = link_to projects_dashboard_filter_path(sort: sort_value_name) do
- = sort_title_name
diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml
index a980f49542..78f695be91 100644
--- a/app/views/dashboard/_sidebar.html.haml
+++ b/app/views/dashboard/_sidebar.html.haml
@@ -1,18 +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
= render 'shared/promo'
diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml
index 6e76f95b34..4e7d663972 100644
--- a/app/views/dashboard/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/_zero_authorized_projects.html.haml
@@ -1,3 +1,4 @@
+- publicish_project_count = Project.publicish(current_user).count
%h3.page-title Welcome to GitLab!
%p.light Self hosted Git management application.
%hr
@@ -35,7 +36,7 @@
%i.fa.fa-plus
New Group
--if @publicish_project_count > 0
+-if publicish_project_count > 0
%hr
%div
.dashboard-intro-icon
@@ -43,7 +44,7 @@
.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.
diff --git a/app/views/profiles/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
similarity index 79%
rename from app/views/profiles/groups/index.html.haml
rename to app/views/dashboard/groups/index.html.haml
index daf76636ff..50e90b1c17 100644
--- a/app/views/profiles/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -23,10 +23,11 @@
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
+ = link_to leave_dashboard_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
+ = image_tag group_icon(group.path), class: "avatar s40 avatar-tile"
= link_to group, class: 'group-name' do
%strong= group.name
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 03d4b3d8bb..0000000000
--- a/app/views/dashboard/projects.html.haml
+++ /dev/null
@@ -1,60 +0,0 @@
-%h3.page-title
- My Projects
-
- = link_to new_project_path, class: "btn btn-new pull-right" do
- %i.fa.fa-plus
- New Project
-
-%p.light
- All projects you have access to are listed here. Public projects are not included here unless you are a member
-%hr
-.side-filters
- = render "projects_filter"
-.dash-projects
- %ul.bordered-list.my-projects.top-list
- - @projects.each do |project|
- %li.my-project-row
- %h4.project-title
- .pull-left
- = project_icon(project, alt: '', class: 'avatar project-avatar s60')
- .project-access-icon
- = visibility_level_icon(project.visibility_level)
- = link_to project_path(project), class: dom_class(project) do
- %strong= project.name_with_namespace
-
- - if project.forked_from_project
-
- %small
- %i.fa.fa-code-fork
- Forked from:
- = link_to project.forked_from_project.name_with_namespace, namespace_project_path(project.namespace, project.forked_from_project)
-
- - if current_user.can_leave_project?(project)
- .pull-right
- = link_to leave_namespace_project_team_members_path(project.namespace, 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
-
- .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.html.haml b/app/views/dashboard/show.html.haml
index f973f4829a..fa8946011b 100644
--- a/app/views/dashboard/show.html.haml
+++ b/app/views/dashboard/show.html.haml
@@ -1,4 +1,4 @@
-- if @has_authorized_projects
+- if @projects.any?
.dashboard.row
%section.activities.col-md-8
= render 'activities'
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/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/index.html.haml b/app/views/explore/projects/index.html.haml
index cb93b300d6..5086b58cd0 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -1,30 +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
- = button_tag 'Search', class: "btn btn-primary wide"
-
- .pull-right
- .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_path(sort: sort_value_recently_created) do
- = sort_title_recently_created
- = link_to explore_projects_path(sort: sort_value_oldest_created) do
- = sort_title_oldest_created
- = link_to explore_projects_path(sort: sort_value_recently_updated) do
- = sort_title_recently_updated
- = link_to explore_projects_path(sort: sort_value_oldest_updated) do
- = sort_title_oldest_updated
+ = render 'filter'
%hr
.public-projects
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index b505760fa8..6f53e125c4 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -1,23 +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-avatar
- = project_icon(project, alt: '', class: 'avatar s40')
- .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/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index 21029c3a07..6267006f63 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -8,6 +8,9 @@
%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
@@ -19,7 +22,7 @@
%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
+ = link_to leave_dashboard_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny 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
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 7f0b2832ca..9febaab04a 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -36,11 +36,11 @@
= 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)
%div
%br
- - milestone.projects.each do |project|
- %span.label.label-default
- = project.name
+ - 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
= paginate @group_milestones, theme: "gitlab"
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index e3606d167a..dd2d84499b 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -45,8 +45,7 @@
#{@group_milestone.closed_items_count} closed
–
#{@group_milestone.open_items_count} open
- .progress.progress-info
- .progress-bar{style: "width: #{@group_milestone.percent_complete}%;"}
+ = milestone_progress_bar(@group_milestone)
%ul.nav.nav-tabs
%li.active
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index d5e6cb46f3..976868ec8f 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -4,7 +4,7 @@
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
+ = 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
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
new file mode 100644
index 0000000000..58de5b7c86
--- /dev/null
+++ b/app/views/help/ui.html.haml
@@ -0,0 +1,208 @@
+- 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 '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#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/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
index bd6bb3c720..3d6d2bfc00 100644
--- a/app/views/layouts/_public_head_panel.html.haml
+++ b/app/views/layouts/_public_head_panel.html.haml
@@ -12,7 +12,7 @@
- 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'
+ = 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
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 48c7c99942..e4f630c6a1 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -3,12 +3,22 @@
= link_to root_path, title: 'Home', class: 'shortcuts-activity' do
%i.fa.fa-dashboard
%span
- Activity
- = nav_link(path: 'dashboard#projects') do
- = link_to projects_dashboard_path, title: 'Projects', class: 'shortcuts-projects' do
- %i.fa.fa-cube
+ Your Projects
+ = nav_link(path: 'projects#starred') do
+ = link_to starred_dashboard_projects_path, title: 'Starred Projects' do
+ %i.fa.fa-star
%span
- Projects
+ 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 assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do
%i.fa.fa-exclamation-circle
@@ -26,4 +36,3 @@
%i.fa.fa-question-circle
%span
Help
-
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 0914d2a167..d88e862829 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -43,11 +43,6 @@
%i.fa.fa-image
%span
Design
- = nav_link(controller: :groups) do
- = link_to profile_groups_path, title: 'Groups' do
- %i.fa.fa-group
- %span
- Groups
= nav_link(path: 'profiles#history') do
= link_to history_profile_path, title: 'History' do
%i.fa.fa-history
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 8cca80e524..7eec93abdf 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -16,6 +16,18 @@
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
@@ -27,5 +39,5 @@
- if @target_url
#{link_to "View it on GitLab", @target_url}
= email_action @target_url
- - if @project
+ - 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/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index a45d1dedcd..039b92df2b 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -1,6 +1,12 @@
%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|
@@ -9,22 +15,52 @@
%div
%span by #{commit.author_name}
%i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
- %pre #{commit.safe_message}
+ %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
- = color_email_diff(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 fa355cb526..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, namespace_project_url(@project.namespace, @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, namespace_project_commit_url(@project.namespace, @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 f124637c07..6bafcb5655 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -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
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index e3cd323927..6cf5c81c19 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -50,7 +50,7 @@
%p You will receive all notifications from projects in which you participate
.form-actions
- = f.submit 'Save changes', class: "btn btn-save"
+ = f.submit 'Save changes', class: "btn btn-create"
.clearfix
%hr
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 3b1ebbfaf5..4b04b113e8 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -35,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/show.html.haml b/app/views/profiles/show.html.haml
index b2808c46c0..1a7bc353bf 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -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
@@ -89,10 +89,12 @@
= 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"
- 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
+ .col-sm-2
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml
index 5c52f91927..07d4d60276 100644
--- a/app/views/projects/_bitbucket_import_modal.html.haml
+++ b/app/views/projects/_bitbucket_import_modal.html.haml
@@ -10,4 +10,4 @@
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/butbucket.md'}.
\ No newline at end of file
+ == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md'}.
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index bfacab5e48..a7cd129b63 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -50,6 +50,7 @@
= f.select(:milestone_id, milestone_options(issuable),
{ include_blank: 'Select milestone' }, { class: 'select2' })
- else
+ .prepend-top-10
%span.light No open milestones available.
- if can? current_user, :admin_milestone, issuable.project
@@ -63,6 +64,7 @@
= 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.
- if can? current_user, :admin_label, issuable.project
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 64cc3fad6c..9ff61f3887 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -15,7 +15,7 @@
- else
= link_to title, '#'
-%ul.blob-commit-info.bs-callout.bs-callout-info.hidden-xs
+%ul.blob-commit-info.well.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 20e51d18da..9b5eb84a86 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -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 c9a6b3ebd9..af1f342afb 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -1,4 +1,4 @@
-.bs-callout.bs-callout-warning
+.alert.alert-warning
%h4
Too many changes.
.pull-right
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/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index 097374e112..f1248ac2af 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -12,7 +12,7 @@
%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
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.
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 7defc8787a..cbbcb1d06c 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -25,11 +25,11 @@
.clearfix
.issues_bulk_update.hide
= form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do
- = select_tag('update[status]', options_for_select([['Open', 'open'], ['Closed', 'closed']]), prompt: "Status")
+ = 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 :status, params[:status]
+ = hidden_field_tag :state_event, params[:state_event]
= button_tag "Update issues", class: "btn update_selected_issues btn-save"
.issues-holder
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index 95912536e4..2305fce112 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -2,7 +2,7 @@
-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
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 73eccfa556..bf80afe878 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -99,11 +99,11 @@
- if @diffs.present?
= render "projects/diffs/diffs", diffs: @diffs, project: @project
- elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE
- .bs-callout.bs-callout-danger
+ .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
- .bs-callout.bs-callout-danger
+ .alert.alert-danger
%h4 This comparison includes a huge diff.
%p To preserve performance the line changes are not shown.
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index cfef1d5e4c..786b5f3906 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -3,7 +3,7 @@
- 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
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 ee7fd0ef15..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), :"data-no-turbolink" => "data-no-turbolink"
+ = 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), :"data-no-turbolink" => "data-no-turbolink"
+ = 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), :"data-no-turbolink" => "data-no-turbolink"
+ = 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/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml
index 46f2df1b18..42fbd0cd2c 100644
--- a/app/views/projects/milestones/_merge_request.html.haml
+++ b/app/views/projects/milestones/_merge_request.html.haml
@@ -3,3 +3,6 @@
= 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/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index d32b2ba271..dcf56541db 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -23,5 +23,4 @@
= 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/show.html.haml b/app/views/projects/milestones/show.html.haml
index fea96f3701..110d896734 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -43,8 +43,7 @@
%span.light #{@milestone.percent_complete}% complete
%span.pull-right= @milestone.expires_at
- .progress.progress-info
- .progress-bar{style: "width: #{@milestone.percent_complete}%;"}
+ = milestone_progress_bar(@milestone)
%ul.nav.nav-tabs
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 025c4fd550..00b912742b 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -34,7 +34,7 @@
%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.
@@ -65,7 +65,7 @@
%i.fa.fa-bitbucket
Import projects from Bitbucket
= render 'bitbucket_import_modal'
-
+
- unless request.host == 'gitlab.com'
.project-import.form-group
.col-sm-2
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index dc20e96732..cfe2808417 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -2,7 +2,7 @@
%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"}
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 0ebf2f78fe..3492dd5bab 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -18,7 +18,7 @@
%li= msg
- if @service.help.present?
- .bs-callout
+ .alert.alert-info
= preserve do
= markdown @service.help
@@ -27,16 +27,63 @@
.col-sm-10
= f.check_box :active
+ .form-group
+ = f.label :url, "Trigger", class: 'control-label'
+ - if @service.supported_events.length > 1
+ .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]
+ - 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
@@ -48,6 +95,8 @@
= 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'
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 787cfd9304..74b0739565 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -40,7 +40,7 @@
%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
diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml
index 9de61afd0f..eb81544740 100644
--- a/app/views/projects/team_members/_team_member.html.haml
+++ b/app/views/projects/team_members/_team_member.html.haml
@@ -12,4 +12,7 @@
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
%p
%strong= user.name
+ - if user.blocked?
+ %label.label.label-danger
+ %strong Blocked
%span.cgray= user.username
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 5875f71bac..b34dd53e3b 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -15,7 +15,7 @@
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false
- if @group.persisted?
- .bs-callout.bs-callout-danger
+ .alert.alert-danger
%ul
%li Changing group path can have unintended side effects.
%li Renaming group path will rename directory for all related projects
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index 1a2946bacc..089179e677 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,8 +1,8 @@
- 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} to your profile
+ 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
+ = 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'
+ = link_to 'Remind later', '#', class: 'hide-no-ssh-message alert-link'
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/_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/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 c925a48f55..6c7779be30 100644
--- a/app/views/users/_projects.html.haml
+++ b/app/views/users/_projects.html.haml
@@ -1,21 +1,13 @@
- if @contributed_projects.present?
.panel.panel-default
.panel-heading Projects contributed to
- %ul.well-list
- - @contributed_projects.sort_by(&:star_count).reverse.each do |project|
- %li
- = link_to_project project
- %span.pull-right.light
- %i.fa.fa-star
- = project.star_count
+ = 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
- %ul.well-list
- - @projects.sort_by(&:star_count).reverse.each do |project|
- %li
- = link_to_project project
- %span.pull-right.light
- %i.fa.fa-star
- = project.star_count
+ = render 'shared/projects_list',
+ projects: @projects.sort_by(&:star_count).reverse,
+ projects_limit: 10, stars: true, avatar: false
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 5e82d5780c..abd6b22978 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,5 +1,7 @@
.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 avatar-tile s90", alt: ''
= @user.name
@@ -19,10 +21,11 @@
= render 'groups', groups: @groups
%hr
- .user-calendar
- %h4.center.light
- %i.fa.fa-spinner.fa-spin
- %hr
+ .hidden-xs
+ .user-calendar
+ %h4.center.light
+ %i.fa.fa-spinner.fa-spin
+ %hr
%h4
User Activity
@@ -33,7 +36,7 @@
%i.fa.fa-rss
= render @events
- .col-md-4
+ %aside.col-md-4
= render 'profile', user: @user
= render 'projects'
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index e3f6f3a6ae..e59ca81def 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -1,25 +1,41 @@
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
diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb
index 613bae351d..e1a99d9cad 100644
--- a/app/workers/irker_worker.rb
+++ b/app/workers/irker_worker.rb
@@ -57,9 +57,9 @@ class IrkerWorker
end
def send_branch_updates(push_data, project, repo_name, committer, branch)
- if push_data['before'] =~ /^000000/
+ if push_data['before'] == Gitlab::Git::BLANK_SHA
send_new_branch project, repo_name, committer, branch
- elsif push_data['after'] =~ /^000000/
+ elsif push_data['after'] == Gitlab::Git::BLANK_SHA
send_del_branch repo_name, committer, branch
end
end
@@ -83,7 +83,7 @@ class IrkerWorker
return if push_data['total_commits_count'] == 0
# Next message is for number of commit pushed, if any
- if push_data['before'] =~ /^000000/
+ 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
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 1406cba2db..ecc6c8e53a 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -33,7 +33,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)
@@ -44,10 +44,4 @@ class PostReceive
def log(message)
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
-
- private
-
- def tag?(ref)
- !!(/refs\/tags\/(.*)/.match(ref))
- end
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 42a617b628..2806239d4c 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -43,6 +43,7 @@ production: &base
# 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 config/initializers/smtp_settings.rb.sample
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 12a33a6e1b..a489baed67 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -137,6 +137,7 @@ 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
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
index e00923e7e0..f0fe2fdfa4 100644
--- a/config/initializers/smtp_settings.rb.sample
+++ b/config/initializers/smtp_settings.rb.sample
@@ -3,6 +3,9 @@
# 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
@@ -14,6 +17,6 @@ if Rails.env.production?
domain: "gitlab.company.com",
authentication: :login,
enable_starttls_auto: true,
- openssl_verify_mode: 'none'
+ openssl_verify_mode: 'peer' # See ActionMailer documentation for other possible options
}
end
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/routes.rb b/config/routes.rb
index c505c8d335..c76fa537dc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -7,9 +7,8 @@ Gitlab::Application.routes.draw do
authorized_applications: 'oauth/authorized_applications',
authorizations: 'oauth/authorizations'
end
- #
+
# Search
- #
get 'search' => 'search#show'
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
@@ -33,14 +32,12 @@ 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/:category/*file' => 'help#show'
get 'help/shortcuts'
+ get 'help/ui' => 'help#ui'
#
# Global snippets
@@ -74,7 +71,7 @@ Gitlab::Application.routes.draw do
get :callback
get :jobs
end
-
+
resource :gitorious, only: [:create, :new], controller: :gitorious do
get :status
get :callback
@@ -211,11 +208,6 @@ 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
@@ -231,10 +223,25 @@ Gitlab::Application.routes.draw 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] do
+ member do
+ delete :leave
+ end
+ end
+
+ resources :projects, only: [] do
+ collection do
+ get :starred
+ end
+ end
+ end
end
#
@@ -260,7 +267,7 @@ Gitlab::Application.routes.draw do
resources :ldap_group_links, only: [:index, :create, :destroy]
resources :group_members, only: [:create, :update, :destroy]
resource :avatar, only: [:destroy]
- resources :milestones
+ resources :milestones, only: [:index, :show, :update]
end
get "/audit_events" => "audit_events#group_log"
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index d8b4f5c7c3..29253b71f4 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -35,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/05_users.rb b/db/fixtures/development/05_users.rb
index b697f58d4e..24952a1f66 100644
--- a/db/fixtures/development/05_users.rb
+++ b/db/fixtures/development/05_users.rb
@@ -1,30 +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 do |s|
- s.username = "user#{i}"
- s.name = "User #{i}"
- s.email = "user#{i}@example.com"
- s.confirmed_at = DateTime.now
- s.password = '12345678'
- end
+ 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/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/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/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/schema.rb b/db/schema.rb
index 2352ec2fe5..5c6d2c3eb2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20150225214822) do
+ActiveRecord::Schema.define(version: 20150306023112) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -291,9 +291,9 @@ ActiveRecord::Schema.define(version: 20150225214822) do
end
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", 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|
@@ -399,7 +399,7 @@ ActiveRecord::Schema.define(version: 20150225214822) do
t.boolean "merge_requests_rebase_enabled", default: false
t.string "import_type"
t.string "import_source"
- t.boolean "merge_requests_rebase_default", default: false
+ t.boolean "merge_requests_rebase_default", default: true
end
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
@@ -424,9 +424,14 @@ ActiveRecord::Schema.define(version: 20150225214822) do
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 "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
diff --git a/doc/development/README.md b/doc/development/README.md
index c31e5d7ae9..d5d264be19 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -5,3 +5,4 @@
- [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/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/integration/README.md b/doc/integration/README.md
index af981d0fbb..23bdc1ebf5 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -12,6 +12,7 @@ See the documentation below for details on how to configure these services.
- [Slack](slack.md) Integrate with the Slack chat service
- [Kerberos](kerberos.md) Integrate with Kerberos
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
+- [Gmail](gitlab_buttons_in_gmail.md) Adds GitLab actions to messages
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).
diff --git a/doc/integration/github.md b/doc/integration/github.md
index a5c2913151..50eb22ca58 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -53,7 +53,7 @@ GitHub will generate an application ID and secret key for you to use.
"app_id" => "YOUR_APP_ID",
"app_secret" => "YOUR_APP_SECRET",
"url" => "https://github.com/",
- "args" => { "scope" => "user:email" } }
+ "args" => { "scope" => "user:email" }
}
]
```
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 49ffaa62af..216f1f11a9 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -58,7 +58,7 @@ GitLab.com will generate an application ID and secret key for you to use.
"name" => "gitlab",
"app_id" => "YOUR_APP_ID",
"app_secret" => "YOUR_APP_SECRET",
- "args" => { "scope" => "api" } }
+ "args" => { "scope" => "api" }
}
]
```
@@ -81,4 +81,4 @@ GitLab.com will generate an application ID and secret key for you to use.
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.
\ No newline at end of file
+If everything goes well the user will be returned to your GitLab instance and will be signed in.
diff --git a/doc/integration/google.md b/doc/integration/google.md
index d7b741ece6..e1c14c7c94 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -55,7 +55,7 @@ To enable the Google OAuth2 OmniAuth provider you must register your application
"name" => "google_oauth2",
"app_id" => "YOUR_APP_ID",
"app_secret" => "YOUR_APP_SECRET",
- "args" => { "access_type" => "offline", "approval_prompt" => '' } }
+ "args" => { "access_type" => "offline", "approval_prompt" => '' }
}
]
```
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index dd44c1eb86..a7e5faf2a6 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -200,3 +200,7 @@ Consider creating a post on Hacker News.
## Update GitLab.com with the stable version
- Deploy the package (should not need downtime because of the small difference with RC1)
+
+## Release new AMIs
+
+[Follow this guide](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md)
diff --git a/doc/release/patch.md b/doc/release/patch.md
index 80afa19b6c..5397343e71 100644
--- a/doc/release/patch.md
+++ b/doc/release/patch.md
@@ -54,3 +54,4 @@ CE=false be rake release['x.x.x']
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. [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 b67e0f37a0..6ed2526449 100644
--- a/doc/release/security.md
+++ b/doc/release/security.md
@@ -18,11 +18,12 @@ Please report suspected security vulnerabilities in private to ([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, :label, :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
@@ -290,6 +292,30 @@ module Gitlab
end
end
+ 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)
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/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index 9aa5c8967a..0cc6b0ac69 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -9,6 +9,7 @@ module Gitlab
# ref: String,
# user_id: String,
# user_name: String,
+ # user_email: String
# project_id: String,
# repository: {
# name: String,
@@ -29,12 +30,14 @@ module Gitlab
# Hash to be passed as post_receive_data
data = {
+ object_kind: "push",
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,
@@ -55,6 +58,7 @@ module Gitlab
data[:commits] << commit.hook_attrs(project)
end
+ data[:commits] = "" if data[:commits].count == 0
data
end
@@ -62,12 +66,13 @@ module Gitlab
# existing project and commits to test web hooks
def build_sample(project, user)
commits = project.repository.commits(project.default_branch, nil, 3)
- build(project, user, commits.last.id, commits.first.id, "refs/heads/#{project.default_branch}", commits)
+ 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 && ref.start_with?('refs/tags/')
- tag_name = Gitlab::Git.extract_ref_name(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
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 9ba5e36a8b..16879501a0 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,13 +1,13 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor
- attr_accessor :users, :labels, :issues, :merge_requests, :snippets, :commits
+ attr_accessor :users, :labels, :issues, :merge_requests, :snippets, :commits, :commit_ranges
include Markdown
def initialize
- @users, @labels, @issues, @merge_requests, @snippets, @commits =
- [], [], [], [], [], []
+ @users, @labels, @issues, @merge_requests, @snippets, @commits, @commit_ranges =
+ [], [], [], [], [], [], []
end
def analyze(string, project)
@@ -62,6 +62,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, _)
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index ab7c8ad89f..6830d15875 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -13,6 +13,9 @@ module Gitlab
build_issue_url(id)
when :merge_request
build_merge_request_url(id)
+ when :note
+ build_note_url(id)
+
end
end
@@ -27,5 +30,31 @@ module Gitlab
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)
+ snippet_url(snippet,
+ host: Gitlab.config.gitlab['url'],
+ anchor: "note_#{note.id}")
+ end
+ end
end
end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index 714261f815..713d7c39a1 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -3,8 +3,9 @@ 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
@@ -34,7 +35,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end
formatter = Rugments::Formatters::HTML.new(
- cssclass: "code highlight white #{lexer.tag}"
+ cssclass: "code highlight #{@color_scheme} #{lexer.tag}"
)
formatter.format(lexer.lex(code))
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 43115915de..976c4b5f22 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
@@ -781,6 +782,10 @@ 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
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/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/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 6ce1d7446f..77cd37c22d 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -40,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/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
index d1f00a3dd8..67238e3ab7 100644
--- a/spec/features/security/dashboard_access_spec.rb
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -25,8 +25,8 @@ describe "Dashboard access", feature: true do
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 { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
@@ -52,4 +52,12 @@ describe "Dashboard access", feature: true do
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/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index 5f254c42e5..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 { expect(new_user_session_path).not_to 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 { 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
+ 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 { 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
+ 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 { 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
+ 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 { 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
+ 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 { 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
+ 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 { 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/groups" do
- subject { profile_groups_path }
-
- 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
+ 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/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index c4a192ac1a..b392371deb 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -4,6 +4,8 @@ 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)
@@ -50,4 +52,14 @@ describe EventsHelper do
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 27f506d7f0..66474a6241 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -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) }
@@ -53,6 +54,53 @@ describe GitlabMarkdownHelper do
to have_selector('a.gfm.foo')
end
+ 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 = "What happened in #{earlier_commit.id}...#{commit.id}"
+ expect(gfm(actual)).to match(expected)
+ end
+
+ it "should link using a short id" do
+ 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) }
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/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 226395d46d..484950b120 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -31,6 +31,11 @@ describe Gitlab::ReferenceExtractor do
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)
expect(subject.users).to eq([
@@ -110,5 +115,19 @@ describe Gitlab::ReferenceExtractor do
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/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 94b2fd5508..5153ed15af 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -16,4 +16,62 @@ describe Gitlab::UrlBuilder do
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/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index a5f2200197..d53f6e2793 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -5,6 +5,7 @@ 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) }
@@ -23,7 +24,7 @@ describe Notify do
shared_examples 'an email sent from GitLab' do
it 'is sent from GitLab' do
sender = subject.header[:from].addrs[0]
- expect(sender.display_name).to eq('GitLab')
+ expect(sender.display_name).to eq(gitlab_sender_display_name)
expect(sender.address).to eq(gitlab_sender)
end
end
@@ -568,9 +569,10 @@ 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) { namespace_project_compare_path(project.namespace, 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]
@@ -583,7 +585,7 @@ describe Notify do
end
it 'has the correct subject' do
- is_expected.to 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
@@ -597,6 +599,58 @@ describe Notify do
it 'contains a link to the diff' do
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
describe 'email on push with a single commit' do
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index cb43fdb7fc..d1027f64d1 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -2,16 +2,17 @@
#
# 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)
+# id :integer not null, primary key
+# default_projects_limit :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)
#
require 'spec_helper'
diff --git a/spec/models/asana_service_spec.rb b/spec/models/asana_service_spec.rb
index 83e39f87f3..13c8d54a2a 100644
--- a/spec/models/asana_service_spec.rb
+++ b/spec/models/asana_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'
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index ee7f780c8f..91730da1ee 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_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'
diff --git a/spec/models/project_services/buildbox_service_spec.rb b/spec/models/project_services/buildbox_service_spec.rb
index 050363e14c..39d7df54cf 100644
--- a/spec/models/project_services/buildbox_service_spec.rb
+++ b/spec/models/project_services/buildbox_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'
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index b34e36bc94..73f68301a3 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_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'
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index fe5d62b2f5..d44064bbe6 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_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'
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
index 0cd255f08e..8bfb19e524 100644
--- a/spec/models/project_services/gitlab_ci_service_spec.rb
+++ b/spec/models/project_services/gitlab_ci_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'
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index c474f4a2d9..959044dc72 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -2,16 +2,21 @@
#
# 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)
+# 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
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
index bbd5245ad3..d55399bc36 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_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'
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index f1d8048b26..6ebff9f21b 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_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'
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 188626a7a2..5a18fd09bf 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_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'
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/project_services/slack_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb
similarity index 64%
rename from spec/models/project_services/slack_message_spec.rb
rename to spec/models/project_services/slack_service/push_message_spec.rb
index 7197a94e53..10963481a1 100644
--- a/spec/models/project_services/slack_message_spec.rb
+++ b/spec/models/project_services/slack_service/push_message_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe SlackMessage do
- subject { SlackMessage.new(args) }
+describe SlackService::PushMessage do
+ subject { SlackService::PushMessage.new(args) }
let(:args) {
{
@@ -31,17 +31,37 @@ describe SlackMessage do
)
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
@@ -55,7 +75,7 @@ describe SlackMessage do
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
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 8a75d8987a..c36506644b 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_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'
@@ -34,7 +39,7 @@ describe SlackService do
let(:slack) { SlackService.new }
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+ 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' }
@@ -48,10 +53,43 @@ describe SlackService do
)
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" do
- slack.execute(sample_data)
+ 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
@@ -59,11 +97,11 @@ describe SlackService do
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(sample_data)
+ 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
@@ -73,7 +111,60 @@ describe SlackService do
and_return(
double(:slack_service).as_null_object
)
- slack.execute(sample_data)
+ 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/repository_spec.rb b/spec/models/repository_spec.rb
index eeb0f3d9ee..b3a38f6c5b 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -18,4 +18,27 @@ describe Repository do
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 9fe3f8cfb1..633ead05b2 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -2,15 +2,19 @@
#
# 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)
+# 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'
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b31acde606..ac20e0e817 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,47 +2,53 @@
#
# 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
-# 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
-# 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
-# last_credential_check_at :datetime
-# github_access_token :string(255)
+# 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'
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 5e2cb3c614..78bac58cd7 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -49,6 +49,7 @@ describe GitPushService do
subject { @push_data }
+ 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) }
@@ -196,15 +197,6 @@ describe GitPushService do
service.execute(project, user, @blankrev, @newrev, 'refs/heads/other')
end
-
- it "finds references in the first push to a default branch" do
- allow(project.repository).to receive(:commits_between).with(@blankrev, @newrev).and_return([])
- allow(project.repository).to receive(:commits).with(@newrev).and_return([commit])
-
- expect(Note).to 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
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index 504213e667..a97c55011c 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -21,10 +21,8 @@ describe Issues::BulkUpdateService do
create(:issue, project: @project)
end
@params = {
- update: {
- status: 'closed',
- issues_ids: @issues.map(&:id)
- }
+ state_event: 'close',
+ issues_ids: @issues.map(&:id)
}
end
@@ -46,10 +44,8 @@ describe Issues::BulkUpdateService do
create(:closed_issue, project: @project)
end
@params = {
- update: {
- status: 'reopen',
- issues_ids: @issues.map(&:id)
- }
+ state_event: 'reopen',
+ issues_ids: @issues.map(&:id)
}
end
@@ -69,10 +65,8 @@ describe Issues::BulkUpdateService do
before do
@new_assignee = create :user
@params = {
- update: {
- issues_ids: [issue.id],
- assignee_id: @new_assignee.id
- }
+ issues_ids: [issue.id],
+ assignee_id: @new_assignee.id
}
end
@@ -88,7 +82,7 @@ describe Issues::BulkUpdateService do
@project.issues.first.update_attribute(:assignee, @new_assignee)
expect(@project.issues.first.assignee).not_to be_nil
- @params[:update][:assignee_id] = -1
+ @params[:assignee_id] = -1
Issues::BulkUpdateService.new(@project, @user, @params).execute
expect(@project.issues.first.assignee).to be_nil
@@ -98,7 +92,7 @@ describe Issues::BulkUpdateService do
@project.issues.first.update_attribute(:assignee, @new_assignee)
expect(@project.issues.first.assignee).not_to be_nil
- @params[:update][:assignee_id] = ''
+ @params[:assignee_id] = ''
Issues::BulkUpdateService.new(@project, @user, @params).execute
expect(@project.issues.first.assignee).not_to be_nil
@@ -110,10 +104,8 @@ describe Issues::BulkUpdateService do
before do
@milestone = create :milestone
@params = {
- update: {
- issues_ids: [issue.id],
- milestone_id: @milestone.id
- }
+ issues_ids: [issue.id],
+ milestone_id: @milestone.id
}
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 2830da8781..879df0c9c6 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -61,7 +61,7 @@ describe MergeRequests::RefreshService do
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('new commit') }
+ it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') }
it { expect(@fork_merge_request).to be_open }
end
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: <