diff --git a/CHANGELOG b/CHANGELOG index 67ded53ad7..247eb1e364 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,16 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) - - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu) + - Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu) + - Omit filename in Content-Disposition header in raw file download to avoid RFC 6266 encoding issues (Stan HU) + - Fix broken Wiki Page History (Stan Hu) + - Prevent anchors from being hidden by header (Stan Hu) + - Fix bug where only the first 15 Bitbucket issues would be imported (Stan Hu) + - Sort issues by creation date in Bitbucket importer (Stan Hu) + - Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with ISO-encoded files (Stan Hu) + - Prevent too many redirects upon login when home page URL is set to external_url (Stan Hu) + - Improve dropdown positioning on the project home page (Hannes Rosenögger) + - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu) - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - Remove satellites @@ -13,6 +22,31 @@ v 8.0.0 (unreleased) - Search for comments should be case insensetive - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) - Ability to search milestones + - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu) + - Move dashboard activity to separate page (for your projects and starred projects) + - Improve performance of git blame + - Limit content width to 1200px for most of pages to improve readability on big screens + - Fix 500 error when submit project snippet without body + - Improve search page usability + - Bring more UI consistency in way how projects, snippets and groups lists are rendered + - Make all profiles public + - Fixed login failure when extern_uid changes (Joel Koglin) + - Don't notify users without access to the project when they are (accidentally) mentioned in a note. + - Retrieving oauth token with LDAP credentials + - Load Application settings from running database unless env var USE_DB=false + - Added Drone CI integration (Kirill Zaitsev) + - Refactored service API and added automatically service docs generator (Kirill Zaitsev) + - Added web_url key project hook_attrs (Kirill Zaitsev) + - Add ability to get user information by ID of an SSH key via the API + - Fix bug which IE cannot show image at markdown when the image is raw file of gitlab + - Add support for Crowd + +v 7.14.1 + - Improve abuse reports management from admin area + - Fix "Reload with full diff" URL button in compare branch view (Stan Hu) + - Only include base URL in OmniAuth full_host parameter (Stan Hu) + - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu) + - Ability to enable SSL verification for Webhooks v 7.14.0 - Fix bug where non-project members of the target project could set labels on new merge requests. @@ -90,7 +124,7 @@ v 7.13.4 v 7.13.3 - Fix bug causing Bitbucket importer to crash when OAuth application had been removed. - Allow users to send abuse reports - - Remove satellites + - Remove satellites - Link username to profile on Group Members page (Tom Webster) v 7.13.2 @@ -298,6 +332,7 @@ v 7.11.0 - Protect OmniAuth request phase against CSRF. - Don't send notifications to mentioned users that don't have access to the project in question. - Add search issues/MR by number + - Change plots to bar graphs in commit statistics screen - Move snippets UI to fluid layout - Improve UI for sidebar. Increase separation between navigation and content - Improve new project command options (Ben Bodenmiller) diff --git a/Gemfile b/Gemfile index 3aa3c72e08..cca8fc38e5 100644 --- a/Gemfile +++ b/Gemfile @@ -25,6 +25,7 @@ gem 'omniauth-kerberos', group: :kerberos gem 'omniauth-gitlab' gem 'omniauth-bitbucket' gem 'omniauth-saml', '~> 1.4.0' +gem 'omniauth_crowd' gem 'doorkeeper', '2.1.3' gem "rack-oauth2", "~> 1.0.5" @@ -34,16 +35,11 @@ gem 'rqrcode-rails3' gem 'attr_encrypted', '1.3.4' # Browser detection -gem "browser", '~> 0.8.0' +gem "browser", '~> 1.0.0' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.2.14' - -# Ruby/Rack Git Smart-HTTP Server Handler -# GitLab fork with a lot of changes (improved thread-safety, better memory usage etc) -# For full list of changes see https://github.com/SaitoWu/grack/compare/master...gitlabhq:master -gem 'gitlab-grack', '~> 2.0.2', require: 'grack' +gem "gitlab_git", '~> 7.2.15' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes @@ -273,6 +269,6 @@ gem "newrelic_rpm" gem 'octokit', '3.7.0' -gem "mail_room", "~> 0.4.0" +gem "mail_room", "~> 0.4.2" gem 'email_reply_parser' diff --git a/Gemfile.lock b/Gemfile.lock index 5278fe243a..2df318f338 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,7 +76,7 @@ GEM ruby_parser (~> 3.5.0) sass (~> 3.0) terminal-table (~> 1.4) - browser (0.8.0) + browser (1.0.0) builder (3.2.2) byebug (3.2.0) columnize (~> 0.8) @@ -263,8 +263,6 @@ GEM flowdock (~> 0.7) gitlab-grit (>= 2.4.1) multi_json - gitlab-grack (2.0.2) - rack (~> 1.5.1) gitlab-grit (2.7.2) charlock_holmes (~> 0.6) diff-lcs (~> 1.1) @@ -276,7 +274,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.1.0) gemojione (~> 2.0) - gitlab_git (7.2.14) + gitlab_git (7.2.15) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -372,7 +370,7 @@ GEM systemu (~> 2.6.2) mail (2.6.3) mime-types (>= 1.16, < 3) - mail_room (0.4.0) + mail_room (0.4.2) method_source (0.8.2) mime-types (1.25.1) mimemagic (0.3.0) @@ -436,6 +434,10 @@ GEM omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) + omniauth_crowd (2.2.3) + activesupport + nokogiri (>= 1.4.4) + omniauth (~> 1.0) opennebula (4.12.1) json nokogiri @@ -462,7 +464,7 @@ GEM pyu-ruby-sasl (0.0.3.3) quiet_assets (1.0.2) railties (>= 3.1, < 5.0) - racc (1.4.10) + racc (1.4.12) rack (1.5.5) rack-accept (0.4.5) rack (>= 0.4) @@ -755,7 +757,7 @@ DEPENDENCIES binding_of_caller bootstrap-sass (~> 3.0) brakeman - browser (~> 0.8.0) + browser (~> 1.0.0) byebug cal-heatmap-rails (~> 0.0.1) capybara (~> 2.4.0) @@ -787,10 +789,9 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) github-markup gitlab-flowdock-git-hook (~> 1.0.1) - gitlab-grack (~> 2.0.2) gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.1) - gitlab_git (~> 7.2.14) + gitlab_git (~> 7.2.15) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.1) gollum-lib (~> 4.0.2) @@ -808,7 +809,7 @@ DEPENDENCIES jquery-ui-rails kaminari (~> 0.15.1) letter_opener - mail_room (~> 0.4.0) + mail_room (~> 0.4.2) minitest (~> 5.3.0) mousetrap-rails mysql2 @@ -824,6 +825,7 @@ DEPENDENCIES omniauth-saml (~> 1.4.0) omniauth-shibboleth omniauth-twitter + omniauth_crowd org-ruby (= 0.9.12) pg poltergeist (~> 1.6.0) @@ -883,4 +885,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.10.6 + 1.10.5 diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/OFL.txt new file mode 100755 index 0000000000..a9b845ed1d --- /dev/null +++ b/app/assets/fonts/OFL.txt @@ -0,0 +1,92 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/assets/fonts/SourceSansPro-Black.ttf b/app/assets/fonts/SourceSansPro-Black.ttf new file mode 100755 index 0000000000..cb89a2d171 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Black.ttf differ diff --git a/app/assets/fonts/SourceSansPro-BlackItalic.ttf b/app/assets/fonts/SourceSansPro-BlackItalic.ttf new file mode 100755 index 0000000000..c719243c0d Binary files /dev/null and b/app/assets/fonts/SourceSansPro-BlackItalic.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf b/app/assets/fonts/SourceSansPro-Bold.ttf new file mode 100755 index 0000000000..50d81bdad5 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Bold.ttf differ diff --git a/app/assets/fonts/SourceSansPro-BoldItalic.ttf b/app/assets/fonts/SourceSansPro-BoldItalic.ttf new file mode 100755 index 0000000000..d20dd0c5ec Binary files /dev/null and b/app/assets/fonts/SourceSansPro-BoldItalic.ttf differ diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf b/app/assets/fonts/SourceSansPro-ExtraLight.ttf new file mode 100755 index 0000000000..bb4176c6ff Binary files /dev/null and b/app/assets/fonts/SourceSansPro-ExtraLight.ttf differ diff --git a/app/assets/fonts/SourceSansPro-ExtraLightItalic.ttf b/app/assets/fonts/SourceSansPro-ExtraLightItalic.ttf new file mode 100755 index 0000000000..2c34f3b8dc Binary files /dev/null and b/app/assets/fonts/SourceSansPro-ExtraLightItalic.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Italic.ttf b/app/assets/fonts/SourceSansPro-Italic.ttf new file mode 100755 index 0000000000..e5a1a86e63 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Italic.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Light.ttf b/app/assets/fonts/SourceSansPro-Light.ttf new file mode 100755 index 0000000000..5f64679f6b Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Light.ttf differ diff --git a/app/assets/fonts/SourceSansPro-LightItalic.ttf b/app/assets/fonts/SourceSansPro-LightItalic.ttf new file mode 100755 index 0000000000..88a6778d24 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-LightItalic.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf b/app/assets/fonts/SourceSansPro-Regular.ttf new file mode 100755 index 0000000000..91e9ea5757 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Regular.ttf differ diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf b/app/assets/fonts/SourceSansPro-Semibold.ttf new file mode 100755 index 0000000000..5020594826 Binary files /dev/null and b/app/assets/fonts/SourceSansPro-Semibold.ttf differ diff --git a/app/assets/fonts/SourceSansPro-SemiboldItalic.ttf b/app/assets/fonts/SourceSansPro-SemiboldItalic.ttf new file mode 100755 index 0000000000..2c5ad3008c Binary files /dev/null and b/app/assets/fonts/SourceSansPro-SemiboldItalic.ttf differ diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee index 777c62dc1b..6380374741 100644 --- a/app/assets/javascripts/activities.js.coffee +++ b/app/assets/javascripts/activities.js.coffee @@ -1,7 +1,7 @@ class @Activities constructor: -> Pager.init 20, true - $(".event_filter_link").bind "click", (event) => + $(".event-filter .btn").bind "click", (event) => event.preventDefault() @toggleFilter($(event.currentTarget)) @reloadActivities() @@ -12,7 +12,7 @@ class @Activities toggleFilter: (sender) -> - sender.parent().toggleClass "active" + sender.toggleClass "active" event_filters = $.cookie("event_filter") filter = sender.attr("id").split("_")[0] if event_filters diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index bb0a0c51fd..8e987ac4e8 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -94,16 +94,18 @@ window.unbindEvents = -> $(document).off('scroll') window.shiftWindow = -> - scrollBy 0, -50 + scrollBy 0, -100 document.addEventListener("page:fetch", unbindEvents) -# Scroll the window to avoid the topnav bar -# https://github.com/twitter/bootstrap/issues/1768 -if location.hash - setTimeout shiftWindow, 1 window.addEventListener "hashchange", shiftWindow +window.onload = -> + # Scroll the window to avoid the topnav bar + # https://github.com/twitter/bootstrap/issues/1768 + if location.hash + setTimeout shiftWindow, 100 + $ -> $(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF") @@ -116,6 +118,12 @@ $ -> $('.remove-row').bind 'ajax:success', -> $(this).closest('li').fadeOut() + $('.js-remove-tr').bind 'ajax:before', -> + $(this).hide() + + $('.js-remove-tr').bind 'ajax:success', -> + $(this).closest('tr').fadeOut() + # Initialize select2 selects $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 81e7379927..5bf0b30217 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -51,10 +51,10 @@ class Dispatcher MergeRequests.init() when 'dashboard:show', 'root:show' new Dashboard() + when 'dashboard:activity' new Activities() when 'dashboard:projects:starred' new Activities() - new ProjectsList() when 'projects:commit:show' new Commit() new Diff() @@ -69,7 +69,6 @@ class Dispatcher when 'groups:show' new Activities() shortcut_handler = new ShortcutsNavigation() - new ProjectsList() when 'groups:group_members:index' new GroupMembers() new UsersSelect() @@ -95,8 +94,6 @@ class Dispatcher when 'users:show' new User() new Activities() - when 'admin:users:show' - new ProjectsList() switch path.first() when 'admin' diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 0021d17d85..b7f2c63c5a 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -123,6 +123,7 @@ class @Notes if @isNewNote(note) @note_ids.push(note.id) $('ul.main-notes-list').append(note.html) + $('.js-syntax-highlight').syntaxHighlight() @initTaskList() ### diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee index c0e36d1ccc..db5faf71fa 100644 --- a/app/assets/javascripts/projects_list.js.coffee +++ b/app/assets/javascripts/projects_list.js.coffee @@ -8,7 +8,7 @@ class @ProjectsList $(".projects-list-filter").keyup -> terms = $(this).val() - uiBox = $(this).closest('.panel') + uiBox = $(this).closest('.projects-list-holder') if terms == "" || terms == undefined uiBox.find(".projects-list li").show() else diff --git a/app/assets/javascripts/syntax_highlight.coffee b/app/assets/javascripts/syntax_highlight.coffee new file mode 100644 index 0000000000..71295cd4b0 --- /dev/null +++ b/app/assets/javascripts/syntax_highlight.coffee @@ -0,0 +1,12 @@ +# Applies a syntax highlighting color scheme CSS class to any element with the +# `js-syntax-highlight` class +# +# ### Example Markup +# +#
+# +$.fn.syntaxHighlight = -> + $(this).addClass(gon.user_color_scheme) + +$(document).on 'ready page:load', -> + $('.js-syntax-highlight').syntaxHighlight() diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee index 8a0564a909..a1462cf3ca 100644 --- a/app/assets/javascripts/zen_mode.js.coffee +++ b/app/assets/javascripts/zen_mode.js.coffee @@ -38,6 +38,8 @@ class @ZenMode @active_checkbox = $(checkbox) @active_checkbox.prop('checked', true) @active_zen_area = @active_checkbox.parent().find('textarea') + # Prevent a user-resized textarea from persisting to fullscreen + @active_zen_area.removeAttr('style') @active_zen_area.focus() exitZenMode: => diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 1a5f11df7d..46f7feddf8 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -12,6 +12,7 @@ */ +@import "base/fonts"; @import "base/variables"; @import "base/mixins"; @import "base/layout"; diff --git a/app/assets/stylesheets/base/fonts.scss b/app/assets/stylesheets/base/fonts.scss new file mode 100644 index 0000000000..e214567eca --- /dev/null +++ b/app/assets/stylesheets/base/fonts.scss @@ -0,0 +1,25 @@ +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 300; + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), font-url('SourceSansPro-Light.ttf'); +} +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 400; + src: local('Source Sans Pro'), local('SourceSansPro-Regular'), font-url('SourceSansPro-Regular.ttf'); +} +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 600; + src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), font-url('SourceSansPro-Semibold.ttf'); +} +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), font-url('SourceSansPro-Bold.ttf'); +} diff --git a/app/assets/stylesheets/base/gl_variables.scss b/app/assets/stylesheets/base/gl_variables.scss index 56f4c794e1..6d3b4b1e73 100644 --- a/app/assets/stylesheets/base/gl_variables.scss +++ b/app/assets/stylesheets/base/gl_variables.scss @@ -42,17 +42,18 @@ $font-size-base: $gl-font-size; // //## 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-base-vertical: 9px; +$padding-base-horizontal: $gl-padding; +$component-active-color: #fff; +$component-active-bg: $brand-info; //== Forms // //## $input-color: $text-color; -$input-border: #DDD; -$input-border-focus: $brand-info; +$input-border: #e7e9ed; +$input-border-focus: #7F8FA4; $legend-color: $text-color; @@ -111,8 +112,8 @@ $alert-border-radius: 0; $panel-border-radius: 0; $panel-default-text: $text-color; -$panel-default-border: $border-color; -$panel-default-heading-bg: $background-color; +$panel-default-border: #E7E9ED; +$panel-default-heading-bg: #F8FAFC; //== Wells @@ -131,3 +132,15 @@ $code-bg: #f9f2f4; $kbd-color: #fff; $kbd-bg: #333; + +//== Buttons +// +//## +$btn-default-color: $gl-text-color; +$btn-default-bg: #fff; +$btn-default-border: #e7e9ed; + +//== Nav +// +//## +$nav-link-padding: 13px $gl-padding; diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/base/layout.scss index 690d89a5c1..b91c15d891 100644 --- a/app/assets/stylesheets/base/layout.scss +++ b/app/assets/stylesheets/base/layout.scss @@ -20,3 +20,7 @@ html { .navless-container { margin-top: 30px; } + +.container-limited { + max-width: $fixed-layout-width; +} diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss index 7beef1845e..8698109bc4 100644 --- a/app/assets/stylesheets/base/mixins.scss +++ b/app/assets/stylesheets/base/mixins.scss @@ -55,8 +55,11 @@ } @mixin md-typography { - font-size: 15px; - line-height: 1.5; + color: #444; + + a { + color: #3084bb; + } img { max-width: 100%; @@ -157,3 +160,58 @@ white-space: nowrap; max-width: $max_width; } + +/* + * Base mixin for lists in GitLab + */ +@mixin basic-list { + margin: 5px 0px; + padding: 0px; + list-style: none; + + > li { + padding: 10px 0; + border-bottom: 1px solid #EEE; + overflow: hidden; + display: block; + margin: 0px; + + &:last-child { + border:none + } + + &.active { + background: #f9f9f9; + a { + font-weight: bold; + } + } + + &.hide { + display: none; + } + + &.light { + a { + color: $gl-gray; + } + } + } +} + +@mixin input-big { + height: 36px; + padding: 5px 10px; + font-size: 16px; + line-height: 24px; + color: #7f8fa4; + background-color: #fff; + border-color: #e7e9ed; +} + +@mixin btn-big { + height: 36px; + padding: 5px 10px; + font-size: 16px; + line-height: 24px; +} diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss index cb439a0e0b..21462b3112 100644 --- a/app/assets/stylesheets/base/variables.scss +++ b/app/assets/stylesheets/base/variables.scss @@ -1,27 +1,30 @@ -$style_color: #474D57; $hover: #FFFAF1; -$gl-text-color: #222222; -$gl-link-color: #446e9b; +$gl-text-color: #54565b; +$gl-header-color: #4c4e54; +$gl-link-color: #333c48; $nprogress-color: #c0392b; -$gl-font-size: 14px; +$gl-font-size: 15px; $list-font-size: 15px; -$sidebar_collapsed_width: 52px; +$sidebar_collapsed_width: 62px; $sidebar_width: 230px; $avatar_radius: 50%; $code_font_size: 13px; $code_line_height: 1.5; -$border-color: #E5E5E5; -$background-color: #f5f5f5; -$header-height: 50px; -$readable-width: 1100px; +$border-color: #E7E9ED; +$background-color: #F8FAFC; +$header-height: 58px; +$fixed-layout-width: 1200px; +$gl-gray: #7f8fa4; +$gl-padding: 16px; +$gl-avatar-size: 46px; /* * State colors: */ $gl-primary: #446e9b; -$gl-success: #019875; -$gl-info: #029ACF; +$gl-success: #44c679; +$gl-info: #00aaff; $gl-warning: #EB9532; $gl-danger: #d9534f; @@ -35,4 +38,4 @@ $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; +$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/app/assets/stylesheets/generic/avatar.scss b/app/assets/stylesheets/generic/avatar.scss index 8595887c3b..221cb6a04a 100644 --- a/app/assets/stylesheets/generic/avatar.scss +++ b/app/assets/stylesheets/generic/avatar.scss @@ -23,8 +23,12 @@ &.s24 { width: 24px; height: 24px; margin-right: 8px; } &.s26 { width: 26px; height: 26px; margin-right: 8px; } &.s32 { width: 32px; height: 32px; margin-right: 10px; } + &.s36 { width: 36px; height: 36px; margin-right: 10px; } + &.s46 { width: 46px; height: 46px; margin-right: 15px; } + &.s48 { width: 48px; height: 48px; margin-right: 10px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; } &.s90 { width: 90px; height: 90px; margin-right: 15px; } + &.s140 { width: 140px; height: 140px; margin-right: 20px; } &.s160 { width: 160px; height: 160px; margin-right: 20px; } } @@ -38,5 +42,6 @@ &.s32 { font-size: 22px; line-height: 32px; } &.s60 { font-size: 32px; line-height: 60px; } &.s90 { font-size: 36px; line-height: 90px; } - &.s160 { font-size: 96px; line-height: 1.33; } + &.s140 { font-size: 72px; line-height: 140px; } + &.s160 { font-size: 96px; line-height: 160px; } } diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/generic/blocks.scss index 3536a68f41..abf4657dd5 100644 --- a/app/assets/stylesheets/generic/blocks.scss +++ b/app/assets/stylesheets/generic/blocks.scss @@ -1,19 +1,47 @@ .light-well { - background: #f9f9f9; + background-color: #f8fafc; padding: 15px; } .centered-light-block { text-align: center; - color: #888; + color: $gl-gray; margin: 20px; } .nothing-here-block { text-align: center; padding: 20px; - color: #666; + color: $gl-gray; font-weight: normal; font-size: 16px; line-height: 36px; } + +.gray-content-block { + margin: -$gl-padding; + background-color: #f8fafc; + padding: $gl-padding; + margin-bottom: 0px; + border-top: 1px solid #e7e9ed; + border-bottom: 1px solid #e7e9ed; + color: $gl-gray; + + &.second-block { + margin-top: -1px; + margin-bottom: 0; + } + + &.footer-block { + margin-top: 0; + margin-bottom: -$gl-padding; + } + + .title { + color: $gl-text-color; + } + + .oneline { + line-height: 44px; + } +} diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index cd6bf64c0a..e823750909 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -72,3 +72,19 @@ } } } + +.btn-group-next { + .btn { + padding: 9px 0px; + font-size: 15px; + color: #7f8fa4; + border-color: #e7e9ed; + width: 140px; + + &.active { + border-color: $gl-info; + background: $gl-info; + color: #fff; + } + } +} diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index bf5c7a8d75..b995486588 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -1,5 +1,5 @@ /** COLORS **/ -.cgray { color: gray } +.cgray { color: $gl-gray; } .clgray { color: #BBB } .cred { color: #D12F19 } .cgreen { color: #4a2 } @@ -7,6 +7,7 @@ /** COMMON CLASSES **/ .prepend-top-10 { margin-top:10px } +.prepend-top-default { margin-top: $gl-padding; } .prepend-top-20 { margin-top:20px } .prepend-left-10 { margin-left:10px } .prepend-left-20 { margin-left:20px } @@ -20,10 +21,10 @@ .underlined-link { text-decoration: underline; } .hint { font-style: italic; color: #999; } -.light { color: #888 } +.light { color: $gl-gray; } .slead { - color: #666; + color: $gl-gray; font-size: 15px; margin-bottom: 12px; font-weight: normal; @@ -74,8 +75,6 @@ pre { color: $gl-link-color; } -.help li { color:$style_color; } - .back-link { font-size: 14px; } @@ -132,10 +131,6 @@ p.time { text-shadow: none; } -.highlight_word { - background: #fafe3d; -} - .thin_area{ height: 150px; } @@ -307,7 +302,7 @@ table { } .btn-sign-in { - margin-top: 7px; + margin-top: 15px; text-shadow: none; } @@ -359,14 +354,14 @@ table { } .description { - font-size: 16px; + font-size: $gl-font-size; color: #666; margin-top: 8px; } } .profiler-results { - top: 50px !important; + top: 73px !important; .profiler-button, .profiler-controls { @@ -375,21 +370,41 @@ table { } .center-top-menu { - border-bottom: 1px solid #EEE; + padding: 0; + margin: 0; list-style: none; text-align: center; - padding-bottom: 15px; - margin-bottom: 15px; + margin-top: 5px; + margin-bottom: $gl-padding; + height: 56px; + margin-top: -$gl-padding; + padding-top: $gl-padding; li { display: inline-block; a { - padding: 10px; + padding: 14px; + font-size: 17px; + line-height: 28px; + color: #7f8fa4; + border-bottom: 2px solid transparent; + + &:hover, &:active, &:focus { + text-decoration: none; + } } &.active a { - color: #666; + color: #4c4e54; + border-bottom: 2px solid #1cacfc; + } + + .badge { + font-weight: normal; + background-color: #fff; + background-color: #eee; + color: #78a; } } } diff --git a/app/assets/stylesheets/generic/filters.scss b/app/assets/stylesheets/generic/filters.scss index bd93a79722..8e6922c923 100644 --- a/app/assets/stylesheets/generic/filters.scss +++ b/app/assets/stylesheets/generic/filters.scss @@ -2,31 +2,6 @@ margin-right: 15px; } -.issues-state-filters { - li.active a { - border-color: #DDD !important; - - &, &:hover, &:active, &.active { - background: #f5f5f5 !important; - border-bottom: 1px solid #f5f5f5 !important; - } - } -} - -.issues-details-filters { - font-size: 13px; - background: #f5f5f5; - margin: -10px 0; - padding: 10px 15px; - margin-top: -15px; - border-left: 1px solid #DDD; - border-right: 1px solid #DDD; - - .btn { - font-size: 13px; - } -} - @media (min-width: 800px) { .issues-filters, .issues_bulk_update { diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 31e2ad8669..b758a526fb 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -20,33 +20,32 @@ header { } &.navbar-gitlab { + padding: 0 20px; z-index: 100; margin-bottom: 0; min-height: $header-height; + background-color: #fff; border: none; - width: 100%; + border-bottom: 1px solid #EEE; - .container { - background: #FFF; + .container-fluid { width: 100% !important; - padding: 0; filter: none; + padding: 0; .nav > li > a { - color: #888; - font-size: 14px; + color: #7f8fa4; + font-size: 18px; padding: 0; - background-color: #f5f5f5; margin: ($header-height - 28) / 2 0; margin-left: 10px; - border-radius: 40px; height: 28px; width: 28px; line-height: 28px; text-align: center; &:hover, &:focus, &:active { - background-color: #EEE; + background-color: #FFF; } } @@ -56,6 +55,7 @@ header { border-radius: 0; position: absolute; right: 2px; + top: 15px; &:hover { background-color: #EEE; @@ -64,66 +64,22 @@ header { } } - .header-logo { - border-bottom: 1px solid transparent; - float: left; - height: $header-height; - width: $sidebar_width; - overflow: hidden; - transition-duration: .3s; - - a { - float: left; - height: $header-height; - width: 100%; - padding: ($header-height - 36 ) / 2 8px; - overflow: hidden; - - img { - width: 36px; - height: 36px; - float: left; - } - - .gitlab-text-container { - width: 230px; - - h3 { - width: 158px; - float: left; - margin: 0; - margin-left: 14px; - font-size: 18px; - line-height: $header-height - 14; - font-weight: normal; - } - } - } - - &:hover { - background-color: #EEE; - } - } - .header-content { - border-bottom: 1px solid #EEE; - padding-right: 35px; height: $header-height; .title { margin: 0; - padding: 0 15px 0 35px; overflow: hidden; - font-size: 18px; + font-size: 19px; line-height: $header-height; - font-weight: bold; - color: #444; + font-weight: normal; + color: #4c4e54; text-overflow: ellipsis; vertical-align: top; white-space: nowrap; a { - color: #444; + color: #4c4e54; &:hover { text-decoration: underline; } @@ -138,7 +94,7 @@ header { .search { margin-right: 10px; margin-left: 10px; - margin-top: ($header-height - 28) / 2; + margin-top: ($header-height - 36) / 2; form { margin: 0; @@ -149,13 +105,8 @@ header { width: 220px; background-image: image-url("icon-search.png"); background-repeat: no-repeat; - background-position: 10px; - height: inherit; - padding: 4px 6px; - padding-left: 25px; - font-size: 13px; - background-color: #f5f5f5; - border-color: #f5f5f5; + background-position: 195px; + @include input-big; &:focus { @include box-shadow(none); @@ -168,15 +119,7 @@ header { } @mixin collapsed-header { - .header-logo { - width: $sidebar_collapsed_width; - } - - .header-content { - .title { - margin-left: 30px; - } - } + margin-left: $sidebar_collapsed_width; } @media (max-width: $screen-md-max) { @@ -191,16 +134,14 @@ header { } .header-expanded { + margin-left: $sidebar_width; } } @media (max-width: $screen-xs-max) { - header .container { + header .container-fluid { font-size: 18px; - .title { - } - .navbar-nav { margin: 0px; float: none !important; diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss index c502d953c7..3bfed8de77 100644 --- a/app/assets/stylesheets/generic/lists.scss +++ b/app/assets/stylesheets/generic/lists.scss @@ -49,8 +49,6 @@ } } - .author { color: #999; } - .list-item-name { float: left; position: relative; @@ -71,15 +69,6 @@ font-size: $list-font-size; line-height: 18px; } - - .row_title { - color: $gray-dark; - - &:hover { - color: $text-color; - text-decoration: underline; - } - } } } @@ -93,28 +82,12 @@ ol, ul { /** light list with border-bottom between li **/ ul.bordered-list { - margin: 5px 0px; - padding: 0px; - li { - padding: 5px 0; - border-bottom: 1px solid #EEE; - overflow: hidden; - display: block; - margin: 0px; - &:last-child { border:none } - &.active { - background: #f9f9f9; - a { font-weight: bold; } - } - - &.light { - a { color: #777; } - } - } + @include basic-list; &.top-list { li:first-child { padding-top: 0; + h4, h5 { margin-top: 0; } @@ -125,3 +98,28 @@ ul.bordered-list { li.task-list-item { list-style-type: none; } + +ul.content-list { + @include basic-list; + + margin: 0; + padding: 0; + + > li { + padding: $gl-padding; + border-color: #f1f2f4; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + color: $gl-gray; + + .avatar { + margin-right: 15px; + } + + .controls { + padding-top: 10px; + float: right; + } + } +} + diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index bb7b9356c7..36ae126f86 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -80,6 +80,23 @@ %ul.notes .note-role, .note-actions { display: none; } + + .center-top-menu { + height: 45px; + + li a { + font-size: 14px; + padding: 19px 10px; + } + } + + .projects-search-form { + margin: 0 -5px !important; + + .btn { + display: none; + } + } } @media (max-width: $screen-sm-max) { diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss index d8e0dc028d..f0860de1c4 100644 --- a/app/assets/stylesheets/generic/selects.scss +++ b/app/assets/stylesheets/generic/selects.scss @@ -3,9 +3,9 @@ .select2-choice { background: #FFF; border-color: #DDD; - height: 34px; - padding: 6px 14px; - font-size: 14px; + height: 42px; + padding: 8px $gl-padding; + font-size: $gl-font-size; line-height: 1.42857143; @include border-radius(4px); @@ -13,7 +13,7 @@ .select2-arrow { background: #FFF; border-left: none; - padding-top: 3px; + padding-top: 5px; } } } diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss index b96664d30d..22720c2e1d 100644 --- a/app/assets/stylesheets/generic/sidebar.scss +++ b/app/assets/stylesheets/generic/sidebar.scss @@ -18,13 +18,21 @@ } .content-wrapper { + min-height: 100vh; width: 100%; padding: 20px; - background: #FFF; + background: #f1f4f8; + + .container-fluid { + background: #FFF; + padding: $gl-padding; + border: 1px solid #e7e9ed; + min-height: 90vh; + } } .nav-sidebar { - margin-top: 29 + $header-height; + margin-top: 14 + $header-height; margin-bottom: 50px; transition-duration: .3s; list-style: none; @@ -43,13 +51,14 @@ } a { - padding: 8px 15px; - font-size: 13px; - line-height: 18px; + padding: 7px 15px; + font-size: $gl-font-size; + line-height: 24px; color: $gray; display: block; text-decoration: none; - padding-left: 16px; + padding-left: 22px; + font-weight: normal; &:hover { text-decoration: none; @@ -60,9 +69,9 @@ } i { - width: 20px; + width: 16px; color: $gray-light; - margin-right: 23px; + margin-right: 13px; } .count { @@ -108,17 +117,31 @@ } @mixin folded-sidebar { - padding-left: 50px; + padding-left: 60px; transition-duration: .3s; .sidebar-wrapper { width: $sidebar_collapsed_width; + .header-logo { + width: $sidebar_collapsed_width; + + a { + padding-left: 12px; + + .gitlab-text-container { + display: none; + } + } + } + .nav-sidebar { width: $sidebar_collapsed_width; li a { - padding-left: 16px; + span { + display: none; + } } } @@ -128,21 +151,25 @@ } .sidebar-user { + padding-left: 12px; width: $sidebar_collapsed_width; + + .username { + display: none; + } } } } .collapse-nav a { + width: $sidebar_width; position: fixed; - top: $header-height; - left: 198px; + bottom: 0; font-size: 13px; background: transparent; - width: 32px; - height: 28px; + height: 40px; text-align: center; - line-height: 28px; + line-height: 40px; transition-duration: .3s; } @@ -176,15 +203,60 @@ } .sidebar-user { + padding: 9px 22px; position: fixed; - bottom: 0; + bottom: 40px; width: $sidebar_width; - padding: 10px; overflow: hidden; transition-duration: .3s; .username { - margin-top: 5px; + margin-left: 10px; width: $sidebar_width - 2 * 10px; + font-size: 16px; + line-height: 34px; + } +} + +.sidebar-wrapper { + .header-logo { + border-bottom: 1px solid transparent; + float: left; + height: $header-height; + width: $sidebar_width; + overflow: hidden; + transition-duration: .3s; + + a { + float: left; + height: $header-height; + width: 100%; + padding: 10px 22px; + overflow: hidden; + + img { + width: 36px; + height: 36px; + float: left; + } + + .gitlab-text-container { + width: 230px; + + h3 { + width: 158px; + float: left; + margin: 0; + margin-left: 14px; + font-size: 19px; + line-height: 41px; + font-weight: normal; + } + } + } + + &:hover { + background-color: #EEE; + } } } diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index 34b4ee3e17..73034c84f9 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -9,6 +9,11 @@ margin-bottom: 5px; } +h1, h2, h3, h4, h5, h6 { + color: $gl-header-color; + font-weight: 500; +} + /** CODE **/ pre { font-family: $monospace_font; diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index c8cb18ec35..8323a8598e 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -21,6 +21,12 @@ pre.code.highlight.dark, background-color: #557 !important; } + // Search result highlight + span.highlight_word { + background: #ffe792; + color: #000000; + } + .hll { background-color: #373b41 } .c { color: #969896 } /* Comment */ .err { color: #cc6666 } /* Error */ diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 001e8b3102..e838167433 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -21,6 +21,12 @@ pre.code.monokai, background-color: #49483e !important; } + // Search result highlight + span.highlight_word { + background: #ffe792; + color: #000000; + } + .hll { background-color: #49483e } .c { color: #75715e } /* Comment */ .err { color: #960050; background-color: #1e0010 } /* Error */ diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index f5b827e7c0..bd41480aef 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -21,6 +21,11 @@ pre.code.highlight.solarized-dark, background-color: #174652 !important; } + // Search result highlight + span.highlight_word { + background: #094554; + } + /* Solarized Dark For use with Jekyll and Pygments diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 6b44c00c30..4cc6286387 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -21,6 +21,11 @@ pre.code.highlight.solarized-light, background-color: #ddd8c5 !important; } + // Search result highlight + span.highlight_word { + background: #eee8d5; + } + /* Solarized Light For use with Jekyll and Pygments diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index a52ffc971d..e0edfb80b4 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -21,6 +21,11 @@ pre.code.highlight.white, background-color: #f8eec7 !important; } + // Search result highlight + span.highlight_word { + background: #fafe3d; + } + .hll { background-color: #f8f8f8 } .c { color: #999988; font-style: italic; } .err { color: #a61717; background-color: #e3d2d2; } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index e7125c0399..5436c6dad9 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -61,10 +61,6 @@ } } -.file-stats a { - color: $style_color; -} - .file-stats { .new-file { a { diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 359f4073e8..de2ae93df3 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -52,7 +52,7 @@ li.commit { } .commit-row-message { - color: #444; + color: $gl-link-color; &:hover { text-decoration: underline; @@ -88,12 +88,12 @@ li.commit { } .commit-row-info { - color: #777; + color: $gl-gray; line-height: 24px; font-size: 13px; a { - color: #777; + color: $gl-gray; } .committed_ago { diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index 9a3b543ad1..25a86cd0f9 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -2,7 +2,7 @@ .side { .panel { .panel-heading { - background: #EEE; + background: $background-color; border-top-left-radius: 0; } border-top-left-radius: 0; @@ -23,41 +23,6 @@ } } -.project-row, .group-row { - padding: 0 !important; - font-size: 14px; - line-height: 24px; - - a { - display: block; - padding: 8px 15px; - } - - .project-name, .group-name { - font-weight: 500; - } - - .arrow { - float: right; - margin: 0; - font-size: 20px; - } - - .last-activity { - float: right; - font-size: 12px; - color: #AAA; - display: block; - .date { - color: #777; - } - } -} - -.project-description { - overflow: hidden; -} - .project-access-icon { margin-left: 10px; float: left; @@ -73,12 +38,11 @@ float: left; .avatar { - margin-top: -8px; - margin-left: -15px; - @include border-radius(0px); + @include border-radius(50%); } + .identicon { - line-height: 40px; + line-height: 46px; } } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index d4af7506d5..ca2ee45542 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -1,70 +1,58 @@ -/** - * Events labels - * - */ -.event_label { - &.pushed { - padding: 0 2px; - } - - &.opened { - padding: 0 2px; - } - - &.closed { - padding: 0 2px; - } - - &.merged { - padding: 0 2px; - } - - &.left, - &.joined { - padding: 0 2px; - float: none; - } -} - /** * Dashboard events feed * */ .event-item { - &:first-child { - padding-top: 0; - } + font-size: $gl-font-size; + padding: $gl-padding; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + border-bottom: 1px solid #f1f2f4; + color: #7f8fa4; &.event-inline { .avatar { position: relative; top: -2px; } + + .event-title { + line-height: 44px; + } + + .event-item-timestamp { + line-height: 44px; + } + } + + a { + color: #4c4e54; + } + + .avatar { + margin-right: 15px; } - padding: 12px 0px; - border-bottom: 1px solid #eee; .event-title { - max-width: 70%; @include str-truncated(calc(100% - 174px)); - font-weight: 500; - font-size: 14px; + font-weight: 600; + .author_name { color: #333; } } + .event-body { - font-size: 13px; - margin-left: 35px; + margin-left: 63px; margin-right: 80px; - color: #777; .event-note { margin-top: 5px; word-wrap: break-word; .md { - font-size: 13px; + color: #7f8fa4; + font-size: $gl-font-size; iframe.twitter-share-button { vertical-align: bottom; @@ -94,7 +82,7 @@ .event-note-icon { color: #777; float: left; - font-size: 16px; + font-size: $gl-font-size; line-height: 16px; margin-right: 5px; } @@ -116,7 +104,7 @@ &:last-child { border:none } .event_commits { - margin-top: 5px; + margin-top: 9px; li { &.commit { @@ -125,10 +113,12 @@ padding-left: 0; border: none; .commit-row-title { - font-size: 12px; + font-size: $gl-font-size; } } + &.commits-stat { + margin-top: 3px; display: block; padding: 3px; padding-left: 0; @@ -142,7 +132,6 @@ .event-item-timestamp { float: right; - color: #999; line-height: 22px; } } @@ -186,12 +175,3 @@ } } } - -.event_filter { - li a { - font-size: 13px; - padding: 5px 10px; - background: $background-color; - margin-left: 4px; - } -} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 3f617e72b0..2d32d82e10 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -46,8 +46,13 @@ .btn { font-size: 13px; } } -.issuable-details { - .description { - max-width: $readable-width; +.project-issuable-filter { + .controls { + float: right; + margin-top: 7px; + } + + .center-top-menu { + text-align: left; } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 3572f33e91..7928b6220f 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -10,7 +10,7 @@ } .issue-info { - color: #999; + color: $gl-gray; font-size: 13px; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 10fce5b3da..f0fb68d342 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -136,7 +136,7 @@ } .merge-request-info { - color: #999; + color: $gl-gray; font-size: 13px; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 29d3dbc25e..361fd63bc7 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -16,6 +16,10 @@ .project-home-panel { text-align: center; + background: #f7f8fa; + margin: -$gl-padding; + padding: $gl-padding; + padding-top: 40px; .project-identicon-holder { margin-bottom: 15px; @@ -30,14 +34,20 @@ } } + .project-home-dropdown { + margin: 11px 3px 0; + } + .project-home-desc { h1 { margin: 0; margin-bottom: 10px; - font-size: 26px; + font-size: 23px; + font-weight: normal; } p { + color: #7f8fa4; display: inline; } } @@ -45,6 +55,10 @@ .git-clone-holder { max-width: 600px; margin: 20px auto; + + .form-control { + background: #FFF; + } } .visibility-level-label { @@ -55,17 +69,18 @@ } .project-repo-buttons { - margin-top: 25px; + margin-top: $gl-padding; margin-bottom: 25px; .btn { @extend .btn-info; + text-transform: uppercase; + font-size: 15px; + line-height: 20px; + padding: 8px 14px; + border-radius: 3px; margin-left: 10px; - font-weight: bold; - font-size: 14px; - line-height: 16px; - padding: 8px 12px; .count { padding-left: 7px; @@ -155,78 +170,6 @@ ul.nav.nav-projects-tabs { margin: 0px; } -.my-projects, -.public-projects { - li { - .project-info { - margin-bottom: 10px; - overflow: hidden; - } - - .access-icon { - color: #AAA; - margin-left: 10px; - i { - color: #AAA; - } - } - } -} - -.public-clone { - background: #EEE; - color: #777; - padding: 6px 10px; - margin: 1px; - font-weight: normal; -} - -.public-projects .repo-info { - color: #777; - - a { - color: #777; - } -} - -.project-side { - .project-fork-icon { - float: left; - font-size: 26px; - margin-right: 10px; - line-height: 1.5; - } - - .panel { - @include border-radius(3px); - - .panel-heading, .panel-footer { - font-weight: normal; - background-color: transparent; - color: #666; - border-color: #EEE; - } - - .actions { - margin-top: 10px; - } - - .nav-pills a { - padding: 10px; - font-weight: bold; - color: $gl-link-color; - } - - .nav { - margin-bottom: 15px; - } - } - - .ci-status-image { - max-height: 22px; - } -} - .transfer-project .select2-container { min-width: 200px; } @@ -249,10 +192,10 @@ ul.nav.nav-projects-tabs { .breadcrumb.repo-breadcrumb { padding: 0; - line-height: 34px; - background: white; + line-height: 42px; + background: transparent; border: none; - font-size: 16px; + margin: 0; > li + li:before { padding: 0 3px; @@ -298,10 +241,23 @@ table.table.protected-branches-list tr.no-border { .project-stats { text-align: center; + margin-top: 0; + margin-bottom: 0; + padding-top: 5px; + padding-bottom: 0; - ul.nav-pills { display:inline-block; } - li { display:inline; } - a { float:left; } + ul.nav-pills { + display:inline-block; + } + + li { + display:inline; + } + + a { + float:left; + font-size: 17px; + } li.missing a { color: #bbb; @@ -316,3 +272,69 @@ table.table.protected-branches-list tr.no-border { pre.light-well { border-color: #f1f1f1; } + +.projects-search-form { + margin: -$gl-padding; + background-color: #f8fafc; + padding: $gl-padding; + margin-bottom: 0px; + border-top: 1px solid #e7e9ed; + border-bottom: 1px solid #e7e9ed; +} + +/* + * Projects list rendered on dashboard and user page + */ +.projects-list { + @include basic-list; + + .project-row { + padding: $gl-padding; + border-color: #f1f2f4; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + + &.no-description { + .project { + line-height: 44px; + } + } + + .project-full-name { + @include str-truncated; + font-weight: 600; + color: #4c4e54; + } + + .pull-right.light { + line-height: 45px; + color: #7f8fa4; + } + + .project-description { + color: #7f8fa4; + + p { + @include str-truncated; + margin-bottom: 0; + color: #7f8fa4; + } + } + } + + .bottom { + padding-top: $gl-padding; + padding-bottom: 0; + } +} + +.panel .projects-list li { + padding: 10px 15px; + margin: 0; +} + +.project-show-activity { + .activity-filter-block { + margin-top: -1px; + } +} diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index bdaa17ac33..3aaa96da60 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -1,7 +1,19 @@ .search-results { .search-result-row { - border-bottom: 1px solid #EEE; - padding-bottom: 10px; - margin-bottom: 10px; + border-bottom: 1px solid #DDD; + padding-bottom: 15px; + margin-bottom: 15px; } } + +.search-holder { + max-width: 600px; + margin: 0 auto; + margin-bottom: 20px; + + input { + border-color: #BBB; + font-weight: bold; + } +} + diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index d79591d991..a3d7aba054 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -6,3 +6,27 @@ .snippet-form-holder .file-holder .file-title { padding: 2px; } + + +.snippet-row { + .snippet-title { + font-size: 15px; + font-weight: bold; + line-height: 20px; + margin-bottom: 2px; + + .monospace { + font-weight: normal; + } + } + + .snippet-info { + color: #888; + font-size: 13px; + line-height: 24px; + + a { + color: #888; + } + } +} diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 5f1a3db4fb..587d09a04a 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -63,15 +63,15 @@ padding-right: 8px; .commit-author-name { - color: gray; + color: $gl-gray; } } .tree_commit { - color: gray; + color: $gl-gray; .tree-commit-link { - color: gray; + color: $gl-gray; &:hover { text-decoration: underline; @@ -117,7 +117,6 @@ .readme-holder { margin: 0 auto; - max-width: $readable-width; .readme-file-title { font-size: 14px; diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss index 3589cb88d0..8d9a0aae56 100644 --- a/app/assets/stylesheets/themes/gitlab-theme.scss +++ b/app/assets/stylesheets/themes/gitlab-theme.scss @@ -7,27 +7,27 @@ * $color-dark - */ @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { - header { - &.navbar-gitlab { - .header-logo { - background-color: $color-darker; - border-color: $color-darker; + .page-with-sidebar { + .header-logo { + background-color: $color; + border-color: $color; - a { + a { + color: $color-light; + + h3 { color: $color-light; } + } - &:hover { - background-color: $color-dark; - a { - color: #FFF; - } + &:hover { + background-color: $color-darker; + a { + color: #FFF; } } } - } - .page-with-sidebar { .collapse-nav a { color: #FFF; background: $color; @@ -87,7 +87,7 @@ } $theme-blue: #2980B9; -$theme-charcoal: #474D57; +$theme-charcoal: #333c47; $theme-graphite: #888888; $theme-gray: #373737; $theme-green: #019875; @@ -99,7 +99,7 @@ body { } &.ui_charcoal { - @include gitlab-theme(#979DA7, $theme-charcoal, #373D47, #24272D); + @include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272D); } &.ui_graphite { diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb index 34f37bca4a..38a5a9fca0 100644 --- a/app/controllers/admin/abuse_reports_controller.rb +++ b/app/controllers/admin/abuse_reports_controller.rb @@ -4,8 +4,13 @@ class Admin::AbuseReportsController < Admin::ApplicationController end def destroy - AbuseReport.find(params[:id]).destroy + abuse_report = AbuseReport.find(params[:id]) - redirect_to admin_abuse_reports_path, notice: 'Report was removed' + if params[:remove_user] + abuse_report.user.destroy + end + + abuse_report.destroy + render nothing: true end end diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 690096bdbc..d670386f8c 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController end def hook_params - params.require(:hook).permit(:url) + params.require(:hook).permit(:url, :enable_ssl_verification) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 12d439b0b3..cb1cf13d34 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -55,7 +55,9 @@ class ApplicationController < ActionController::Base def authenticate_user!(*args) # If user is not signed-in and tries to access root_path - redirect him to landing page - if current_application_settings.home_page_url.present? + # Don't redirect to the default URL to prevent endless redirections + if current_application_settings.home_page_url.present? && + current_application_settings.home_page_url.chomp('/') != Gitlab.config.gitlab['url'].chomp('/') if current_user.nil? && root_path == request.path redirect_to current_application_settings.home_page_url and return end @@ -190,11 +192,12 @@ class ApplicationController < ActionController::Base end def add_gon_variables + gon.api_version = API::API.version + gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s gon.default_issues_tracker = Project.new.default_issue_tracker.to_param - gon.api_version = API::API.version - gon.relative_url_root = Gitlab.config.gitlab.relative_url_root - gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s - gon.max_file_size = current_application_settings.max_attachment_size; + gon.max_file_size = current_application_settings.max_attachment_size + gon.relative_url_root = Gitlab.config.gitlab.relative_url_root + gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class if current_user gon.current_user_id = current_user.id diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index d2f0c43929..2bc2e5e58f 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,6 +1,6 @@ class DashboardController < Dashboard::ApplicationController - before_action :load_projects - before_action :event_filter, only: :show + before_action :load_projects, except: :activity + before_action :event_filter, only: :activity respond_to :html @@ -10,13 +10,8 @@ class DashboardController < Dashboard::ApplicationController respond_to do |format| format.html - - format.json do - load_events - pager_json("events/_events", @events.count) - end - format.atom do + event_filter load_events render layout: false end @@ -40,6 +35,19 @@ class DashboardController < Dashboard::ApplicationController end end + def activity + @last_push = current_user.recent_push + + respond_to do |format| + format.html + + format.json do + load_events + pager_json("events/_events", @events.count) + end + end + end + protected def load_projects @@ -47,7 +55,14 @@ class DashboardController < Dashboard::ApplicationController end def load_events - @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) + project_ids = + if params[:filter] == "starred" + current_user.starred_projects + else + current_user.authorized_projects + end.pluck(:id) + + @events = Event.in_projects(project_ids) @events = @event_filter.apply_filter(@events).with_associations @events = @events.limit(20).offset(params[:offset] || 0) end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 76062446c9..4e5b4125f5 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -53,6 +53,7 @@ class Projects::HooksController < Projects::ApplicationController end def hook_params - params.require(:hook).permit(:url, :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events) + params.require(:hook).permit(:url, :push_events, :issues_events, + :merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification) end end diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 647c145407..5f6fbce795 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -17,8 +17,7 @@ class Projects::RawController < Projects::ApplicationController send_data( @blob.data, type: type, - disposition: 'inline', - filename: @blob.name + disposition: 'inline' ) else not_found! @@ -30,6 +29,8 @@ class Projects::RawController < Projects::ApplicationController def get_blob_type if @blob.text? 'text/plain; charset=utf-8' + elsif @blob.image? + @blob.content_type else 'application/octet-stream' end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 0110553247..3a22ed832a 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -2,13 +2,13 @@ class Projects::ServicesController < Projects::ApplicationController ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain, :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, - :build_key, :server, :teamcity_url, :build_type, + :build_key, :server, :teamcity_url, :drone_url, :build_type, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :colorize_messages, :channels, :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :notify, :color, - :server_host, :server_port, :default_irc_uri] + :server_host, :server_port, :default_irc_uri, :enable_ssl_verification] # Authorize before_action :authorize_admin_project! before_action :service, only: [:edit, :update, :test] diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 6430663742..b07a2a8db2 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -30,9 +30,14 @@ class Projects::SnippetsController < Projects::ApplicationController def create @snippet = CreateSnippetService.new(@project, current_user, snippet_params).execute - respond_with(@snippet, - location: namespace_project_snippet_path(@project.namespace, - @project, @snippet)) + + if @snippet.valid? + respond_with(@snippet, + location: namespace_project_snippet_path(@project.namespace, + @project, @snippet)) + else + render :new + end end def edit diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 50512cb6dc..51c26a6a46 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -5,7 +5,6 @@ class Projects::WikisController < Projects::ApplicationController before_action :authorize_create_wiki!, only: [:edit, :create, :history] before_action :authorize_admin_wiki!, only: :destroy before_action :load_project_wiki - include WikiHelper def pages @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 8389f07a3b..cfa565cd03 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -8,6 +8,8 @@ class SessionsController < Devise::SessionsController def new if Gitlab.config.ldap.enabled @ldap_servers = Gitlab::LDAP::Config.servers + else + @ldap_servers = [] end super diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2bb5c338cf..1484356a7f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -51,10 +51,6 @@ class UsersController < ApplicationController def set_user @user = User.find_by_username!(params[:username]) - - unless current_user || @user.public_profile? - return authenticate_user! - end end def authorized_projects_ids diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb index a79bd47d98..f3f4d461ef 100644 --- a/app/finders/trending_projects_finder.rb +++ b/app/finders/trending_projects_finder.rb @@ -2,13 +2,21 @@ class TrendingProjectsFinder def execute(current_user, start_date = nil) start_date ||= Date.today - 1.month - projects = projects_for(current_user) - # Determine trending projects based on comments count # for period of time - ex. month - projects.joins(:notes).where('notes.created_at > ?', start_date). - select("projects.*, count(notes.id) as ncount"). - group("projects.id").reorder("ncount DESC") + trending_project_ids = Note. + select("notes.project_id, count(notes.project_id) as pcount"). + where('notes.created_at > ?', start_date). + group("project_id"). + reorder("pcount DESC"). + map(&:project_id) + + sql_order_ids = trending_project_ids.reverse. + map { |project_id| "id = #{project_id}" }.join(", ") + + # Get list of projects that user allowed to see + projects = projects_for(current_user) + projects.where(id: trending_project_ids).reorder(sql_order_ids) end private diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 0e7a37b4cc..d9502181c4 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -1,6 +1,6 @@ module AuthHelper PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze - FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos'].freeze + FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos', 'crowd'].freeze def ldap_enabled? Gitlab.config.ldap.enabled @@ -26,6 +26,10 @@ module AuthHelper auth_providers.select { |provider| form_based_provider?(provider) } end + def crowd_enabled? + auth_providers.include? :crowd + end + def button_based_providers auth_providers.reject { |provider| form_based_provider?(provider) } end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 1bd3ec5e0e..6ffa1a7121 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -137,7 +137,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do + link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do 'Inline' end end @@ -148,7 +148,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do + link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do 'Side-by-side' end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 8428281f8f..76602614bc 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -27,16 +27,13 @@ module EventsHelper key = key.to_s active = 'active' if @event_filter.active?(key) link_opts = { - class: 'event_filter_link', + class: "event-filter-link btn btn-default #{active}", id: "#{key}_event_filter", title: "Filter by #{tooltip.downcase}", - data: { toggle: 'tooltip', placement: 'top' } } - content_tag :li, class: "filter_icon #{active}" do - link_to request.path, link_opts do - icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip) - end + link_to request.path, link_opts do + content_tag(:span, ' ' + tooltip) end end @@ -66,7 +63,7 @@ module EventsHelper end words << "at" elsif event.target - words << "##{event.target_iid}:" + words << "##{event.target_iid}:" words << event.target.title if event.target.respond_to?(:title) words << "at" end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index eb3f72a307..1ebfd92f11 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -1,9 +1,6 @@ require 'nokogiri' module GitlabMarkdownHelper - include Gitlab::Markdown - include PreferencesHelper - # Use this in places where you would normally use link_to(gfm(...), ...). # # It solves a problem occurring with nested links (i.e. @@ -22,7 +19,7 @@ module GitlabMarkdownHelper escape_once(body) end - gfm_body = gfm(escaped_body, {}, html_options) + gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: current_user) fragment = Nokogiri::XML::DocumentFragment.parse(gfm_body) if fragment.children.size == 1 && fragment.children[0].name == 'a' @@ -39,32 +36,38 @@ module GitlabMarkdownHelper end end + # Add any custom CSS classes to the GFM-generated reference links + if html_options[:class] + fragment.css('a.gfm').add_class(html_options[:class]) + end + fragment.to_html.html_safe end - MARKDOWN_OPTIONS = { - no_intra_emphasis: true, - tables: true, - fenced_code_blocks: true, - strikethrough: true, - lax_spacing: true, - space_after_headers: true, - superscript: true, - footnotes: true - }.freeze + def markdown(text, context = {}) + context.merge!( + current_user: current_user, + path: @path, + project: @project, + project_wiki: @project_wiki, + ref: @ref + ) - def markdown(text, options={}) - unless @markdown && options == @options - @options = options + Gitlab::Markdown.render(text, context) + end - # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch - rend = Redcarpet::Render::GitlabHTML.new(self, user_color_scheme_class, options) + # TODO (rspeicher): Remove all usages of this helper and just call `markdown` + # with a custom pipeline depending on the content being rendered + def gfm(text, options = {}) + options.merge!( + current_user: current_user, + path: @path, + project: @project, + project_wiki: @project_wiki, + ref: @ref + ) - # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use - @markdown = Redcarpet::Markdown.new(rend, MARKDOWN_OPTIONS) - end - - @markdown.render(text).html_safe + Gitlab::Markdown.gfm(text, options) end def asciidoc(text) diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index b067cb54a4..82eebf4245 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -30,4 +30,13 @@ module GroupsHelper image_path('no_group_avatar.png') end end + + def group_title(group, name, url) + content_tag :span do + link_to( + simple_sanitize(group.name), group_path(group) + ) + ' · '.html_safe + + link_to(simple_sanitize(name), url) + end + end end diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 01b6a63552..8473d6d75d 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -23,4 +23,12 @@ module PageLayoutHelper @sidebar end end + + def fluid_layout(enabled = false) + if @fluid_layout.nil? + @fluid_layout = enabled + else + @fluid_layout + end + end end diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index ea774e28ec..7f1b6a6992 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -1,25 +1,5 @@ # Helper methods for per-User preferences module PreferencesHelper - COLOR_SCHEMES = { - 1 => 'white', - 2 => 'dark', - 3 => 'solarized-light', - 4 => 'solarized-dark', - 5 => 'monokai', - } - COLOR_SCHEMES.default = 'white' - - # Helper method to access the COLOR_SCHEMES - # - # The keys are the `color_scheme_ids` - # The values are the `name` of the scheme. - # - # The preview images are `name-scheme-preview.png` - # The stylesheets should use the css class `.name` - def color_schemes - COLOR_SCHEMES.freeze - end - # Maps `dashboard` values to more user-friendly option text DASHBOARD_CHOICES = { projects: 'Your Projects (default)', @@ -50,12 +30,11 @@ module PreferencesHelper end def user_application_theme - theme = Gitlab::Themes.by_id(current_user.try(:theme_id)) - theme.css_class + Gitlab::Themes.for_user(current_user).css_class end - def user_color_scheme_class - COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user) + def user_color_scheme + Gitlab::ColorSchemes.for_user(current_user).css_class end def prefer_readme? diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb deleted file mode 100644 index f8a96516e6..0000000000 --- a/app/helpers/wiki_helper.rb +++ /dev/null @@ -1,24 +0,0 @@ -module WikiHelper - # Rails v4.1.9+ escapes all model IDs, converting slashes into %2F. The - # only way around this is to implement our own path generators. - def namespace_project_wiki_path(namespace, project, wiki_page, *args) - slug = - case wiki_page - when Symbol - wiki_page - when String - wiki_page - else - wiki_page.slug - end - namespace_project_path(namespace, project) + "/wikis/#{slug}" - end - - def edit_namespace_project_wiki_path(namespace, project, wiki_page, *args) - namespace_project_wiki_path(namespace, project, wiki_page) + '/edit' - end - - def history_namespace_project_wiki_path(namespace, project, wiki_page, *args) - namespace_project_wiki_path(namespace, project, wiki_page) + '/history' - end -end diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb index 89aceda82d..883f1c73ad 100644 --- a/app/mailers/email_rejection_mailer.rb +++ b/app/mailers/email_rejection_mailer.rb @@ -3,6 +3,8 @@ class EmailRejectionMailer < BaseMailer @reason = reason @original_message = Mail::Message.new(original_raw) + return unless @original_message.from + headers = { to: @original_message.from, subject: "[Rejected] #{@original_message.subject}" diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index c8c39db11b..07c87a7fe8 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: abuse_reports +# +# id :integer not null, primary key +# reporter_id :integer +# user_id :integer +# message :text +# created_at :datetime +# updated_at :datetime +# + class AbuseReport < ActiveRecord::Base belongs_to :reporter, class_name: "User" belongs_to :user diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 46fb85336e..9a8251bdad 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -25,6 +25,7 @@ class WebHook < ActiveRecord::Base default_value_for :note_events, false default_value_for :merge_requests_events, false default_value_for :tag_push_events, false + default_value_for :enable_ssl_verification, false # HTTParty timeout default_timeout Gitlab.config.gitlab.webhook_timeout @@ -41,7 +42,7 @@ class WebHook < ActiveRecord::Base "Content-Type" => "application/json", "X-Gitlab-Event" => hook_name.singularize.titleize }, - verify: false) + verify: enable_ssl_verification) else post_url = url.gsub("#{parsed_url.userinfo}@", "") auth = { @@ -54,7 +55,7 @@ class WebHook < ActiveRecord::Base "Content-Type" => "application/json", "X-Gitlab-Event" => hook_name.singularize.titleize }, - verify: false, + verify: enable_ssl_verification, basic_auth: auth) end rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e diff --git a/app/models/issue.rb b/app/models/issue.rb index 2456b7d0dc..fc7e9abe29 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -2,19 +2,20 @@ # # Table name: issues # -# id :integer not null, primary key -# title :string(255) -# assignee_id :integer -# author_id :integer -# project_id :integer -# created_at :datetime -# updated_at :datetime -# position :integer default(0) -# branch_name :string(255) -# description :text -# milestone_id :integer -# state :string(255) -# iid :integer +# id :integer not null, primary key +# title :string(255) +# assignee_id :integer +# author_id :integer +# project_id :integer +# created_at :datetime +# updated_at :datetime +# position :integer default(0) +# branch_name :string(255) +# description :text +# milestone_id :integer +# state :string(255) +# iid :integer +# updated_by_id :integer # require 'carrierwave/orm/activerecord' diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 467b90861f..93faa13387 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -19,6 +19,7 @@ # description :text # position :integer default(0) # locked_at :datetime +# updated_by_id :integer # require Rails.root.join("app/models/commit") diff --git a/app/models/note.rb b/app/models/note.rb index 36cad8f583..89d81ab1de 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -15,6 +15,7 @@ # noteable_id :integer # system :boolean default(FALSE), not null # st_diff :text +# updated_by_id :integer # require 'carrierwave/orm/activerecord' diff --git a/app/models/project.rb b/app/models/project.rb index 69f9af91c5..8e33a4b2f0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -73,6 +73,7 @@ class Project < ActiveRecord::Base has_many :services has_one :gitlab_ci_service, dependent: :destroy has_one :campfire_service, dependent: :destroy + has_one :drone_ci_service, dependent: :destroy has_one :emails_on_push_service, dependent: :destroy has_one :irker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy @@ -613,6 +614,7 @@ class Project < ActiveRecord::Base name: name, ssh_url: ssh_url_to_repo, http_url: http_url_to_repo, + web_url: web_url, namespace: namespace.name, visibility_level: visibility_level } diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index a714bc8224..9e5da6f45d 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -23,7 +23,7 @@ require "addressable/uri" class BuildkiteService < CiService ENDPOINT = "https://buildkite.com" - prop_accessor :project_url, :token + prop_accessor :project_url, :token, :enable_ssl_verification validates :project_url, presence: true, if: :activated? validates :token, presence: true, if: :activated? @@ -37,6 +37,7 @@ class BuildkiteService < CiService def compose_service_hook hook = service_hook || build_service_hook hook.url = webhook_url + hook.enable_ssl_verification = enable_ssl_verification hook.save end @@ -96,7 +97,11 @@ class BuildkiteService < CiService { type: 'text', name: 'project_url', - placeholder: "#{ENDPOINT}/example/project" } + placeholder: "#{ENDPOINT}/example/project" }, + + { type: 'checkbox', + name: 'enable_ssl_verification', + title: "Enable SSL verification" } ] end diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index 803402c83e..88186113c6 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -25,12 +25,24 @@ class CiService < Service def category :ci end - + + def valid_token?(token) + self.respond_to?(:token) && self.token.present? && self.token == token + end + def supported_events %w(push) end - # Return complete url to build page + def merge_request_page(iid, sha, ref) + commit_page(sha, ref) + end + + def commit_page(sha, ref) + build_page(sha, ref) + end + + # Return complete url to merge_request page # # Ex. # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c @@ -45,10 +57,27 @@ class CiService < Service # # # Ex. - # @service.commit_status('13be4ac') + # @service.merge_request_status(9, '13be4ac', 'dev') # # => 'success' # - # @service.commit_status('2abe4ac') + # @service.merge_request_status(10, '2abe4ac', 'dev) + # # => 'running' + # + # + def merge_request_status(iid, sha, ref) + commit_status(sha, ref) + end + + # Return string with build status or :error symbol + # + # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped' + # + # + # Ex. + # @service.commit_status('13be4ac', 'master') + # # => 'success' + # + # @service.commit_status('2abe4ac', 'dev') # # => 'running' # # diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb new file mode 100644 index 0000000000..3e2b7faecd --- /dev/null +++ b/app/models/project_services/drone_ci_service.rb @@ -0,0 +1,190 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +class DroneCiService < CiService + + prop_accessor :drone_url, :token, :enable_ssl_verification + validates :drone_url, + presence: true, + format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated? + validates :token, + presence: true, + format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated? + + after_save :compose_service_hook, if: :activated? + + def compose_service_hook + hook = service_hook || build_service_hook + hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join + hook.enable_ssl_verification = enable_ssl_verification + hook.save + end + + def execute(data) + case data[:object_kind] + when 'push' + service_hook.execute(data) if push_valid?(data) + when 'merge_request' + service_hook.execute(data) if merge_request_valid?(data) + when 'tag_push' + service_hook.execute(data) if tag_push_valid?(data) + end + end + + def allow_target_ci? + true + end + + def supported_events + %w(push merge_request tag_push) + end + + def merge_request_status_path(iid, sha = nil, ref = nil) + url = [drone_url, + "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}", + "?access_token=#{token}"] + + URI.join(*url).to_s + end + + def commit_status_path(sha, ref) + url = [drone_url, + "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}", + "?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"] + + URI.join(*url).to_s + end + + def merge_request_status(iid, sha, ref) + response = HTTParty.get(merge_request_status_path(iid), verify: enable_ssl_verification) + + if response.code == 200 and response['status'] + case response['status'] + when 'killed' + :canceled + when 'failure', 'error' + # Because drone return error if some test env failed + :failed + else + response["status"] + end + else + :error + end + rescue Errno::ECONNREFUSED + :error + end + + def commit_status(sha, ref) + response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification) + + if response.code == 200 and response['status'] + case response['status'] + when 'killed' + :canceled + when 'failure', 'error' + # Because drone return error if some test env failed + :failed + else + response["status"] + end + else + :error + end + rescue Errno::ECONNREFUSED + :error + end + + def merge_request_page(iid, sha, ref) + url = [drone_url, + "gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"] + + URI.join(*url).to_s + end + + def commit_page(sha, ref) + url = [drone_url, + "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}", + "?branch=#{URI::encode(ref.to_s)}"] + + URI.join(*url).to_s + end + + def commit_coverage(sha, ref) + nil + end + + def build_page(sha, ref) + commit_page(sha, ref) + end + + def builds_path + url = [drone_url, "#{project.namespace.path}/#{project.path}"] + + URI.join(*url).to_s + end + + def status_img_path + url = [drone_url, + "api/badges/#{project.namespace.path}/#{project.path}/status.svg", + "?branch=#{URI::encode(project.default_branch)}"] + + URI.join(*url).to_s + end + + def title + 'Drone CI' + end + + def description + 'Drone is a Continuous Integration platform built on Docker, written in Go' + end + + def to_param + 'drone_ci' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: 'Drone CI project specific token' }, + { type: 'text', name: 'drone_url', placeholder: 'http://drone.example.com' }, + { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } + ] + end + + private + + def tag_push_valid?(data) + data[:total_commits_count] > 0 && !Gitlab::Git.blank_ref?(data[:after]) + end + + def push_valid?(data) + opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id, + source_branch: Gitlab::Git.ref_name(data[:ref])) + + opened_merge_requests.empty? && data[:total_commits_count] > 0 && + !Gitlab::Git.blank_ref?(data[:after]) + end + + def merge_request_valid?(data) + ['opened', 'reopened'].include?(data[:object_attributes][:state]) && + data[:object_attributes][:merge_status] == 'unchecked' + end +end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index ecdcd48ae6..acbbc9935b 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -21,7 +21,7 @@ class GitlabCiService < CiService API_PREFIX = "api/v1" - prop_accessor :project_url, :token + prop_accessor :project_url, :token, :enable_ssl_verification validates :project_url, presence: true, format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated? @@ -34,6 +34,7 @@ class GitlabCiService < CiService def compose_service_hook hook = service_hook || build_service_hook hook.url = [project_url, "/build", "?token=#{token}"].join("") + hook.enable_ssl_verification = enable_ssl_verification hook.save end @@ -136,7 +137,8 @@ class GitlabCiService < CiService def fields [ { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' }, - { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' } + { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' }, + { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } ] end diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 460ca40be3..33b113a2a2 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -1,3 +1,16 @@ +# == Schema Information +# +# Table name: sent_notifications +# +# id :integer not null, primary key +# project_id :integer +# noteable_id :integer +# noteable_type :string(255) +# recipient_id :integer +# commit_id :string(255) +# reply_key :string(255) not null +# + class SentNotification < ActiveRecord::Base belongs_to :project belongs_to :noteable, polymorphic: true diff --git a/app/models/service.rb b/app/models/service.rb index dcef2866c3..60fcc9d285 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -135,6 +135,7 @@ class Service < ActiveRecord::Base buildkite campfire custom_issue_tracker + drone_ci emails_on_push external_wiki flowdock diff --git a/app/models/user.rb b/app/models/user.rb index f70761074c..bff8eeed96 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,62 +2,58 @@ # # 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 -# 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) -# location :string(255) -# encrypted_otp_secret :string(255) -# encrypted_otp_secret_iv :string(255) -# encrypted_otp_secret_salt :string(255) -# otp_required_for_login :boolean default(FALSE), not null -# otp_backup_codes :text -# public_email :string(255) default(""), not null -# dashboard :integer default(0) -# project_view :integer default(0) +# 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 +# notification_email :string(255) +# hide_no_password :boolean default(FALSE) +# password_automatically_set :boolean default(FALSE) +# location :string(255) +# encrypted_otp_secret :string(255) +# encrypted_otp_secret_iv :string(255) +# encrypted_otp_secret_salt :string(255) +# otp_required_for_login :boolean default(FALSE), not null +# otp_backup_codes :text +# public_email :string(255) default(""), not null +# dashboard :integer default(0) +# project_view :integer default(0) # require 'carrierwave/orm/activerecord' @@ -104,7 +100,7 @@ class User < ActiveRecord::Base # Profile has_many :keys, dependent: :destroy has_many :emails, dependent: :destroy - has_many :identities, dependent: :destroy + has_many :identities, dependent: :destroy, autosave: true # Groups has_many :members, dependent: :destroy @@ -637,10 +633,6 @@ class User < ActiveRecord::Base email.start_with?('temp-email-for-oauth') end - def public_profile? - authorized_projects.public_only.any? - end - def avatar_url(size = nil) if avatar.present? [gitlab_config.url, avatar.url].join diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 3735a13636..e294b23bc2 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -107,12 +107,17 @@ class NotificationService recipients = [] + mentioned_users = note.mentioned_users + mentioned_users.select! do |user| + user.can?(:read_project, note.project) + end + # Add all users participating in the thread (author, assignee, comment authors) participants = if target.respond_to?(:participants) target.participants(note.author) else - note.mentioned_users + mentioned_users end recipients = recipients.concat(participants) @@ -120,8 +125,8 @@ class NotificationService recipients = add_project_watchers(recipients, note.project) # Reject users with Mention notification level, except those mentioned in _this_ note. - recipients = reject_mention_users(recipients - note.mentioned_users, note.project) - recipients = recipients + note.mentioned_users + recipients = reject_mention_users(recipients - mentioned_users, note.project) + recipients = recipients + mentioned_users recipients = reject_muted_users(recipients, note.project) diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index 4449721ae3..d3afc658cd 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -3,7 +3,7 @@ %tr %td - if reporter - = link_to reporter.name, [:admin, reporter] + = link_to reporter.name, reporter - else (removed) %td @@ -12,12 +12,15 @@ = abuse_report.message %td - if user - = link_to user.name, [:admin, user] + = link_to user.name, user - else (removed) %td - if user - = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning" - = link_to 'Remove user', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove" + = link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true), + data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, remote: true, method: :delete, class: "btn btn-xs btn-remove js-remove-tr" + %td - = link_to 'Remove report', [:admin, abuse_report], method: :delete, class: "btn btn-xs btn-close" + - if user + = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs" + = link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr" diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml index 4a25848f15..2e8746146d 100644 --- a/app/views/admin/abuse_reports/index.html.haml +++ b/app/views/admin/abuse_reports/index.html.haml @@ -9,7 +9,7 @@ %th Reported at %th Message %th User - %th + %th Primary action %th = render @abuse_reports = paginate @abuse_reports diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index e74e1e85f4..b120f4dea6 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -18,6 +18,13 @@ = f.label :url, "URL:", class: 'control-label' .col-sm-10 = f.text_field :url, class: "form-control" + .form-group + = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox' + .col-sm-10 + .checkbox + = f.label :enable_ssl_verification do + = f.check_box :enable_ssl_verification + %strong Enable SSL verification .form-actions = f.submit "Add System Hook", class: "btn btn-create" %hr @@ -32,6 +39,7 @@ .list-item-name = link_to admin_hook_path(hook) do %strong= hook.url + %p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} .pull-right = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm" diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index 213b5d65b3..1db56542af 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -1,13 +1,13 @@ .hidden-xs = render "events/event_last_push", event: @last_push +.gray-content-block - if current_user %ul.nav.nav-pills.event_filter.pull-right %li.pull-right = link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do %i.fa.fa-rss - = render 'shared/event_filter' - %hr + .content_list = spinner diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml new file mode 100644 index 0000000000..9f4be025bf --- /dev/null +++ b/app/views/dashboard/_activity_head.html.haml @@ -0,0 +1,7 @@ +%ul.center-top-menu + %li{ class: ("active" unless params[:filter]) } + = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do + Your Projects + %li{ class: ("active" if params[:filter] == 'starred') } + = link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do + Starred Projects diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 8a397a84e0..dcd6c97d44 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -3,5 +3,5 @@ = link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do Your Groups = nav_link(page: [explore_groups_path]) do - = link_to explore_groups_path, title: 'Explore groups', data: {toggle: 'tooltip', placement: 'bottom'} do + = link_to explore_groups_path, title: 'Explore groups', data: {placement: 'bottom'} do Explore Groups diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index d676576067..ef9b9ce756 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -1,5 +1,5 @@ -.panel.panel-default - .panel-heading.clearfix +.projects-list-holder + .projects-search-form .input-group = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' - if current_user.can_create_project? @@ -7,4 +7,4 @@ = link_to new_project_path, class: 'btn btn-success' do New project - = render 'shared/projects_list', projects: @projects, projects_limit: 20 + = render 'shared/projects/list', projects: @projects diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index f7be194c69..13a5eae3cd 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -5,6 +5,6 @@ = nav_link(page: starred_dashboard_projects_path) do = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do Starred Projects - = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do - = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do + = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do + = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do Explore Projects diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml new file mode 100644 index 0000000000..3e24338af6 --- /dev/null +++ b/app/views/dashboard/activity.html.haml @@ -0,0 +1,9 @@ += content_for :meta_tags do + - if current_user + = auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity") + +- header_title "Activity", activity_dashboard_path += render 'dashboard/activity_head' + +%section.activities + = render 'activities' diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index 0860fe3c76..c249f5cace 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -1,39 +1,19 @@ - page_title "Groups" +- header_title "Groups", dashboard_groups_path = render 'dashboard/groups_head' -.slead - Group members have access to all group projects. +.gray-content-block - if current_user.can_create_group? %span.pull-right.hidden-xs - = link_to new_group_path, class: "btn btn-new btn-sm" do + = link_to new_group_path, class: "btn btn-new" do %i.fa.fa-plus New Group -.panel.panel-default - .panel-heading - %strong Groups - (#{@group_members.count}) - %ul.well-list - - @group_members.each do |group_member| - - group = group_member.group - %li - .pull-right.hidden-xs - - if can?(current_user, :admin_group, group) - = link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do - %i.fa.fa-cogs - Settings + .title Welcome to the groups! + Group members have access to all group projects. - = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do - %i.fa.fa-sign-out - Leave - - = image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs" - = link_to group, class: 'group-name' do - %strong= group.name - - as - %strong #{group_member.human_access} - - %div.light - #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")} +%ul.content-list + - @group_members.each do |group_member| + - group = group_member.group + = render 'shared/groups/group', group: group, group_member: group_member = paginate @group_members diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 94318d1bcf..cd602e897b 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -1,21 +1,17 @@ - page_title "Issues" +- header_title "Issues", issues_dashboard_path(assignee_id: current_user.id) = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues") -%h3.page-title - Issues - -%p.light - List all issues from all projects you have access to. -%hr .append-bottom-20 .pull-right - if current_user - .hidden-xs.pull-left - = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do + .hidden-xs.pull-left.prepend-top-20 + = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: '' do %i.fa.fa-rss = render 'shared/issuable/filter', type: :issues + = render 'shared/issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 90611d562b..d1f332fa0d 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -1,11 +1,6 @@ - page_title "Merge Requests" -%h3.page-title - Merge Requests +- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) - -%p.light - List all merge requests from all projects you have access to. -%hr .append-bottom-20 = render 'shared/issuable/filter', type: :merge_requests = render 'shared/merge_requests' diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index 9a9a5e139a..21b25c3986 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -1,21 +1,19 @@ - page_title "Milestones" -%h3.page-title - Milestones - %span.pull-right #{@dashboard_milestones.count} milestones +- header_title "Milestones", dashboard_milestones_path -%p.light - List all milestones from all projects you have access to. - -%hr = render 'shared/milestones_filter' + +.gray-content-block + .oneline + List all milestones from all projects you have access to. + .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| - = render 'milestone', milestone: milestone + %ul.content-list + - if @dashboard_milestones.blank? + %li + .nothing-here-block No milestones to show + - else + - @dashboard_milestones.each do |milestone| + = render 'milestone', milestone: milestone = paginate @dashboard_milestones, theme: "gitlab" diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 98b8cde476..2fd7a1cf16 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -1,25 +1,9 @@ - page_title "Starred Projects" +- header_title "Projects", (current_user ? root_path : explore_root_path) = render 'dashboard/projects_head' - if @projects.any? - = render 'shared/show_aside' - - .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? - %span.input-group-btn - = link_to new_project_path, class: 'btn btn-success' do - New project - - = render 'shared/projects_list', projects: @projects, - projects_limit: 20, stars: true, avatar: false - + = render 'dashboard/projects' - else %h3 You don't 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 a3a32b6932..1d5324e0d7 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -2,16 +2,13 @@ - if current_user = auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity") +- header_title "Projects", (current_user ? root_path : explore_root_path) = render 'dashboard/projects_head' +- if @last_push + = render "events/event_last_push", event: @last_push + - if @projects.any? - = render 'shared/show_aside' - - .dashboard.row - %section.activities.col-md-8 - = render 'activities' - %aside.col-md-4 - = render 'sidebar' - + = render 'projects' - else = render "zero_authorized_projects" diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml new file mode 100644 index 0000000000..4974bb7f7f --- /dev/null +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -0,0 +1,9 @@ += form_tag(user_omniauth_authorize_path("crowd"), id: 'new_crowd_user' ) do + = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"} + = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} + - if devise_mapping.rememberable? + .remember-me.checkbox + %label{for: "remember_me"} + = check_box_tag :remember_me, '1', false, id: 'remember_me' + %span Remember me + = button_tag "Sign in", class: "btn-save btn" \ No newline at end of file diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index bb5e479697..41ad2c231d 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -8,15 +8,21 @@ .login-body - if form_based_providers.any? %ul.nav.nav-tabs + - if crowd_enabled? + %li.active + = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero?)} + %li{class: (:active if i.zero? && !crowd_enabled?)} = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - if signin_enabled? %li = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' .tab-content + - if crowd_enabled? + %div.tab-pane.active{id: "tab-crowd"} + = render 'devise/sessions/new_crowd' - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} = render 'devise/sessions/new_ldap', server: server - if signin_enabled? %div#tab-signin.tab-pane diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index 742b74a67c..ad63841ccf 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -1,5 +1,5 @@ %li.commit .commit-row-title = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: '' -   + · = gfm event_commit_title(commit[:message]), project: project diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 0faab4458e..9aacc79d68 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -3,8 +3,8 @@ .event-item-timestamp #{time_ago_with_tooltip(event.created_at)} - = cache [event, "v1"] do - = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' + = cache [event, "v2.1"] do + = image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:'' - if event.created_project? = render "events/event/created_project", event: event - elsif event.push? diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index 501412642d..6a0c6cba41 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -9,6 +9,6 @@ #{time_ago_with_tooltip(event.created_at)} .pull-right - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-sm" do + = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do Create Merge Request %hr diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index 07bec1697f..830fec0b4a 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -4,7 +4,7 @@ = event.action_name = event_note_title_html(event) at - + - if event.project = link_to_project event.project - else @@ -13,7 +13,6 @@ .event-body .event-note .md - %i.fa.fa-comment-o.event-note-icon = event_note(event.target.note, project: event.project) - note = event.target - if note.attachment.url diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 7dcefd330a..e8a6752de8 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -1,14 +1,15 @@ - page_title "Groups" +- header_title "Groups", (current_user ? dashboard_groups_path : explore_groups_path) - if current_user = render 'dashboard/groups_head' -.clearfix.append-bottom-10 +.gray-content-block.clearfix .pull-left = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| = hidden_field_tag :sort, @sort .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search" .form-group - = button_tag 'Search', class: "btn btn-primary wide" + = button_tag 'Search', class: "btn btn-default" .pull-right .dropdown.inline @@ -30,19 +31,9 @@ = link_to explore_groups_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated -%ul.bordered-list +%ul.content-list - @groups.each do |group| - %li - .clearfix - %h4 - = link_to group_path(id: group.path) do - = group.name - .clearfix - %p - = truncate group.description, length: 150 - .clearfix - %p.light - #{pluralize(group.members.size, 'member')}, #{pluralize(group.projects.count, 'project')} + = render 'shared/groups/group', group: group - unless @groups.present? .nothing-here-block No public groups diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 4b91291caf..5a3d689d1e 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -3,7 +3,7 @@ .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search" .form-group - = button_tag 'Search', class: "btn btn-primary wide" + = button_tag 'Search', class: "btn btn-success" .pull-right.hidden-sm.hidden-xs - if current_user diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml deleted file mode 100644 index 1e8a89e366..0000000000 --- a/app/views/explore/projects/_project.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -%li - %h4.project-title - .project-access-icon - = visibility_level_icon(project.visibility_level) - = link_to project.name_with_namespace, [project.namespace.becomes(Namespace), project] - %span.pull-right - %i.fa.fa-star - = project.star_count - - .project-info - - if project.description.present? - .project-description.str-truncated - = markdown(project.description, pipeline: :description) - - .repo-info - - unless project.empty_repo? - = link_to pluralize(round_commit_count(project), 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch) - · - = link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project) - · - = link_to pluralize(project.repository.tag_names.count, 'tag'), namespace_project_tags_path(project.namespace, project) - - else - %i.fa.fa-exclamation-triangle - Empty repository diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml new file mode 100644 index 0000000000..669079e952 --- /dev/null +++ b/app/views/explore/projects/_projects.html.haml @@ -0,0 +1,6 @@ +- if projects.any? + .public-projects + = render 'shared/projects/list', projects: projects +- else + .nothing-here-block + No such projects diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 4956081e1e..9df5b3830a 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -1,13 +1,7 @@ - page_title "Projects" - if current_user = render 'dashboard/projects_head' -.clearfix +.gray-content-block.clearfix = render 'filter' -%br -.public-projects - %ul.bordered-list.top-list - = render @projects - - unless @projects.present? - .nothing-here-block No public projects - - = paginate @projects, theme: "gitlab" += render 'projects', projects: @projects += paginate @projects, theme: "gitlab" diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index fdccbe5692..a9df32f3d7 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -1,14 +1,13 @@ - page_title "Starred Projects" - if current_user = render 'dashboard/projects_head' + .explore-trending-block - .lead - %i.fa.fa-star - See most starred projects + .gray-content-block .pull-right = render 'explore/projects/dropdown' - .public-projects - %ul.bordered-list - = render @starred_projects - + .oneline + %i.fa.fa-star + See most starred projects + = render 'projects', projects: @starred_projects = paginate @starred_projects, theme: 'gitlab' diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index 98a4174b42..c1ef06f6cd 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -1,18 +1,18 @@ - page_title "Trending Projects" - if current_user = render 'dashboard/projects_head' -.explore-title - %h3 - Explore GitLab - %p.lead - Discover projects and groups. Share your projects with others -%hr +- else + .explore-title + %h3 + Explore GitLab + %p.lead + Discover projects and groups. Share your projects with others + %br .explore-trending-block - .lead - %i.fa.fa-comments-o - See most discussed projects for last month + .gray-content-block .pull-right = render 'explore/projects/dropdown' - .public-projects - %ul.bordered-list - = render @trending_projects + .oneline + %i.fa.fa-comments-o + See most discussed projects for last month + = render 'projects', projects: @trending_projects diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 4f8aec1c67..9ac56b1e5f 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -1,4 +1,4 @@ -.panel.panel-default +.panel.panel-default.projects-list-holder .panel-heading.clearfix .input-group = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' @@ -7,4 +7,4 @@ = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-success' do New project - = render 'shared/projects_list', projects: @projects, projects_limit: 20 + = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index f0d9078255..08d97e418a 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,25 +1,24 @@ - page_title "Issues" +- header_title group_title(@group, "Issues", issues_group_path(@group)) = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues") -%h3.page-title - Issues -%p.light - Only issues from - %strong #{@group.name} - group are listed here. - - if current_user - To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. -%hr -.append-bottom-20 += render 'shared/issuable/filter', type: :issues +.gray-content-block.second-block .pull-right - if current_user .hidden-xs.pull-left - = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do + = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token) do %i.fa.fa-rss + %div + Only issues from + %strong #{@group.name} + group are listed here. + - if current_user + To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. - = render 'shared/issuable/filter', type: :issues -= render 'shared/issues' +.prepend-top-default + = render 'shared/issues' diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index ca85a15870..425ad8331b 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -1,14 +1,13 @@ - page_title "Merge Requests" -%h3.page-title - Merge Requests +- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group)) -%p.light - Only merge requests from - %strong #{@group.name} - group are listed here. - - if current_user - To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. -%hr -.append-bottom-20 - = render 'shared/issuable/filter', type: :merge_requests -= render 'shared/merge_requests' += render 'shared/issuable/filter', type: :merge_requests +.gray-content-block.second-block + %div + Only merge requests from + %strong #{@group.name} + group are listed here. + - if current_user + To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. +.prepend-top-default + = render 'shared/merge_requests' diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 385222fa5b..2bbcad5fdf 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -1,23 +1,17 @@ - page_title "Milestones" -%h3.page-title - Milestones - %span.pull-right #{@group_milestones.count} milestones +- header_title group_title(@group, "Milestones", group_milestones_path(@group)) -%p.light += render 'shared/milestones_filter' +.gray-content-block Only milestones from %strong #{@group.name} group are listed here. - -%hr - -= render 'shared/milestones_filter' .milestones - .panel.panel-default - %ul.well-list - - if @group_milestones.blank? - %li - .nothing-here-block No milestones to show - - else - - @group_milestones.each do |milestone| - = render 'milestone', milestone: milestone + %ul.content-list + - if @group_milestones.blank? + %li + .nothing-here-block No milestones to show + - else + - @group_milestones.each do |milestone| + = render 'milestone', milestone: milestone = paginate @group_milestones, theme: "gitlab" diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index d31dae7d64..0577f4ec14 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -17,7 +17,7 @@ = render 'shared/show_aside' .row - %section.activities.col-md-8 + %section.activities.col-md-7 .hidden-xs - if current_user = render "events/event_last_push", event: @last_push @@ -33,5 +33,5 @@ .content_list = spinner - %aside.side.col-md-4 + %aside.side.col-md-5 = render "projects", projects: @projects diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 96e15783a3..c1746676ae 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,6 +1,11 @@ .page-with-sidebar{ class: nav_sidebar_class } = render "layouts/broadcast" .sidebar-wrapper.nicescroll + .header-logo + = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do + = brand_header_logo + .gitlab-text-container + %h3 GitLab - if defined?(sidebar) && sidebar = render "layouts/nav/#{sidebar}" - elsif current_user @@ -9,12 +14,12 @@ = render partial: 'layouts/collapse_button' - if current_user = link_to current_user, class: 'sidebar-user' do - = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32' + = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36' .username = current_user.username .content-wrapper - .container-fluid + = render "layouts/flash" + %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" } .content - = render "layouts/flash" .clearfix = yield diff --git a/app/views/layouts/dashboard.html.haml b/app/views/layouts/dashboard.html.haml index c72eca10bf..fad7de6943 100644 --- a/app/views/layouts/dashboard.html.haml +++ b/app/views/layouts/dashboard.html.haml @@ -1,5 +1,6 @@ - page_title "Dashboard" -- header_title "Dashboard", root_path +- unless @header_title + - header_title "Dashboard", root_path - sidebar "dashboard" = render template: "layouts/application" diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml index 17fee9c510..9098554e6f 100644 --- a/app/views/layouts/explore.html.haml +++ b/app/views/layouts/explore.html.haml @@ -1,6 +1,7 @@ - page_title "Explore" - if current_user - - header_title "Dashboard", root_path + - unless @header_title + - header_title "Projects", (current_user ? root_path : explore_root_path) - else - header_title "Explore GitLab", explore_root_path - sidebar "dashboard" diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index db7dbf9bfe..4f00d01d4c 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,5 +1,6 @@ - page_title @group.name -- header_title @group.name, group_path(@group) +- unless @header_title + - header_title @group.name, group_path(@group) - sidebar "group" unless sidebar = render template: "layouts/application" diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 12ddbe6f1b..3892f71c0e 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,10 +1,5 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } - .container - .header-logo - = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do - = brand_header_logo - .gitlab-text-container - %h3 GitLab + %div{ class: fluid_layout ? "container-fluid" : "container-fluid" } .header-content %button.navbar-toggle{type: 'button'} %span.sr-only Toggle navigation @@ -17,26 +12,14 @@ %li.visible-sm.visible-xs = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('search') - -#%li.hidden-xs - = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('question-circle fw') - -#%li - = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('globe fw') - -#%li - = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('clipboard fw') - if current_user.is_admin? %li = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('wrench fw') - if current_user.can_create_project? - %li.hidden-xs + %li = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('plus fw') - -#%li - = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('cog fw') %li = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('sign-out') diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml index 15c2e292be..a6a26518a0 100644 --- a/app/views/layouts/header/_public.html.haml +++ b/app/views/layouts/header/_public.html.haml @@ -1,14 +1,9 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } - .container - .header-logo - = link_to explore_root_path, class: "home" do - = brand_header_logo - .gitlab-text-container - %h3 GitLab + %div{ class: fluid_layout ? "container-fluid" : "container-fluid" } .header-content - unless current_controller?('sessions') .pull-right - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success btn-sm' + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success' %h1.title= title diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 8f010196d1..0cf1c3d5d2 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,9 +1,14 @@ %ul.nav.nav-sidebar = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do = link_to (current_user ? root_path : explore_root_path), title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do - = icon('dashboard fw') + = icon('home fw') %span Projects + = nav_link(path: 'dashboard#activity') do + = link_to activity_dashboard_path, title: 'Activity', data: {placement: 'right'} do + = icon('dashboard fw') + %span + Activity = nav_link(controller: :groups) do = link_to (current_user ? dashboard_groups_path : explore_groups_path), title: 'Groups', data: {placement: 'right'} do = icon('group fw') @@ -29,7 +34,7 @@ %span.count= current_user.assigned_merge_requests.opened.count = nav_link(controller: :snippets) do = link_to (current_user ? user_snippets_path(current_user) : snippets_path), title: 'Your snippets', data: {placement: 'right'} do - = icon('dashboard fw') + = icon('clipboard fw') %span Snippets - if current_user @@ -37,7 +42,7 @@ = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('user fw') %span - Profile + Profile Settings = nav_link(controller: :help) do = link_to help_path, title: 'Help', data: {placement: 'right'} do = icon('question-circle fw') diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index d17d1c5fbd..5e7b902622 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -100,7 +100,7 @@ - if project_nav_tab? :snippets = nav_link(controller: :snippets) do = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets', data: {placement: 'right'} do - = icon('file-text-o fw') + = icon('clipboard fw') %span Snippets diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 3193206fe1..77d2ccbf76 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,5 +1,5 @@ -- page_title "Settings" -- header_title "Settings", profile_path +- page_title "Profile Settings" +- header_title "Profile Settings", profile_path - sidebar "profile" = render template: "layouts/application" diff --git a/app/views/layouts/snippets.html.haml b/app/views/layouts/snippets.html.haml index b77fe09fc2..d9c90d4fce 100644 --- a/app/views/layouts/snippets.html.haml +++ b/app/views/layouts/snippets.html.haml @@ -1,6 +1,6 @@ - page_title 'Snippets' - if current_user - - header_title "Dashboard", root_path + - header_title "Snippets", user_snippets_path(current_user) - else - header_title 'Snippets', snippets_path - sidebar "dashboard" diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 9480a19f5b..db7fa2eabe 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -48,7 +48,7 @@ = f.radio_button :notification_level, Notification::N_WATCH .level-title Watch - %p You will receive all notifications from projects in which you participate + %p You will receive notifications for any activity .form-actions = f.submit 'Save changes', class: "btn btn-create" diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 1134317ee0..aa0361a0a1 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -22,11 +22,11 @@ .panel-heading Syntax highlighting theme .panel-body - - color_schemes.each do |color_scheme_id, color_scheme| + - Gitlab::ColorSchemes.each do |scheme| = label_tag do - .preview= image_tag "#{color_scheme}-scheme-preview.png" - = f.radio_button :color_scheme_id, color_scheme_id - = color_scheme.tr('-_', ' ').titleize + .preview= image_tag "#{scheme.css_class}-scheme-preview.png" + = f.radio_button :color_scheme_id, scheme.id + = scheme.name .panel.panel-default .panel-heading diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 9fdeddfcc7..c519e52e59 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -100,11 +100,6 @@ %hr = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" - - if @user.public_profile? - .alert.alert-info - %h4 Public profile - %p Your profile is publicly visible because you joined public project(s) - .row .col-md-7 diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index ee02b7f6a6..1261f6254d 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,5 +1,5 @@ = render 'projects/last_push' -.hidden-xs +.gray-content-block.activity-filter-block - if current_user %ul.nav.nav-pills.event_filter.pull-right %li @@ -7,7 +7,6 @@ %i.fa.fa-rss = render 'shared/event_filter' - %hr .content_list{:"data-href" => activity_project_path(@project)} = spinner diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index bec40ec27a..b93036e78e 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -2,7 +2,7 @@ .project-home-panel.clearfix{:class => ("empty-project" if empty_repo)} .project-identicon-holder = project_icon(@project, alt: '', class: 'project-avatar avatar s90') - .project-home-desc.lead + .project-home-desc %h1= @project.name - if @project.description.present? = markdown(@project.description, pipeline: :description) diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index a3ff7ce2f1..c1ec42aefc 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -27,7 +27,7 @@ .light = commit_author_link(commit, avatar: false) authored - #{time_ago_with_tooltip(commit.committed_date)} + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} %td.lines.blame-numbers %pre - line_count = blame_group[:lines].count diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index a693c4b282..cc0ec9483d 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -1,20 +1,20 @@ - commit = @repository.commit(branch.target) %li(class="js-branch-#{branch.name}") - %h4 + %div = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do %strong.str-truncated= branch.name +   - if branch.name == @repository.root_ref - %span.label.label-info default + %span.label.label-primary default - elsif @repository.merged_to_root_ref? branch.name - %span.label.label-primary.has_tooltip(title="Merged into #{@repository.root_ref}") - %i.fa.fa-check + %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}") merged - if @project.protected_branch? branch.name %span.label.label-success %i.fa.fa-lock protected - .pull-right + .controls.hidden-xs - if create_mr_button?(@repository.root_ref, branch.name) = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do = icon('plus') @@ -30,8 +30,7 @@ = icon("trash-o") - if commit - %ul.list-unstyled - = render 'projects/commits/inline_commit', commit: commit, project: @project + = render 'projects/branches/commit', commit: commit, project: @project - else %p Cant find HEAD commit for this branch diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml new file mode 100644 index 0000000000..68326e65d8 --- /dev/null +++ b/app/views/projects/branches/_commit.html.haml @@ -0,0 +1,7 @@ +.branch-commit.light + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" + · + %span.str-truncated + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" + · + #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 80acc93790..6e2dc2d271 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -1,7 +1,6 @@ - page_title "Branches" = render "projects/commits/head" -%h3.page-title - Branches +.gray-content-block .pull-right - if can? current_user, :push_code, @project = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do @@ -24,9 +23,10 @@ = sort_title_recently_updated = link_to namespace_project_branches_path(sort: 'last_updated') do = sort_title_oldest_updated -%hr + .oneline + Protected branches can be managed in project settings - unless @branches.empty? - %ul.bordered-list.top-list.all-branches + %ul.content-list.all-branches - @branches.each do |branch| = render "projects/branches/branch", branch: branch = paginate @branches, theme: 'gitlab' diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index cade930c8c..bc7625e898 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -2,7 +2,7 @@ %span.dropdown %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"} = icon('plus') - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown - if can?(current_user, :create_issue, @project) %li = link_to url_for_new_issue do diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index e3d8cd0fdd..50c0fd6803 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,22 +1,18 @@ -%ul.nav.nav-tabs +%ul.center-top-menu = nav_link(controller: [:commit, :commits]) do = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do - = icon("history") Commits %span.badge= number_with_delimiter(@repository.commit_count) = nav_link(controller: :compare) do = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref) do - = icon("exchange") Compare = nav_link(html_options: {class: branches_tab_class}) do = link_to namespace_project_branches_path(@project.namespace, @project) do - = icon("code-fork") Branches %span.badge.js-totalbranch-count= @repository.branches.size = nav_link(controller: :tags) do = link_to namespace_project_tags_path(@project.namespace, @project) do - = icon("tags") Tags %span.badge.js-totaltags-count= @repository.tags.length diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 55054a3197..a01a99458a 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -5,22 +5,23 @@ = render "head" -.tree-ref-holder - = render 'shared/ref_switcher', destination: 'commits' +.gray-content-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'commits' -.commits-feed-holder.hidden-xs.hidden-sm - - if create_mr_button?(@repository.root_ref, @ref) - = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do - = icon('plus') - Create Merge Request + .commits-feed-holder.hidden-xs.hidden-sm + - if create_mr_button?(@repository.root_ref, @ref) + = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do + = icon('plus') + Create Merge Request - - if current_user && current_user.private_token - = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'prepend-left-10 btn' do - = icon("rss") + - if current_user && current_user.private_token + = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'prepend-left-10 btn' do + = icon("rss") -%ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs %div{id: dom_id(@project)} #commits-list= render "commits", project: @project diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 3019893d12..efc25eda26 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,5 +1,5 @@ = form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do - .clearfix.append-bottom-20 + .clearfix - if params[:to] && params[:from] = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'} .form-group diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index d1e579a2ed..43d00726c4 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -1,9 +1,7 @@ - page_title "Compare" = render "projects/commits/head" -%h3.page-title - Compare View -%p.slead +.gray-content-block Compare branches, tags or commit ranges. %br Fill input field with commit id like @@ -14,4 +12,5 @@ %br Changes are shown from the version in the first field to the version in the second field. -= render "form" +.prepend-top-20 + = render "form" diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 3670dd5c13..8800ffdf48 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,16 +1,16 @@ - page_title "#{params[:from]}...#{params[:to]}" = render "projects/commits/head" -%h3.page-title - Compare View -= render "form" +.gray-content-block + = render "form" - if @commits.present? - = render "projects/commits/commit_list" - = render "projects/diffs/diffs", diffs: @diffs, project: @project + .prepend-top-20 + = render "projects/commits/commit_list" + = render "projects/diffs/diffs", diffs: @diffs, project: @project - else - .light-well + .light-well.prepend-top-20 .center %h4 There isn't anything to compare. diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml index 2e9c5dc08c..8e24c778b7 100644 --- a/app/views/projects/deploy_keys/index.html.haml +++ b/app/views/projects/deploy_keys/index.html.haml @@ -1,4 +1,5 @@ - page_title "Deploy Keys" + %h3.page-title Deploy keys allow read-only access to the repository diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 52c1e03040..30943f49bb 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,3 +1,6 @@ +- if params[:view] == 'parallel' + - fluid_layout true + .prepend-top-20.append-bottom-20 .pull-right .btn-group diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index caed0e69dc..f99bc9a85e 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -3,7 +3,7 @@ Too many changes to show. .pull-right - unless diff_hard_limit_enabled? - = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: :html)), class: "btn btn-sm btn-warning" + = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm btn-warning" - if current_controller?(:commit) or current_controller?(:merge_requests) - if current_controller?(:commit) diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 141acbdcf7..a357736bf5 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -50,39 +50,42 @@ datasets : [{ fillColor : "rgba(220,220,220,0.5)", strokeColor : "rgba(220,220,220,1)", - pointColor : "rgba(220,220,220,1)", - pointStrokeColor : "#EEE", + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, data : #{@commits_per_time.values.to_json} }] } ctx = $("#hour-chart").get(0).getContext("2d"); - new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) data = { labels : #{@commits_per_week_days.keys.to_json}, datasets : [{ fillColor : "rgba(220,220,220,0.5)", strokeColor : "rgba(220,220,220,1)", - pointColor : "rgba(220,220,220,1)", - pointStrokeColor : "#EEE", + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, data : #{@commits_per_week_days.values.to_json} }] } ctx = $("#weekday-chart").get(0).getContext("2d"); - new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) data = { labels : #{@commits_per_month.keys.to_json}, datasets : [{ fillColor : "rgba(220,220,220,0.5)", strokeColor : "rgba(220,220,220,1)", - pointColor : "rgba(220,220,220,1)", - pointStrokeColor : "#EEE", + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, data : #{@commits_per_month.values.to_json} }] } ctx = $("#month-chart").get(0).getContext("2d"); - new Chart(ctx).Line(data, {"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + new Chart(ctx).Bar(data, {"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index eadbf61fdd..85dbfd6786 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -55,6 +55,13 @@ %strong Merge Request events %p.light This url will be triggered when a merge request is created + .form-group + = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox' + .col-sm-10 + .checkbox + = f.label :enable_ssl_verification do + = f.check_box :enable_ssl_verification + %strong Enable SSL verification .form-actions = f.submit "Add Web Hook", class: "btn btn-create" @@ -74,3 +81,4 @@ - %w(push_events tag_push_events issues_events note_events merge_requests_events).each do |trigger| - if hook.send(trigger) %span.label.label-gray= trigger.titleize + SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 5d243adb5f..a3399c57aa 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,9 +1,8 @@ -.panel.panel-default - %ul.well-list.issues-list - = render @issues - - if @issues.blank? - %li - .nothing-here-block No issues to show +%ul.content-list.issues-list + = render @issues + - if @issues.blank? + %li + .nothing-here-block No issues to show - if @issues.present? .pull-right diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index d06225f548..24314d1140 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -3,8 +3,8 @@ - if current_user = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") -.append-bottom-10 - .pull-right +.project-issuable-filter + .controls .pull-left - if current_user .hidden-xs.pull-left diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 0bcd543fee..4295c828da 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -14,11 +14,6 @@ %span %i.fa.fa-ban CLOSED - - else - %span.hidden-xs.hidden-sm - %span.label-branch< - %i.fa.fa-code-fork - %span= merge_request.target_branch - note_count = merge_request.mr_and_commit_notes.user.count - if merge_request.assignee   diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml index b8a0ca9a42..d86707b3d9 100644 --- a/app/views/projects/merge_requests/_merge_requests.html.haml +++ b/app/views/projects/merge_requests/_merge_requests.html.haml @@ -1,9 +1,8 @@ -.panel.panel-default - %ul.well-list.mr-list - = render @merge_requests - - if @merge_requests.blank? - %li - .nothing-here-block No merge requests to show +%ul.content-list.mr-list + = render @merge_requests + - if @merge_requests.blank? + %li + .nothing-here-block No merge requests to show - if @merge_requests.present? .pull-right diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 007f6c6a78..ec1838eb48 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,4 +1,7 @@ - page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" +- if params[:view] == 'parallel' + - fluid_layout true + .merge-request{'data-url' => merge_request_path(@merge_request)} .merge-request-details.issuable-details = render "projects/merge_requests/show/mr_title" diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 72fbe2e27a..d3a576977c 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,7 +1,7 @@ - page_title "Merge Requests" = render 'projects/last_push' -.append-bottom-10 - .pull-right +.project-issuable-filter + .controls = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) - if can? current_user, :create_merge_request, @project diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 995eecd783..2b8fd67158 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -1,18 +1,21 @@ - page_title "Milestones" -.pull-right - - if can? current_user, :admin_milestone, @project - = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do - %i.fa.fa-plus - New Milestone = render 'shared/milestones_filter' -.milestones - .panel.panel-default - %ul.well-list - = render @milestones +.gray-content-block + .pull-right + - if can? current_user, :admin_milestone, @project + = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do + %i.fa.fa-plus + New Milestone + .oneline + Milestone allows you to group issues and set due date for it - - if @milestones.blank? - %li - .nothing-here-block No milestones to show +.milestones + %ul.content-list + = render @milestones + + - if @milestones.blank? + %li + .nothing-here-block No milestones to show = paginate @milestones, theme: "gitlab" diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 507f2c7beb..d5c4ee7197 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -11,7 +11,7 @@ = render "home_panel" -.project-stats +.project-stats.gray-content-block %ul.nav.nav-pills %li = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do @@ -63,21 +63,22 @@ = icon("exclamation-triangle fw") Archived project! Repository is read-only -%hr %section - if prefer_readme? - = render 'projects/readme' + .project-show-readme + = render 'projects/readme' - else - = render 'projects/activity' + .project-show-activity + = render 'projects/activity' - if current_user - access = user_max_access_in_project(current_user, @project) - if access - %hr - %p.light - You have #{access} access to this project. - - if @project.project_member_by_id(current_user) - = link_to leave_namespace_project_project_members_path(@project.namespace, @project), - data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do - Leave this project \ No newline at end of file + .prepend-top-20 + .gray-content-block.footer-block.center + You have #{access} access to this project. + - if @project.project_member_by_id(current_user) + = link_to leave_namespace_project_project_members_path(@project.namespace, @project), + data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do + Leave this project diff --git a/app/views/projects/snippets/_snippet.html.haml b/app/views/projects/snippets/_snippet.html.haml deleted file mode 100644 index b2c35edc44..0000000000 --- a/app/views/projects/snippets/_snippet.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -%li - %h4.snippet-title - = link_to reliable_snippet_path(snippet) do - = truncate(snippet.title, length: 60) - %span.cgray.monospace.tiny.pull-right - = snippet.file_name - - .snippet-info - = "##{snippet.id}" - %span - by - = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16" - = snippet.author_name - %span.light - #{time_ago_with_tooltip(snippet.created_at)} diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 30081673ff..45d4de6a38 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -8,9 +8,8 @@ %p.light Share code pastes with others out of git repository -%hr %ul.bordered-list - = render partial: "projects/snippets/snippet", collection: @snippets + = render partial: "shared/snippets/snippet", collection: @snippets - if @snippets.empty? %li .nothing-here-block Nothing here. diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 28ad272322..2ca295fc5f 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -1,13 +1,14 @@ - commit = @repository.commit(tag.target) %li - %h4 + %div = link_to namespace_project_commits_path(@project.namespace, @project, tag.name), class: "" do - %i.fa.fa-tag - = tag.name + %strong + %i.fa.fa-tag + = tag.name - if tag.message.present?   = strip_gpg_signature(tag.message) - .pull-right + .controls - if can? current_user, :download_code, @project = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-xs' - if can?(current_user, :admin_project, @project) @@ -15,8 +16,7 @@ %i.fa.fa-trash-o - if commit - %ul.list-unstyled - = render 'projects/commits/inline_commit', commit: commit, project: @project + = render 'projects/branches/commit', commit: commit, project: @project - else %p Cant find HEAD commit for this tag diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index d4652a47cb..1503a4330f 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -1,21 +1,18 @@ - page_title "Tags" = render "projects/commits/head" -%h3.page-title - Git Tags +.gray-content-block - if can? current_user, :push_code, @project .pull-right = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do %i.fa.fa-add-sign New tag - -%p.light - Tags give the ability to mark specific points in history as being important -%hr + .oneline + Tags give the ability to mark specific points in history as being important .tags - unless @tags.empty? - %ul.bordered-list + %ul.content-list - @tags.each do |tag| = render 'tag', tag: @repository.find_tag(tag) diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml index 5048154cb2..367a87927d 100644 --- a/app/views/projects/tree/_tree.html.haml +++ b/app/views/projects/tree/_tree.html.haml @@ -14,7 +14,7 @@ %small %i.fa.fa-plus -%div#tree-content-holder.tree-content-holder +%div#tree-content-holder.tree-content-holder.prepend-top-20 %table#tree-slider{class: "table_#{@hex_path} tree-table" } %thead %tr diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 788bb8cf1e..acc2c8b2f7 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,8 +1,8 @@ %span.pull-right - if (@page && @page.persisted?) - = link_to history_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do + = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do Page History - if can?(current_user, :create_wiki, @project) - = link_to edit_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do + = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do %i.fa.fa-pencil-square-o Edit diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 804a1b52db..ec87c3c765 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -3,10 +3,10 @@ = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) = nav_link(path: 'wikis#pages') do - = link_to 'Pages', pages_namespace_project_wikis_path(@project.namespace, @project) + = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) = nav_link(path: 'wikis#git_access') do - = link_to git_access_namespace_project_wikis_path(@project.namespace, @project) do + = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do %i.fa.fa-download Git Access diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 5c4dd7f91a..90c4b83cf3 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -10,7 +10,7 @@ - if @page.historical? .warning_message This is an old version of this page. - You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", history_namespace_project_wiki_path(@project.namespace, @project, @page)}. + You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. %hr diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml index a75cd7bd80..d637abfa76 100644 --- a/app/views/search/_category.html.haml +++ b/app/views/search/_category.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-pills.search-filter +%ul.nav.nav-tabs.search-filter - if @project %li{class: ("active" if @scope == 'blobs')} = link_to search_filter_path(scope: 'blobs') do diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index e2d0cab9e7..ec478a5963 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -1,6 +1,5 @@ .dropdown.inline - %button.dropdown-toggle.btn.btn{type: 'button', 'data-toggle' => 'dropdown'} - %i.fa.fa-tags + %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} %span.light Group: - if @group.present? %strong= @group.name @@ -17,8 +16,7 @@ = group.name .dropdown.inline.prepend-left-10.project-filter - %button.dropdown-toggle.btn.btn{type: 'button', 'data-toggle' => 'dropdown'} - %i.fa.fa-tags + %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} %span.light Project: - if @project.present? %strong= @project.name_with_namespace diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml index 5ee70be1ad..3938c545ca 100644 --- a/app/views/search/_form.html.haml +++ b/app/views/search/_form.html.haml @@ -1,12 +1,14 @@ -= form_tag search_path, method: :get, class: 'form-inline' do |f| += form_tag search_path, method: :get do |f| = hidden_field_tag :project_id, params[:project_id] = hidden_field_tag :group_id, params[:group_id] = hidden_field_tag :snippets, params[:snippets] = hidden_field_tag :scope, params[:scope] + .search-holder.clearfix - .form-group + .input-group = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true - = button_tag 'Search', class: "btn btn-primary" + %span.input-group-btn + = button_tag 'Search', class: "btn btn-primary" - unless params[:snippets].eql? 'true' - .pull-right - = render 'filter' + %br + = render 'filter' diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 741c780ad9..2a38c98dcf 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -1,7 +1,7 @@ - if @search_results.empty? = render partial: "search/results/empty" - else - .light + %p.light Search results for %code = @search_term @@ -11,10 +11,13 @@ - elsif @group in group #{link_to @group.name, @group} - %br .results.prepend-top-10 .search-results - = render partial: "search/results/#{@scope.singularize}", collection: @objects + - if @scope == 'projects' + .term + = render 'shared/projects/list', projects: @objects + - else + = render partial: "search/results/#{@scope.singularize}", collection: @objects = paginate @objects, theme: 'gitlab' :javascript diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index 58f58eff54..0fe8a3b490 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -7,4 +7,4 @@ %strong = blob.filename .file-content.code.term - = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, user_color_scheme_class: 'white' + = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline diff --git a/app/views/search/results/_project.html.haml b/app/views/search/results/_project.html.haml deleted file mode 100644 index 195cf06c8e..0000000000 --- a/app/views/search/results/_project.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -.search-result-row - %h4 - = link_to [project.namespace.becomes(Namespace), project] do - %span.term= project.name_with_namespace - - if project.description.present? - %span.light.term= project.description diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index 9509985391..9a4f9fb948 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -23,7 +23,7 @@ .nothing-here-block Empty file - else .file-content.code - %div.highlighted-data{class: user_color_scheme_class} + %div.highlighted-data{ class: user_color_scheme } .line-numbers - snippet_blob[:snippet_chunks].each do |snippet| - unless snippet[:data].empty? diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index c03438eb95..f5859481d4 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -7,4 +7,4 @@ %strong = wiki_blob.filename .file-content.code.term - = render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline, user_color_scheme_class: 'white' + = render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 60f9e9ac9d..f4f3dcfc29 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -1,7 +1,5 @@ - page_title @search_term = render 'search/form' -%hr - if @search_term = render 'search/category' - %hr = render 'search/results' diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 07672359db..2cd422e772 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -4,7 +4,7 @@ .input-group-btn %button{ | type: 'button', | - class: "btn btn-sm #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", | + class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", | :"data-clone" => project.ssh_url_to_repo, | :"data-title" => "Add an SSH key to your profile
to pull or push via SSH", :"data-html" => "true", @@ -13,13 +13,13 @@ .input-group-btn %button{ | type: 'button', | - class: "btn btn-sm #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", | + class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", | :"data-clone" => project.http_url_to_repo, | :"data-title" => "Set a password on your account
to pull or push via #{gitlab_config.protocol.upcase}", :"data-html" => "true", :"data-container" => "body"} = gitlab_config.protocol.upcase - = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control input-sm", readonly: true + = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true - if project.kind_of?(Project) .input-group-addon .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" } diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 334db60690..8495774acc 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-pills.event_filter +.btn-group.btn-group-next.event-filter = event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.comments, 'Comments' diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index d6a2e177da..57c3aff3e1 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -1,4 +1,4 @@ -.file-content.code{class: user_color_scheme_class} +.file-content.code.js-syntax-highlight{ class: user_color_scheme } .line-numbers - if blob.data.present? - blob.data.lines.each_index do |index| diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index f685ae7726..cbdecda4ff 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -1,14 +1,11 @@ -.milestones-filters.append-bottom-10 - %ul.nav.nav-tabs +.milestones-filters + %ul.center-top-menu %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} = link_to milestones_filter_path(state: 'opened') do - %i.fa.fa-exclamation-circle Open %li{class: ("active" if params[:state] == 'closed')} = link_to milestones_filter_path(state: 'closed') do - %i.fa.fa-check-circle Closed %li{class: ("active" if params[:state] == 'all')} = link_to milestones_filter_path(state: 'all') do - %i.fa.fa-compass All diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml deleted file mode 100644 index 6bd61455d2..0000000000 --- a/app/views/shared/_project.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -= cache [project.namespace, 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') - %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 diff --git a/app/views/shared/_projects_list.html.haml b/app/views/shared/_projects_list.html.haml deleted file mode 100644 index 4c58092af4..0000000000 --- a/app/views/shared/_projects_list.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -- projects_limit = 20 unless local_assigns[:projects_limit] -- avatar = true unless local_assigns[:avatar] == false -- stars = false unless local_assigns[:stars] == true -%ul.well-list.projects-list - - projects.each_with_index do |project, i| - %li{class: (i >= projects_limit) ? 'project-row hide' : 'project-row'} - = render "shared/project", project: project, avatar: avatar, stars: stars - - if projects.blank? - %li - .nothing-here-block There are no projects here. - - if projects.count > projects_limit - %li.bottom - %span.light - #{projects_limit} of #{pluralize(projects.count, 'project')} displayed. - %span - = link_to '#', class: 'js-expand' do - Show all diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml new file mode 100644 index 0000000000..a54c5fa8c3 --- /dev/null +++ b/app/views/shared/groups/_group.html.haml @@ -0,0 +1,22 @@ +- group_member = local_assigns[:group_member] +%li + - if group_member + .controls.hidden-xs + - if can?(current_user, :admin_group, group) + = link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do + %i.fa.fa-cogs + + = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do + %i.fa.fa-sign-out + + = image_tag group_icon(group), class: "avatar s46 hidden-xs" + = link_to group, class: 'group-name' do + %strong= group.name + + - if group_member + as + %span #{group_member.human_access} + + %div.light + #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")} + diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index bcaa48c7a1..8f16773077 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -1,33 +1,28 @@ .issues-filters .issues-state-filters - %ul.nav.nav-tabs + %ul.center-top-menu %li{class: ("active" if params[:state] == 'opened')} = link_to page_filter_path(state: 'opened') do - = icon('exclamation-circle') #{state_filters_text_for(:opened, @project)} - if defined?(type) && type == :merge_requests %li{class: ("active" if params[:state] == 'merged')} = link_to page_filter_path(state: 'merged') do - = icon('check-circle') #{state_filters_text_for(:merged, @project)} %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed') do - = icon('ban') #{state_filters_text_for(:closed, @project)} - else %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed') do - = icon('check-circle') #{state_filters_text_for(:closed, @project)} %li{class: ("active" if params[:state] == 'all')} = link_to page_filter_path(state: 'all') do - = icon('compass') #{state_filters_text_for(:all, @project)} - .issues-details-filters + .issues-details-filters.gray-content-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) .check-all-holder diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml new file mode 100644 index 0000000000..021e3b689a --- /dev/null +++ b/app/views/shared/projects/_list.html.haml @@ -0,0 +1,19 @@ +- projects_limit = 20 unless local_assigns[:projects_limit] +- avatar = true unless local_assigns[:avatar] == false +- stars = true unless local_assigns[:stars] == false + +%ul.projects-list + - projects.each_with_index do |project, i| + - css_class = (i >= projects_limit) ? 'hide' : nil + = render "shared/projects/project", project: project, + avatar: avatar, stars: stars, css_class: css_class + + - if projects.count > projects_limit + %li.bottom.center + .light + #{projects_limit} of #{pluralize(projects.count, 'project')} displayed. + = link_to '#', class: 'js-expand' do + Show all + +:coffeescript + new ProjectsList() diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml new file mode 100644 index 0000000000..5318c6011f --- /dev/null +++ b/app/views/shared/projects/_project.html.haml @@ -0,0 +1,24 @@ +- avatar = true unless local_assigns[:avatar] == false +- stars = true unless local_assigns[:stars] == false +- css_class = '' unless local_assigns[:css_class] +- css_class += " no-description" unless project.description.present? +%li.project-row{ class: css_class } + = cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2.1'] do + = link_to project_path(project), class: dom_class(project) do + - if avatar + .dash-project-avatar + = project_icon(project, alt: '', class: 'avatar project-avatar s46') + %span.project-full-name + %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 + - if project.description.present? + .project-description + = markdown(project.description, pipeline: :description) diff --git a/app/views/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml similarity index 53% rename from app/views/snippets/_snippet.html.haml rename to app/views/shared/snippets/_snippet.html.haml index 5bb2866434..69a713ad9a 100644 --- a/app/views/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -1,12 +1,12 @@ -%li - %h4.snippet-title +%li.snippet-row + .snippet-title = link_to reliable_snippet_path(snippet) do = truncate(snippet.title, length: 60) - if snippet.private? %span.label.label-gray %i.fa.fa-lock private - %span.cgray.monospace.tiny.pull-right + %span.monospace.pull-right = snippet.file_name %small.pull-right.cgray @@ -14,10 +14,8 @@ = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) .snippet-info - = "##{snippet.id}" - %span - by - = link_to user_snippets_path(snippet.author) do - = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' - = snippet.author_name - %span.light #{time_ago_with_tooltip(snippet.created_at)} + = link_to user_snippets_path(snippet.author) do + = image_tag avatar_icon(snippet.author_email), class: "avatar s24", alt: '' + = snippet.author_name + authored #{time_ago_with_tooltip(snippet.created_at)} + diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 40df42b6cf..d9aa4dd1d2 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -1,5 +1,5 @@ %ul.bordered-list - = render partial: 'snippet', collection: @snippets + = render partial: 'shared/snippets/snippet', collection: @snippets - if @snippets.empty? %li .nothing-here-block Nothing here. diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml index 62a967a2e0..d704407c4d 100644 --- a/app/views/snippets/current_user_index.html.haml +++ b/app/views/snippets/current_user_index.html.haml @@ -1,14 +1,15 @@ - page_title "Your Snippets" = render 'head' -.slead - Share code pastes with others out of git repository - +.gray-content-block .pull-right - = link_to new_snippet_path, class: "btn btn-new btn-sm", title: "New Snippet" do + = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do Add new snippet -%ul.nav.nav-tabs + .oneline + Share code pastes with others out of git repository + +%ul.nav.nav-tabs.prepend-top-20 = nav_tab :scope, nil do = link_to user_snippets_path(@user) do All diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 8608815e0a..3b62dd2a6e 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -2,8 +2,14 @@ - if current_user = render 'head' -.slead - Public snippets created by you and other users are listed here +.gray-content-block + - if current_user + .pull-right + = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do + Add new snippet + + .oneline + Public snippets created by you and other users are listed here = render 'snippets' diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml index 297fa53739..a126a858ea 100644 --- a/app/views/users/_projects.html.haml +++ b/app/views/users/_projects.html.haml @@ -1,13 +1,13 @@ - if local_assigns.has_key?(:contributed_projects) && contributed_projects.present? .panel.panel-default.contributed-projects .panel-heading Projects contributed to - = render 'shared/projects_list', + = render 'shared/projects/list', projects: contributed_projects.sort_by(&:star_count).reverse, projects_limit: 5, stars: true, avatar: false - if local_assigns.has_key?(:projects) && projects.present? .panel.panel-default .panel-heading Personal projects - = render 'shared/projects_list', + = 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 64b7f25ad3..aa4e8722fb 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -7,7 +7,7 @@ = render 'shared/show_aside' .row - %section.col-md-8 + %section.col-md-7 .header-with-avatar = link_to avatar_icon(@user.email, 400), target: '_blank' do = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' @@ -59,7 +59,7 @@ .content_list = spinner - %aside.col-md-4 + %aside.col-md-5 = render 'profile', user: @user = render 'projects', projects: @projects, contributed_projects: @contributed_projects diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index a588a1f45e..8cfb96ef37 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -18,6 +18,8 @@ class EmailReceiverWorker def handle_failure(raw, e) Rails.logger.warn("Email can not be processed: #{e}\n\n#{raw}") + return unless raw.present? + can_retry = false reason = nil diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 1d21addece..916a99bb27 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -4,7 +4,7 @@ class EmailsOnPushWorker def perform(project_id, recipients, push_data, options = {}) options.symbolize_keys! options.reverse_merge!( - send_from_committer_email: false, + send_from_committer_email: false, disable_diffs: false ) send_from_committer_email = options[:send_from_committer_email] @@ -16,9 +16,9 @@ class EmailsOnPushWorker ref = push_data["ref"] author_id = push_data["user_id"] - action = + action = if Gitlab::Git.blank_ref?(before_sha) - :create + :create elsif Gitlab::Git.blank_ref?(after_sha) :delete else @@ -42,17 +42,22 @@ class EmailsOnPushWorker end recipients.split(" ").each do |recipient| - Notify.repository_push_email( - project_id, - recipient, - author_id: author_id, - ref: ref, - action: action, - compare: compare, - reverse_compare: reverse_compare, - send_from_committer_email: send_from_committer_email, - disable_diffs: disable_diffs - ).deliver + begin + Notify.repository_push_email( + project_id, + recipient, + author_id: author_id, + ref: ref, + action: action, + compare: compare, + reverse_compare: reverse_compare, + send_from_committer_email: send_from_committer_email, + disable_diffs: disable_diffs + ).deliver + # These are input errors and won't be corrected even if Sidekiq retries + rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e + logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}") + end end ensure compare = nil diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb index 6a8665c179..5d1a8555b7 100644 --- a/app/workers/merge_worker.rb +++ b/app/workers/merge_worker.rb @@ -14,6 +14,10 @@ class MergeWorker if result[:status] == :success && params[:should_remove_source_branch].present? DeleteBranchService.new(merge_request.source_project, current_user). execute(merge_request.source_branch) + + merge_request.source_project.repository.expire_branch_names end + + result end end diff --git a/bin/daemon_with_pidfile b/bin/daemon_with_pidfile new file mode 100755 index 0000000000..f138c27a0e --- /dev/null +++ b/bin/daemon_with_pidfile @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby +# daemon_with_pidfile +# +# Daemonize, write a pidfile, and exec the remainder of the command line. + +def main(pidfile, cmd) + if middle_pid = Process.fork + # outer process + # Do not exit the outer process before the middle process finishes + Process.waitpid(middle_pid) + exit $?.exitstatus + end + + if final_pid = Process.fork + # middle process + open(pidfile, 'w') { |f| f.puts final_pid } + exit + end + + # Standard daemon things: become session leader, ignore SIGHUP, close stdin. + Signal.trap("HUP", "IGNORE") + Process.setsid + IO.new(0).close + + exec(*cmd) +end + +if ARGV.count < 2 + abort "Usage: #$0 pidfile command [args...]" +end + +pidfile = ARGV.shift +main(pidfile, ARGV) diff --git a/bin/mail_room b/bin/mail_room index f4f1a170c0..74a84f5b2b 100755 --- a/bin/mail_room +++ b/bin/mail_room @@ -19,9 +19,7 @@ get_mail_room_pid() start() { - bundle exec mail_room -q -c $mail_room_config >> $mail_room_logfile 2>&1 & - PID=$! - echo $PID > $mail_room_pidfile + bin/daemon_with_pidfile $mail_room_pidfile bundle exec mail_room -q -c $mail_room_config >> $mail_room_logfile 2>&1 } stop() diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index c7b60a1d4b..9eb99dae45 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -246,6 +246,11 @@ production: &base # issuer: 'https://gitlab.example.com', # name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' # } } + # - { name: 'crowd', + # args: { + # crowd_server_url: 'CROWD SERVER URL', + # application_name: 'YOUR_APP_NAME', + # application_password: 'YOUR_APP_PASSWORD' } } diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index bd26ac1da2..c47e5dab27 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -8,7 +8,7 @@ class Settings < Settingslogic def gitlab_on_standard_port? gitlab.port.to_i == (gitlab.https ? 443 : 80) end - + # get host without www, thanks to http://stackoverflow.com/a/6674363/1233435 def get_host_without_www(url) url = URI.encode(url) @@ -32,14 +32,12 @@ class Settings < Settingslogic end end + def build_base_gitlab_url + base_gitlab_url.join('') + end + def build_gitlab_url - custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}" - [ gitlab.protocol, - "://", - gitlab.host, - custom_port, - gitlab.relative_url_root - ].join('') + (base_gitlab_url + [gitlab.relative_url_root]).join('') end # check that values in `current` (string or integer) is a contant in `modul`. @@ -64,6 +62,17 @@ class Settings < Settingslogic end value end + + private + + def base_gitlab_url + custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}" + [ gitlab.protocol, + "://", + gitlab.host, + custom_port + ] + end end end @@ -123,6 +132,7 @@ Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].ni Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" Settings.gitlab['email_display_name'] ||= "GitLab" Settings.gitlab['email_reply_to'] ||= "noreply@#{Settings.gitlab.host}" +Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url) Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' Settings.gitlab['user_home'] ||= begin diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb index 7f73546ac8..70ed10e827 100644 --- a/config/initializers/7_omniauth.rb +++ b/config/initializers/7_omniauth.rb @@ -11,7 +11,7 @@ if Gitlab::LDAP::Config.enabled? end end -OmniAuth.config.full_host = Settings.gitlab['url'] +OmniAuth.config.full_host = Settings.gitlab['base_url'] OmniAuth.config.allowed_request_methods = [:post] #In case of auto sign-in, the GET method is used (users don't get to click on a button) OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present? diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 091548348b..2ce24592f8 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -238,7 +238,7 @@ Devise.setup do |config| provider_arguments.concat provider['args'] when Hash # A Hash from the configuration will be passed as is. - provider_arguments << provider['args'] + provider_arguments << provider['args'].symbolize_keys end config.omniauth provider['name'].to_sym, *provider_arguments diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 6139ddbe6c..66ac88e9f4 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -12,8 +12,7 @@ Doorkeeper.configure do end resource_owner_from_credentials do |routes| - u = User.find_by(email: params[:username]) || User.find_by(username: params[:username]) - u if u && u.valid_password?(params[:password]) + Gitlab::Auth.new.find(params[:username], params[:password]) end # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. diff --git a/config/mail_room.yml.example b/config/mail_room.yml.example index 28366eb739..dd8edfc42e 100644 --- a/config/mail_room.yml.example +++ b/config/mail_room.yml.example @@ -14,6 +14,8 @@ # :name: "inbox" # # Always "sidekiq". # :delivery_method: sidekiq + # # Always true. + # :delete_after_delivery: true # :delivery_options: # # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. # :redis_url: redis://localhost:6379 diff --git a/config/routes.rb b/config/routes.rb index d7307a61ed..25c286b308 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -30,12 +30,7 @@ Gitlab::Application.routes.draw do end # Enable Grack support - mount Grack::Bundle.new({ - git_path: Gitlab.config.git.bin_path, - project_root: Gitlab.config.gitlab_shell.repos_path, - upload_pack: Gitlab.config.gitlab_shell.upload_pack, - receive_pack: Gitlab.config.gitlab_shell.receive_pack - }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post] + mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post] # Help get 'help' => 'help#index' @@ -261,6 +256,7 @@ Gitlab::Application.routes.draw do member do get :issues get :merge_requests + get :activity end scope module: :dashboard do @@ -410,16 +406,20 @@ Gitlab::Application.routes.draw do end end - resources :wikis, only: [:show, :edit, :destroy, :create], constraints: { id: /[a-zA-Z.0-9_\-\/]+/ } do - collection do - get :pages - put ':id' => 'wikis#update' - get :git_access - end + WIKI_SLUG_ID = { id: /[a-zA-Z.0-9_\-\/]+/ } unless defined? WIKI_SLUG_ID - member do - get 'history' - end + scope do + # Order matters to give priority to these matches + get '/wikis/git_access', to: 'wikis#git_access' + get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' + post '/wikis', to: 'wikis#create' + + get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID + get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID + + get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID + delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID + put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID end resource :repository, only: [:show, :create] do diff --git a/db/migrate/20150817163600_deduplicate_user_identities.rb b/db/migrate/20150817163600_deduplicate_user_identities.rb new file mode 100644 index 0000000000..fab669c290 --- /dev/null +++ b/db/migrate/20150817163600_deduplicate_user_identities.rb @@ -0,0 +1,14 @@ +class DeduplicateUserIdentities < ActiveRecord::Migration + def change + execute 'DROP TABLE IF EXISTS tt_migration_DeduplicateUserIdentities;' + execute 'CREATE TEMPORARY TABLE tt_migration_DeduplicateUserIdentities AS SELECT id,provider,user_id FROM identities;' + execute 'DELETE FROM identities WHERE id NOT IN ( SELECT MIN(id) FROM tt_migration_DeduplicateUserIdentities GROUP BY user_id, provider);' + execute 'DROP TABLE IF EXISTS tt_migration_DeduplicateUserIdentities;' + end + + def down + # This is an irreversible migration; + # If someone is trying to rollback for other reasons, we should not throw an Exception. + # raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20150824002011_add_enable_ssl_verification.rb b/db/migrate/20150824002011_add_enable_ssl_verification.rb new file mode 100644 index 0000000000..093c068fbd --- /dev/null +++ b/db/migrate/20150824002011_add_enable_ssl_verification.rb @@ -0,0 +1,5 @@ +class AddEnableSslVerification < ActiveRecord::Migration + def change + add_column :web_hooks, :enable_ssl_verification, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 108c48bf32..36568dd4ed 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: 20150818213832) do +ActiveRecord::Schema.define(version: 20150824002011) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -487,12 +487,12 @@ ActiveRecord::Schema.define(version: 20150818213832) do add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "users", force: true do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -500,22 +500,22 @@ ActiveRecord::Schema.define(version: 20150818213832) do t.datetime "created_at" t.datetime "updated_at" t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", default: 1, null: false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", default: 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" t.datetime "last_credential_check_at" @@ -524,20 +524,20 @@ ActiveRecord::Schema.define(version: 20150818213832) do t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false t.string "location" t.string "encrypted_otp_secret" t.string "encrypted_otp_secret_iv" t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false + t.boolean "otp_required_for_login", default: false, null: false t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 + t.string "public_email", default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree @@ -566,13 +566,14 @@ ActiveRecord::Schema.define(version: 20150818213832) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", default: "ProjectHook" + t.string "type", default: "ProjectHook" t.integer "service_id" - t.boolean "push_events", default: true, null: false - t.boolean "issues_events", default: false, null: false - t.boolean "merge_requests_events", default: false, null: false - t.boolean "tag_push_events", default: false - t.boolean "note_events", default: false, null: false + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false + t.boolean "tag_push_events", default: false + t.boolean "note_events", default: false, null: false + t.boolean "enable_ssl_verification", default: false end add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree diff --git a/doc/api/README.md b/doc/api/README.md index f369c3fd97..6b8528de50 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -21,6 +21,7 @@ - [Groups](groups.md) - [Namespaces](namespaces.md) - [Settings](settings.md) +- [Keys](keys.md) ## Clients diff --git a/doc/api/keys.md b/doc/api/keys.md new file mode 100644 index 0000000000..faa6f212b4 --- /dev/null +++ b/doc/api/keys.md @@ -0,0 +1,46 @@ +# Keys + +## Get SSH key with user by ID of an SSH key + +Get SSH key with user by ID of an SSH key. Note only administrators can lookup SSH key with user by ID of an SSH key. + +``` +GET /keys/:id +``` + +Parameters: + +- `id` (required) - The ID of an SSH key + +```json +{ + "id": 1, + "title": "Sample key 25", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt1256k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2015-09-03T07:24:44.627Z", + "user": { + "name": "John Smith", + "username": "john_smith", + "id": 25, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/cfa35b8cd2ec278026357769582fa563?s=40\u0026d=identicon", + "web_url": "http://localhost:3000/u/john_smith", + "created_at": "2015-09-03T07:24:01.670Z", + "is_admin": false, + "bio": null, + "skype": "", + "linkedin": "", + "twitter": "", + "website_url": "", + "email": "john@example.com", + "theme_id": 2, + "color_scheme_id": 1, + "projects_limit": 10, + "current_sign_in_at": null, + "identities": [], + "can_create_group": true, + "can_create_project": true, + "two_factor_enabled": false + } +} +``` diff --git a/doc/api/services.md b/doc/api/services.md index cbf767d1b2..bc5049dd30 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -1,8 +1,296 @@ # Services +## Asana + +Asana - Teamwork without email + +### Create/Edit Asana service + +Set Asana service for a project. + +> This service adds commit messages as comments to Asana tasks. Once enabled, commit messages are checked for Asana task URLs (for example, `https://app.asana.com/0/123456/987654`) or task IDs starting with # (for example, `#987654`). Every task ID found will get the commit comment added to it. You can also close a task with a message containing: `fix #123456`. You can find your Api Keys here: http://developer.asana.com/documentation/#api_keys + +``` +PUT /projects/:id/services/asana +``` + +Parameters: + +- `api_key` (**required**) - User API token. User must have access to task,all comments will be attributed to this user. +- `restrict_to_branch` (optional) - Comma-separated list of branches which will beautomatically inspected. Leave blank to include all branches. + +### Delete Asana service + +Delete Asana service for a project. + +``` +DELETE /projects/:id/services/asana +``` + +## Assembla + +Project Management Software (Source Commits Endpoint) + +### Create/Edit Assembla service + +Set Assembla service for a project. + +``` +PUT /projects/:id/services/assembla +``` + +Parameters: + +- `token` (**required**) +- `subdomain` (optional) + +### Delete Assembla service + +Delete Assembla service for a project. + +``` +DELETE /projects/:id/services/assembla +``` + +## Atlassian Bamboo CI + +A continuous integration and build server + +### Create/Edit Atlassian Bamboo CI service + +Set Atlassian Bamboo CI service for a project. + +> You must set up automatic revision labeling and a repository trigger in Bamboo. + +``` +PUT /projects/:id/services/bamboo +``` + +Parameters: + +- `bamboo_url` (**required**) - Bamboo root URL like https://bamboo.example.com +- `build_key` (**required**) - Bamboo build plan key like KEY +- `username` (**required**) - A user with API access, if applicable +- `password` (**required**) + +### Delete Atlassian Bamboo CI service + +Delete Atlassian Bamboo CI service for a project. + +``` +DELETE /projects/:id/services/bamboo +``` + +## Buildkite + +Continuous integration and deployments + +### Create/Edit Buildkite service + +Set Buildkite service for a project. + +``` +PUT /projects/:id/services/buildkite +``` + +Parameters: + +- `token` (**required**) - Buildkite project GitLab token +- `project_url` (**required**) - https://buildkite.com/example/project +- `enable_ssl_verification` (optional) - Enable SSL verification + +### Delete Buildkite service + +Delete Buildkite service for a project. + +``` +DELETE /projects/:id/services/buildkite +``` + +## Campfire + +Simple web-based real-time group chat + +### Create/Edit Campfire service + +Set Campfire service for a project. + +``` +PUT /projects/:id/services/campfire +``` + +Parameters: + +- `token` (**required**) +- `subdomain` (optional) +- `room` (optional) + +### Delete Campfire service + +Delete Campfire service for a project. + +``` +DELETE /projects/:id/services/campfire +``` + +## Custom Issue Tracker + +Custom issue tracker + +### Create/Edit Custom Issue Tracker service + +Set Custom Issue Tracker service for a project. + +``` +PUT /projects/:id/services/custom-issue-tracker +``` + +Parameters: + +- `new_issue_url` (**required**) - New Issue url +- `issues_url` (**required**) - Issue url +- `project_url` (**required**) - Project url +- `description` (optional) - Custom issue tracker +- `title` (optional) - Custom Issue Tracker + +### Delete Custom Issue Tracker service + +Delete Custom Issue Tracker service for a project. + +``` +DELETE /projects/:id/services/custom-issue-tracker +``` + +## Drone CI + +Drone is a Continuous Integration platform built on Docker, written in Go + +### Create/Edit Drone CI service + +Set Drone CI service for a project. + +``` +PUT /projects/:id/services/drone-ci +``` + +Parameters: + +- `token` (**required**) - Drone CI project specific token +- `drone_url` (**required**) - http://drone.example.com +- `enable_ssl_verification` (optional) - Enable SSL verification + +### Delete Drone CI service + +Delete Drone CI service for a project. + +``` +DELETE /projects/:id/services/drone-ci +``` + +## Emails on push + +Email the commits and diff of each push to a list of recipients. + +### Create/Edit Emails on push service + +Set Emails on push service for a project. + +``` +PUT /projects/:id/services/emails-on-push +``` + +Parameters: + +- `recipients` (**required**) - Emails separated by whitespace +- `disable_diffs` (optional) - Disable code diffs +- `send_from_committer_email` (optional) - Send from committer + +### Delete Emails on push service + +Delete Emails on push service for a project. + +``` +DELETE /projects/:id/services/emails-on-push +``` + +## External Wiki + +Replaces the link to the internal wiki with a link to an external wiki. + +### Create/Edit External Wiki service + +Set External Wiki service for a project. + +``` +PUT /projects/:id/services/external-wiki +``` + +Parameters: + +- `external_wiki_url` (**required**) - The URL of the external Wiki + +### Delete External Wiki service + +Delete External Wiki service for a project. + +``` +DELETE /projects/:id/services/external-wiki +``` + +## Flowdock + +Flowdock is a collaboration web app for technical teams. + +### Create/Edit Flowdock service + +Set Flowdock service for a project. + +``` +PUT /projects/:id/services/flowdock +``` + +Parameters: + +- `token` (**required**) - Flowdock Git source token + +### Delete Flowdock service + +Delete Flowdock service for a project. + +``` +DELETE /projects/:id/services/flowdock +``` + +## Gemnasium + +Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities. + +### Create/Edit Gemnasium service + +Set Gemnasium service for a project. + +``` +PUT /projects/:id/services/gemnasium +``` + +Parameters: + +- `api_key` (**required**) - Your personal API KEY on gemnasium.com +- `token` (**required**) - The project's slug on gemnasium.com + +### Delete Gemnasium service + +Delete Gemnasium service for a project. + +``` +DELETE /projects/:id/services/gemnasium +``` + ## GitLab CI -### Edit GitLab CI service +Continuous integration server from GitLab + +### Create/Edit GitLab CI service Set GitLab CI service for a project. @@ -12,12 +300,13 @@ PUT /projects/:id/services/gitlab-ci Parameters: -- `token` (required) - CI project token -- `project_url` (required) - CI project URL +- `token` (**required**) - GitLab CI project specific token +- `project_url` (**required**) - http://ci.gitlabhq.com/projects/3 +- `enable_ssl_verification` (optional) - Enable SSL verification ### Delete GitLab CI service -Delete GitLab CI service settings for a project. +Delete GitLab CI service for a project. ``` DELETE /projects/:id/services/gitlab-ci @@ -25,17 +314,24 @@ DELETE /projects/:id/services/gitlab-ci ## HipChat -### Edit HipChat service +Private group chat and IM -Set HipChat service for project. +### Create/Edit HipChat service + +Set HipChat service for a project. ``` PUT /projects/:id/services/hipchat ``` + Parameters: -- `token` (required) - HipChat token -- `room` (required) - HipChat room name +- `token` (**required**) - Room token +- `color` (optional) +- `notify` (optional) +- `room` (optional) - Room name or ID +- `api_version` (optional) - Leave blank for default (v2) +- `server` (optional) - Leave blank for default. https://hipchat.example.com ### Delete HipChat service @@ -44,3 +340,197 @@ Delete HipChat service for a project. ``` DELETE /projects/:id/services/hipchat ``` + +## Irker (IRC gateway) + +Send IRC messages, on update, to a list of recipients through an Irker gateway. + +### Create/Edit Irker (IRC gateway) service + +Set Irker (IRC gateway) service for a project. + +> NOTE: Irker does NOT have built-in authentication, which makes it vulnerable to spamming IRC channels if it is hosted outside of a firewall. Please make sure you run the daemon within a secured network to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html. + +``` +PUT /projects/:id/services/irker +``` + +Parameters: + +- `recipients` (**required**) - Recipients/channels separated by whitespaces +- `default_irc_uri` (optional) - irc://irc.network.net:6697/ +- `server_port` (optional) - 6659 +- `server_host` (optional) - localhost +- `colorize_messages` (optional) + +### Delete Irker (IRC gateway) service + +Delete Irker (IRC gateway) service for a project. + +``` +DELETE /projects/:id/services/irker +``` + +## JIRA + +Jira issue tracker + +### Create/Edit JIRA service + +Set JIRA service for a project. + +> Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) for details. Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html) + +``` +PUT /projects/:id/services/jira +``` + +Parameters: + +- `new_issue_url` (**required**) - New Issue url +- `project_url` (**required**) - Project url +- `issues_url` (**required**) - Issue url +- `description` (optional) - Jira issue tracker + +### Delete JIRA service + +Delete JIRA service for a project. + +``` +DELETE /projects/:id/services/jira +``` + +## PivotalTracker + +Project Management Software (Source Commits Endpoint) + +### Create/Edit PivotalTracker service + +Set PivotalTracker service for a project. + +``` +PUT /projects/:id/services/pivotaltracker +``` + +Parameters: + +- `token` (**required**) + +### Delete PivotalTracker service + +Delete PivotalTracker service for a project. + +``` +DELETE /projects/:id/services/pivotaltracker +``` + +## Pushover + +Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop. + +### Create/Edit Pushover service + +Set Pushover service for a project. + +``` +PUT /projects/:id/services/pushover +``` + +Parameters: + +- `api_key` (**required**) - Your application key +- `user_key` (**required**) - Your user key +- `priority` (**required**) +- `device` (optional) - Leave blank for all active devices +- `sound` (optional) + +### Delete Pushover service + +Delete Pushover service for a project. + +``` +DELETE /projects/:id/services/pushover +``` + +## Redmine + +Redmine issue tracker + +### Create/Edit Redmine service + +Set Redmine service for a project. + +``` +PUT /projects/:id/services/redmine +``` + +Parameters: + +- `new_issue_url` (**required**) - New Issue url +- `project_url` (**required**) - Project url +- `issues_url` (**required**) - Issue url +- `description` (optional) - Redmine issue tracker + +### Delete Redmine service + +Delete Redmine service for a project. + +``` +DELETE /projects/:id/services/redmine +``` + +## Slack + +A team communication tool for the 21st century + +### Create/Edit Slack service + +Set Slack service for a project. + +``` +PUT /projects/:id/services/slack +``` + +Parameters: + +- `webhook` (**required**) - https://hooks.slack.com/services/... +- `username` (optional) - username +- `channel` (optional) - #channel + +### Delete Slack service + +Delete Slack service for a project. + +``` +DELETE /projects/:id/services/slack +``` + +## JetBrains TeamCity CI + +A continuous integration and build server + +### Create/Edit JetBrains TeamCity CI service + +Set JetBrains TeamCity CI service for a project. + +> The build configuration in Teamcity must use the build format number %build.vcs.number% you will also want to configure monitoring of all branches so merge requests build, that setting is in the vsc root advanced settings. + +``` +PUT /projects/:id/services/teamcity +``` + +Parameters: + +- `teamcity_url` (**required**) - TeamCity root URL like https://teamcity.example.com +- `build_type` (**required**) - Build configuration ID +- `username` (**required**) - A user with permissions to trigger a manual build +- `password` (**required**) + +### Delete JetBrains TeamCity CI service + +Delete JetBrains TeamCity CI service for a project. + +``` +DELETE /projects/:id/services/teamcity +``` + diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 53f8095cb1..a4a980cf0e 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -27,3 +27,9 @@ You can find results under the `doc/code` directory. ``` bundle exec rake gitlab:generate_docs ``` + +## Generate API documentation for project services (e.g. Slack) + +``` +bundle exec rake services:doc +``` diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md index b03cca4029..addd3b6b6e 100644 --- a/doc/gitlab-basics/command-line-commands.md +++ b/doc/gitlab-basics/command-line-commands.md @@ -72,3 +72,8 @@ You will be asked for an administrator’s password. ``` sudo ``` + +### Tell where you are +``` +pwd +``` diff --git a/doc/install/installation.md b/doc/install/installation.md index 73e36fa7e5..52031e9b9a 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -125,13 +125,25 @@ Install the Bundler Gem: sudo gem install bundler --no-ri --no-rdoc -## 3. System Users +## 3. Go + +Since GitLab 8.0, Git HTTP requests are handled by gitlab-git-http-server. +This is a small daemon written in Go. +To install gitlab-git-http-server we need a Go compiler. + + curl -O --progress https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz + echo '5817fa4b2252afdb02e11e8b9dc1d9173ef3bd5a go1.5.linux-amd64.tar.gz' | shasum -c - && \ + sudo tar -C /usr/local -xzf go1.5.linux-amd64.tar.gz + sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ + rm go1.5.linux-amd64.tar.gz + +## 4. System Users Create a `git` user for GitLab: sudo adduser --disabled-login --gecos 'GitLab' git -## 4. Database +## 5. Database We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md). *Note*: because we need to make use of extensions you need at least pgsql 9.1. @@ -157,7 +169,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Quit the database session gitlabhq_production> \q -## 5. Redis +## 6. Redis sudo apt-get install redis-server @@ -187,7 +199,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Add git to the redis group sudo usermod -aG redis git -## 6. GitLab +## 7. GitLab # We'll install GitLab into home directory of the user "git" cd /home/git @@ -297,6 +309,13 @@ GitLab Shell is an SSH access and repository management software developed speci **Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up gitlab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)". +### Install gitlab-git-http-server + + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git + cd gitlab-git-http-server + sudo -u git -H make + ### Initialize Database and Activate Advanced Features sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production @@ -345,7 +364,7 @@ Check if GitLab and its environment are configured correctly: # or sudo /etc/init.d/gitlab restart -## 7. Nginx +## 8. Nginx **Note:** Nginx is the officially supported web server for GitLab. If you cannot or do not want to use Nginx as your web server, have a look at the [GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/). diff --git a/doc/integration/crowd.md b/doc/integration/crowd.md new file mode 100644 index 0000000000..2ecc8795ac --- /dev/null +++ b/doc/integration/crowd.md @@ -0,0 +1,58 @@ +# Crowd OmniAuth Provider + +To enable the Crowd OmniAuth provider you must register your application with Crowd. To configure Crowd integration you need an application name and password. + +1. On your GitLab server, open the configuration file. + + For omnibus package: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + For instalations from source: + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. + +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "crowd", + "args" => { + "crowd_server_url" => "CROWD", + "application_name" => "YOUR_APP_NAME", + "application_password" => "YOUR_APP_PASSWORD" + } + } + ] + ``` + + For installations from source: + + ``` + - { name: 'crowd', + args: { + crowd_server_url: 'CROWD SERVER URL', + application_name: 'YOUR_APP_NAME', + application_password: 'YOUR_APP_PASSWORD' } } + ``` + +1. Change 'YOUR_APP_NAME' to the application name from Crowd applications page. + +1. Change 'YOUR_APP_PASSWORD' to the application password you've set. + +1. Save the configuration file. + +1. Restart GitLab for the changes to take effect. + +On the sign in page there should now be a Crowd tab in the sign in form. \ No newline at end of file diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index 2010cb9b8a..c5cecbc2f2 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -76,6 +76,7 @@ Now we can choose one or more of the Supported Providers below to continue confi - [Shibboleth](shibboleth.md) - [Twitter](twitter.md) - [SAML](saml.md) +- [Crowd](crowd.md) ## Enable OmniAuth for an Existing User diff --git a/doc/reply_by_email/README.md b/doc/reply_by_email/README.md index f1dcef5de2..1f704e65bc 100644 --- a/doc/reply_by_email/README.md +++ b/doc/reply_by_email/README.md @@ -65,6 +65,8 @@ In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. :name: "inbox" # Always "sidekiq". :delivery_method: sidekiq + # Always true. + :delete_after_delivery: true :delivery_options: # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. :redis_url: redis://localhost:6379 @@ -163,6 +165,8 @@ sudo gitlab-ctl restart mailroom :name: "inbox" # Always "sidekiq". :delivery_method: sidekiq + # Always true. + :delete_after_delivery: true :delivery_options: # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml. :redis_url: redis://localhost:6379 diff --git a/doc/update/7.14-to-8.0.md b/doc/update/7.14-to-8.0.md new file mode 100644 index 0000000000..29a38d07b3 --- /dev/null +++ b/doc/update/7.14-to-8.0.md @@ -0,0 +1,159 @@ +# From 7.14 to 8.0 + +### 0. Double-check your Git version + +**This notice applies only to /usr/local/bin/git** + +If you compiled Git from source on your GitLab server then please double-check +that you are using a version that protects against CVE-2014-9390. For six +months after this vulnerability became known the GitLab installation guide +still contained instructions that would install the outdated, 'vulnerable' Git +version 2.1.2. + +Run the following command to get your current Git version. + +``` +/usr/local/bin/git --version +``` + +If you see 'No such file or directory' then you did not install Git according +to the outdated instructions from the GitLab installation guide and you can go +to the next step 'Stop server' below. + +If you see a version string then it should be v1.8.5.6, v1.9.5, v2.0.5, v2.1.4, +v2.2.1 or newer. You can use the [instructions in the GitLab source +installation +guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies) +to install a newer version of Git. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-0-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-0-stable-ee +``` + +### 4. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.6.4 +``` + +### 5. Install gitlab-git-http-server + +First we download Go 1.5 and install it into /usr/local/go. + + curl -O --progress https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz + echo '5817fa4b2252afdb02e11e8b9dc1d9173ef3bd5a go1.5.linux-amd64.tar.gz' | shasum -c - && \ + sudo tar -C /usr/local -xzf go1.5.linux-amd64.tar.gz + sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ + rm go1.5.linux-amd64.tar.gz + +Now we download gitlab-git-http-server and install it in /home/git/gitlab-git-http-server. + + cd /home/git + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git + cd gitlab-git-http-server + sudo -u git -H make + +If you put your Git repositories in a directory different from /home/git/repositories, you need to tell gitlab-git-http-server about it via /etc/gitlab/default. +See lib/support/init.d/gitlab.default.example for the options. + +### 6. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 7. Update config files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`. + +``` +git diff origin/7-14-stable:config/gitlab.yml.example origin/8-0-stable:config/gitlab.yml.example +`````` + +#### New NGINX configuration + +Because of the new gitlab-git-http-server you need to update your NGINX configuration. +If you skip this step 'git clone' and 'git push' over HTTP(S) will stop working. + +``` +# Remove '-ssl' twice in the diff command below if you use HTTP instead of HTTPS +git diff origin/7-14-stable:lib/support/nginx/gitlab-ssl origin/8-0-stable:lib/support/nginx/gitlab-ssl +``` + +### 8. Start application + + sudo service gitlab start + sudo service nginx restart + +### 9. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (7.14) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.13 to 7.14](7.13-to-7.14.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` +If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above. diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index 73717ffc7d..09400d9b16 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -279,6 +279,7 @@ X-Gitlab-Event: Note Hook "name": "Gitlab Test", "ssh_url": "git@example.com:gitlab-org/gitlab-test.git", "http_url": "http://example.com/gitlab-org/gitlab-test.git", + "web_url": "http://example.com/gitlab-org/gitlab-test", "namespace": "Gitlab Org", "visibility_level": 10 }, @@ -286,6 +287,7 @@ X-Gitlab-Event: Note Hook "name": "Gitlab Test", "ssh_url": "git@example.com:gitlab-org/gitlab-test.git", "http_url": "http://example.com/gitlab-org/gitlab-test.git", + "web_url": "http://example.com/gitlab-org/gitlab-test", "namespace": "Gitlab Org", "visibility_level": 10 }, @@ -462,6 +464,7 @@ X-Gitlab-Event: Merge Request Hook "name": "awesome_project", "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", "http_url": "http://example.com/awesome_space/awesome_project.git", + "web_url": "http://example.com/awesome_space/awesome_project", "visibility_level": 20, "namespace": "awesome_space" }, @@ -469,6 +472,7 @@ X-Gitlab-Event: Merge Request Hook "name": "awesome_project", "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", "http_url": "http://example.com/awesome_space/awesome_project.git", + "web_url": "http://example.com/awesome_space/awesome_project", "visibility_level": 20, "namespace": "awesome_space" }, diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md index cd98d1b985..5cde90993d 100644 --- a/doc/workflow/importing/README.md +++ b/doc/workflow/importing/README.md @@ -8,5 +8,5 @@ ### Note * If you'd like to migrate from a self-hosted GitLab instance to GitLab.com, you can copy your repos by changing the remote and pushing to the new server; but issues and merge requests can't be imported. -* Repositories are imported to GitLab via HTTP. -If the repository is too large, it can timeout. We have a soft limit of 10GB. +* You can import any Git repository via HTTP from the New Project page. +If the repository is too large, it can timeout. diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index 3efa92cb86..2d77c6d117 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -17,4 +17,4 @@ If you want to import from a GitHub Enterprise instance, you need to use GitLab * To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data. ### Note -When you import your projects from GitHub, it is not possible to keep your labels and milestones and issue numbers won't match. We are working on improving this in the near future. +When you import your projects from GitHub, it is not possible to keep your labels and milestones. We are working on improving this in the near future. diff --git a/features/admin/hooks.feature b/features/admin/hooks.feature new file mode 100644 index 0000000000..5ca332d9f1 --- /dev/null +++ b/features/admin/hooks.feature @@ -0,0 +1,9 @@ +@admin +Feature: Admin Hooks + Background: + Given I sign in as an admin + + Scenario: On Admin Hooks + Given I visit admin hooks page + Then I submit the form with enabled SSL verification + And I see new hook with enabled SSL verification \ No newline at end of file diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index 1959d32708..392d4235ef 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -10,6 +10,10 @@ Feature: Dashboard Scenario: I should see projects list Then I should see "New Project" link Then I should see "Shop" project link + + @javascript + Scenario: I should see activity list + And I visit dashboard activity page Then I should see project "Shop" activity feed Scenario: I should see groups list @@ -26,12 +30,12 @@ Feature: Dashboard @javascript Scenario: I should see User joined Project event Given user with name "John Doe" joined project "Shop" - When I visit dashboard page + When I visit dashboard activity page Then I should see "John Doe joined project Shop" event @javascript Scenario: I should see User left Project event Given user with name "John Doe" joined project "Shop" And user with name "John Doe" left project "Shop" - When I visit dashboard page + When I visit dashboard activity page Then I should see "John Doe left project Shop" event diff --git a/features/dashboard/event_filters.feature b/features/dashboard/event_filters.feature index ec5680caba..96399ea21a 100644 --- a/features/dashboard/event_filters.feature +++ b/features/dashboard/event_filters.feature @@ -6,7 +6,7 @@ Feature: Event Filters And this project has push event And this project has new member event And this project has merge request event - And I visit dashboard page + And I visit dashboard activity page @javascript Scenario: I should see all events @@ -16,7 +16,7 @@ Feature: Event Filters @javascript Scenario: I should see only pushed events - When I click "push" event filter + When I click "push" event filter Then I should see push event And I should not see new member event And I should not see merge request event @@ -38,11 +38,11 @@ Feature: Event Filters @javascript Scenario: I should see only selected events while page reloaded When I click "push" event filter - And I visit dashboard page + And I visit dashboard activity page Then I should see push event And I should not see new member event When I click "team" event filter - And I visit dashboard page + And I visit dashboard activity page Then I should see push event And I should see new member event And I should not see merge request event diff --git a/features/login_form.feature b/features/login_form.feature new file mode 100644 index 0000000000..b4d9575448 --- /dev/null +++ b/features/login_form.feature @@ -0,0 +1,5 @@ +Feature: Login form + Scenario: I see crowd form + Given Crowd integration enabled + When I visit sign in page + Then I should see Crowd login form \ No newline at end of file diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index c4b206edc9..3ebc8a39aa 100644 --- a/features/project/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -41,6 +41,7 @@ Feature: Project Commits Scenario: I browse big commit Given I visit big commit page Then I see big commit warning + And I see "Reload with full diff" link Scenario: I browse a commit with an image Given I visit a commit with an image that changed diff --git a/features/project/hooks.feature b/features/project/hooks.feature index 1a60846a23..627738004c 100644 --- a/features/project/hooks.feature +++ b/features/project/hooks.feature @@ -13,6 +13,11 @@ Feature: Project Hooks When I submit new hook Then I should see newly created hook + Scenario: I add new hook with SSL verification enabled + Given I visit project hooks page + When I submit new hook with SSL verification enabled + Then I should see newly created hook with SSL verification enabled + Scenario: I test hook Given project has hook And I visit project hooks page diff --git a/features/project/wiki.feature b/features/project/wiki.feature index 2ebfa3c166..af970ecf2d 100644 --- a/features/project/wiki.feature +++ b/features/project/wiki.feature @@ -91,3 +91,15 @@ Feature: Project Wiki And I view the page history of a Wiki page that has a path Then I should see a non-escaped path And I should see the page history + + @javascript + Scenario: View an old page version of a Wiki page + Given I create a New page with paths + And I click on the "Pages" button + And I edit the Wiki page with a path + Then I should see a non-escaped path + And I should see the Editing page + And I change the content + Then I click on Page History + And I should see the page history + And I should see a link with a version ID diff --git a/features/steps/admin/hooks.rb b/features/steps/admin/hooks.rb new file mode 100644 index 0000000000..541e25fcb7 --- /dev/null +++ b/features/steps/admin/hooks.rb @@ -0,0 +1,15 @@ +class Spinach::Features::AdminHooks < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedAdmin + + step "I submit the form with enabled SSL verification" do + fill_in 'hook_url', with: 'http://google.com' + check "Enable SSL verification" + click_on "Add System Hook" + end + + step "I see new hook with enabled SSL verification" do + expect(page).to have_content "SSL Verification: enabled" + end +end diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb index 59c73fe63f..c33813e550 100644 --- a/features/steps/dashboard/starred_projects.rb +++ b/features/steps/dashboard/starred_projects.rb @@ -8,7 +8,7 @@ class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps end step 'I should not see project "Shop"' do - page.within 'aside' do + page.within '.projects-list' do expect(page).not_to have_content('Shop') end end diff --git a/features/steps/login_form.rb b/features/steps/login_form.rb new file mode 100644 index 0000000000..b9ff6ae67f --- /dev/null +++ b/features/steps/login_form.rb @@ -0,0 +1,25 @@ +class Spinach::Features::LoginForm < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedSnippet + include SharedUser + include SharedSearch + + step 'Crowd integration enabled' do + @providers_orig = Gitlab::OAuth::Provider.providers + @omniauth_conf_orig = Gitlab.config.omniauth.enabled + expect(Gitlab::OAuth::Provider).to receive(:providers).and_return([:crowd]) + allow_any_instance_of(ApplicationHelper).to receive(:user_omniauth_authorize_path).and_return(root_path) + expect(Gitlab.config.omniauth).to receive(:enabled).and_return(true) + end + + step 'I should see Crowd login form' do + expect(page).to have_selector '#tab-crowd form' + Gitlab::OAuth::Provider.stub(:providers).and_return(@providers_orig) + Gitlab.config.omniauth.stub(:enabled).and_return(@omniauth_conf_orig) + end + + step 'I visit sign in page' do + visit new_user_session_path + end +end diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index e6330ec457..23e67371f9 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -52,7 +52,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I see compared refs' do - expect(page).to have_content "Compare View" expect(page).to have_content "Commits (1)" expect(page).to have_content "Showing 2 changed files" end @@ -79,6 +78,12 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(page).to have_content "Too many changes" end + step 'I see "Reload with full diff" link' do + link = find_link('Reload with full diff') + expect(link[:href]).to end_with('?force_show_diff=true') + expect(link[:href]).not_to include('.html') + end + step 'I visit a commit with an image that changed' do visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id) end @@ -92,4 +97,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps step 'I see inline diff button' do expect(page).to have_content "Inline" end + + step 'I click side-by-side diff button' do + find('#parallel-diff-btn').click + end end diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb index 04e3bf78ed..df4a23a371 100644 --- a/features/steps/project/hooks.rb +++ b/features/steps/project/hooks.rb @@ -28,11 +28,24 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1) end + step 'I submit new hook with SSL verification enabled' do + @url = FFaker::Internet.uri("http") + fill_in "hook_url", with: @url + check "hook_enable_ssl_verification" + expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1) + end + step 'I should see newly created hook' do expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project) expect(page).to have_content(@url) end + step 'I should see newly created hook with SSL verification enabled' do + expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project) + expect(page).to have_content(@url) + expect(page).to have_content("SSL Verification: enabled") + end + step 'I click test hook button' do stub_request(:post, @hook.url).to_return(status: 200) click_link 'Test Hook' diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 778dce0635..c92998631f 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -128,7 +128,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I should see the proper Inline and Side-by-side links' do - expect(page).to have_css('#commit-diff-viewtype', count: 2) + expect(page).to have_css('#parallel-diff-btn', count: 1) + expect(page).to have_css('#inline-diff-btn', count: 1) end step 'I switch to the merge request\'s comments tab' do @@ -225,13 +226,13 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I click link "Hide inline discussion" of the second file' do page.within '.files [id^=diff]:nth-child(2)' do - find('.js-toggle-diff-comments').click + find('.js-toggle-diff-comments').trigger('click') end end step 'I click link "Show inline discussion" of the second file' do page.within '.files [id^=diff]:nth-child(2)' do - find('.js-toggle-diff-comments').click + find('.js-toggle-diff-comments').trigger('click') end end diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index eebfaee1ed..02207dbffa 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -3,7 +3,6 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps include SharedProject include SharedNote include SharedPaths - include WikiHelper step 'I click on the Cancel button' do page.within(:css, ".form-actions") do @@ -165,6 +164,10 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps click_on 'Page History' end + step 'I click on Page History' do + click_on 'Page History' + end + step 'I should see the page history' do expect(page).to have_content('History for') end @@ -174,6 +177,10 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps click_button "Search" end + step 'I should see a link with a version ID' do + find('a[href*="?version_id"]') + end + def wiki @project_wiki = ProjectWiki.new(project, current_user) end diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb index 92e099315d..eb2ccd9d01 100644 --- a/features/steps/shared/active_tab.rb +++ b/features/steps/shared/active_tab.rb @@ -6,7 +6,7 @@ module SharedActiveTab end def ensure_active_sub_tab(content) - expect(find('div.content ul.nav-tabs li.active')).to have_content(content) + expect(find('div.content ul.center-top-menu li.active')).to have_content(content) end def ensure_active_sub_nav(content) @@ -18,7 +18,7 @@ module SharedActiveTab end step 'no other sub tabs should be active' do - expect(page).to have_selector('div.content ul.nav-tabs li.active', count: 1) + expect(page).to have_selector('div.content ul.center-top-menu li.active', count: 1) end step 'no other sub navs should be active' do diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 27a95aeb19..72621911a3 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -198,7 +198,7 @@ module SharedDiffNote end step 'I click side-by-side diff button' do - click_link "Side-by-side" + find('#parallel-diff-btn').trigger('click') end step 'I see side-by-side diff button' do diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index ca8fbb4910..b4deccb652 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -71,6 +71,10 @@ module SharedPaths visit dashboard_path end + step 'I visit dashboard activity page' do + visit activity_dashboard_path + end + step 'I visit dashboard projects page' do visit projects_dashboard_path end diff --git a/features/user.feature b/features/user.feature index 69618e929c..35eae842e7 100644 --- a/features/user.feature +++ b/features/user.feature @@ -14,11 +14,6 @@ Feature: User And I should not see project "Internal" And I should see project "Community" - Scenario: I visit user "John Doe" page while not signed in when he is not authorized to a public project - Given "John Doe" owns internal project "Internal" - When I visit user "John Doe" page - Then I should be redirected to sign in page - # Signed in as someone else Scenario: I visit user "John Doe" page while signed in as someone else when he owns a public project diff --git a/lib/api/api.rb b/lib/api/api.rb index eebd44ea5b..c09488d354 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -50,5 +50,6 @@ module API mount Branches mount Labels mount Settings + mount Keys end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 1f9dd6bc15..8dddcd7ccc 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -199,6 +199,10 @@ module API expose :id, :title, :key, :created_at end + class SSHKeyWithUser < SSHKey + expose :user, using: Entities::UserFull + end + class Note < Grape::Entity expose :id expose :note, as: :body diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 1ebf9a1f02..76c9cc2e3a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -55,6 +55,32 @@ module API end end + def project_service + @project_service ||= begin + underscored_service = params[:service_slug].underscore + + if Service.available_services_names.include?(underscored_service) + user_project.build_missing_services + + service_method = "#{underscored_service}_service" + + send_service(service_method) + end + end + + @project_service || not_found!("Service") + end + + def send_service(service_method) + user_project.send(service_method) + end + + def service_attributes + @service_attributes ||= project_service.fields.inject([]) do |arr, hash| + arr << hash[:name].to_sym + end + end + def find_group(id) begin group = Group.find(id) diff --git a/lib/api/keys.rb b/lib/api/keys.rb new file mode 100644 index 0000000000..2b723b7950 --- /dev/null +++ b/lib/api/keys.rb @@ -0,0 +1,20 @@ +module API + # Keys API + class Keys < Grape::API + before { authenticate! } + + resource :keys do + # Get single ssh key by id. Only available to admin users. + # + # Example Request: + # GET /keys/:id + get ":id" do + authenticated_as_admin! + + key = Key.find(params[:id]) + + present key, with: Entities::SSHKeyWithUser + end + end + end +end diff --git a/lib/api/services.rb b/lib/api/services.rb index 3ad59cf3ad..73645cedea 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -4,73 +4,49 @@ module API before { authenticate! } before { authorize_admin_project } + resource :projects do - # Set GitLab CI service for project - # - # Parameters: - # token (required) - CI project token - # project_url (required) - CI project url + # Set service for project # # Example Request: + # # PUT /projects/:id/services/gitlab-ci - put ":id/services/gitlab-ci" do - required_attributes! [:token, :project_url] - attrs = attributes_for_keys [:token, :project_url] - user_project.build_missing_services + # + put ':id/services/:service_slug' do + if project_service + validators = project_service.class.validators.select do |s| + s.class == ActiveRecord::Validations::PresenceValidator && + s.attributes != [:project_id] + end - if user_project.gitlab_ci_service.update_attributes(attrs.merge(active: true)) - true - else - not_found! + required_attributes! validators.map(&:attributes).flatten.uniq + attrs = attributes_for_keys service_attributes + + if project_service.update_attributes(attrs.merge(active: true)) + true + else + not_found! + end end end - # Delete GitLab CI service settings + # Delete service for project # # Example Request: - # DELETE /projects/:id/services/gitlab-ci - delete ":id/services/gitlab-ci" do - if user_project.gitlab_ci_service - user_project.gitlab_ci_service.update_attributes( - active: false, - token: nil, - project_url: nil - ) - end - end - - # Set Hipchat service for project # - # Parameters: - # token (required) - Hipchat token - # room (required) - Hipchat room name + # DELETE /project/:id/services/gitlab-ci # - # Example Request: - # PUT /projects/:id/services/hipchat - put ':id/services/hipchat' do - required_attributes! [:token, :room] - attrs = attributes_for_keys [:token, :room] - user_project.build_missing_services - - if user_project.hipchat_service.update_attributes( - attrs.merge(active: true)) - true - else - not_found! - end - end - - # Delete Hipchat service settings - # - # Example Request: - # DELETE /projects/:id/services/hipchat - delete ':id/services/hipchat' do - if user_project.hipchat_service - user_project.hipchat_service.update_attributes( - active: false, - token: nil, - room: nil - ) + delete ':id/services/:service_slug' do + if project_service + attrs = service_attributes.inject({}) do |hash, key| + hash.merge!(key => nil) + end + + if project_service.update_attributes(attrs.merge(active: false)) + true + else + not_found! + end end end end diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 12292f614e..0353b3b7ed 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -1,6 +1,14 @@ require_relative 'shell_env' module Grack + class AuthSpawner + def self.call(env) + # Avoid issues with instance variables in Grack::Auth persisting across + # requests by creating a new instance for each request. + Auth.new({}).call(env) + end + end + class Auth < Rack::Auth::Basic attr_accessor :user, :project, :env @@ -10,7 +18,7 @@ module Grack @request = Rack::Request.new(env) @auth = Request.new(env) - @gitlab_ci = false + @ci = false # Need this patch due to the rails mount # Need this if under RELATIVE_URL_ROOT @@ -26,13 +34,9 @@ module Grack auth! if project && authorized_request? - if ENV['GITLAB_GRACK_AUTH_ONLY'] == '1' - # Tell gitlab-git-http-server the request is OK, and what the GL_ID is - render_grack_auth_ok - else - @app.call(env) - end - elsif @user.nil? && !@gitlab_ci + # Tell gitlab-git-http-server the request is OK, and what the GL_ID is + render_grack_auth_ok + elsif @user.nil? && !@ci unauthorized else render_not_found @@ -51,8 +55,8 @@ module Grack # Allow authentication for GitLab CI service # if valid token passed - if gitlab_ci_request?(login, password) - @gitlab_ci = true + if ci_request?(login, password) + @ci = true return end @@ -64,12 +68,17 @@ module Grack end end - def gitlab_ci_request?(login, password) - if login == "gitlab-ci-token" && project && project.gitlab_ci? - token = project.gitlab_ci_service.token + def ci_request?(login, password) + matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) - if token.present? && token == password && git_cmd == 'git-upload-pack' - return true + if project && matched_login.present? && git_cmd == 'git-upload-pack' + underscored_service = matched_login['s'].underscore + + if Service.available_services_names.include?(underscored_service) + service_method = "#{underscored_service}_service" + service = project.send(service_method) + + return service && service.activated? && service.valid_token?(password) end end @@ -128,11 +137,13 @@ module Grack end def authorized_request? - return true if @gitlab_ci + return true if @ci case git_cmd when *Gitlab::GitAccess::DOWNLOAD_COMMANDS - if user + if !Gitlab.config.gitlab_shell.upload_pack + false + elsif user Gitlab::GitAccess.new(user, project).download_access_check.allowed? elsif project.public? # Allow clone/fetch for public projects @@ -141,7 +152,9 @@ module Grack false end when *Gitlab::GitAccess::PUSH_COMMANDS - if user + if !Gitlab.config.gitlab_shell.receive_pack + false + elsif user # Skip user authorization on upload request. # It will be done by the pre-receive hook in the repository. true diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb index aec44b8c87..d88a6eaac6 100644 --- a/lib/gitlab/bitbucket_import/client.rb +++ b/lib/gitlab/bitbucket_import/client.rb @@ -52,11 +52,26 @@ module Gitlab end def issues(project_identifier) - JSON.parse(get("/api/1.0/repositories/#{project_identifier}/issues").body) + all_issues = [] + offset = 0 + per_page = 50 # Maximum number allowed by Bitbucket + index = 0 + + begin + issues = JSON.parse(get(issue_api_endpoint(project_identifier, per_page, offset)).body) + # Find out how many total issues are present + total = issues["count"] if index == 0 + all_issues.concat(issues["issues"]) + offset += issues["issues"].count + index += 1 + end while all_issues.count < total + + all_issues end def issue_comments(project_identifier, issue_id) - JSON.parse(get("/api/1.0/repositories/#{project_identifier}/issues/#{issue_id}/comments").body) + comments = JSON.parse(get("/api/1.0/repositories/#{project_identifier}/issues/#{issue_id}/comments").body) + comments.sort_by { |comment| comment["utc_created_on"] } end def project(project_identifier) @@ -100,6 +115,10 @@ module Gitlab response end + def issue_api_endpoint(project_identifier, per_page, offset) + "/api/1.0/repositories/#{project_identifier}/issues?sort=utc_created_on&limit=#{per_page}&start=#{offset}" + end + def config Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"} end diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index d8a7d29f1b..2355b3c6dd 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -20,8 +20,18 @@ module Gitlab #Issues && Comments issues = client.issues(project_identifier) - issues["issues"].each do |issue| - body = @formatter.author_line(issue["reported_by"]["username"], issue["content"]) + issues.each do |issue| + body = '' + reporter = nil + author = 'Anonymous' + + if issue["reported_by"] && issue["reported_by"]["username"] + reporter = issue["reported_by"]["username"] + author = reporter + end + + body = @formatter.author_line(author) + body += issue["content"] comments = client.issue_comments(project_identifier, issue["local_id"]) @@ -30,14 +40,20 @@ module Gitlab end comments.each do |comment| - body += @formatter.comment(comment["author_info"]["username"], comment["utc_created_on"], comment["content"]) + author = 'Anonymous' + + if comment["author_info"] && comment["author_info"]["username"] + author = comment["author_info"]["username"] + end + + body += @formatter.comment(author, comment["utc_created_on"], comment["content"]) end project.issues.create!( description: body, title: issue["title"], state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened', - author_id: gl_user_id(project, issue["reported_by"]["username"]) + author_id: gl_user_id(project, reporter) ) end @@ -47,9 +63,13 @@ module Gitlab private def gl_user_id(project, bitbucket_id) - user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s) - (user && user.id) || project.creator_id - end + if bitbucket_id + user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s) + (user && user.id) || project.creator_id + else + project.creator_id + end + end end end end diff --git a/lib/gitlab/color_schemes.rb b/lib/gitlab/color_schemes.rb new file mode 100644 index 0000000000..9c4664df90 --- /dev/null +++ b/lib/gitlab/color_schemes.rb @@ -0,0 +1,67 @@ +module Gitlab + # Module containing GitLab's syntax color scheme definitions and helper + # methods for accessing them. + module ColorSchemes + # Struct class representing a single Scheme + Scheme = Struct.new(:id, :name, :css_class) + + SCHEMES = [ + Scheme.new(1, 'White', 'white'), + Scheme.new(2, 'Dark', 'dark'), + Scheme.new(3, 'Solarized Light', 'solarized-light'), + Scheme.new(4, 'Solarized Dark', 'solarized-dark'), + Scheme.new(5, 'Monokai', 'monokai') + ].freeze + + # Convenience method to get a space-separated String of all the color scheme + # classes that might be applied to a code block. + # + # Returns a String + def self.body_classes + SCHEMES.collect(&:css_class).uniq.join(' ') + end + + # Get a Scheme by its ID + # + # If the ID is invalid, returns the default Scheme. + # + # id - Integer ID + # + # Returns a Scheme + def self.by_id(id) + SCHEMES.detect { |s| s.id == id } || default + end + + # Returns the number of defined Schemes + def self.count + SCHEMES.size + end + + # Get the default Scheme + # + # Returns a Scheme + def self.default + by_id(1) + end + + # Iterate through each Scheme + # + # Yields the Scheme object + def self.each(&block) + SCHEMES.each(&block) + end + + # Get the Scheme for the specified user, or the default + # + # user - User record + # + # Returns a Scheme + def self.for_user(user) + if user + by_id(user.color_scheme_id) + else + default + end + end + end +end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 1a2a50a14d..0ea1b6a2f6 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -4,7 +4,7 @@ module Gitlab key = :current_application_settings RequestStore.store[key] ||= begin - if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('application_settings') + if connect_to_db? ApplicationSetting.current || ApplicationSetting.create_from_defaults else fake_application_settings @@ -26,5 +26,17 @@ module Gitlab import_sources: Settings.gitlab['import_sources'] ) end + + private + + def connect_to_db? + use_db = if ENV['USE_DB'] == "false" + false + else + true + end + + use_db && ActiveRecord::Base.connection.active? && ActiveRecord::Base.connection.table_exists?('application_settings') + end end end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 8c106a6173..bd7340a80f 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -18,7 +18,8 @@ module Gitlab direction: :asc).each do |issue| if issue.pull_request.nil? - body = @formatter.author_line(issue.user.login, issue.body) + body = @formatter.author_line(issue.user.login) + body += issue.body if issue.comments > 0 body += @formatter.comments_header diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index 50594d2b24..e24b94d615 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -18,7 +18,8 @@ module Gitlab issues = client.issues(project_identifier) issues.each do |issue| - body = @formatter.author_line(issue["author"]["name"], issue["description"]) + body = @formatter.author_line(issue["author"]["name"]) + body += issue["description"] comments = client.issue_comments(project_identifier, issue["id"]) diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb index 72e041a90b..3e54456e93 100644 --- a/lib/gitlab/import_formatter.rb +++ b/lib/gitlab/import_formatter.rb @@ -8,8 +8,8 @@ module Gitlab "\n\n\n**Imported comments:**\n" end - def author_line(author, body) - "*Created by: #{author}*\n\n#{body}" + def author_line(author) + "*Created by: #{author}*\n\n" end end end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index f7f3ba9ad7..04a2223747 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -44,9 +44,14 @@ module Gitlab gl_user.skip_reconfirmation! gl_user.email = auth_hash.email - # Build new identity only if we dont have have same one - gl_user.identities.find_or_initialize_by(provider: auth_hash.provider, - extern_uid: auth_hash.uid) + # find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved. + identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider } + identity ||= gl_user.identities.build(provider: auth_hash.provider) + + # For a new user set extern_uid to the LDAP DN + # For an existing user with matching email but changed DN, update the DN. + # For an existing user with no change in DN, this line changes nothing. + identity.extern_uid = auth_hash.uid gl_user end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 9f6e19a09f..097caf67a6 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -5,6 +5,32 @@ module Gitlab # # See the files in `lib/gitlab/markdown/` for specific processing information. module Markdown + # Convert a Markdown String into an HTML-safe String of HTML + # + # markdown - Markdown String + # context - Hash of context options passed to our HTML Pipeline + # + # Returns an HTML-safe String + def self.render(markdown, context = {}) + html = renderer.render(markdown) + html = gfm(html, context) + + html.html_safe + end + + # Convert a Markdown String into HTML without going through the HTML + # Pipeline. + # + # Note that because the pipeline is skipped, SanitizationFilter is as well. + # Do not output the result of this method to the user. + # + # markdown - Markdown String + # + # Returns a String + def self.render_without_gfm(markdown) + renderer.render(markdown) + end + # Provide autoload paths for filters to prevent a circular dependency error autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter' autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter' @@ -18,6 +44,7 @@ module Gitlab autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter' autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter' autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter' + autoload :SyntaxHighlightFilter, 'gitlab/markdown/syntax_highlight_filter' autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter' autoload :TaskListFilter, 'gitlab/markdown/task_list_filter' autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' @@ -28,8 +55,7 @@ module Gitlab # options - A Hash of options used to customize output (default: {}): # :xhtml - output XHTML instead of HTML # :reference_only_path - Use relative path for reference links - # html_options - extra options for the reference links as given to link_to - def gfm(text, options = {}, html_options = {}) + def self.gfm(text, options = {}) return text if text.nil? # Duplicate the string so we don't alter the original, then call to_str @@ -40,8 +66,8 @@ module Gitlab options.reverse_merge!( xhtml: false, reference_only_path: true, - project: @project, - current_user: current_user + project: options[:project], + current_user: options[:current_user] ) @pipeline ||= HTML::Pipeline.new(filters) @@ -61,12 +87,11 @@ module Gitlab current_user: options[:current_user], only_path: options[:reference_only_path], project: options[:project], - reference_class: html_options[:class], # RelativeLinkFilter - ref: @ref, - requested_path: @path, - project_wiki: @project_wiki + ref: options[:ref], + requested_path: options[:path], + project_wiki: options[:project_wiki] } result = @pipeline.call(text, context) @@ -83,14 +108,36 @@ module Gitlab private + def self.renderer + @markdown ||= begin + renderer = Redcarpet::Render::HTML.new + Redcarpet::Markdown.new(renderer, redcarpet_options) + end + end + + def self.redcarpet_options + # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use + @redcarpet_options ||= { + fenced_code_blocks: true, + footnotes: true, + lax_spacing: true, + no_intra_emphasis: true, + space_after_headers: true, + strikethrough: true, + superscript: true, + tables: true + }.freeze + end + # Filters used in our pipeline # # SanitizationFilter should come first so that all generated reference HTML # goes through untouched. # # See https://github.com/jch/html-pipeline#filters for more filters. - def filters + def self.filters [ + Gitlab::Markdown::SyntaxHighlightFilter, Gitlab::Markdown::SanitizationFilter, Gitlab::Markdown::RelativeLinkFilter, diff --git a/lib/gitlab/markdown/autolink_filter.rb b/lib/gitlab/markdown/autolink_filter.rb index 541f1d88ff..c37c3bc55b 100644 --- a/lib/gitlab/markdown/autolink_filter.rb +++ b/lib/gitlab/markdown/autolink_filter.rb @@ -1,3 +1,4 @@ +require 'gitlab/markdown' require 'html/pipeline/filter' require 'uri' diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb index a9f1ee9c16..8613150894 100644 --- a/lib/gitlab/markdown/commit_range_reference_filter.rb +++ b/lib/gitlab/markdown/commit_range_reference_filter.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab module Markdown # HTML filter that replaces commit range references with links. diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb index eacdf8a6d3..5696b4fa58 100644 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ b/lib/gitlab/markdown/commit_reference_filter.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab module Markdown # HTML filter that replaces commit references with links. diff --git a/lib/gitlab/markdown/cross_project_reference.rb b/lib/gitlab/markdown/cross_project_reference.rb index 66c256c510..855748fdcc 100644 --- a/lib/gitlab/markdown/cross_project_reference.rb +++ b/lib/gitlab/markdown/cross_project_reference.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab module Markdown # Common methods for ReferenceFilters that support an optional cross-project diff --git a/lib/gitlab/markdown/emoji_filter.rb b/lib/gitlab/markdown/emoji_filter.rb index 6794ab9c89..da10e4d376 100644 --- a/lib/gitlab/markdown/emoji_filter.rb +++ b/lib/gitlab/markdown/emoji_filter.rb @@ -1,6 +1,7 @@ +require 'action_controller' +require 'gitlab/markdown' require 'gitlab_emoji' require 'html/pipeline/filter' -require 'action_controller' module Gitlab module Markdown diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb index afd28dd8cf..f7c43e1ca8 100644 --- a/lib/gitlab/markdown/external_issue_reference_filter.rb +++ b/lib/gitlab/markdown/external_issue_reference_filter.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab module Markdown # HTML filter that replaces external issue tracker references with links. diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/external_link_filter.rb index c539e0fb82..29e51b6ade 100644 --- a/lib/gitlab/markdown/external_link_filter.rb +++ b/lib/gitlab/markdown/external_link_filter.rb @@ -1,3 +1,4 @@ +require 'gitlab/markdown' require 'html/pipeline/filter' module Gitlab diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb index ab6f6bc1cf..01320f8079 100644 --- a/lib/gitlab/markdown/issue_reference_filter.rb +++ b/lib/gitlab/markdown/issue_reference_filter.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab module Markdown # HTML filter that replaces issue references with links. References to diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index 2186f36f85..3d7445a27f 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab module Markdown # HTML filter that replaces label references with links. diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index 884f60f9d5..48248f5219 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab module Markdown # HTML filter that replaces merge request references with links. References diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index 47ee1d99da..9b293c957d 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/string/output_safety' +require 'gitlab/markdown' require 'html/pipeline/filter' module Gitlab @@ -9,7 +10,6 @@ module Gitlab # # Context options: # :project (required) - Current project, ignored if reference is cross-project. - # :reference_class - Custom CSS class added to reference links. # :only_path - Generate path-only links. # # Results: @@ -70,7 +70,7 @@ module Gitlab end def reference_class(type) - "gfm gfm-#{type} #{context[:reference_class]}".strip + "gfm gfm-#{type}" end # Iterate through the document's text nodes, yielding the current node's diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb index 30f50b8299..8c5cf51bfe 100644 --- a/lib/gitlab/markdown/relative_link_filter.rb +++ b/lib/gitlab/markdown/relative_link_filter.rb @@ -1,3 +1,4 @@ +require 'gitlab/markdown' require 'html/pipeline/filter' require 'uri' diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb index 74b3a8d274..68ed57f625 100644 --- a/lib/gitlab/markdown/sanitization_filter.rb +++ b/lib/gitlab/markdown/sanitization_filter.rb @@ -1,3 +1,4 @@ +require 'gitlab/markdown' require 'html/pipeline/filter' require 'html/pipeline/sanitization_filter' diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb index 92979a356d..9e1aab936c 100644 --- a/lib/gitlab/markdown/snippet_reference_filter.rb +++ b/lib/gitlab/markdown/snippet_reference_filter.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab module Markdown # HTML filter that replaces snippet references with links. References to diff --git a/lib/gitlab/markdown/syntax_highlight_filter.rb b/lib/gitlab/markdown/syntax_highlight_filter.rb new file mode 100644 index 0000000000..86f4385753 --- /dev/null +++ b/lib/gitlab/markdown/syntax_highlight_filter.rb @@ -0,0 +1,39 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' +require 'rouge/plugins/redcarpet' + +module Gitlab + module Markdown + # HTML Filter to highlight fenced code blocks + # + class SyntaxHighlightFilter < HTML::Pipeline::Filter + include Rouge::Plugins::Redcarpet + + def call + doc.search('pre > code').each do |node| + highlight_node(node) + end + + doc + end + + def highlight_node(node) + language = node.attr('class') + code = node.text + + highlighted = block_code(code, language) + + # Replace the parent `pre` element with the entire highlighted block + node.parent.replace(highlighted) + end + + private + + # Override Rouge::Plugins::Redcarpet#rouge_formatter + def rouge_formatter(lexer) + Rouge::Formatters::HTMLGitlab.new( + cssclass: "code highlight js-syntax-highlight #{lexer.tag}") + end + end + end +end diff --git a/lib/gitlab/markdown/table_of_contents_filter.rb b/lib/gitlab/markdown/table_of_contents_filter.rb index 38887c9778..bbb3bf7fc8 100644 --- a/lib/gitlab/markdown/table_of_contents_filter.rb +++ b/lib/gitlab/markdown/table_of_contents_filter.rb @@ -1,3 +1,4 @@ +require 'gitlab/markdown' require 'html/pipeline/filter' module Gitlab diff --git a/lib/gitlab/markdown/task_list_filter.rb b/lib/gitlab/markdown/task_list_filter.rb index c6eb2e2bf6..2f133ae850 100644 --- a/lib/gitlab/markdown/task_list_filter.rb +++ b/lib/gitlab/markdown/task_list_filter.rb @@ -1,3 +1,4 @@ +require 'gitlab/markdown' require 'task_list/filter' module Gitlab diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb index a4aec7a05d..1871e52df0 100644 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ b/lib/gitlab/markdown/user_reference_filter.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab module Markdown # HTML filter that replaces user or group references with links. diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index e836b05ff2..0961bd8042 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -1,3 +1,5 @@ +require 'gitlab/markdown' + module Gitlab # Extract possible GFM references from an arbitrary String for further processing. class ReferenceExtractor @@ -10,7 +12,7 @@ module Gitlab def analyze(text) references.clear - @text = markdown.render(text.dup) + @text = Gitlab::Markdown.render_without_gfm(text) end %i(user label issue merge_request snippet commit commit_range).each do |type| @@ -21,10 +23,6 @@ module Gitlab private - def markdown - @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, GitlabMarkdownHelper::MARKDOWN_OPTIONS) - end - def references @references ||= Hash.new do |references, type| type = type.to_sym @@ -42,7 +40,7 @@ module Gitlab # Returns the results Array for the requested filter type def pipeline_result(filter_type) klass = filter_type.to_s.camelize + 'ReferenceFilter' - filter = "Gitlab::Markdown::#{klass}".constantize + filter = Gitlab::Markdown.const_get(klass) context = { project: project, diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb index 5209df9279..83f91de810 100644 --- a/lib/gitlab/themes.rb +++ b/lib/gitlab/themes.rb @@ -37,6 +37,11 @@ module Gitlab THEMES.detect { |t| t.id == id } || default end + # Returns the number of defined Themes + def self.count + THEMES.size + end + # Get the default Theme # # Returns a Theme @@ -51,6 +56,19 @@ module Gitlab THEMES.each(&block) end + # Get the Theme for the specified user, or the default + # + # user - User record + # + # Returns a Theme + def self.for_user(user) + if user + by_id(user.theme_id) + else + default + end + end + private def self.default_id diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index 11b0d44f34..779819bc2b 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -23,12 +23,12 @@ module Gitlab def build_issue_url(id) issue = Issue.find(id) - issue_url(issue, host: Gitlab.config.gitlab['url']) + issue_url(issue) end def build_merge_request_url(id) merge_request = MergeRequest.find(id) - merge_request_url(merge_request, host: Gitlab.config.gitlab['url']) + merge_request_url(merge_request) end def build_note_url(id) @@ -37,22 +37,18 @@ module Gitlab namespace_project_commit_url(namespace_id: note.project.namespace, id: note.commit_id, project_id: note.project, - host: Gitlab.config.gitlab['url'], anchor: "note_#{note.id}") elsif note.for_issue? issue = Issue.find(note.noteable_id) issue_url(issue, - host: Gitlab.config.gitlab['url'], anchor: "note_#{note.id}") elsif note.for_merge_request? merge_request = MergeRequest.find(note.noteable_id) merge_request_url(merge_request, - host: Gitlab.config.gitlab['url'], anchor: "note_#{note.id}") elsif note.for_project_snippet? snippet = Snippet.find(note.noteable_id) project_snippet_url(snippet, - host: Gitlab.config.gitlab['url'], anchor: "note_#{note.id}") end end diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb deleted file mode 100644 index f57b56cbdf..0000000000 --- a/lib/redcarpet/render/gitlab_html.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'active_support/core_ext/string/output_safety' - -class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML - attr_reader :template - alias_method :h, :template - - def initialize(template, color_scheme, options = {}) - @template = template - @color_scheme = color_scheme - @options = options.dup - - @options.reverse_merge!( - # Handled further down the line by Gitlab::Markdown::SanitizationFilter - escape_html: false, - project: @template.instance_variable_get("@project") - ) - - super(options) - end - - def normal_text(text) - ERB::Util.html_escape_once(text) - end - - # Stolen from Rouge::Plugins::Redcarpet as this module is not required - # from Rouge's gem root. - def block_code(code, language) - lexer = Rouge::Lexer.find_fancy(language, code) || Rouge::Lexers::PlainText - - # XXX HACK: Redcarpet strips hard tabs out of code blocks, - # so we assume you're not using leading spaces that aren't tabs, - # and just replace them here. - if lexer.tag == 'make' - code.gsub!(/^ /, "\t") - end - - formatter = Rouge::Formatters::HTMLGitlab.new( - cssclass: "code highlight #{@color_scheme} #{lexer.tag}" - ) - formatter.format(lexer.lex(code)) - end - - def postprocess(full_document) - h.gfm(full_document, @options) - end -end diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 41a2f254db..a80e7e7743 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -37,6 +37,10 @@ web_server_pid_path="$pid_path/unicorn.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" mail_room_enabled=false mail_room_pid_path="$pid_path/mail_room.pid" +gitlab_git_http_server_pid_path="$pid_path/gitlab-git-http-server.pid" +gitlab_git_http_server_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-git-http-server.socket -authBackend http://127.0.0.1:8080" +gitlab_git_http_server_repo_root='/home/git/repositories' +gitlab_git_http_server_log="$app_root/log/gitlab-git-http-server.log" shell_path="/bin/bash" # Read configuration variable file if it is present @@ -72,6 +76,11 @@ check_pids(){ else spid=0 fi + if [ -f "$gitlab_git_http_server_pid_path" ]; then + hpid=$(cat "$gitlab_git_http_server_pid_path") + else + hpid=0 + fi if [ "$mail_room_enabled" = true ]; then if [ -f "$mail_room_pid_path" ]; then mpid=$(cat "$mail_room_pid_path") @@ -85,7 +94,7 @@ check_pids(){ wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid i=0; - while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; }; do + while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_git_http_server_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; }; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -120,6 +129,12 @@ check_status(){ else sidekiq_status="-1" fi + if [ $hpid -ne 0 ]; then + kill -0 "$hpid" 2>/dev/null + gitlab_git_http_server_status="$?" + else + gitlab_git_http_server_status="-1" + fi if [ "$mail_room_enabled" = true ]; then if [ $mpid -ne 0 ]; then kill -0 "$mpid" 2>/dev/null @@ -128,7 +143,7 @@ check_status(){ mail_room_status="-1" fi fi - if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; }; then + if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && [ $gitlab_git_http_server_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; }; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -156,6 +171,13 @@ check_stale_pids(){ exit 1 fi fi + if [ "$hpid" != "0" ] && [ "$gitlab_git_http_server_status" != "0" ]; then + echo "Removing stale gitlab-git-http-server pid. This is most likely caused by gitlab-git-http-server crashing the last time it ran." + if ! rm "$gitlab_git_http_server_pid_path"; then + echo "Unable to remove stale pid, exiting" + exit 1 + fi + fi if [ "$mail_room_enabled" = true ] && [ "$mpid" != "0" ] && [ "$mail_room_status" != "0" ]; then echo "Removing stale MailRoom job dispatcher pid. This is most likely caused by MailRoom crashing the last time it ran." if ! rm "$mail_room_pid_path"; then @@ -168,7 +190,7 @@ check_stale_pids(){ ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_git_http_server_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then echo "GitLab is not running." exit fi @@ -184,6 +206,9 @@ start_gitlab() { if [ "$sidekiq_status" != "0" ]; then echo "Starting GitLab Sidekiq" fi + if [ "$gitlab_git_http_server_status" != "0" ]; then + echo "Starting gitlab-git-http-server" + fi if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then echo "Starting GitLab MailRoom" fi @@ -205,6 +230,17 @@ start_gitlab() { RAILS_ENV=$RAILS_ENV bin/background_jobs start & fi + if [ "$gitlab_git_http_server_status" = "0" ]; then + echo "The gitlab-git-http-server is already running with pid $spid, not restarting" + else + # No need to remove a socket, gitlab-git-http-server does this itself + $app_root/bin/daemon_with_pidfile $gitlab_git_http_server_pid_path \ + $app_root/../gitlab-git-http-server/gitlab-git-http-server \ + $gitlab_git_http_server_options \ + $gitlab_git_http_server_repo_root \ + >> $gitlab_git_http_server_log 2>&1 & + fi + if [ "$mail_room_enabled" = true ]; then # If MailRoom is already running, don't start it again. if [ "$mail_room_status" = "0" ]; then @@ -226,33 +262,27 @@ stop_gitlab() { if [ "$web_status" = "0" ]; then echo "Shutting down GitLab Unicorn" + RAILS_ENV=$RAILS_ENV bin/web stop fi if [ "$sidekiq_status" = "0" ]; then echo "Shutting down GitLab Sidekiq" + RAILS_ENV=$RAILS_ENV bin/background_jobs stop + fi + if [ "$gitlab_git_http_server_status" = "0" ]; then + echo "Shutting down gitlab-git-http-server" + kill -- $(cat $gitlab_git_http_server_pid_path) fi if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then echo "Shutting down GitLab MailRoom" - fi - - # If the Unicorn web server is running, tell it to stop; - if [ "$web_status" = "0" ]; then - RAILS_ENV=$RAILS_ENV bin/web stop - fi - # And do the same thing for the Sidekiq. - if [ "$sidekiq_status" = "0" ]; then - RAILS_ENV=$RAILS_ENV bin/background_jobs stop - fi - # And do the same thing for the MailRoom. - if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then RAILS_ENV=$RAILS_ENV bin/mail_room stop fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; do + while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_git_http_server_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; do sleep 1 check_status printf "." - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_git_http_server_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then printf "\n" break fi @@ -262,6 +292,7 @@ stop_gitlab() { # Cleaning up unused pids rm "$web_server_pid_path" 2>/dev/null # rm "$sidekiq_pid_path" 2>/dev/null # Sidekiq seems to be cleaning up it's own pid. + rm -f "$gitlab_git_http_server_pid_path" if [ "$mail_room_enabled" = true ]; then rm "$mail_room_pid_path" 2>/dev/null fi @@ -272,7 +303,7 @@ stop_gitlab() { ## Prints the status of GitLab and it's components. print_status() { check_status - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_git_http_server_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then echo "GitLab is not running." return fi @@ -286,9 +317,14 @@ print_status() { else printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n" fi + if [ "$gitlab_git_http_server_status" = "0" ]; then + echo "The gitlab-git-http-server with pid $hpid is running." + else + printf "The gitlab-git-http-server is \033[31mnot running\033[0m.\n" + fi if [ "$mail_room_enabled" = true ]; then if [ "$mail_room_status" = "0" ]; then - echo "The GitLab MailRoom email processor with pid $spid is running." + echo "The GitLab MailRoom email processor with pid $mpid is running." else printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" fi @@ -324,7 +360,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; then + if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_git_http_server" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; then stop_gitlab fi start_gitlab diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index fd70cb7cc7..aab5acaa72 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -30,6 +30,16 @@ web_server_pid_path="$pid_path/unicorn.pid" # The default is "$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" +gitlab_git_http_server_pid_path="$pid_path/gitlab-git-http-server.pid" +# The -listenXxx settings determine where gitlab-git-http-server +# listens for connections from NGINX. To listen on localhost:8181, write +# '-listenNetwork tcp -listenAddr localhost:8181'. +# The -authBackend setting tells gitlab-git-http-server where it can reach +# Unicorn. +gitlab_git_http_server_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-git-http-server.socket -authBackend http://127.0.0.1:8080" +gitlab_git_http_server_repo_root="/home/git/repositories" +gitlab_git_http_server_log="$app_root/log/gitlab-git-http-server.log" + # mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled. # This is required for the Reply by email feature. # The default is "false" diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index efa0898900..17f89c8beb 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -38,10 +38,9 @@ upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; } -## Experimental: gitlab-git-http-server -# upstream gitlab-git-http-server { -# server localhost:8181; -# } +upstream gitlab-git-http-server { + server unix:/home/git/gitlab/tmp/sockets/gitlab-git-http-server.socket fail_timeout=0; +} ## Normal HTTP host server { @@ -114,25 +113,24 @@ server { proxy_pass http://gitlab; } - ## Experimental: send Git HTTP traffic to gitlab-git-http-server instead of Unicorn - # location ~ [-\/\w\.]+\.git\/ { - # ## If you use HTTPS make sure you disable gzip compression - # ## to be safe against BREACH attack. - # # gzip off; + location ~ [-\/\w\.]+\.git\/ { + ## If you use HTTPS make sure you disable gzip compression + ## to be safe against BREACH attack. + # gzip off; - # ## https://github.com/gitlabhq/gitlabhq/issues/694 - # ## Some requests take more than 30 seconds. - # proxy_read_timeout 300; - # proxy_connect_timeout 300; - # proxy_redirect off; + ## https://github.com/gitlabhq/gitlabhq/issues/694 + ## Some requests take more than 30 seconds. + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_redirect off; - # proxy_set_header Host $http_host; - # proxy_set_header X-Real-IP $remote_addr; - # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; - # proxy_pass http://gitlab-git-http-server; - # } + proxy_pass http://gitlab-git-http-server; + } ## Enable gzip compression as per rails guide: ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 314525518f..5ba39fc41a 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -42,10 +42,9 @@ upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; } -## Experimental: gitlab-git-http-server -# upstream gitlab-git-http-server { -# server localhost:8181; -# } +upstream gitlab-git-http-server { + server unix:/home/git/gitlab/tmp/sockets/gitlab-git-http-server.socket fail_timeout=0; +} ## Redirects all HTTP traffic to the HTTPS host server { @@ -161,25 +160,24 @@ server { proxy_pass http://gitlab; } - ## Experimental: send Git HTTP traffic to gitlab-git-http-server instead of Unicorn - # location ~ [-\/\w\.]+\.git\/ { - # ## If you use HTTPS make sure you disable gzip compression - # ## to be safe against BREACH attack. - # gzip off; + location ~ [-\/\w\.]+\.git\/ { + ## If you use HTTPS make sure you disable gzip compression + ## to be safe against BREACH attack. + gzip off; - # ## https://github.com/gitlabhq/gitlabhq/issues/694 - # ## Some requests take more than 30 seconds. - # proxy_read_timeout 300; - # proxy_connect_timeout 300; - # proxy_redirect off; + ## https://github.com/gitlabhq/gitlabhq/issues/694 + ## Some requests take more than 30 seconds. + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_redirect off; - # proxy_set_header Host $http_host; - # proxy_set_header X-Real-IP $remote_addr; - # proxy_set_header X-Forwarded-Ssl on; - # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - # proxy_set_header X-Forwarded-Proto $scheme; - # proxy_pass http://gitlab-git-http-server; - # } + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Ssl on; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://gitlab-git-http-server; + } ## Enable gzip compression as per rails guide: ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 9815582d02..bcd25ccbaf 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -278,7 +278,7 @@ namespace :gitlab do fix_and_rerun end end - + def check_uploads print "Uploads directory setup correctly? ... " @@ -331,15 +331,18 @@ namespace :gitlab do end def check_redis_version - print "Redis version >= 2.0.0? ... " + min_redis_version = "2.4.0" + print "Redis version >= #{min_redis_version}? ... " redis_version = run(%W(redis-cli --version)) - if redis_version.try(:match, /redis-cli 2.\d.\d/) || redis_version.try(:match, /redis-cli 3.\d.\d/) + redis_version = redis_version.try(:match, /redis-cli (.*)/) + if redis_version && + (Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version)) puts "yes".green else puts "no".red try_fixing_it( - "Update your redis server to a version >= 2.0.0" + "Update your redis server to a version >= #{min_redis_version}" ) for_more_information( "gitlab-public-wiki/wiki/Trouble-Shooting-Guide in section sidekiq" @@ -488,7 +491,7 @@ namespace :gitlab do else puts "wrong or missing hooks".red try_fixing_it( - sudo_gitlab("#{gitlab_shell_path}/bin/create-hooks"), + sudo_gitlab("#{File.join(gitlab_shell_path, 'bin/create-hooks')}"), 'Check the hooks_path in config/gitlab.yml', 'Check your gitlab-shell installation' ) diff --git a/lib/tasks/services.rake b/lib/tasks/services.rake new file mode 100644 index 0000000000..3f276a5e12 --- /dev/null +++ b/lib/tasks/services.rake @@ -0,0 +1,89 @@ +services_template = <<-ERB +# Services + +<% services.each do |service| %> +## <%= service[:title] %> + + +<% unless service[:description].blank? %> +<%= service[:description] %> +<% end %> + + +### Create/Edit <%= service[:title] %> service + +Set <%= service[:title] %> service for a project. +<% unless service[:help].blank? %> + +> <%= service[:help].gsub("\n", ' ') %> + +<% end %> + +``` +PUT /projects/:id/services/<%= service[:dashed_name] %> + +``` + +Parameters: + +<% service[:params].each do |param| %> +- `<%= param[:name] %>` <%= param[:required] ? "(**required**)" : "(optional)" %><%= [" -", param[:description]].join(" ").gsub("\n", '') unless param[:description].blank? %> + +<% end %> + +### Delete <%= service[:title] %> service + +Delete <%= service[:title] %> service for a project. + +``` +DELETE /projects/:id/services/<%= service[:dashed_name] %> + +``` + +<% end %> +ERB + +namespace :services do + task doc: :environment do + services = Service.available_services_names.map do |s| + service_start = Time.now + klass = "#{s}_service".classify.constantize + + service = klass.new + + service_hash = {} + + service_hash[:title] = service.title + service_hash[:dashed_name] = s.dasherize + service_hash[:description] = service.description + service_hash[:help] = service.help + service_hash[:params] = service.fields.map do |p| + param_hash = {} + + param_hash[:name] = p[:name] + param_hash[:description] = p[:placeholder] || p[:title] + param_hash[:required] = klass.validators_on(p[:name].to_sym).any? do |v| + v.class == ActiveRecord::Validations::PresenceValidator + end + + param_hash + end.sort_by { |p| p[:required] ? 0 : 1 } + + puts "Collected data for: #{service.title}, #{Time.now-service_start}" + service_hash + end + + doc_start = Time.now + doc_path = File.join(Rails.root, 'doc', 'api', 'services.md') + + result = ERB.new(services_template, 0 , '>') + .result(OpenStruct.new(services: services).instance_eval { binding }) + + File.open(doc_path, 'w') do |f| + f.write result + end + + puts "write a new service.md to: #{doc_path.to_s}, #{Time.now-doc_start}" + + end +end diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb new file mode 100644 index 0000000000..c114f34202 --- /dev/null +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Projects::RawController do + let(:public_project) { create(:project, :public) } + + describe "#show" do + context 'regular filename' do + let(:id) { 'master/README.md' } + + it 'delivers ASCII file' do + get(:show, + namespace_id: public_project.namespace.to_param, + project_id: public_project.to_param, + id: id) + + expect(response.status).to eq(200) + expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8') + expect(response.header['Content-Disposition']). + to eq("inline") + end + end + + context 'image header' do + let(:id) { 'master/files/images/6049019_460s.jpg' } + + it 'set image content type header' do + get(:show, + namespace_id: public_project.namespace.to_param, + project_id: public_project.to_param, + id: id) + + expect(response.status).to eq(200) + expect(response.header['Content-Type']).to eq('image/jpeg') + end + end + end +end diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb index 29fcbc5e19..8d287ded29 100644 --- a/spec/factories/abuse_reports.rb +++ b/spec/factories/abuse_reports.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: abuse_reports +# +# id :integer not null, primary key +# reporter_id :integer +# user_id :integer +# message :text +# created_at :datetime +# updated_at :datetime +# + # Read about factories at https://github.com/thoughtbot/factory_girl FactoryGirl.define do diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 3b7adfe439..6080d0ccde 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -19,6 +19,7 @@ # description :text # position :integer default(0) # locked_at :datetime +# updated_by_id :integer # FactoryGirl.define do diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index e1009d5916..9d777ddfcc 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -15,6 +15,7 @@ # noteable_id :integer # system :boolean default(FALSE), not null # st_diff :text +# updated_by_id :integer # require_relative '../support/repo_helpers' diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index 0c1bc53cdb..7852c39fee 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -77,7 +77,7 @@ describe "GitLab Flavored Markdown", feature: true do it "should render details in issues#show" do visit namespace_project_issue_path(project.namespace, project, @issue) - expect(page).to have_link("@#{fred.username}") + expect(page).to have_link(fred.to_reference) end end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 3da4dfc2b2..c557a1061a 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -64,8 +64,8 @@ describe 'GitLab Markdown', feature: true do it 'parses fenced code blocks' do aggregate_failures do - expect(doc).to have_selector('pre.code.highlight.white.c') - expect(doc).to have_selector('pre.code.highlight.white.python') + expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.c') + expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.python') end end @@ -179,7 +179,7 @@ describe 'GitLab Markdown', feature: true do before(:all) do @feat = MarkdownFeature.new - # `gfm` helper depends on a `@project` variable + # `markdown` helper expects a `@project` variable @project = @feat.project @html = markdown(@feat.raw_markdown) @@ -224,8 +224,4 @@ describe 'GitLab Markdown', feature: true do def current_user @feat.user end - - def user_color_scheme_class - :white - end end diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index da58ab9846..e68a5ec29a 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -28,8 +28,7 @@ describe EventsHelper do it 'should display the first line of a code block' do input = "```\nCode block\nwith two lines\n```" - expected = '
' \
-               'Code block...
' + expected = %r{Code block\.\.\.} expect(event_note(input)).to match(expected) end @@ -55,7 +54,7 @@ describe EventsHelper do it 'should preserve code color scheme' do input = "```ruby\ndef test\n 'hello world'\nend\n```" - expected = '
' \
+    expected = '
' \
       "def test\n" \
       "  \'hello world\'\n" \
       "end" \
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index a42ccb9b50..5639b3db91 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -19,28 +19,23 @@ describe GitlabMarkdownHelper do
     @project = project
   end
 
