diff --git a/CHANGELOG b/CHANGELOG index e761dc7606..103b5c6293 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,21 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.0.0 (unreleased) + - Fix broken sort in merge request API (Stan Hu) + - Bump rouge to 1.10.1 to remove warning noise and fix other syntax highlighting bugs (Stan Hu) + - Gracefully handle errors in syntax highlighting by leaving the block unformatted (Stan Hu) + - Add "replace" and "upload" functionalities to allow user replace existing file and upload new file into current repository + - Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu) + - Fix emoji URLs in Markdown when relative_url_root is used (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) + - Import forked repositories asynchronously to prevent large repositories from timing out (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) + - 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) - - Fix "Reload with full diff" URL button in compare branch view (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 @@ -18,14 +30,42 @@ v 7.14.0 - 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) - - Improve abuse reports management from admin area - - Move dashboard activity to separate page + - 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 and group 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 + - Global Labels that are available to all projects + - Fix highlighting of deleted lines in diffs. + - Added service API endpoint to retrieve service parameters (Petheő Bence) + - Add FogBugz project import (Jared Szechy) + - Sort users autocomplete lists by user (Allister Antosik) -v 7.14.1 (unreleased) +v 7.14.3 + - No changes + +v 7.14.2 + - Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with ISO-encoded files (Stan Hu) + +v 7.14.1 + - Improve abuse reports management from admin area + - Fix "Reload with full diff" URL button in compare branch view (Stan Hu) + - Disabled DNS lookups for SSH in docker image (Rowan Wookey) - 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. @@ -109,7 +149,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 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 2714f5313a..57cf282ebb 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.6.4 +2.6.5 diff --git a/Gemfile b/Gemfile index 0effd590f7..202a96b181 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" gem 'gssapi', group: :kerberos @@ -39,12 +40,7 @@ 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 @@ -163,6 +159,9 @@ gem "slack-notifier", "~> 1.0.0" # Asana integration gem 'asana', '~> 0.0.6' +# FogBugz integration +gem 'ruby-fogbugz', '~> 0.2.1' + # d3 gem 'd3_rails', '~> 3.5.5' @@ -267,6 +266,7 @@ group :test do gem 'email_spec', '~> 1.6.0' gem 'webmock', '~> 1.21.0' gem 'test_after_commit' + gem 'sham_rack' end group :production do @@ -277,6 +277,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 0341157132..5c79ed5440 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -253,7 +253,7 @@ GEM ruby-progressbar (~> 1.4) gemnasium-gitlab-service (0.2.6) rugged (~> 0.21) - gemojione (2.0.0) + gemojione (2.0.1) json gherkin-ruby (0.3.1) racc @@ -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) @@ -275,9 +273,9 @@ GEM charlock_holmes (~> 0.6.6) escape_utils (~> 0.2.4) mime-types (~> 1.19) - gitlab_emoji (0.1.0) + gitlab_emoji (0.1.1) gemojione (~> 2.0) - gitlab_git (7.2.14) + gitlab_git (7.2.15) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -294,7 +292,7 @@ GEM github-markup (~> 1.3.1) gollum-grit_adapter (~> 0.1, >= 0.1.1) nokogiri (~> 1.6.4) - rouge (~> 1.9) + rouge (~> 1.10.1) sanitize (~> 2.1.0) stringex (~> 2.5.1) gon (5.0.1) @@ -375,7 +373,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) @@ -439,6 +437,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 @@ -465,7 +467,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) @@ -545,7 +547,7 @@ GEM netrc (~> 0.7) rinku (1.7.3) rotp (1.6.1) - rouge (1.9.1) + rouge (1.10.1) rqrcode (0.4.2) rqrcode-rails3 (0.1.7) rqrcode (>= 0.4.2) @@ -576,6 +578,8 @@ GEM powerpack (~> 0.0.6) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.4) + ruby-fogbugz (0.2.1) + crack (~> 0.4) ruby-progressbar (1.7.1) ruby-saml (1.0.0) nokogiri (>= 1.5.10) @@ -610,6 +614,8 @@ GEM thor (~> 0.14) settingslogic (2.0.9) sexp_processor (4.4.5) + sham_rack (1.3.6) + rack shoulda-matchers (2.8.0) activesupport (>= 3.0.0) sidekiq (3.3.0) @@ -790,11 +796,10 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) github-markup gitlab-flowdock-git-hook (~> 1.0.1) - gitlab-grack (~> 2.0.2) gitlab-license (~> 0.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) @@ -813,7 +818,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 @@ -830,6 +835,7 @@ DEPENDENCIES omniauth-saml (~> 1.4.0) omniauth-shibboleth omniauth-twitter + omniauth_crowd org-ruby (= 0.9.12) pg poltergeist (~> 1.6.0) @@ -849,12 +855,14 @@ DEPENDENCIES rqrcode-rails3 rspec-rails (~> 3.3.0) rubocop (= 0.28.0) + ruby-fogbugz (~> 0.2.1) sanitize (~> 2.0) sass-rails (~> 4.0.5) sdoc seed-fu select2-rails (~> 3.5.9) settingslogic + sham_rack shoulda-matchers (~> 2.8.0) sidekiq (~> 3.3) sidetiq (= 0.6.3) @@ -887,6 +895,3 @@ DEPENDENCIES virtus webmock (~> 1.21.0) wikicloth (= 0.8.1) - -BUNDLED WITH - 1.10.6 diff --git a/README.md b/README.md index 9129da2367..4f1cfb729c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [](https://ci.gitlab.com/projects/1?ref=master) [](https://semaphoreci.com/gitlabhq/gitlabhq) [](https://codeclimate.com/github/gitlabhq/gitlabhq) -[](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master) +[](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master) ## Canonical source 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 8daccce3a6..10fc0a6d77 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -94,18 +94,20 @@ 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 $.timeago.settings.allowFuture = true +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") diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee new file mode 100644 index 0000000000..3ab3ba6675 --- /dev/null +++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee @@ -0,0 +1,67 @@ +class @BlobFileDropzone + constructor: (form, method) -> + form_dropzone = form.find('.dropzone') + Dropzone.autoDiscover = false + dropzone = form_dropzone.dropzone( + autoDiscover: false + autoProcessQueue: false + url: form.attr('action') + # Rails uses a hidden input field for PUT + # http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails + method: method + clickable: true + uploadMultiple: false + paramName: "file" + maxFilesize: gon.max_file_size or 10 + parallelUploads: 1 + maxFiles: 1 + addRemoveLinks: true + previewsContainer: '.dropzone-previews' + headers: + "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") + + init: -> + this.on 'addedfile', (file) -> + $('.dropzone-alerts').html('').hide() + commit_message = form.find('#commit_message')[0] + + if /^Upload/.test(commit_message.placeholder) + commit_message.placeholder = 'Upload ' + file.name + + return + + this.on 'removedfile', (file) -> + commit_message = form.find('#commit_message')[0] + + if /^Upload/.test(commit_message.placeholder) + commit_message.placeholder = 'Upload new file' + + return + + this.on 'success', (header, response) -> + window.location.href = response.filePath + return + + this.on 'maxfilesexceeded', (file) -> + @removeFile file + return + + this.on 'sending', (file, xhr, formData) -> + formData.append('commit_message', form.find('#commit_message').val()) + return + + # Override behavior of adding error underneath preview + error: (file, errorMessage) -> + stripped = $("
").html(errorMessage).text(); + $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show() + @removeFile file + return + ) + + submitButton = form.find('#submit-all')[0] + submitButton.addEventListener 'click', (e) -> + e.preventDefault() + e.stopPropagation() + alert "Please select a file" if dropzone[0].dropzone.getQueuedFiles().length == 0 + dropzone[0].dropzone.processQueue() + return false diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index d0a8088d5d..e057a943f3 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -55,7 +55,6 @@ class Dispatcher new Activities() when 'dashboard:projects:starred' new Activities() - new ProjectsList() when 'projects:commit:show' new Commit() new Diff() @@ -70,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() @@ -100,8 +98,6 @@ class Dispatcher new GroupsSelect() when 'admin:emails:show' new AdminEmailSelect() - when 'admin:users:show' - new ProjectsList() switch path.first() when 'admin' diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index a0dcaa8c27..6f789e668a 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -167,6 +167,7 @@ class @DropzoneInput dataType: "json" ).success (data) -> preview.html data.body + preview.syntaxHighlight() renderReferencedUsers data.references.users diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 0021d17d85..ce638c2641 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -122,7 +122,9 @@ class @Notes # or skip if rendered if @isNewNote(note) @note_ids.push(note.id) - $('ul.main-notes-list').append(note.html) + $('ul.main-notes-list'). + append(note.html). + syntaxHighlight() @initTaskList() ### diff --git a/app/assets/javascripts/syntax_highlight.coffee b/app/assets/javascripts/syntax_highlight.coffee new file mode 100644 index 0000000000..980f0232d1 --- /dev/null +++ b/app/assets/javascripts/syntax_highlight.coffee @@ -0,0 +1,20 @@ +# Syntax Highlighter +# +# Applies a syntax highlighting color scheme CSS class to any element with the +# `js-syntax-highlight` class +# +# ### Example Markup +# +# +# +$.fn.syntaxHighlight = -> + if $(this).hasClass('js-syntax-highlight') + # Given the element itself, apply highlighting + $(this).addClass(gon.user_color_scheme) + else + # Given a parent element, recurse to any of its applicable children + $children = $(this).find('.js-syntax-highlight') + $children.syntaxHighlight() if $children.length + +$(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_bootstrap.scss b/app/assets/stylesheets/base/gl_bootstrap.scss index 21acbfa5e5..eb8d23d645 100644 --- a/app/assets/stylesheets/base/gl_bootstrap.scss +++ b/app/assets/stylesheets/base/gl_bootstrap.scss @@ -85,14 +85,14 @@ // Labels .label { padding: 2px 4px; - font-size: 12px; + font-size: 13px; font-style: normal; font-weight: normal; display: inline-block; &.label-gray { - background-color: #eee; - color: #999; + background-color: #f8fafc; + color: $gl-gray; text-shadow: none; } @@ -156,10 +156,16 @@ * Add some extra stuff to panels * */ -.panel { - .panel-heading { - font-weight: bold; +.container-blank .panel .panel-heading { + font-size: 17px; + line-height: 38px; +} + +.panel { + box-shadow: none; + + .panel-heading { .panel-head-actions { position: relative; top: -5px; @@ -182,6 +188,10 @@ .pagination { margin: 0; } + + .btn { + min-width: 124px; + } } &.panel-small { @@ -209,6 +219,12 @@ } } +.alert-help { + background-color: $background-color; + border: 1px solid $border-color; + color: $gl-gray; +} + // Typography ================================================================= .text-primary, diff --git a/app/assets/stylesheets/base/gl_variables.scss b/app/assets/stylesheets/base/gl_variables.scss index 56f4c794e1..bfef5f78f8 100644 --- a/app/assets/stylesheets/base/gl_variables.scss +++ b/app/assets/stylesheets/base/gl_variables.scss @@ -22,6 +22,10 @@ $brand-info: $gl-info; $brand-warning: $gl-warning; $brand-danger: $gl-danger; +$border-radius-base: 3px !default; +$border-radius-large: 5px !default; +$border-radius-small: 2px !default; + //== Scaffolding // @@ -42,17 +46,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; @@ -109,11 +114,12 @@ $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-border-radius: 2px; +$panel-default-text: $text-color; +$panel-default-border: $border-color; +$panel-default-heading-bg: $background-color; +$panel-footer-bg: $background-color; +$panel-inner-border: $border-color; //== Wells // @@ -131,3 +137,22 @@ $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; + +//== Code +// +//## +$pre-bg: #f8fafc !default; +$pre-color: $gl-gray !default; +$pre-border-color: #e7e9ed; diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/base/layout.scss index 734b95e26c..b91c15d891 100644 --- a/app/assets/stylesheets/base/layout.scss +++ b/app/assets/stylesheets/base/layout.scss @@ -21,7 +21,6 @@ html { 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..a2f6c3e21f 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: $md-text-color; + + a { + color: $md-link-color; + } img { max-width: 100%; @@ -157,3 +160,94 @@ 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-bottom: 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; +} + +@mixin nav-menu { + padding: 0; + margin: 0; + list-style: none; + margin-top: 5px; + height: 56px; + + li { + display: inline-block; + + a { + padding: 14px; + font-size: 17px; + line-height: 28px; + color: #7f8fa4; + border-bottom: 2px solid transparent; + + &:hover, &:active, &:focus { + text-decoration: none; + } + } + + &.active a { + 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/base/variables.scss b/app/assets/stylesheets/base/variables.scss index 26d0a1e536..2fc7bf1720 100644 --- a/app/assets/stylesheets/base/variables.scss +++ b/app/assets/stylesheets/base/variables.scss @@ -1,27 +1,32 @@ -$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; +$md-text-color: #444; +$md-link-color: #3084bb; $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; +$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 +40,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..ce024272a3 100644 --- a/app/assets/stylesheets/generic/blocks.scss +++ b/app/assets/stylesheets/generic/blocks.scss @@ -1,19 +1,61 @@ .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; + + &.top-block { + border-top: none; + } + + &.middle-block { + margin-top: 0; + margin-bottom: 0; + } + + &.clear-block { + margin-bottom: $gl-padding - 1px; + padding-bottom: $gl-padding; + } + + &.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: 42px; + } +} diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index cd6bf64c0a..46ef595ddf 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -10,7 +10,7 @@ } &.btn-save { - @extend .btn-primary; + @extend .btn-success; } &.btn-remove { @@ -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 365dd5078c..84ebc42f67 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; } @@ -316,7 +311,7 @@ table { } .btn-sign-in { - margin-top: 7px; + margin-top: 15px; text-shadow: none; } @@ -368,14 +363,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 { @@ -384,21 +379,23 @@ table { } .center-top-menu { - border-bottom: 1px solid #EEE; - list-style: none; + @include nav-menu; 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; - } - - &.active a { - color: #666; - } + &.no-bottom { + margin-bottom: 0; } } + +.dropzone .dz-preview .dz-progress { + border-color: $border-color !important; +} + +.dropzone .dz-preview .dz-progress .dz-upload { + background: $gl-success !important; +} diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/generic/files.scss index f845342c67..9dd7774788 100644 --- a/app/assets/stylesheets/generic/files.scss +++ b/app/assets/stylesheets/generic/files.scss @@ -3,7 +3,11 @@ * */ .file-holder { - border: 1px solid $border-color; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + border: none; + border-top: 1px solid #E7E9EE; + border-bottom: 1px solid #E7E9EE; margin-bottom: 1em; table { @@ -49,7 +53,7 @@ } &.wiki { - padding: 25px; + padding: $gl-padding; .highlight { margin-bottom: 9px; @@ -90,7 +94,7 @@ border-right: none; } background: #fff; - padding: 8px; + padding: 10px $gl-padding; } .lines { pre { @@ -100,6 +104,33 @@ border: none; } } + img.avatar { + border: 0 none; + float: none; + margin: 0; + padding: 0; + } + td.blame-commit { + background: #f9f9f9; + min-width: 350px; + + .commit-author-link { + color: #888; + } + } + td.blame-numbers { + pre { + color: #AAA; + white-space: pre; + } + background: #f1f1f1; + border-left: 1px solid #DDD; + } + td.lines { + code { + font-family: $monospace_font; + } + } } &.logs { 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 6a29b32e19..b758a526fb 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -24,29 +24,28 @@ header { z-index: 100; margin-bottom: 0; min-height: $header-height; + background-color: #fff; border: none; border-bottom: 1px solid #EEE; .container-fluid { - background: #FFF; width: 100% !important; 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; @@ -70,16 +70,16 @@ header { .title { margin: 0; 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; } @@ -94,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; @@ -105,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); diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index 869e586839..b1fb87a683 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -5,10 +5,13 @@ */ .issue-box { + @include border-radius(3px); + display: inline-block; - padding: 4px 13px; + padding: 10px $gl-padding; font-weight: normal; - margin-right: 5px; + margin-right: 10px; + font-size: $gl-font-size; &.issue-box-closed { background-color: $gl-danger; @@ -21,7 +24,7 @@ } &.issue-box-open { - background-color: $gl-success; + background-color: #019875; color: #FFF; } 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/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss index a4fc82e90b..ed0333d233 100644 --- a/app/assets/stylesheets/generic/markdown_area.scss +++ b/app/assets/stylesheets/generic/markdown_area.scss @@ -65,8 +65,11 @@ position: relative; } -.md-header ul { - float: left; +.md-header { + ul { + float: left; + margin-bottom: 1px; + } } .referenced-users { @@ -80,7 +83,7 @@ .md-preview-holder { background: #FFF; border: 1px solid #ddd; - min-height: 100px; + min-height: 169px; padding: 5px; box-shadow: none; } @@ -105,7 +108,7 @@ .markdown-area { background: #FFF; border: 1px solid #ddd; - min-height: 100px; + min-height: 140px; padding: 5px; box-shadow: none; width: 100%; 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 b79093dda3..3331a50610 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 320bdb1c76..3d055f0e66 100644 --- a/app/assets/stylesheets/generic/sidebar.scss +++ b/app/assets/stylesheets/generic/sidebar.scss @@ -18,14 +18,28 @@ } .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; + + &.container-blank { + background: none; + padding: 0; + border: none; + } + } } .nav-sidebar { - margin-top: 29 + $header-height; - margin-bottom: 50px; + margin-top: 14 + $header-height; + margin-bottom: 100px; transition-duration: .3s; list-style: none; overflow: hidden; @@ -43,13 +57,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 +75,9 @@ } i { - width: 20px; + width: 16px; color: $gray-light; - margin-right: 23px; + margin-right: 13px; } .count { @@ -108,41 +123,59 @@ } @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; + } } } .collapse-nav a { - left: 0px; width: $sidebar_collapsed_width; } .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; + left: 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,16 +209,18 @@ } .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; } } @@ -202,7 +237,7 @@ float: left; height: $header-height; width: 100%; - padding: ($header-height - 36 ) / 2 8px; + padding: 10px 22px; overflow: hidden; img { @@ -219,8 +254,8 @@ float: left; margin: 0; margin-left: 14px; - font-size: 18px; - line-height: $header-height - 14; + font-size: 19px; + line-height: 41px; font-weight: normal; } } diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss index 97831eb7c2..74bbaabad3 100644 --- a/app/assets/stylesheets/generic/timeline.scss +++ b/app/assets/stylesheets/generic/timeline.scss @@ -1,119 +1,50 @@ .timeline { - list-style: none; - padding: 20px 0 20px; - position: relative; + @include basic-list; - &:before { - top: 0; - bottom: 0; - position: absolute; - content: " "; - width: 3px; - background-color: #eeeeee; - margin-left: 29px; - } + margin: 0; + padding: 0; .timeline-entry { - position: relative; - margin-top: 5px; - margin-left: 30px; - margin-bottom: 10px; - clear: both; + padding: $gl-padding; + border-color: #f1f2f4; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + color: $gl-gray; + border-bottom: 1px solid #f1f2f4; + border-right: 1px solid #f1f2f4; - - &:target { - .timeline-entry-inner .timeline-content { - -webkit-animation:target-note 2s linear; - background: $hover; - } + &:last-child { + border-bottom: none; } - .timeline-entry-inner { - position: relative; - margin-left: -20px; + .avatar { + margin-right: 15px; + } - &:before, &:after { - content: " "; - display: table; - } - - .timeline-icon { - margin-top: 2px; - background: #fff; - color: #737881; - float: left; - @include border-radius($avatar_radius); - @include box-shadow(0 0 0 3px #EEE); - overflow: hidden; - - .avatar { - margin: 0; - padding: 0; - } - } - - .timeline-content { - position: relative; - background: $background-color; - padding: 10px 15px; - margin-left: 60px; - - img { - max-width: 100%; - } - - &:after { - content: ''; - display: block; - position: absolute; - width: 0; - height: 0; - border-style: solid; - border-width: 9px 9px 9px 0; - border-color: transparent $background-color transparent transparent; - left: 0; - top: 10px; - margin-left: -9px; - } - } + .controls { + padding-top: 10px; + float: right; } } - .system-note .timeline-entry-inner { - .timeline-icon { - background: none; - margin-left: 12px; - margin-top: 0; - @include box-shadow(none); - - span { - margin: 0 2px; - font-size: 16px; - color: #eeeeee; - } + .note-text { + p:last-child { + margin-bottom: 0; } + } - .timeline-content { - background: none; - margin-left: 45px; - padding: 0px 15px; - - &:after { border: 0; } - - .note-header { - span { font-size: 12px; } - - .avatar { - margin-right: 5px; - } - } - - .note-text { - font-size: 12px; - margin-left: 20px; - } + .system-note { + .note-text { + color: $gl-gray !important; } } + + .diff-file { + border: 1px solid $border-color; + border-bottom: none; + margin-left: 0; + margin-right: 0; + } } @media (max-width: $screen-xs-max) { @@ -132,3 +63,8 @@ } } } + +.discussion .timeline-entry { + margin: 0; + border-right: none; +} 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/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss index 7e86a0fe4b..32e2c020e0 100644 --- a/app/assets/stylesheets/generic/zen.scss +++ b/app/assets/stylesheets/generic/zen.scss @@ -4,7 +4,7 @@ } .zen-enter-link { - color: #888; + color: $gl-gray; position: absolute; top: 0px; right: 4px; @@ -13,7 +13,7 @@ .zen-leave-link { display: none; - color: #888; + color: $gl-text-color; position: absolute; top: 10px; right: 10px; 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..5de589109b 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -5,15 +5,19 @@ pre.code.highlight.white, background-color: #fff; color: #333; - pre.highlight, .line-numbers, .line-numbers a { + background-color: $background-color !important; + color: $gl-gray !important; + } + + pre.highlight { background-color: #fff !important; color: #333 !important; } pre.code { - border-left: 1px solid #bbb; + border-left: 1px solid $border-color; } // highlight line via anchor @@ -21,6 +25,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/appearances.scss b/app/assets/stylesheets/pages/appearances.scss index 93c02f10f3..e2070f17c3 100644 --- a/app/assets/stylesheets/pages/appearances.scss +++ b/app/assets/stylesheets/pages/appearances.scss @@ -4,7 +4,7 @@ } .appearance-light-logo-preview { - background-color: $style_color; + background-color: $background-color; max-width: 72px; padding: 10px; margin-bottom: 10px; diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index e7125c0399..051ca3792c 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -26,14 +26,6 @@ margin-top: 10px; } -.commit-stat-summary { - color: #666; - font-size: 14px; - font-weight: normal; - padding: 3px 0; - margin-bottom: 10px; -} - .commit-info-row { margin-bottom: 10px; .avatar { @@ -47,11 +39,6 @@ } .commit-box { - margin: 10px 0; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - padding: 20px 0; - .commit-title { margin: 0; } @@ -61,10 +48,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 c1103a1c2e..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; @@ -38,11 +38,11 @@ float: left; .avatar { - @include border-radius(0px); + @include border-radius(50%); } .identicon { - line-height: 40px; + line-height: 46px; } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 1557c243db..5e7e59a6af 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1,12 +1,14 @@ .diff-file { - border: 1px solid $border-color; - margin-bottom: 1em; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + border: none; + border-bottom: 1px solid #E7E9EE; .diff-header { position: relative; background: $background-color; border-bottom: 1px solid $border-color; - padding: 10px 15px; + padding: 10px 16px; color: #555; z-index: 10; @@ -45,7 +47,7 @@ overflow-y: hidden; background: #FFF; color: #333; - font-size: $code_font_size; + .old { span.idiff { background-color: #f8cbcb; @@ -82,7 +84,7 @@ border: none; margin: 0px; padding: 0px; - td { + .line_holder td { line-height: $code_line_height; font-size: $code_font_size; } @@ -367,3 +369,7 @@ white-space: pre-wrap; } +.inline-parallel-buttons { + float: right; + margin-top: -5px; +} diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 759ba6b1c2..1d565477dd 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -9,6 +9,10 @@ width: 100%; } + .ace_gutter-cell { + background-color: $background-color; + } + .cancel-btn { color: #B94A48; &:hover { @@ -32,14 +36,12 @@ .file-title { @extend .monospace; - font-size: 14px; - padding: 5px; } .editor-ref { background: $background-color; padding: 11px 15px; - border-right: 1px solid #CCC; + border-right: 1px solid $border-color; display: inline-block; margin: -5px -5px; margin-right: 10px; @@ -50,5 +52,15 @@ display: inline-block; width: 200px; } + + .form-control { + margin-top: -3px; + } + } + + .form-actions { + margin: -$gl-padding; + margin-top: 0; + padding: $gl-padding } } 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 586e7b5f8d..b5c61f7f91 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -25,8 +25,6 @@ } .issuable-context-title { - font-size: 14px; - line-height: 1.4; margin-bottom: 5px; .avatar { @@ -34,14 +32,50 @@ } label { - color: #666; + color: $gl-gray; font-weight: normal; margin-right: 4px; } } -.issuable-affix .context { - font-size: 13px; +.project-issuable-filter { + .controls { + float: right; + margin-top: 7px; + } - .btn { font-size: 13px; } + .center-top-menu { + text-align: left; + } +} + +.issuable-details { + .page-title { + margin-top: -15px; + padding: 10px 0; + margin-bottom: 0; + color: $gl-gray; + font-size: 16px; + + .author { + color: $gl-gray; + } + + .issue-id { + font-size: 19px; + color: $gl-text-color; + } + } + + .issue-title { + margin: 0; + } + + .description { + margin-top: 6px; + + p:last-child { + margin-bottom: 0; + } + } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 3572f33e91..4bf58cb4a5 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -1,6 +1,6 @@ .issues-list { .issue { - padding: 10px 15px; + padding: 10px $gl-padding; position: relative; .issue-title { @@ -10,8 +10,7 @@ } .issue-info { - color: #999; - font-size: 13px; + color: $gl-gray; } .issue-check { @@ -47,10 +46,6 @@ } } -.participants { - margin-bottom: 20px; -} - .issue-search-form { margin: 0; height: 24px; @@ -137,11 +132,6 @@ form.edit-issue { } } -h2.issue-title { - margin-top: 0; - font-weight: bold; -} - .issue-form .select2-container { width: 250px !important; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 10fce5b3da..d8c8e5ad0a 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -3,10 +3,10 @@ * */ .mr-state-widget { - background: #FAFAFA; + background: #f8fafc; margin-bottom: 20px; - color: #666; - border: 1px solid #e5e5e5; + color: $gl-gray; + border: 1px solid #eef0f2; @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05)); @include border-radius(3px); @@ -29,6 +29,14 @@ padding: 5px; line-height: 20px; + &.right { + float: right; + padding-top: 12px; + a { + color: $gl-gray; + } + } + .remove_source_checkbox { margin: 0; } @@ -36,7 +44,7 @@ } .ci_widget { - border-bottom: 1px solid #EEE; + border-bottom: 1px solid #eef0f2; i { margin-right: 4px; @@ -89,20 +97,14 @@ } } -@media(min-width: $screen-sm-max) { - .merge-request .merge-request-tabs{ - li { - a { - padding: 15px 40px; - font-size: 14px; - } - } - } -} - .merge-request .merge-request-tabs{ - margin-top: 30px; - margin-bottom: 20px; + @include nav-menu; + margin: -$gl-padding; + padding: $gl-padding; + text-align: center; + border-top: 1px solid #e7e9ed; + margin-top: 18px; + margin-bottom: 3px; } .mr_source_commit, @@ -136,8 +138,7 @@ } .merge-request-info { - color: #999; - font-size: 13px; + color: $gl-gray; } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 15e3948e40..e80dc9e84a 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -6,4 +6,8 @@ li.milestone { h4 { font-weight: bold; } + + .progress { + height: 6px; + } } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 203f9374ce..b311d26d67 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -72,9 +72,13 @@ .common-note-form { margin: 0; - background: #F9F9F9; - padding: 5px; - border: 1px solid #DDD; + background: #f8fafc; + padding: $gl-padding; + margin-left: -$gl-padding; + margin-right: -$gl-padding; + border-right: 1px solid #f1f2f4; + border-top: 1px solid #f1f2f4; + margin-bottom: -$gl-padding; } .note-form-actions { @@ -142,9 +146,9 @@ } .discussion-reply-holder { - background: #f9f9f9; + background: $background-color; padding: 10px 15px; - border-top: 1px solid #DDD; + border-top: 1px solid $border-color; } } @@ -166,6 +170,6 @@ background: #FFF; padding: 5px; margin-top: -11px; - border: 1px solid #DDD; + border: 1px solid $border-color; font-size: 13px; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 85c828ec1a..2a77f065ae 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -14,6 +14,19 @@ ul.notes { margin: 0px; padding: 0px; + .system-note { + font-size: 14px; + padding-top: 10px; + padding-bottom: 10px; + background: #f8fafc; + + .timeline-icon { + .avatar { + visibility: hidden; + } + } + } + .discussion-header, .note-header { @extend .cgray; @@ -34,10 +47,8 @@ ul.notes { content: "\00b7"; } - font-size: 13px; - a { - @extend .cgray; + color: $gl-gray; &:hover { text-decoration: underline; @@ -45,8 +56,9 @@ ul.notes { } } .author { - color: #333; - font-weight: bold; + color: #4c4e54; + margin-right: 3px; + &:hover { color: $gl-link-color; } @@ -59,7 +71,7 @@ ul.notes { margin-top: 1px; border: 1px solid #bbb; background-color: transparent; - color: #999; + color: $gl-gray; } } @@ -133,8 +145,6 @@ ul.notes { } .diff-file .notes_holder { - font-size: 13px; - line-height: 18px; font-family: $regular_font; td { @@ -176,8 +186,7 @@ ul.notes { a { margin-left: 5px; - - color: #999; + color: $gl-gray; i.fa { font-size: 16px; @@ -226,8 +235,6 @@ ul.notes { filter: alpha(opacity=0); &:hover { - width: 38px; - font-size: 20px; background: $gl-info; color: #FFF; @include show-add-diff-note; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 4d065c3bdf..53004fca35 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; @@ -38,20 +42,22 @@ h1 { margin: 0; margin-bottom: 10px; - font-size: 26px; - font-weight: bold; + font-size: 23px; + font-weight: normal; } p { - font-size: 18px; - color: #666; - display: inline; + color: #7f8fa4; } } .git-clone-holder { max-width: 600px; margin: 20px auto; + + .form-control { + background: #FFF; + } } .visibility-level-label { @@ -62,17 +68,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; @@ -162,78 +169,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; } @@ -256,10 +191,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; @@ -268,26 +203,18 @@ ul.nav.nav-projects-tabs { } .fork-namespaces { - .thumbnail { + .fork-thumbnail { + text-align: center; + margin-bottom: $gl-padding; - &.fork-exists-thumbnail { - border-color: #EEE; - - .caption { - color: #999; - } + .caption { + padding: $gl-padding 0; + min-height: 30px; } - &.fork-thumbnail { - border-color: #AAA; - - &:hover { - background-color: $hover; - } - } - - a { - text-decoration: none; + img { + @include border-radius(50%); + max-width: 100px; } } } @@ -305,10 +232,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; @@ -325,32 +265,67 @@ pre.light-well { } .projects-search-form { - max-width: 600px; - margin: 0 auto; - margin-bottom: 20px; - - input { - border-color: #BBB; - } + margin: -$gl-padding; + background-color: #f8fafc; + padding: $gl-padding; + margin-bottom: 0px; + border-top: 1px solid #e7e9ed; + border-bottom: 1px solid #e7e9ed; } -.project-row { - .project-full-name { - font-weight: bold; - font-size: 15px; - } +/* + * Projects list rendered on dashboard and user page + */ +.projects-list { + @include basic-list; - .project-description { - color: #888; - font-size: 13px; + .project-row { + padding: $gl-padding; + border-color: #f1f2f4; + margin-left: -$gl-padding; + margin-right: -$gl-padding; - p { - margin-bottom: 0; - color: #888; + &.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; + } } -.my-projects .project-row { - padding: 10px 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 81e2aa7bb9..271cc547e2 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -63,51 +63,21 @@ 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; } } } - - .blame { - img.avatar { - border: 0 none; - float: none; - margin: 0; - padding: 0; - } - td.blame-commit { - background: #f9f9f9; - min-width: 350px; - - .commit-author-link { - color: #888; - } - } - td.blame-numbers { - pre { - color: #AAA; - white-space: pre; - } - background: #f1f1f1; - border-left: 1px solid #DDD; - } - td.lines { - code { - font-family: $monospace_font; - } - } - } } .tree-ref-holder { @@ -132,20 +102,30 @@ list-style: none; margin: 0; padding: 0; - margin-bottom: 10px; + margin-bottom: 5px; .commit { - padding: 10px 15px; + padding: $gl-padding 0; .commit-row-title { - font-size: 13px; - .commit-row-message { font-weight: normal; - color: #555; } } } } #modal-remove-blob > .modal-dialog { width: 850px; } + +.blob-upload-dropzone-previews { + text-align: center; + border: 2px; + border-style: dashed; + border-color: $border-color; + min-height: 200px; +} + +.upload-link { + font-weight: normal; + color: $md-link-color; +} diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss index 77b62c3153..8d9a0aae56 100644 --- a/app/assets/stylesheets/themes/gitlab-theme.scss +++ b/app/assets/stylesheets/themes/gitlab-theme.scss @@ -9,15 +9,19 @@ @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { .page-with-sidebar { .header-logo { - background-color: $color-darker; - border-color: $color-darker; + background-color: $color; + border-color: $color; a { color: $color-light; + + h3 { + color: $color-light; + } } &:hover { - background-color: $color-dark; + background-color: $color-darker; a { color: #FFF; } @@ -83,7 +87,7 @@ } $theme-blue: #2980B9; -$theme-charcoal: #474D57; +$theme-charcoal: #333c47; $theme-graphite: #888888; $theme-gray: #373737; $theme-green: #019875; @@ -95,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/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/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb new file mode 100644 index 0000000000..3b070e65d0 --- /dev/null +++ b/app/controllers/admin/labels_controller.rb @@ -0,0 +1,58 @@ +class Admin::LabelsController < Admin::ApplicationController + before_action :set_label, only: [:show, :edit, :update, :destroy] + + def index + @labels = Label.templates.page(params[:page]).per(PER_PAGE) + end + + def show + end + + def new + @label = Label.new + end + + def edit + end + + def create + @label = Label.new(label_params) + @label.template = true + + if @label.save + redirect_to admin_labels_url, notice: "Label was created" + else + render :new + end + end + + def update + if @label.update(label_params) + redirect_to admin_labels_path, notice: 'label was successfully updated.' + else + render :edit + end + end + + def destroy + @label.destroy + @labels = Label.templates + + respond_to do |format| + format.html do + redirect_to(admin_labels_path, notice: 'Label was removed') + end + format.js + end + end + + private + + def set_label + @label = Label.find(params[:id]) + end + + def label_params + params[:label].permit(:title, :color) + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5f48895efc..f3b3b35690 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,4 +1,5 @@ require 'gon' +require 'fogbugz' class ApplicationController < ActionController::Base include Gitlab::CurrentSettings @@ -20,7 +21,7 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception helper_method :abilities, :can?, :current_application_settings - helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :git_import_enabled? + helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -55,7 +56,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 +193,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 @@ -340,6 +344,10 @@ class ApplicationController < ActionController::Base current_application_settings.import_sources.include?('google_code') end + def fogbugz_import_enabled? + current_application_settings.import_sources.include?('fogbugz') + end + def git_import_enabled? current_application_settings.import_sources.include?('git') end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 64f2786dc0..90efc37415 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -33,6 +33,7 @@ class AutocompleteController < ApplicationController @users = @users.non_ldap if params[:skip_ldap] == 'true' @users = @users.search(params[:search]) if params[:search].present? @users = @users.active + @users = @users.reorder(:name) @users = @users.page(params[:page]).per(PER_PAGE) unless params[:search].present? diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index da96171e88..467d0f81ac 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -1,6 +1,21 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController before_action :event_filter + def index + @projects = current_user.authorized_projects.sorted_by_activity.non_archived + @projects = @projects.includes(:namespace) + @last_push = current_user.recent_push + + respond_to do |format| + format.html + format.atom do + event_filter + load_events + render layout: false + end + end + end + def starred @projects = current_user.starred_projects @projects = @projects.includes(:namespace, :forked_from_project, :tags) diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb new file mode 100644 index 0000000000..f4354c6d8c --- /dev/null +++ b/app/controllers/dashboard/snippets_controller.rb @@ -0,0 +1,10 @@ +class Dashboard::SnippetsController < Dashboard::ApplicationController + def index + @snippets = SnippetsFinder.new.execute(current_user, + filter: :by_user, + user: current_user, + scope: params[:scope] + ) + @snippets = @snippets.page(params[:page]).per(PER_PAGE) + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index d745131694..4ebb3d7276 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,23 +1,8 @@ class DashboardController < Dashboard::ApplicationController - before_action :load_projects before_action :event_filter, only: :activity respond_to :html - def show - @projects = @projects.includes(:namespace) - @last_push = current_user.recent_push - - respond_to do |format| - format.html - format.atom do - event_filter - load_events - render layout: false - end - end - end - def merge_requests @merge_requests = get_merge_requests_collection @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) @@ -50,12 +35,15 @@ class DashboardController < Dashboard::ApplicationController protected - def load_projects - @projects = current_user.authorized_projects.sorted_by_activity.non_archived - 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/explore/application_controller.rb b/app/controllers/explore/application_controller.rb index 4b275033d2..461fc059a3 100644 --- a/app/controllers/explore/application_controller.rb +++ b/app/controllers/explore/application_controller.rb @@ -1,3 +1,5 @@ class Explore::ApplicationController < ApplicationController + skip_before_action :authenticate_user!, :reject_blocked + layout 'explore' end diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index 55cda0cff1..9575a87ee4 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -1,7 +1,4 @@ class Explore::GroupsController < Explore::ApplicationController - skip_before_action :authenticate_user!, - :reject_blocked, :set_current_user_for_observers - def index @groups = GroupsFinder.new.execute(current_user) @groups = @groups.search(params[:search]) if params[:search].present? diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 6c733c1ae4..a5aeaed66c 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -1,7 +1,4 @@ class Explore::ProjectsController < Explore::ApplicationController - skip_before_action :authenticate_user!, - :reject_blocked - def index @projects = ProjectsFinder.new.execute(current_user) @tags = @projects.tags_on(:tags) diff --git a/app/controllers/explore/snippets_controller.rb b/app/controllers/explore/snippets_controller.rb new file mode 100644 index 0000000000..b70ac51d06 --- /dev/null +++ b/app/controllers/explore/snippets_controller.rb @@ -0,0 +1,6 @@ +class Explore::SnippetsController < Explore::ApplicationController + def index + @snippets = SnippetsFinder.new.execute(current_user, filter: :all) + @snippets = @snippets.page(params[:page]).per(PER_PAGE) + end +end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 55b2a728bd..9eb160f619 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -4,7 +4,7 @@ class GroupsController < Groups::ApplicationController before_action :group, except: [:new, :create] # Authorize - before_action :authorize_read_group!, except: [:new, :create] + before_action :authorize_read_group!, except: [:show, :new, :create] before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects] before_action :authorize_create_group!, only: [:new, :create] @@ -14,6 +14,10 @@ class GroupsController < Groups::ApplicationController layout :determine_layout + def index + redirect_to(current_user ? dashboard_groups_path : explore_groups_path) + end + def new @group = Group.new end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 71831c5380..ad00948da5 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -1,4 +1,6 @@ class HelpController < ApplicationController + skip_before_action :authenticate_user!, :reject_blocked + layout 'help' def index diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb new file mode 100644 index 0000000000..849646cd66 --- /dev/null +++ b/app/controllers/import/fogbugz_controller.rb @@ -0,0 +1,104 @@ +class Import::FogbugzController < Import::BaseController + before_action :verify_fogbugz_import_enabled + before_action :user_map, only: [:new_user_map, :create_user_map] + + rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized + + def new + + end + + def callback + begin + res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys) + rescue + # If the URI is invalid various errors can occur + return redirect_to new_import_fogbugz_path, alert: 'Could not connect to FogBugz, check your URL' + end + session[:fogbugz_token] = res.get_token + session[:fogbugz_uri] = params[:uri] + + redirect_to new_user_map_import_fogbugz_path + end + + def new_user_map + + end + + def create_user_map + user_map = params[:users] + + unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? } + flash.now[:alert] = 'All users must have a name.' + + render 'new_user_map' and return + end + + session[:fogbugz_user_map] = user_map + + flash[:notice] = 'The user map has been saved. Continue by selecting the projects you want to import.' + + redirect_to status_import_fogbugz_path + end + + def status + unless client.valid? + return redirect_to new_import_fogbugz_path + end + + @repos = client.repos + + @already_added_projects = current_user.created_projects.where(import_type: 'fogbugz') + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.reject! { |repo| already_added_projects_names.include? repo.name } + end + + def jobs + jobs = current_user.created_projects.where(import_type: 'fogbugz').to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id] + repo = client.repo(@repo_id) + fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] } + @target_namespace = current_user.namespace + @project_name = repo.name + + namespace = @target_namespace + + umap = session[:fogbugz_user_map] || client.user_map + + @project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, namespace, current_user, umap).execute + end + + private + + def client + @client ||= Gitlab::FogbugzImport::Client.new(token: session[:fogbugz_token], uri: session[:fogbugz_uri]) + end + + def user_map + @user_map ||= begin + user_map = client.user_map + + stored_user_map = session[:fogbugz_user_map] + user_map.update(stored_user_map) if stored_user_map + + user_map + end + end + + def fogbugz_unauthorized(exception) + redirect_to new_import_fogbugz_path, alert: exception.message + end + + def import_params + params.permit(:uri, :email, :password) + end + + def verify_fogbugz_import_enabled + not_found! unless fogbugz_import_enabled? + end +end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index eb3c823353..8ef10a17f5 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -24,7 +24,7 @@ class InvitesController < ApplicationController path = if current_user - dashboard_path + dashboard_projects_path else new_user_session_path end @@ -73,7 +73,7 @@ class InvitesController < ApplicationController path = group_path(group) else label = "who knows what" - path = dashboard_path + path = dashboard_projects_path end [label, path] diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb index 83eec1bf4a..282012c60a 100644 --- a/app/controllers/namespaces_controller.rb +++ b/app/controllers/namespaces_controller.rb @@ -14,7 +14,7 @@ class NamespacesController < ApplicationController if user redirect_to user_path(user) - elsif group && can?(current_user, :read_group, group) + elsif group redirect_to group_path(group) elsif current_user.nil? authenticate_user! diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 100d3d3b31..d7be212c33 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -18,6 +18,12 @@ class Projects::BlobController < Projects::ApplicationController before_action :after_edit_path, only: [:edit, :update] def new + @title = 'Upload' + @placeholder = 'Upload new file' + @button_title = 'Upload file' + @form_path = namespace_project_create_blob_path(@project.namespace, @project, @id) + @method = :post + commit unless @repository.empty? end @@ -26,14 +32,25 @@ class Projects::BlobController < Projects::ApplicationController if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) + respond_to do |format| + format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } + format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } } + end else flash[:alert] = result[:message] - render :new + respond_to do |format| + format.html { render :new } + format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } } + end end end def show + @title = "Replace #{@blob.name}" + @placeholder = @title + @button_title = 'Replace file' + @form_path = namespace_project_update_blob_path(@project.namespace, @project, @id) + @method = :put end def edit @@ -45,10 +62,16 @@ class Projects::BlobController < Projects::ApplicationController if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to after_edit_path + respond_to do |format| + format.html { redirect_to after_edit_path } + format.json { render json: { message: "success", filePath: after_edit_path } } + end else flash[:alert] = result[:message] - render :edit + respond_to do |format| + format.html { render :edit } + format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } } + end end end @@ -146,11 +169,19 @@ class Projects::BlobController < Projects::ApplicationController @file_path = if action_name.to_s == 'create' + if params[:file].present? + params[:file_name] = params[:file].original_filename + end File.join(@path, File.basename(params[:file_name])) else @path end + if params[:file].present? + params[:content] = Base64.encode64(params[:file].read) + params[:encoding] = 'base64' + end + @commit_params = { file_path: @file_path, current_branch: @current_branch, diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 9e72597ea8..8a785076bb 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -13,10 +13,14 @@ class Projects::ForksController < Projects::ApplicationController @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute if @forked_project.saved? && @forked_project.forked? - redirect_to( - namespace_project_path(@forked_project.namespace, @forked_project), - notice: 'Project was successfully forked.' - ) + if @forked_project.import_in_progress? + redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project) + else + redirect_to( + namespace_project_path(@forked_project.namespace, @forked_project), + notice: 'Project was successfully forked.' + ) + end else render :error 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/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 89d1ff5cfc..2d1c335ec1 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -91,7 +91,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController log_audit_event(@project_member, action: :destroy) respond_to do |format| - format.html { redirect_to dashboard_path } + format.html { redirect_to dashboard_projects_path } format.js { render nothing: true } 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 a22821ec20..4a82ddf8d0 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -2,15 +2,14 @@ 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, :jira_issue_transition_id, :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/projects_controller.rb b/app/controllers/projects_controller.rb index 20e82dbe90..e61f585bcb 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -10,6 +10,10 @@ class ProjectsController < ApplicationController layout :determine_layout + def index + redirect_to(current_user ? root_path : explore_root_path) + end + def new @project = Project.new end @@ -105,7 +109,7 @@ class ProjectsController < ApplicationController if request.referer.include?('/admin') redirect_to admin_namespaces_projects_path else - redirect_to dashboard_path + redirect_to dashboard_projects_path end rescue Projects::DestroyService::DestroyError => ex redirect_to edit_project_path(@project), alert: ex.message diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index fdfe00dc13..54171ff67c 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -6,10 +6,10 @@ # # For users who haven't customized the setting, we simply delegate to # `DashboardController#show`, which is the default. -class RootController < DashboardController - before_action :redirect_to_custom_dashboard, only: [:show] +class RootController < Dashboard::ProjectsController + before_action :redirect_to_custom_dashboard, only: [:index] - def show + def index super end @@ -20,6 +20,7 @@ class RootController < DashboardController case current_user.dashboard when 'stars' + flash.keep redirect_to starred_dashboard_projects_path else return 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/snippets_controller.rb b/app/controllers/snippets_controller.rb index 8e7e45c781..9f9f9a92f1 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -24,13 +24,9 @@ class SnippetsController < ApplicationController scope: params[:scope] }). page(params[:page]).per(PER_PAGE) - if @user == current_user - render 'current_user_index' - else - render 'user_index' - end + render 'index' else - @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(PER_PAGE) + redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path) end end 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/application_helper.rb b/app/helpers/application_helper.rb index f4fa06f147..0a9630e173 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -201,7 +201,7 @@ module ApplicationHelper class: "#{html_class} js-timeago", datetime: time.to_time.getutc.iso8601, title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'), - data: { toggle: 'tooltip', placement: placement } + data: { toggle: 'tooltip', placement: placement, container: 'body' } element += javascript_tag "$('.js-timeago').timeago()" unless skip_js diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index bbc305e109..c4dcada79c 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 @@ -30,6 +30,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/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index d0fae255a0..e0816f4e71 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -17,6 +17,14 @@ module GitlabRoutingHelper namespace_project_path(project.namespace, project, *args) end + def project_files_path(project, *args) + namespace_project_tree_path(project.namespace, project, @ref || project.repository.root_ref) + end + + def project_commits_path(project, *args) + namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) + end + def activity_project_path(project, *args) activity_namespace_project_path(project.namespace, project, *args) end 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 8473d6d75d..df37be51ce 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -31,4 +31,26 @@ module PageLayoutHelper @fluid_layout end end + + def blank_container(enabled = false) + if @blank_container.nil? + @blank_container = enabled + else + @blank_container + end + end + + def container_class + css_class = "container-fluid" + + unless fluid_layout + css_class += " container-limited" + end + + if blank_container + css_class += " container-blank" + end + + css_class + 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/projects_helper.rb b/app/helpers/projects_helper.rb index cff0010861..9c259bc1c2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -43,24 +43,22 @@ module ProjectsHelper end end - def project_title(project) - if project.group - content_tag :span do - link_to( - simple_sanitize(project.group.name), group_path(project.group) - ) + ' / ' + - link_to(simple_sanitize(project.name), - project_path(project)) - end - else - owner = project.namespace.owner - content_tag :span do - link_to( - simple_sanitize(owner.name), user_path(owner) - ) + ' / ' + - link_to(simple_sanitize(project.name), - project_path(project)) + def project_title(project, name = nil, url = nil) + namespace_link = + if project.group + link_to(simple_sanitize(project.group.name), group_path(project.group)) + else + owner = project.namespace.owner + link_to(simple_sanitize(owner.name), user_path(owner)) end + + project_link = link_to(simple_sanitize(project.name), project_path(project)) + + full_title = namespace_link + ' / ' + project_link + full_title += ' · '.html_safe + link_to(simple_sanitize(name), url) if name + + content_tag :span do + full_title end end @@ -323,6 +321,45 @@ module ProjectsHelper end end + def current_ref + @ref || @repository.try(:root_ref) + end + + def detect_project_title(project) + name, url = + if current_controller? 'wikis' + ['Wiki', get_project_wiki_path(project)] + elsif current_controller? 'project_members' + ['Members', namespace_project_project_members_path(project.namespace, project)] + elsif current_controller? 'labels' + ['Labels', namespace_project_labels_path(project.namespace, project)] + elsif current_controller? 'members' + ['Members', project_files_path(project)] + elsif current_controller? 'commits' + ['Commits', project_commits_path(project)] + elsif current_controller? 'graphs' + ['Graphs', namespace_project_graph_path(project.namespace, project, current_ref)] + elsif current_controller? 'network' + ['Network', namespace_project_network_path(project.namespace, project, current_ref)] + elsif current_controller? 'milestones' + ['Milestones', namespace_project_milestones_path(project.namespace, project)] + elsif current_controller? 'snippets' + ['Snippets', namespace_project_snippets_path(project.namespace, project)] + elsif current_controller? 'issues' + ['Issues', namespace_project_issues_path(project.namespace, project)] + elsif current_controller? 'merge_requests' + ['Merge Requests', namespace_project_merge_requests_path(project.namespace, project)] + elsif current_controller? 'tree', 'blob' + ['Files', project_files_path(project)] + elsif current_path? 'projects#activity' + ['Activity', activity_project_path(project)] + else + [nil, nil] + end + + project_title(project, name, url) + end + private def filename_path(project, filename) 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/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/appearance.rb b/app/models/appearance.rb index 1f8c4a2204..95f2ab3d28 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -1,3 +1,18 @@ +# == Schema Information +# +# Table name: appearances +# +# id :integer not null, primary key +# title :string(255) +# description :text +# logo :string(255) +# updated_by :integer +# created_at :datetime +# updated_at :datetime +# dark_logo :string(255) +# light_logo :string(255) +# + class Appearance < ActiveRecord::Base validates :title, presence: true validates :description, presence: true diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index a0d7c895de..a0bf444bd8 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -84,7 +84,7 @@ class ApplicationSetting < ActiveRecord::Base default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], - import_sources: ['github','bitbucket','gitlab','gitorious','google_code','git'] + import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'] ) end 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/label.rb b/app/models/label.rb index 230631b518..4a22bd5340 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -24,7 +24,7 @@ class Label < ActiveRecord::Base validates :color, format: { with: /\A#[0-9A-Fa-f]{6}\Z/ }, allow_blank: false - validates :project, presence: true + validates :project, presence: true, unless: Proc.new { |service| service.template? } # Don't allow '?', '&', and ',' for label titles validates :title, @@ -34,6 +34,8 @@ class Label < ActiveRecord::Base default_scope { order(title: :asc) } + scope :templates, -> { where(template: true) } + alias_attribute :name, :title def self.reference_prefix @@ -78,4 +80,8 @@ class Label < ActiveRecord::Base def open_issues_count issues.opened.count end + + def template? + template + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 72e8e62956..5a7650a1af 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 deb1e24710..27de2daae7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -43,6 +43,8 @@ class Project < ActiveRecord::Base extend Gitlab::ConfigHelper extend Enumerize + UNKNOWN_IMPORT_URL = 'http://unknown.git' + default_value_for :archived, false default_value_for :visibility_level, gitlab_config_features.visibility_level default_value_for :issues_enabled, gitlab_config_features.issues @@ -74,6 +76,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 @@ -147,7 +150,7 @@ class Project < ActiveRecord::Base validates_uniqueness_of :path, scope: :namespace_id validates :import_url, format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' }, - if: :import? + if: :external_import? validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :avatar_type, @@ -279,7 +282,13 @@ class Project < ActiveRecord::Base end def add_import_job - RepositoryImportWorker.perform_in(2.seconds, id) + if forked? + unless RepositoryForkWorker.perform_async(id, forked_from_project.path_with_namespace, self.namespace.path) + import_fail + end + else + RepositoryImportWorker.perform_in(2.seconds, id) + end end def clear_import_data @@ -287,6 +296,10 @@ class Project < ActiveRecord::Base end def import? + external_import? || forked? + end + + def external_import? import_url.present? end @@ -407,6 +420,15 @@ class Project < ActiveRecord::Base end end + def create_labels + Label.templates.each do |label| + label = label.dup + label.template = nil + label.project_id = self.id + label.save + end + end + def find_service(list, name) list.find { |service| service.to_param == name } end @@ -633,6 +655,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 } @@ -717,14 +740,8 @@ class Project < ActiveRecord::Base end def create_repository - if forked? - if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path) - true - else - errors.add(:base, 'Failed to fork repository via gitlab-shell') - false - end - else + # Forked import is handled asynchronously + unless forked? if gitlab_shell.add_repository(path_with_namespace) true else 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/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 36d9874edd..7cd5e89250 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -34,6 +34,12 @@ class SlackService < Service 'slack' end + def help + 'This service sends notifications to your Slack channel.#{code}"
+ end
+
+ # 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 d38b6fb512..9fe42f9ee0 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|
@@ -29,10 +31,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
@@ -50,7 +48,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 457bd31e23..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,6 +317,11 @@ 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 $mpid is running."
@@ -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..7218a4d2f2 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,33 @@ 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;
+ # Do not buffer Git HTTP responses
+ proxy_buffering off;
- # proxy_pass http://gitlab-git-http-server;
- # }
+ # The following settings only work with NGINX 1.7.11 or newer
+ #
+ # # Pass chunked request bodies to gitlab-git-http-server as-is
+ # proxy_request_buffering off;
+ # proxy_http_version 1.1;
+
+ 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;
+ }
## 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..7dabfba87e 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,33 @@ 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;
- # }
+ # Do not buffer Git HTTP responses
+ proxy_buffering off;
+
+ # The following settings only work with NGINX 1.7.11 or newer
+ #
+ # # Pass chunked request bodies to gitlab-git-http-server as-is
+ # proxy_request_buffering off;
+ # proxy_http_version 1.1;
+
+ 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 2b9688c1b4..b8eb13a4fe 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..39541c0b9c
--- /dev/null
+++ b/lib/tasks/services.rake
@@ -0,0 +1,98 @@
+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] %>
+
+```
+
+### Get <%= service[:title] %> service settings
+
+Get <%= service[:title] %> service settings for a project.
+
+```
+GET /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/import/fogbugz_controller_spec.rb b/spec/controllers/import/fogbugz_controller_spec.rb
new file mode 100644
index 0000000000..27b11267d2
--- /dev/null
+++ b/spec/controllers/import/fogbugz_controller_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+require_relative 'import_spec_helper'
+
+describe Import::FogbugzController do
+ include ImportSpecHelper
+
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET status' do
+ before do
+ @repo = OpenStruct.new(name: 'vim')
+ stub_client(valid?: true)
+ end
+
+ it 'assigns variables' do
+ @project = create(:project, import_type: 'fogbugz', creator_id: user.id)
+ stub_client(repos: [@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([@repo])
+ end
+
+ it 'does not show already added project' do
+ @project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim')
+ stub_client(repos: [@repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([@project])
+ expect(assigns(:repos)).to eq([])
+ end
+ end
+end
diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb
index 9c8619722c..7743695871 100644
--- a/spec/controllers/namespaces_controller_spec.rb
+++ b/spec/controllers/namespaces_controller_spec.rb
@@ -46,13 +46,11 @@ describe NamespacesController do
context "when the project doesn't have public projects" do
context "when not signed in" do
- it "redirects to the sign in page" do
+ it "does not redirect to the sign in page" do
get :show, id: group.path
-
- expect(response).to redirect_to(new_user_session_path)
+ expect(response).not_to redirect_to(new_user_session_path)
end
end
-
context "when signed in" do
before do
sign_in(user)
@@ -86,10 +84,10 @@ describe NamespacesController do
end
context "when the user doesn't have access to the project" do
- it "responds with status 404" do
+ it "redirects to the group's page" do
get :show, id: group.path
- expect(response.status).to eq(404)
+ expect(response).to redirect_to(group_path(group))
end
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/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb
index abbbf6855f..64dfe8f34e 100644
--- a/spec/controllers/root_controller_spec.rb
+++ b/spec/controllers/root_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe RootController do
- describe 'GET show' do
+ describe 'GET index' do
context 'with a user' do
let(:user) { create(:user) }
@@ -16,15 +16,15 @@ describe RootController do
end
it 'redirects to their specified dashboard' do
- get :show
+ get :index
expect(response).to redirect_to starred_dashboard_projects_path
end
end
context 'who uses the default dashboard setting' do
it 'renders the default dashboard' do
- get :show
- expect(response).to render_template 'dashboard/show'
+ get :index
+ expect(response).to render_template 'dashboard/projects/index'
end
end
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 0f9780356b..af5d043cf0 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -156,14 +156,6 @@ describe UploadsController do
end
context "when the project doesn't have public projects" do
- context "when not signed in" do
- it "redirects to the sign in page" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png"
-
- expect(response).to redirect_to(new_user_session_path)
- end
- end
-
context "when signed in" do
before do
sign_in(user)
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/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index ad157d742f..f81a3c117f 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -6,7 +6,7 @@ describe "Dashboard Feed", feature: true do
context "projects atom feed via private token" do
it "should render projects atom feed" do
- visit dashboard_path(:atom, private_token: user.private_token)
+ visit dashboard_projects_path(:atom, private_token: user.private_token)
expect(body).to have_selector('feed title')
end
end
@@ -20,7 +20,7 @@ describe "Dashboard Feed", feature: true do
project.team << [user, :master]
issue_event(issue, user)
note_event(note, user)
- visit dashboard_path(:atom, private_token: user.private_token)
+ visit dashboard_projects_path(:atom, private_token: user.private_token)
end
it "should have issue opened event" do
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/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index 9bc6145dda..8f645438cf 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -70,7 +70,7 @@ describe 'Profile > Preferences', feature: true do
expect(page.current_path).to eq starred_dashboard_projects_path
click_link 'Your Projects'
- expect(page.current_path).to eq dashboard_path
+ expect(page.current_path).to eq dashboard_projects_path
end
end
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
index c38cddbb90..788581a26c 100644
--- a/spec/features/security/dashboard_access_spec.rb
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -4,7 +4,7 @@ describe "Dashboard access", feature: true do
include AccessMatchers
describe "GET /dashboard" do
- subject { dashboard_path }
+ subject { dashboard_projects_path }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
@@ -40,7 +40,7 @@ describe "Dashboard access", feature: true do
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :visitor }
end
describe "GET /projects/new" do
diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group_access_spec.rb
index 8ce1538860..4b78e3a61f 100644
--- a/spec/features/security/group_access_spec.rb
+++ b/spec/features/security/group_access_spec.rb
@@ -68,7 +68,7 @@ describe 'Group access', feature: true do
it { is_expected.to be_allowed_for group_member(:guest) }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :visitor }
end
context 'with no projects' do
@@ -77,8 +77,8 @@ describe 'Group access', feature: true do
it { is_expected.to be_allowed_for group_member(:reporter) }
it { is_expected.to be_allowed_for group_member(:guest) }
it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_denied_for :user }
- it { is_expected.to be_denied_for :visitor }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :visitor }
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/syntax_highlight_spec.js.coffee b/spec/javascripts/syntax_highlight_spec.js.coffee
new file mode 100644
index 0000000000..6a73b6bf32
--- /dev/null
+++ b/spec/javascripts/syntax_highlight_spec.js.coffee
@@ -0,0 +1,42 @@
+#= require syntax_highlight
+
+describe 'Syntax Highlighter', ->
+ stubUserColorScheme = (value) ->
+ window.gon ?= {}
+ window.gon.user_color_scheme = value
+
+ describe 'on a js-syntax-highlight element', ->
+ beforeEach ->
+ fixture.set('')
+
+ it 'applies syntax highlighting', ->
+ stubUserColorScheme('monokai')
+
+ $('.js-syntax-highlight').syntaxHighlight()
+
+ expect($('.js-syntax-highlight')).toHaveClass('monokai')
+
+ describe 'on a parent element', ->
+ beforeEach ->
+ fixture.set """
+
+
+
+
+
+ """
+
+ it 'applies highlighting to all applicable children', ->
+ stubUserColorScheme('monokai')
+
+ $('.parent').syntaxHighlight()
+
+ expect($('.parent, .foo')).not.toHaveClass('monokai')
+ expect($('.monokai').length).toBe(2)
+
+ it 'prevents an infinite loop when no matches exist', ->
+ fixture.set('')
+
+ highlight = -> $('div').syntaxHighlight()
+
+ expect(highlight).not.toThrow()
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 8c8df21827..d1b7de8db7 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/syntax_highlight_filter_spec.rb b/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb
new file mode 100644
index 0000000000..6a49067372
--- /dev/null
+++ b/spec/lib/gitlab/markdown/syntax_highlight_filter_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe SyntaxHighlightFilter do
+ include FilterSpecHelper
+
+ it 'highlights valid code blocks' do
+ result = filter('def fun end')
+ expect(result.to_html).to eq("def fun end
\n")
+ end
+
+ it 'passes through invalid code blocks' do
+ allow_any_instance_of(SyntaxHighlightFilter).to receive(:block_code).and_raise(StandardError)
+
+ result = filter('This is a test
')
+ expect(result.to_html).to eq('This is a test
')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index 02d923b036..b2155fab59 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -130,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 13e83ae303..f76f1a575f 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 d3a90af6ab..1ce46a1f94 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/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 942768fa25..35b3d3e296 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -2,11 +2,12 @@ require "spec_helper"
describe API::API, api: true do
include ApiHelpers
+ let(:base_time) { Time.now }
let(:user) { create(:user) }
let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
- let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
- let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test") }
- let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test") }
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
+ let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.seconds) }
+ let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
@@ -74,8 +75,8 @@ describe API::API, api: true do
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- expect(json_response.last['id']).to eq(@mr_earlier.id)
- expect(json_response.first['id']).to eq(@mr_later.id)
+ response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort)
end
it "should return an array of merge_requests in descending order" do
@@ -83,8 +84,8 @@ describe API::API, api: true do
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- expect(json_response.first['id']).to eq(@mr_later.id)
- expect(json_response.last['id']).to eq(@mr_earlier.id)
+ response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort.reverse)
end
it "should return an array of merge_requests ordered by updated_at" do
@@ -92,17 +93,17 @@ describe API::API, api: true do
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- expect(json_response.last['id']).to eq(@mr_earlier.id)
- expect(json_response.first['id']).to eq(@mr_later.id)
+ response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
+ expect(response_dates).to eq(response_dates.sort.reverse)
end
it "should return an array of merge_requests ordered by created_at" do
- get api("/projects/#{project.id}/merge_requests?sort=created_at", user)
+ get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
- expect(json_response.last['id']).to eq(@mr_earlier.id)
- expect(json_response.first['id']).to eq(@mr_later.id)
+ response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort)
end
end
end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 6d29a28580..fb3b235446 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -5,64 +5,57 @@ 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)
+ it "should delete #{service}" do
+ delete api("/projects/#{project.id}/services/#{dashed_service}", user)
+
+ expect(response.status).to eq(200)
+ expect(project.send(service_method).activated?).to be_falsey
+ end
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: "http://ci.example.com/projects/1", active: true
+ describe "GET /projects/:id/services/#{service.dasherize}" do
+ include_context service
- expect(response.status).to eq(404)
- end
+ it "should get #{service} settings" do
+ get 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: "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)
+ end
end
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index dd04582669..dfa18f69e0 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -206,7 +206,7 @@ end
# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests
describe DashboardController, "routing" do
it "to #index" do
- expect(get("/dashboard")).to route_to('dashboard#show')
+ expect(get("/dashboard")).to route_to('dashboard/projects#index')
end
it "to #issues" do
@@ -220,8 +220,8 @@ end
# root / root#show
describe RootController, 'routing' do
- it 'to #show' do
- expect(get('/')).to route_to('root#show')
+ it 'to #index' do
+ expect(get('/')).to route_to('root#index')
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/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index b1d362b422..bba415a0ae 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -17,6 +17,14 @@ describe Projects::CreateService do
expect(project.services).not_to be_empty
end
+ it 'creates labels on Project creation if there are templates' do
+ Label.create(title: "bug", template: true)
+ project = create_project(@user, @opts)
+ project.reload
+
+ expect(project.labels).not_to be_empty
+ end
+
context 'user namespace' do
before do
@project = create_project(@user, @opts)
@@ -101,6 +109,17 @@ describe Projects::CreateService do
end
end
end
+
+ context 'repository creation' do
+ it 'should synchronously create the repository' do
+ expect_any_instance_of(Project).to receive(:create_repository)
+
+ project = create_project(@user, @opts)
+ expect(project).to be_valid
+ expect(project.owner).to eq(@user)
+ expect(project.namespace).to eq(@user.namespace)
+ end
+ end
end
def create_project(user, opts)
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
new file mode 100644
index 0000000000..f12e09c58c
--- /dev/null
+++ b/spec/services/projects/download_service_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Projects::DownloadService do
+ describe 'File service' do
+ before do
+ @user = create :user
+ @project = create :project, creator_id: @user.id, namespace: @user.namespace
+ end
+
+ context 'for a URL that is not on whitelist' do
+ before do
+ url = 'https://code.jquery.com/jquery-2.1.4.min.js'
+ @link_to_file = download_file(@project, url)
+ end
+
+ it { expect(@link_to_file).to eq(nil) }
+ end
+
+ context 'for URLs that are on the whitelist' do
+ before do
+ sham_rack_app = ShamRack.at('mycompany.fogbugz.com').stub
+ sham_rack_app.register_resource('/rails_sample.jpg', File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'), 'image/jpg')
+ sham_rack_app.register_resource('/doc_sample.txt', File.read(Rails.root + 'spec/fixtures/doc_sample.txt'), 'text/plain')
+ end
+
+ after do
+ ShamRack.unmount_all
+ end
+
+ context 'an image file' do
+ before do
+ url = 'http://mycompany.fogbugz.com/rails_sample.jpg'
+ @link_to_file = download_file(@project, url)
+ end
+
+ it { expect(@link_to_file).to have_key('alt') }
+ it { expect(@link_to_file).to have_key('url') }
+ it { expect(@link_to_file).to have_key('is_image') }
+ it { expect(@link_to_file['is_image']).to be true }
+ it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
+ it { expect(@link_to_file['url']).to match('rails_sample.jpg') }
+ it { expect(@link_to_file['alt']).to eq('rails_sample') }
+ end
+
+ context 'a txt file' do
+ before do
+ url = 'http://mycompany.fogbugz.com/doc_sample.txt'
+ @link_to_file = download_file(@project, url)
+ end
+
+ it { expect(@link_to_file).to have_key('alt') }
+ it { expect(@link_to_file).to have_key('url') }
+ it { expect(@link_to_file).to have_key('is_image') }
+ it { expect(@link_to_file['is_image']).to be false }
+ it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
+ it { expect(@link_to_file['url']).to match('doc_sample.txt') }
+ it { expect(@link_to_file['alt']).to eq('doc_sample.txt') }
+ end
+ end
+ end
+
+ def download_file(repository, url)
+ Projects::DownloadService.new(repository, url).execute
+ end
+end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index c04e842c67..7c4bb74b77 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -28,8 +28,7 @@ describe Projects::ForkService do
context 'fork project failure' do
it "fails due to transaction failure" do
@to_project = fork_project(@from_project, @to_user, false)
- expect(@to_project.errors).not_to be_empty
- expect(@to_project.errors[:base]).to include("Failed to fork repository via gitlab-shell")
+ expect(@to_project.import_failed?)
end
end
@@ -100,7 +99,7 @@ describe Projects::ForkService do
end
def fork_project(from_project, user, fork_success = true, params = {})
- allow_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(fork_success)
+ allow(RepositoryForkWorker).to receive(:perform_async).and_return(fork_success)
Projects::ForkService.new(from_project, user, params).execute
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index b8d292cfa4..2e536c1982 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -28,6 +28,7 @@ RSpec.configure do |config|
config.include LoginHelpers, type: :feature
config.include LoginHelpers, type: :request
config.include StubConfiguration
+ config.include RelativeUrl, type: feature
config.include TestEnv
config.infer_spec_type_from_file_location!
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 9df226c3af..7500d0fdf8 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -27,6 +27,9 @@ module MarkdownMatchers
match do |actual|
expect(actual).to have_selector('img.emoji', count: 10)
+
+ image = actual.at_css('img.emoji')
+ expect(image['src'].to_s).to start_with(Gitlab.config.gitlab.url + '/assets')
end
end
diff --git a/spec/support/relative_url.rb b/spec/support/relative_url.rb
new file mode 100644
index 0000000000..72e3ccce75
--- /dev/null
+++ b/spec/support/relative_url.rb
@@ -0,0 +1,8 @@
+# Fix route helpers in tests (e.g. root_path, ...)
+module RelativeUrl
+ extend ActiveSupport::Concern
+
+ included do
+ default_url_options[:script_name] = Rails.application.config.relative_url_root
+ end
+end
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/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)
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
new file mode 100644
index 0000000000..aa03110696
--- /dev/null
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe RepositoryForkWorker do
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:project, forked_from_project: project) }
+
+ subject { RepositoryForkWorker.new }
+
+ describe "#perform" do
+ it "creates a new repository from a fork" do
+ expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).with(
+ project.path_with_namespace,
+ fork_project.namespace.path).
+ and_return(true)
+ expect(ProjectCacheWorker).to receive(:perform_async)
+
+ subject.perform(project.id,
+ project.path_with_namespace,
+ fork_project.namespace.path)
+ end
+
+ it "handles bad fork" do
+ expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
+ subject.perform(project.id,
+ project.path_with_namespace,
+ fork_project.namespace.path)
+ end
+ end
+end