-  describe "#gfm" do
-    it "should forward HTML options to links" do
-      expect(gfm("Fixed in #{commit.id}", { project: @project }, class: 'foo')).
-        to have_selector('a.gfm.foo')
-    end
-
+  describe "#markdown" do
     describe "referencing multiple objects" do
       let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" }
 
       it "should link to the merge request" do
         expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
-        expect(gfm(actual)).to match(expected)
+        expect(markdown(actual)).to match(expected)
       end
 
       it "should link to the commit" do
         expected = namespace_project_commit_path(project.namespace, project, commit)
-        expect(gfm(actual)).to match(expected)
+        expect(markdown(actual)).to match(expected)
       end
 
       it "should link to the issue" do
         expected = namespace_project_issue_path(project.namespace, project, issue)
-        expect(gfm(actual)).to match(expected)
+        expect(markdown(actual)).to match(expected)
       end
     end
   end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index d814b56211..06f69262b7 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -1,72 +1,82 @@
 require 'spec_helper'
 
 describe PreferencesHelper do
-  describe 'user_application_theme' do
-    context 'with a user' do
-      it "returns user's theme's css_class" do
-        user = double('user', theme_id: 3)
-        allow(self).to receive(:current_user).and_return(user)
-        expect(user_application_theme).to eq 'ui_green'
-      end
-
-      it 'returns the default when id is invalid' do
-        user = double('user', theme_id: Gitlab::Themes::THEMES.size + 5)
-
-        allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
-        allow(self).to receive(:current_user).and_return(user)
-
-        expect(user_application_theme).to eq 'ui_charcoal'
-      end
-    end
-
-    context 'without a user' do
-      before do
-        allow(self).to receive(:current_user).and_return(nil)
-      end
-
-      it 'returns the default theme' do
-        expect(user_application_theme).to eq Gitlab::Themes.default.css_class
-      end
-    end
-  end
-
   describe 'dashboard_choices' do
     it 'raises an exception when defined choices may be missing' do
       expect(User).to receive(:dashboards).and_return(foo: 'foo')
-      expect { dashboard_choices }.to raise_error(RuntimeError)
+      expect { helper.dashboard_choices }.to raise_error(RuntimeError)
     end
 
     it 'raises an exception when defined choices may be using the wrong key' do
       expect(User).to receive(:dashboards).and_return(foo: 'foo', bar: 'bar')
-      expect { dashboard_choices }.to raise_error(KeyError)
+      expect { helper.dashboard_choices }.to raise_error(KeyError)
     end
 
     it 'provides better option descriptions' do
-      expect(dashboard_choices).to match_array [
+      expect(helper.dashboard_choices).to match_array [
         ['Your Projects (default)', 'projects'],
         ['Starred Projects',        'stars']
       ]
     end
   end
 
-  describe 'user_color_scheme_class' do
-    context 'with current_user is nil' do
-      it 'should return a string' do
-        allow(self).to receive(:current_user).and_return(nil)
-        expect(user_color_scheme_class).to be_kind_of(String)
+  describe 'user_application_theme' do
+    context 'with a user' do
+      it "returns user's theme's css_class" do
+        stub_user(theme_id: 3)
+
+        expect(helper.user_application_theme).to eq 'ui_green'
+      end
+
+      it 'returns the default when id is invalid' do
+        stub_user(theme_id: Gitlab::Themes.count + 5)
+
+        allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
+
+        expect(helper.user_application_theme).to eq 'ui_charcoal'
       end
     end
 
-    context 'with a current_user' do
-      (1..5).each do |color_scheme_id|
-        context "with color_scheme_id == #{color_scheme_id}" do
-          it 'should return a string' do
-            current_user = double(color_scheme_id: color_scheme_id)
-            allow(self).to receive(:current_user).and_return(current_user)
-            expect(user_color_scheme_class).to be_kind_of(String)
-          end
-        end
+    context 'without a user' do
+      it 'returns the default theme' do
+        stub_user
+
+        expect(helper.user_application_theme).to eq Gitlab::Themes.default.css_class
       end
     end
   end
+
+  describe 'user_color_scheme' do
+    context 'with a user' do
+      it "returns user's scheme's css_class" do
+        allow(helper).to receive(:current_user).
+          and_return(double(color_scheme_id: 3))
+
+        expect(helper.user_color_scheme).to eq 'solarized-light'
+      end
+
+      it 'returns the default when id is invalid' do
+        allow(helper).to receive(:current_user).
+          and_return(double(color_scheme_id: Gitlab::ColorSchemes.count + 5))
+      end
+    end
+
+    context 'without a user' do
+      it 'returns the default theme' do
+        stub_user
+
+        expect(helper.user_color_scheme).
+          to eq Gitlab::ColorSchemes.default.css_class
+      end
+    end
+  end
+
+  def stub_user(messages = {})
+    if messages.empty?
+      allow(helper).to receive(:current_user).and_return(nil)
+    else
+      allow(helper).to receive(:current_user).
+        and_return(double('user', messages))
+    end
+  end
 end
diff --git a/spec/javascripts/zen_mode_spec.js.coffee b/spec/javascripts/zen_mode_spec.js.coffee
index 1f4ea58ad4..4cb3836755 100644
--- a/spec/javascripts/zen_mode_spec.js.coffee
+++ b/spec/javascripts/zen_mode_spec.js.coffee
@@ -29,6 +29,11 @@ describe 'ZenMode', ->
       enterZen()
       expect(Mousetrap.pause).toHaveBeenCalled()
 
+    it 'removes textarea styling', ->
+      $('textarea').attr('style', 'height: 400px')
+      enterZen()
+      expect('textarea').not.toHaveAttr('style')
+
   describe 'in use', ->
     beforeEach ->
       enterZen()
diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb
index dd450e9967..dfe58637ee 100644
--- a/spec/lib/gitlab/bitbucket_import/client_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb
@@ -14,4 +14,38 @@ describe Gitlab::BitbucketImport::Client do
       expect(key).to be_kind_of(Symbol)
     end
   end
+
+  context 'issues' do
+    let(:per_page) { 50 }
+    let(:count) { 95 }
+    let(:sample_issues) do
+      issues = []
+
+      count.times do |i|
+        issues << { local_id: i }
+      end
+
+      issues
+    end
+    let(:first_sample_data) { { count: count, issues: sample_issues[0..per_page - 1] } }
+    let(:second_sample_data) { { count: count, issues: sample_issues[per_page..count] } }
+    let(:project_id) { 'namespace/repo' }
+
+    it 'retrieves issues over a number of pages' do
+      stub_request(:get,
+                   "https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=0").
+          to_return(status: 200,
+                    body: first_sample_data.to_json,
+                    headers: {})
+
+      stub_request(:get,
+                   "https://bitbucket.org/api/1.0/repositories/#{project_id}/issues?limit=50&sort=utc_created_on&start=50").
+          to_return(status: 200,
+                    body: second_sample_data.to_json,
+                    headers: {})
+
+      issues = client.issues(project_id)
+      expect(issues.count).to eq(95)
+    end
+  end
 end
diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb
new file mode 100644
index 0000000000..c7be45dbcd
--- /dev/null
+++ b/spec/lib/gitlab/color_schemes_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::ColorSchemes do
+  describe '.body_classes' do
+    it 'returns a space-separated list of class names' do
+      css = described_class.body_classes
+
+      expect(css).to include('white')
+      expect(css).to include(' solarized-light ')
+      expect(css).to include(' monokai')
+    end
+  end
+
+  describe '.by_id' do
+    it 'returns a scheme by its ID' do
+      expect(described_class.by_id(1).name).to eq 'White'
+      expect(described_class.by_id(4).name).to eq 'Solarized Dark'
+    end
+  end
+
+  describe '.default' do
+    it 'returns the default scheme' do
+      expect(described_class.default.id).to eq 1
+    end
+  end
+
+  describe '.each' do
+    it 'passes the block to the SCHEMES Array' do
+      ids = []
+      described_class.each { |scheme| ids << scheme.id }
+      expect(ids).not_to be_empty
+    end
+  end
+
+  describe '.for_user' do
+    it 'returns default when user is nil' do
+      expect(described_class.for_user(nil).id).to eq 1
+    end
+
+    it "returns user's preferred color scheme" do
+      user = double(color_scheme_id: 5)
+      expect(described_class.for_user(user).id).to eq 5
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 7cfca96f4e..84d9fb54b6 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -47,6 +47,28 @@ describe Gitlab::LDAP::User do
       expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
     end
 
+    it 'connects to existing ldap user if the extern_uid changes' do
+      existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
+      expect{ ldap_user.save }.not_to change{ User.count }
+
+      existing_user.reload
+      expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
+      expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
+      expect(existing_user.id).to eql ldap_user.gl_user.id
+    end
+
+    it 'maintains an identity per provider' do
+      existing_user = create(:omniauth_user, email: 'john@example.com', provider: 'twitter')
+      expect(existing_user.identities.count).to eql(1)
+
+      ldap_user.save
+      expect(ldap_user.gl_user.identities.count).to eql(2)
+
+      # Expect that find_by provider only returns a single instance of an identity and not an Enumerable
+      expect(ldap_user.gl_user.identities.find_by(provider: 'twitter')).to be_instance_of Identity
+      expect(ldap_user.gl_user.identities.find_by(provider: auth_hash.provider)).to be_instance_of Identity
+    end
+
     it "creates a new user if not found" do
       expect{ ldap_user.save }.to change{ User.count }.by(1)
     end
diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
index 5815528448..3c6c84a041 100644
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -75,11 +75,6 @@ module Gitlab::Markdown
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
       end
 
-      it 'includes an optional custom class' do
-        doc = filter("See #{reference}", reference_class: 'custom')
-        expect(doc.css('a').first.attr('class')).to include 'custom'
-      end
-
       it 'includes a data-project-id attribute' do
         doc = filter("See #{reference}")
         link = doc.css('a').first
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
index 05a02de466..9ed438252b 100644
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -71,11 +71,6 @@ module Gitlab::Markdown
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
       end
 
-      it 'includes an optional custom class' do
-        doc = filter("See #{reference}", reference_class: 'custom')
-        expect(doc.css('a').first.attr('class')).to include 'custom'
-      end
-
       it 'includes a data-project-id attribute' do
         doc = filter("See #{reference}")
         link = doc.css('a').first
diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
index f16095bc2b..d8c2970b6b 100644
--- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
@@ -68,11 +68,6 @@ module Gitlab::Markdown
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
       end
 
-      it 'includes an optional custom class' do
-        doc = filter("Issue #{reference}", reference_class: 'custom')
-        expect(doc.css('a').first.attr('class')).to include 'custom'
-      end
-
       it 'supports an :only_path context' do
         doc = filter("Issue #{reference}", only_path: true)
         link = doc.css('a').first.attr('href')
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
index 35b1ba5f13..1dd54f5858 100644
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -68,11 +68,6 @@ module Gitlab::Markdown
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
       end
 
-      it 'includes an optional custom class' do
-        doc = filter("Issue #{reference}", reference_class: 'custom')
-        expect(doc.css('a').first.attr('class')).to include 'custom'
-      end
-
       it 'includes a data-project-id attribute' do
         doc = filter("Issue #{reference}")
         link = doc.css('a').first
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
index fabe0411e4..e32089de37 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -25,11 +25,6 @@ module Gitlab::Markdown
       expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
     end
 
-    it 'includes an optional custom class' do
-      doc = filter("Label #{reference}", reference_class: 'custom')
-      expect(doc.css('a').first.attr('class')).to include 'custom'
-    end
-
     it 'includes a data-project-id attribute' do
       doc = filter("Label #{reference}")
       link = doc.css('a').first
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
index 5cef52b191..66616b9336 100644
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -56,11 +56,6 @@ module Gitlab::Markdown
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
       end
 
-      it 'includes an optional custom class' do
-        doc = filter("Merge #{reference}", reference_class: 'custom')
-        expect(doc.css('a').first.attr('class')).to include 'custom'
-      end
-
       it 'includes a data-project-id attribute' do
         doc = filter("Merge #{reference}")
         link = doc.css('a').first
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
index 678b171e99..fd3f0d20fa 100644
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -55,11 +55,6 @@ module Gitlab::Markdown
         expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
       end
 
-      it 'includes an optional custom class' do
-        doc = filter("Snippet #{reference}", reference_class: 'custom')
-        expect(doc.css('a').first.attr('class')).to include 'custom'
-      end
-
       it 'includes a data-project-id attribute' do
         doc = filter("Snippet #{reference}")
         link = doc.css('a').first
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index a5405e14a7..b2155fab59 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -121,7 +121,6 @@ module Gitlab::Markdown
     end
 
     it 'links with adjacent text' do
-      skip "TODO (rspeicher): Re-enable when usernames can't end in periods."
       doc = filter("Mention me (#{reference}.)")
       expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/)
     end
@@ -131,11 +130,6 @@ module Gitlab::Markdown
       expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
     end
 
-    it 'includes an optional custom class' do
-      doc = filter("Hey #{reference}", reference_class: 'custom')
-      expect(doc.css('a').first.attr('class')).to include 'custom'
-    end
-
     it 'supports an :only_path context' do
       doc = filter("Hey #{reference}", only_path: true)
       link = doc.css('a').first.attr('href')
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
index 9c6c3fd810..e554458e41 100644
--- a/spec/lib/gitlab/themes_spec.rb
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -43,9 +43,6 @@ describe Gitlab::Themes do
       ids = []
       described_class.each { |theme| ids << theme.id }
       expect(ids).not_to be_empty
-
-      # TODO (rspeicher): RSpec 3.x
-      # expect(described_class.each).to yield_with_arg(described_class::Theme)
     end
   end
 end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index d83004a838..635a6e2518 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -1,3 +1,15 @@
+# == Schema Information
+#
+# Table name: abuse_reports
+#
+#  id          :integer          not null, primary key
+#  reporter_id :integer
+#  user_id     :integer
+#  message     :text
+#  created_at  :datetime
+#  updated_at  :datetime
+#
+
 require 'rails_helper'
 
 RSpec.describe AbuseReport, type: :model do
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index bc14ff98fd..de0b2ef4cd 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -22,6 +22,7 @@
 #  user_oauth_applications      :boolean          default(TRUE)
 #  after_sign_out_path          :string(255)
 #  session_expire_delay         :integer          default(10080), not null
+#  import_sources               :text
 #
 
 require 'spec_helper'
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 9bac451c28..cf336d8295 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -2,19 +2,20 @@
 #
 # Table name: issues
 #
-#  id           :integer          not null, primary key
-#  title        :string(255)
-#  assignee_id  :integer
-#  author_id    :integer
-#  project_id   :integer
-#  created_at   :datetime
-#  updated_at   :datetime
-#  position     :integer          default(0)
-#  branch_name  :string(255)
-#  description  :text
-#  milestone_id :integer
-#  state        :string(255)
-#  iid          :integer
+#  id            :integer          not null, primary key
+#  title         :string(255)
+#  assignee_id   :integer
+#  author_id     :integer
+#  project_id    :integer
+#  created_at    :datetime
+#  updated_at    :datetime
+#  position      :integer          default(0)
+#  branch_name   :string(255)
+#  description   :text
+#  milestone_id  :integer
+#  state         :string(255)
+#  iid           :integer
+#  updated_by_id :integer
 #
 
 require 'spec_helper'
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index b91687bc09..17a49013d2 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -19,6 +19,7 @@
 #  description       :text
 #  position          :integer          default(0)
 #  locked_at         :datetime
+#  updated_by_id     :integer
 #
 
 require 'spec_helper'
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 331505a01b..3a0b194ba1 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -15,6 +15,7 @@
 #  noteable_id   :integer
 #  system        :boolean          default(FALSE), not null
 #  st_diff       :text
+#  updated_by_id :integer
 #
 
 require 'spec_helper'
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
new file mode 100644
index 0000000000..bad9a9e6e1
--- /dev/null
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -0,0 +1,107 @@
+# == Schema Information
+#
+# Table name: services
+#
+#  id                    :integer          not null, primary key
+#  type                  :string(255)
+#  title                 :string(255)
+#  project_id            :integer
+#  created_at            :datetime
+#  updated_at            :datetime
+#  active                :boolean          default(FALSE), not null
+#  properties            :text
+#  template              :boolean          default(FALSE)
+#  push_events           :boolean          default(TRUE)
+#  issues_events         :boolean          default(TRUE)
+#  merge_requests_events :boolean          default(TRUE)
+#  tag_push_events       :boolean          default(TRUE)
+#  note_events           :boolean          default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe DroneCiService do
+  describe 'associations' do
+    it { is_expected.to belong_to(:project) }
+    it { is_expected.to have_one(:service_hook) }
+  end
+
+  describe 'validations' do
+    context 'active' do
+      before { allow(subject).to receive(:activated?).and_return(true) }
+
+      it { is_expected.to validate_presence_of(:token) }
+      it { is_expected.to validate_presence_of(:drone_url) }
+      it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+      it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) }
+      it { is_expected.not_to allow_value('token with spaces').for(:token) }
+      it { is_expected.not_to allow_value('token/with%spaces').for(:token) }
+      it { is_expected.not_to allow_value('this is not url').for(:drone_url) }
+      it { is_expected.not_to allow_value('http//noturl').for(:drone_url) }
+      it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) }
+    end
+
+    context 'inactive' do
+      before { allow(subject).to receive(:activated?).and_return(false) }
+
+      it { is_expected.not_to validate_presence_of(:token) }
+      it { is_expected.not_to validate_presence_of(:drone_url) }
+      it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+      it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) }
+      it { is_expected.to allow_value('token with spaces').for(:token) }
+      it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) }
+    end
+  end
+
+  shared_context :drone_ci_service do
+    let(:drone)      { DroneCiService.new }
+    let(:project)    { create(:project, name: 'project') }
+    let(:path)       { "#{project.namespace.path}/#{project.path}" }
+    let(:drone_url)  { 'http://drone.example.com' }
+    let(:sha)        { '2ab7834c' }
+    let(:branch)     { 'dev' }
+    let(:token)      { 'secret' }
+    let(:iid)        { rand(1..9999) }
+
+    before(:each) do
+      allow(drone).to receive_messages(
+        project_id: project.id,
+        project: project,
+        active: true,
+        drone_url: drone_url,
+        token: token
+      )
+    end
+  end
+
+  describe "service page/path methods" do
+    include_context :drone_ci_service
+
+    # URL's
+    let(:commit_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
+    let(:merge_request_page) { "#{drone_url}/gitlab/#{path}/redirect/pulls/#{iid}" }
+    let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
+    let(:merge_request_status_path) { "#{drone_url}/gitlab/#{path}/pulls/#{iid}?access_token=#{token}" }
+
+    it { expect(drone.build_page(sha, branch)).to eq(commit_page) }
+    it { expect(drone.commit_page(sha, branch)).to eq(commit_page) }
+    it { expect(drone.merge_request_page(iid, sha, branch)).to eq(merge_request_page) }
+    it { expect(drone.commit_status_path(sha, branch)).to eq(commit_status_path) }
+    it { expect(drone.merge_request_status_path(iid, sha, branch)).to eq(merge_request_status_path)  }
+  end
+
+  describe "execute" do
+    include_context :drone_ci_service
+
+    let(:user)    { create(:user, username: 'username') }
+    let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+
+    it do
+      service_hook = double
+      expect(service_hook).to receive(:execute)
+      expect(drone).to receive(:service_hook).and_return(service_hook)
+
+      drone.execute(push_sample_data)
+    end
+  end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index a46e789eab..eeb9069aa1 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,62 +2,58 @@
 #
 # 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
-#  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)
-#  location                      :string(255)
-#  encrypted_otp_secret          :string(255)
-#  encrypted_otp_secret_iv       :string(255)
-#  encrypted_otp_secret_salt     :string(255)
-#  otp_required_for_login        :boolean          default(FALSE), not null
-#  otp_backup_codes              :text
-#  public_email                  :string(255)      default(""), not null
-#  dashboard                     :integer          default(0)
-#  project_view                  :integer          default(0)
+#  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
+#  notification_email         :string(255)
+#  hide_no_password           :boolean          default(FALSE)
+#  password_automatically_set :boolean          default(FALSE)
+#  location                   :string(255)
+#  encrypted_otp_secret       :string(255)
+#  encrypted_otp_secret_iv    :string(255)
+#  encrypted_otp_secret_salt  :string(255)
+#  otp_required_for_login     :boolean          default(FALSE), not null
+#  otp_backup_codes           :text
+#  public_email               :string(255)      default(""), not null
+#  dashboard                  :integer          default(0)
+#  project_view               :integer          default(0)
 #
 
 require 'spec_helper'
diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb
new file mode 100644
index 0000000000..d2b87f8871
--- /dev/null
+++ b/spec/requests/api/keys_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe API::API, api: true  do
+  include ApiHelpers
+
+  let(:user)  { create(:user) }
+  let(:admin) { create(:admin) }
+  let(:key)   { create(:key, user: user) }
+  let(:email)   { create(:email, user: user) }
+
+  describe 'GET /keys/:uid' do
+    before { admin }
+
+    context 'when unauthenticated' do
+      it 'should return authentication error' do
+        get api("/keys/#{key.id}")
+        expect(response.status).to eq(401)
+      end
+    end
+
+    context 'when authenticated' do
+      it 'should return 404 for non-existing key' do
+        get api('/keys/999999', admin)
+        expect(response.status).to eq(404)
+        expect(json_response['message']).to eq('404 Not found')
+      end
+
+      it 'should return single ssh key with user information' do
+        user.keys << key
+        user.save
+        get api("/keys/#{key.id}", admin)
+        expect(response.status).to eq(200)
+        expect(json_response['title']).to eq(key.title)
+        expect(json_response['user']['id']).to eq(user.id)
+        expect(json_response['user']['username']).to eq(user.username)
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 6d29a28580..c297904614 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -5,64 +5,47 @@ describe API::API, api: true  do
   let(:user) { create(:user) }
   let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
 
-  describe "POST /projects/:id/services/gitlab-ci" do
-    it "should update gitlab-ci settings" do
-      put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secrettoken', project_url: "http://ci.example.com/projects/1"
+  Service.available_services_names.each do |service|
+    describe "PUT /projects/:id/services/#{service.dasherize}" do
+      include_context service
 
-      expect(response.status).to eq(200)
+      it "should update #{service} settings" do
+        put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs
+
+        expect(response.status).to eq(200)
+      end
+
+      it "should return if required fields missing" do
+        attrs = service_attrs
+        
+        required_attributes = service_attrs_list.select do |attr|
+          service_klass.validators_on(attr).any? do |v| 
+            v.class == ActiveRecord::Validations::PresenceValidator
+          end
+        end
+
+        if required_attributes.empty?
+          expected_code = 200
+        else
+          attrs.delete(required_attributes.shuffle.first)
+          expected_code = 400
+        end
+        
+        put api("/projects/#{project.id}/services/#{dashed_service}", user), attrs
+
+        expect(response.status).to eq(expected_code)
+      end
     end
 
-    it "should return if required fields missing" do
-      put api("/projects/#{project.id}/services/gitlab-ci", user), project_url: "http://ci.example.com/projects/1", active: true
+    describe "DELETE /projects/:id/services/#{service.dasherize}" do
+      include_context service
 
-      expect(response.status).to eq(400)
-    end
+      it "should delete #{service}" do
+        delete api("/projects/#{project.id}/services/#{dashed_service}", user)
 
-    it "should return if the format of token is invalid" do
-      put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "http://ci.example.com/projects/1", active: true
-
-      expect(response.status).to eq(404)
-    end
-
-    it "should return if the format of token is invalid" do
-      put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "ftp://ci.example/projects/1", active: true
-
-      expect(response.status).to eq(404)
-    end
-  end
-
-  describe "DELETE /projects/:id/services/gitlab-ci" do
-    it "should update gitlab-ci settings" do
-      delete api("/projects/#{project.id}/services/gitlab-ci", user)
-
-      expect(response.status).to eq(200)
-      expect(project.gitlab_ci_service).to be_nil
-    end
-  end
-
-  describe 'PUT /projects/:id/services/hipchat' do
-    it 'should update hipchat settings' do
-      put api("/projects/#{project.id}/services/hipchat", user),
-          token: 'secret-token', room: 'test'
-
-      expect(response.status).to eq(200)
-      expect(project.hipchat_service).not_to be_nil
-    end
-
-    it 'should return if required fields missing' do
-      put api("/projects/#{project.id}/services/gitlab-ci", user),
-          token: 'secret-token', active: true
-
-      expect(response.status).to eq(400)
-    end
-  end
-
-  describe 'DELETE /projects/:id/services/hipchat' do
-    it 'should delete hipchat settings' do
-      delete api("/projects/#{project.id}/services/hipchat", user)
-
-      expect(response.status).to eq(200)
-      expect(project.hipchat_service).to be_nil
+        expect(response.status).to eq(200)
+        expect(project.send(service_method).activated?).to be_falsey
+      end
     end
   end
 end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 9da6c9dc94..8865335d0d 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -31,13 +31,16 @@ describe NotificationService do
 
   describe 'Notes' do
     context 'issue note' do
-      let(:project) { create(:empty_project, :public) }
+      let(:project) { create(:empty_project, :private) }
       let(:issue) { create(:issue, project: project, assignee: create(:user)) }
       let(:mentioned_issue) { create(:issue, assignee: issue.assignee) }
-      let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced') }
+      let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @outsider also') }
 
       before do
         build_team(note.project)
+        project.team << [issue.author, :master]
+        project.team << [issue.assignee, :master]
+        project.team << [note.author, :master]
       end
 
       describe :new_note do
@@ -53,6 +56,7 @@ describe NotificationService do
           should_not_email(@u_participating.id)
           should_not_email(@u_disabled.id)
           should_not_email(@unsubscriber.id)
+          should_not_email(@u_outsider_mentioned)
 
           notification.new_note(note)
         end
@@ -444,12 +448,15 @@ describe NotificationService do
     @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION)
     @u_committer = create(:user, username: 'committer')
     @u_not_mentioned = create(:user, username: 'regular', notification_level: Notification::N_PARTICIPATING)
+    @u_outsider_mentioned = create(:user, username: 'outsider')
 
     project.team << [@u_watcher, :master]
     project.team << [@u_participating, :master]
+    project.team << [@u_participant_mentioned, :master]
     project.team << [@u_disabled, :master]
     project.team << [@u_mentioned, :master]
     project.team << [@u_committer, :master]
+    project.team << [@u_not_mentioned, :master]
   end
 
   def add_users_with_subscription(project, issuable)
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
new file mode 100644
index 0000000000..4d007ae55e
--- /dev/null
+++ b/spec/support/services_shared_context.rb
@@ -0,0 +1,21 @@
+Service.available_services_names.each do |service|
+  shared_context service do
+    let(:dashed_service) { service.dasherize }
+    let(:service_method) { "#{service}_service".to_sym }
+    let(:service_klass) { "#{service}_service".classify.constantize }
+    let(:service_attrs_list) { service_klass.new.fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
+    let(:service_attrs) do
+      service_attrs_list.inject({}) do |hash, k|
+        if k =~ /^(token*|.*_token|.*_key)/
+          hash.merge!(k => 'secrettoken')
+        elsif k =~ /^(.*_url|url|webhook)/
+          hash.merge!(k => "http://example.com")
+        elsif service == 'irker' && k == :recipients
+          hash.merge!(k => 'irc://irc.network.net:666/#channel')
+        else
+          hash.merge!(k => "someword")
+        end
+      end
+    end
+  end
+end
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
new file mode 100644
index 0000000000..3600c77107
--- /dev/null
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe EmailsOnPushWorker do
+  include RepoHelpers
+
+  let(:project) { create(:project) }
+  let(:user) { create(:user) }
+  let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+
+  subject { EmailsOnPushWorker.new }
+
+  before do
+    allow(Project).to receive(:find).and_return(project)
+  end
+
+  describe "#perform" do
+    it "sends mail" do
+      subject.perform(project.id, user.email, data.stringify_keys)
+
+      email = ActionMailer::Base.deliveries.last
+      expect(email.subject).to include('Change some files')
+      expect(email.to).to eq([user.email])
+    end
+
+    it "gracefully handles an input SMTP error" do
+      ActionMailer::Base.deliveries.clear
+      allow(Notify).to receive(:repository_push_email).and_raise(Net::SMTPFatalError)
+
+      subject.perform(project.id, user.email, data.stringify_keys)
+
+      expect(ActionMailer::Base.deliveries.count).to eq(0)
+    end
+  end
+end
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
new file mode 100644
index 0000000000..b11c5de94e
--- /dev/null
+++ b/spec/workers/merge_worker_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe MergeWorker do
+  describe "remove source branch" do
+    let!(:merge_request) { create(:merge_request, source_branch: "markdown") }
+    let!(:source_project) { merge_request.source_project }
+    let!(:project) { merge_request.project }
+    let!(:author) { merge_request.author }
+
+    before do
+      source_project.team << [author, :master]
+      source_project.repository.expire_branch_names
+    end
+
+    it 'clears cache of source repo after removing source branch' do
+      expect(source_project.repository.branch_names).to include('markdown')
+
+      MergeWorker.new.perform(
+        merge_request.id, merge_request.author_id,
+        commit_message: 'wow such merge',
+        should_remove_source_branch: true)
+
+      merge_request.reload
+      expect(merge_request).to be_merged
+      expect(source_project.repository.branch_names).not_to include('markdown')
+    end
+  end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 46eae9ab08..e4151b9bb6 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -4,7 +4,7 @@ describe PostReceive do
   let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" }
   let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") }
   let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) }
-  
+
   context "as a resque worker" do
     it "reponds to #perform" do
       expect(PostReceive.new).to respond_to(:perform)