diff options
362 files changed, 3218 insertions, 1840 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5524c9a7fcb..ddf4e31204a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -45,9 +45,9 @@ spinach:other: - ruby - mysql -jasmine:ci: +teaspoon: script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake jasmine:ci + - RAILS_ENV=test bundle exec teaspoon tags: - ruby - mysql @@ -1 +1,2 @@ --color +--format Fuubar diff --git a/CHANGELOG b/CHANGELOG index 0c20479c8b1..93983c63593 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,14 +1,27 @@ Please view this file on the master branch, on stable branches it's out of date. v 7.13.0 (unreleased) + - Only enable HSTS header for HTTPS and port 443 (Stan Hu) + - Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu) + - Fix redirection to home page URL for unauthorized users (Daniel Gerhardt) + - Add branch switching support for graphs (Daniel Gerhardt) + - Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt) + - Remove link leading to a 404 error in Deploy Keys page (Stan Hu) + - Add support for unlocking users in admin settings (Stan Hu) + - Add Irker service configuration options (Stan Hu) + - Fix order of issues imported from GitHub (Hiroyuki Sato) + - Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart) - Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI - Add `two_factor_enabled` field to admin user API (Stan Hu) - Fix invalid timestamps in RSS feeds (Rowan Wookey) - - Fix error when deleting a user who has projects (Stan Hu) - Fix downloading of patches on public merge requests when user logged out (Stan Hu) + - The password for the default administrator (root) account has been changed from "5iveL!fe" to "password". + - Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu) + - Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu) - Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu) - Support commenting on diffs in side-by-side mode (Stan Hu) - Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu) + - Return 40x error codes if branch could not be deleted in UI (Stan Hu) - Remove project visibility icons from dashboard projects list - Rename "Design" profile settings page to "Preferences". - Allow users to customize their default Dashboard page. @@ -19,18 +32,47 @@ v 7.13.0 (unreleased) - Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled. - Show a user's Two-factor Authentication status in the administration area. - Explicit error when commit not found in the CI + - Improve performance for issue and merge request pages + - Users with guest access level can not set assignee, labels or milestones for issue and merge request + - Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels + - Better performance for pages with events list, issues list and commits list + - Faster automerge check and merge itself when source and target branches are in same repository + - Correctly show anonymous authorized applications under Profile > Applications. + - Query Optimization in MySQL. + - Allow users to be blocked and unblocked via the API + - Use native Postgres database cleaning during backup restore + - Redesign project page. Show README as default instead of activity. Move project activity to separate page + - Make left menu more hierarchical and less contextual by adding back item at top + - A fork can’t have a visibility level that is greater than the original project. + - Faster code search in repository and wiki. Fixes search page timeout for big repositories + - Allow administrators to disable 2FA for a specific user + +v 7.12.2 + - Correctly show anonymous authorized applications under Profile > Applications. + - Faster automerge check and merge itself when source and target branches are in same repository + - Audit log for user authentication + - Fix transferring of project to another group using the API. + +v 7.12.1 + - Fix error when deleting a user who has projects (Stan Hu) + - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu) + - Add SAML to list of social_provider (Matt Firtion) + - Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets) + - Fix closed merge request scope at milestone page (Dmitriy Zaporozhets) + - Revert merge request states renaming + - Fix hooks for web based events with external issue references (Daniel Gerhardt) + - Improve performance for issue and merge request pages + - Compress database dumps to reduce backup size -v 7.12.0 (unreleased) +v 7.12.0 - Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu) - Disable changing of target branch in new merge request page when a branch has already been specified (Stan Hu) - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu) - Update oauth button logos for Twitter and Google to recommended assets - - Fix hooks for web based events with external issue references (Daniel Gerhardt) - Update browser gem to version 0.8.0 for IE11 support (Stan Hu) - Fix timeout when rendering file with thousands of lines. - Add "Remember me" checkbox to LDAP signin form. - Add session expiration delay configuration through UI application settings - - Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt) - Don't notify users mentioned in code blocks or blockquotes. - Omit link to generate labels if user does not have access to create them (Stan Hu) - Show warning when a comment will add 10 or more people to the discussion. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9dcf67b1e2..69abadb151a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,7 +67,7 @@ To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gi If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows: -1. Fork the project on GitLab Cloud +1. Fork the project into your personal space on GitLab.com 1. Create a feature branch 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code 1. Add your changes to the [CHANGELOG](CHANGELOG) @@ -2,6 +2,10 @@ source "https://rubygems.org" gem 'rails', '4.1.11' +# Specify a sprockets version due to security issue +# See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY +gem 'sprockets', '~> 2.12.3' + # Default values for AR models gem "default_value_for", "~> 3.0.0" @@ -9,7 +13,7 @@ gem "default_value_for", "~> 3.0.0" gem "mysql2", group: :mysql gem "pg", group: :postgres -# Auth +# Authentication libraries gem "devise", '3.2.4' gem "devise-async", '0.9.0' gem 'omniauth', "~> 1.2.2" @@ -94,7 +98,7 @@ gem "seed-fu" gem 'html-pipeline', '~> 1.11.0' gem 'task_list', '1.0.2', require: 'task_list/railtie' gem 'github-markup' -gem 'redcarpet', '~> 3.3.0' +gem 'redcarpet', '~> 3.3.2' gem 'RedCloth' gem 'rdoc', '~>3.6' gem 'org-ruby', '= 0.9.12' @@ -181,7 +185,7 @@ gem 'mousetrap-rails' # Detect and convert string character encoding gem 'charlock_holmes' -gem "sass-rails", '~> 4.0.2' +gem "sass-rails", '~> 4.0.5' gem "coffee-rails" gem "uglifier" gem 'turbolinks', '~> 2.5.0' @@ -199,7 +203,7 @@ gem 'jquery-ui-rails' gem 'nprogress-rails' gem 'raphael-rails', '~> 2.1.2' gem 'request_store' -gem 'select2-rails' +gem 'select2-rails', '~> 3.5.9' gem 'virtus' group :development do @@ -224,13 +228,14 @@ end group :development, :test do gem 'awesome_print' gem 'byebug' + gem 'fuubar', '~> 2.0.0' gem 'pry-rails' - gem 'coveralls', require: false + gem 'coveralls', '~> 0.8.2', require: false gem 'database_cleaner', '~> 1.4.0' gem 'factory_girl_rails' - gem 'rspec-rails', '~> 3.3.0' - gem 'rubocop', '0.28.0', require: false + gem 'rspec-rails', '~> 3.3.0' + gem 'rubocop', '0.28.0', require: false gem 'spinach-rails' # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) @@ -239,7 +244,7 @@ group :development, :test do # Generate Fake data gem 'ffaker', '~> 2.0.0' - gem 'capybara', '~> 2.3.0' + gem 'capybara', '~> 2.4.0' gem 'capybara-screenshot', '~> 1.0.0' gem 'poltergeist', '~> 1.6.0' @@ -267,4 +272,4 @@ end gem "newrelic_rpm" gem 'octokit', '3.7.0' -gem "rugments", "~> 1.0.0.beta7" +gem "rugments", "~> 1.0.0.beta8" diff --git a/Gemfile.lock b/Gemfile.lock index 8d09fe9bf88..6e571072a4c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,7 +82,7 @@ GEM columnize (~> 0.8) debugger-linecache (~> 1.2) cal-heatmap-rails (0.0.1) - capybara (2.3.0) + capybara (2.4.4) mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) @@ -113,12 +113,12 @@ GEM colorize (0.5.8) columnize (0.9.0) connection_pool (2.1.0) - coveralls (0.7.0) - multi_json (~> 1.3) - rest-client - simplecov (>= 0.7) - term-ansicolor - thor + coveralls (0.8.2) + json (~> 1.8) + rest-client (>= 1.6.8, < 2) + simplecov (~> 0.10.0) + term-ansicolor (~> 1.3) + thor (~> 0.19.1) crack (0.4.2) safe_yaml (~> 1.0.0) creole (0.3.8) @@ -149,6 +149,8 @@ GEM diff-lcs (1.2.5) diffy (3.0.3) docile (1.1.5) + domain_name (0.5.24) + unf (>= 0.0.5, < 1.0.0) doorkeeper (2.1.3) railties (>= 3.2) dotenv (0.9.0) @@ -242,6 +244,9 @@ GEM dotenv (>= 0.7) thor (>= 0.13.6) formatador (0.2.5) + fuubar (2.0.0) + rspec (~> 3.0) + ruby-progressbar (~> 1.4) gemnasium-gitlab-service (0.2.6) rugged (~> 0.21) gemojione (2.0.0) @@ -319,6 +324,8 @@ GEM html-pipeline (1.11.0) activesupport (>= 2) nokogiri (~> 1.4) + http-cookie (1.0.2) + domain_name (~> 0.5) http_parser.rb (0.5.3) httparty (0.13.3) json (~> 1.8) @@ -374,6 +381,7 @@ GEM net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (2.9.2) + netrc (0.10.3) newrelic_rpm (3.9.4.245) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) @@ -499,7 +507,7 @@ GEM trollop rdoc (3.12.2) json (~> 1.4) - redcarpet (3.3.1) + redcarpet (3.3.2) redis (3.1.0) redis-actionpack (4.0.0) actionpack (~> 4) @@ -522,14 +530,20 @@ GEM request_store (1.0.5) rerun (0.10.0) listen (~> 2.7, >= 2.7.3) - rest-client (1.6.7) - mime-types (>= 1.16) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) rinku (1.7.3) rotp (1.6.1) rouge (1.7.7) rqrcode (0.4.2) rqrcode-rails3 (0.1.7) rqrcode (>= 0.4.2) + rspec (3.3.0) + rspec-core (~> 3.3.0) + rspec-expectations (~> 3.3.0) + rspec-mocks (~> 3.3.0) rspec-core (3.3.1) rspec-support (~> 3.3.0) rspec-expectations (3.3.0) @@ -565,15 +579,15 @@ GEM rubyntlm (0.5.0) rubypants (0.2.0) rugged (0.22.2) - rugments (1.0.0.beta7) + rugments (1.0.0.beta8) safe_yaml (1.0.4) sanitize (2.1.0) nokogiri (>= 1.4.4) sass (3.2.19) - sass-rails (4.0.3) + sass-rails (4.0.5) railties (>= 4.0.0, < 5.0) - sass (~> 3.2.0) - sprockets (~> 2.8, <= 2.11.0) + sass (~> 3.2.2) + sprockets (~> 2.8, < 3.0) sprockets-rails (~> 2.0) sawyer (0.6.0) addressable (~> 2.3.5) @@ -584,7 +598,7 @@ GEM seed-fu (2.3.5) activerecord (>= 3.1, < 4.3) activesupport (>= 3.1, < 4.3) - select2-rails (3.5.2) + select2-rails (3.5.9.3) thor (~> 0.14) settingslogic (2.0.9) sexp_processor (4.4.5) @@ -601,11 +615,11 @@ GEM ice_cube (= 0.11.1) sidekiq (>= 3.0.0) simple_oauth (0.1.9) - simplecov (0.9.0) + simplecov (0.10.0) docile (~> 1.1.0) - multi_json - simplecov-html (~> 0.8.0) - simplecov-html (0.8.0) + json (~> 1.8) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) sinatra (1.4.4) rack (~> 1.4) rack-protection (~> 1.4) @@ -630,12 +644,12 @@ GEM spring (>= 0.9.1) spring-commands-teaspoon (0.0.2) spring (>= 0.9.1) - sprockets (2.11.0) + sprockets (2.12.4) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.3.1) + sprockets-rails (2.3.2) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) @@ -650,8 +664,8 @@ GEM teaspoon-jasmine (2.2.0) teaspoon (>= 1.0.0) temple (0.6.7) - term-ansicolor (1.2.2) - tins (~> 0.8) + term-ansicolor (1.3.2) + tins (~> 1.0) terminal-table (1.4.5) test_after_commit (0.2.2) thin (1.6.1) @@ -673,7 +687,7 @@ GEM mime-types (~> 1.19) multi_json (~> 1.7) twitter-stream (~> 0.1) - tins (0.13.1) + tins (1.5.4) trollop (2.1.2) turbolinks (2.5.3) coffee-rails @@ -689,7 +703,7 @@ GEM underscore-rails (1.4.4) unf (0.1.4) unf_ext - unf_ext (0.0.6) + unf_ext (0.0.7.1) unicorn (4.6.3) kgio (~> 2.6) rack @@ -739,13 +753,13 @@ DEPENDENCIES browser (~> 0.8.0) byebug cal-heatmap-rails (~> 0.0.1) - capybara (~> 2.3.0) + capybara (~> 2.4.0) capybara-screenshot (~> 1.0.0) carrierwave charlock_holmes coffee-rails colored - coveralls + coveralls (~> 0.8.2) creole (~> 0.3.6) d3_rails (~> 3.5.5) database_cleaner (~> 1.4.0) @@ -763,6 +777,7 @@ DEPENDENCIES fog (~> 1.25.0) font-awesome-rails (~> 4.2) foreman + fuubar (~> 2.0.0) gemnasium-gitlab-service (~> 0.2) github-markup gitlab-flowdock-git-hook (~> 0.4.2) @@ -814,19 +829,19 @@ DEPENDENCIES rails (= 4.1.11) raphael-rails (~> 2.1.2) rdoc (~> 3.6) - redcarpet (~> 3.3.0) + redcarpet (~> 3.3.2) redis-rails request_store rerun (~> 0.10.0) rqrcode-rails3 rspec-rails (~> 3.3.0) rubocop (= 0.28.0) - rugments (~> 1.0.0.beta7) + rugments (~> 1.0.0.beta8) sanitize (~> 2.0) - sass-rails (~> 4.0.2) + sass-rails (~> 4.0.5) sdoc seed-fu - select2-rails + select2-rails (~> 3.5.9) settingslogic shoulda-matchers (~> 2.8.0) sidekiq (~> 3.3) @@ -841,6 +856,7 @@ DEPENDENCIES spring-commands-rspec (~> 1.0.0) spring-commands-spinach (~> 1.0.0) spring-commands-teaspoon (~> 0.0.2) + sprockets (~> 2.12.3) stamp state_machine task_list (= 1.0.2) @@ -861,4 +877,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.10.3 + 1.10.5 diff --git a/README.md b/README.md index 52a483aa532..7dc0ffb30ee 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ To use EE and get official support please [become a subscriber](https://about.gi ## Code status -- [![build status](https://ci.gitlab.org/projects/1/status.png?ref=master)](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) +- [![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master) on ci.gitlab.com (master branch) - [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq) @@ -62,7 +62,7 @@ The recommended way to install GitLab is using the provided [Omnibus packages](h There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information. -You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password. +You can access a new installation with the login **`root`** and password **`password`**, after login you are required to set a unique password. ## Third-party applications @@ -74,7 +74,7 @@ Since 2011 a minor or major version of GitLab is released on the 22nd of every m ## Upgrading -For updating the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For installations from source there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update) detailing all necessary commands to migrate to the next version. +For upgrading information please see our [update page](https://about.gitlab.com/update/). ## Install a development environment @@ -101,4 +101,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on ## Is it awesome? Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua. -[These people](https://twitter.com/gitlab/favorites) seem to like it. +[These people](https://twitter.com/gitlab/favorites) seem to like it.
\ No newline at end of file diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index c18ea929506..bb120424ccf 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -16,7 +16,6 @@ #= require jquery.scrollTo #= require jquery.blockUI #= require jquery.turbolinks -#= require jquery.sticky-kit.min #= require turbolinks #= require autosave #= require bootstrap diff --git a/app/assets/javascripts/behaviors/requires_input.js.coffee b/app/assets/javascripts/behaviors/requires_input.js.coffee new file mode 100644 index 00000000000..8318fe435b3 --- /dev/null +++ b/app/assets/javascripts/behaviors/requires_input.js.coffee @@ -0,0 +1,39 @@ +# Requires Input behavior +# +# When called on a form with input fields with the `required` attribute, the +# form's submit button will be disabled until all required fields have values. +# +#= require extensions/jquery +# +# ### Example Markup +# +# <form class="js-requires-input"> +# <input type="text" required="required"> +# <input type="submit" value="Submit"> +# </form> +# +$.fn.requiresInput = -> + $form = $(this) + $button = $('button[type=submit], input[type=submit]', $form) + + required = '[required=required]' + fieldSelector = "input#{required}, select#{required}, textarea#{required}" + + requireInput = -> + # Collect the input values of *all* required fields + values = _.map $(fieldSelector, $form), (field) -> field.value + + # Disable the button if any required fields are empty + if values.length && _.any(values, _.isEmpty) + $button.disable() + else + $button.enable() + + # Set initial button state + requireInput() + + $form.on 'change input', fieldSelector, requireInput + +# Triggered on standard document `ready` and on Turbolinks `page:load` events +$(document).on 'ready page:load', -> + $('form.js-requires-input').requiresInput() diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee index 2e91a06daa8..050888f9c15 100644 --- a/app/assets/javascripts/blob/edit_blob.js.coffee +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -11,7 +11,6 @@ class @EditBlob if ace_mode editor.getSession().setMode "ace/mode/" + ace_mode - disableButtonIfEmptyField "#commit_message", ".js-commit-button" $(".js-commit-button").click -> $("#file-content").val editor.getValue() $(".file-editor form").submit() diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee index ab8f98715e8..1f36a53f191 100644 --- a/app/assets/javascripts/blob/new_blob.js.coffee +++ b/app/assets/javascripts/blob/new_blob.js.coffee @@ -11,7 +11,6 @@ class @NewBlob if ace_mode editor.getSession().setMode "ace/mode/" + ace_mode - disableButtonIfEmptyField "#commit_message", ".js-commit-button" $(".js-commit-button").click -> $("#file-content").val editor.getValue() $(".file-editor form").submit() diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 84873e389ea..8ceaef81a07 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -31,20 +31,14 @@ class Dispatcher when 'projects:compare:show' new Diff() when 'projects:issues:new','projects:issues:edit' - GitLab.GfmAutoComplete.setup() shortcut_handler = new ShortcutsNavigation() - new ZenMode() new DropzoneInput($('.issue-form')) - if page == 'projects:issues:new' - new IssuableForm($('.issue-form')) + new IssuableForm($('.issue-form')) when 'projects:merge_requests:new', 'projects:merge_requests:edit' - GitLab.GfmAutoComplete.setup() new Diff() shortcut_handler = new ShortcutsNavigation() - new ZenMode() new DropzoneInput($('.merge-request-form')) - if page == 'projects:merge_requests:new' - new IssuableForm($('.merge-request-form')) + new IssuableForm($('.merge-request-form')) when 'projects:merge_requests:show' new Diff() shortcut_handler = new ShortcutsIssuable() @@ -68,9 +62,11 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() when 'projects:commits:show' shortcut_handler = new ShortcutsNavigation() - when 'projects:show' + when 'projects:activity' new Activities() shortcut_handler = new ShortcutsNavigation() + when 'projects:show' + shortcut_handler = new ShortcutsNavigation() when 'groups:show' new Activities() shortcut_handler = new ShortcutsNavigation() @@ -113,13 +109,6 @@ class Dispatcher new NamespaceSelect() when 'dashboard' shortcut_handler = new ShortcutsDashboardNavigation() - switch path[1] - when 'issues', 'merge_requests' - new UsersSelect() - when 'groups' - switch path[1] - when 'issues', 'merge_requests' - new UsersSelect() when 'profiles' new Profile() when 'projects' @@ -135,8 +124,6 @@ class Dispatcher new ProjectNew() when 'show' new ProjectShow() - when 'issues', 'merge_requests' - new UsersSelect() when 'wikis' new Wikis() shortcut_handler = new ShortcutsNavigation() diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index a7476146010..a4f511301c1 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -25,10 +25,10 @@ class @DropzoneInput form_dropzone = $(form).find('.div-dropzone') form_dropzone.parent().addClass "div-dropzone-wrapper" form_dropzone.append divHover - $(".div-dropzone-hover").append iconPaperclip + form_dropzone.find(".div-dropzone-hover").append iconPaperclip form_dropzone.append divSpinner - $(".div-dropzone-spinner").append iconSpinner - $(".div-dropzone-spinner").css + form_dropzone.find(".div-dropzone-spinner").append iconSpinner + form_dropzone.find(".div-dropzone-spinner").css "opacity": 0 "display": "none" diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee new file mode 100644 index 00000000000..176d9cabefa --- /dev/null +++ b/app/assets/javascripts/issuable_context.js.coffee @@ -0,0 +1,22 @@ +#= require jquery.waitforimages + +class @IssuableContext + constructor: -> + new UsersSelect() + $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}) + + $(".context .inline-update").on "change", "select", -> + $(this).submit() + $(".context .inline-update").on "change", ".js-assignee", -> + $(this).submit() + + $('.issuable-details').waitForImages -> + $('.issuable-affix').affix offset: + top: -> + @top = ($('.issuable-affix').offset().top - 70) + bottom: -> + @bottom = $('.footer').outerHeight(true) + $('.issuable-affix').on 'affix.bs.affix', -> + $(@).width($(@).outerWidth()) + .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> + $(@).width('') diff --git a/app/assets/javascripts/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee index abd58bcf978..48c249943f2 100644 --- a/app/assets/javascripts/issuable_form.js.coffee +++ b/app/assets/javascripts/issuable_form.js.coffee @@ -1,5 +1,9 @@ class @IssuableForm constructor: (@form) -> + GitLab.GfmAutoComplete.setup() + new UsersSelect() + new ZenMode() + @titleField = @form.find("input[name*='[title]']") @descriptionField = @form.find("textarea[name*='[description]']") diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 74d6b80be5e..603a16da1ce 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -3,29 +3,12 @@ class @Issue constructor: -> - $('.edit-issue.inline-update input[type="submit"]').hide() - $(".context .inline-update").on "change", "select", -> - $(this).submit() - $(".context .inline-update").on "change", "#issue_assignee_id", -> - $(this).submit() - # Prevent duplicate event bindings @disableTaskList() if $("a.btn-close").length @initTaskList() - $('.issue-details').waitForImages -> - $('.issuable-affix').affix offset: - top: -> - @top = ($('.issuable-affix').offset().top - 70) - bottom: -> - @bottom = $('.footer').outerHeight(true) - $('.issuable-affix').on 'affix.bs.affix', -> - $(@).width($(@).outerWidth()) - .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> - $(@).width('') - initTaskList: -> $('.issue-details .js-task-list-container').taskList('enable') $(document).on 'tasklist:changed', '.issue-details .js-task-list-container', @updateTaskList @@ -42,5 +25,5 @@ class @Issue $.ajax type: 'PATCH' - url: $('form.js-issue-update').attr('action') + url: $('form.js-issuable-update').attr('action') data: patchData diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee index 1bc8840f9ac..d05bacd7494 100644 --- a/app/assets/javascripts/labels.js.coffee +++ b/app/assets/javascripts/labels.js.coffee @@ -1,7 +1,6 @@ class @Labels constructor: -> form = $('.label-form') - @setupLabelForm(form) @cleanBinding() @addBinding() @updateColorPreview() @@ -14,10 +13,6 @@ class @Labels $(document).off 'click', '.suggest-colors a' $(document).off 'input', 'input#label_color' - # Initializes the form to disable the save button if no color or title is entered - setupLabelForm: (form) -> - disableButtonIfAnyEmptyField form, '.form-control', form.find('.js-save-button') - # Updates the the preview color with the hex-color input updateColorPreview: => previewColor = $('input#label_color').val() diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 5c0bc686111..7462975bd3d 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -10,7 +10,6 @@ class @MergeRequest # action - String, current controller action # constructor: (@opts) -> - @initContextWidget() this.$el = $('.merge-request') this.$('.show-all-commits').on 'click', => @@ -26,28 +25,10 @@ class @MergeRequest if $("a.btn-close").length @initTaskList() - $('.merge-request-details').waitForImages -> - $('.issuable-affix').affix offset: - top: -> - @top = ($('.issuable-affix').offset().top - 70) - bottom: -> - @bottom = $('.footer').outerHeight(true) - $('.issuable-affix').on 'affix.bs.affix', -> - $(@).width($(@).outerWidth()) - .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> - $(@).width('') - # Local jQuery finder $: (selector) -> this.$el.find(selector) - initContextWidget: -> - $('.edit-merge_request.inline-update input[type="submit"]').hide() - $(".context .inline-update").on "change", "select", -> - $(this).submit() - $(".context .inline-update").on "change", "#merge_request_assignee_id", -> - $(this).submit() - showAllCommits: -> this.$('.first-commits').remove() this.$('.all-commits').removeClass 'hide' @@ -68,5 +49,5 @@ class @MergeRequest $.ajax type: 'PATCH' - url: $('form.js-merge-request-update').attr('action') + url: $('form.js-issuable-update').attr('action') data: patchData diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index de9a4c2cc2f..a132a0a9dcc 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -49,13 +49,15 @@ class @MergeRequestTabs # Store the `location` object, allowing for easier stubbing in tests @_location = location + switch @opts.action + when 'commits' + @commitsLoaded = true + when 'diffs' + @diffsLoaded = true + @bindEvents() @activateTab(@opts.action) - switch @opts.action - when 'commits' then @commitsLoaded = true - when 'diffs' then @diffsLoaded = true - bindEvents: -> $(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown @@ -134,7 +136,6 @@ class @MergeRequestTabs url: "#{source}.json" success: (data) => document.getElementById('diffs').innerHTML = data.html - $('.diff-header').trigger('sticky_kit:recalc') @diffsLoaded = true toggleLoading: -> diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee index 836269c44f9..fecdb9fc2e7 100644 --- a/app/assets/javascripts/project_new.js.coffee +++ b/app/assets/javascripts/project_new.js.coffee @@ -3,9 +3,3 @@ class @ProjectNew $('.project-edit-container').on 'ajax:before', => $('.project-edit-container').hide() $('.save-project-loader').show() - - @initEvents() - - - initEvents: -> - disableButtonIfEmptyField '#project_name', '.project-submit' diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee index 6828ae471e5..1fdf28f2528 100644 --- a/app/assets/javascripts/project_show.js.coffee +++ b/app/assets/javascripts/project_show.js.coffee @@ -1,15 +1,3 @@ class @ProjectShow constructor: -> - $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) -> - $(@).toggleClass('on').find('.count').html(data.star_count) - .on 'ajax:error', (e, xhr, status, error) -> - new Flash('Star toggle failed. Try again later.', 'alert') - - $("a[data-toggle='tab']").on "shown.bs.tab", (e) -> - $.cookie "default_view", $(e.target).attr("href"), { expires: 30, path: '/' } - - defaultView = $.cookie("default_view") - if defaultView - $("a[href=" + defaultView + "]").tab "show" - else - $("a[data-toggle='tab']:first").tab "show" + # I kept class for future diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee index 31895fbf2bc..5b6f9e7e3f2 100644 --- a/app/assets/javascripts/shortcuts_navigation.coffee +++ b/app/assets/javascripts/shortcuts_navigation.coffee @@ -4,6 +4,7 @@ class @ShortcutsNavigation extends Shortcuts constructor: -> super() Mousetrap.bind('g p', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project')) + Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity')) Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree')) Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits')) Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network')) diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss index 08cbe911672..d64e79170b9 100644 --- a/app/assets/stylesheets/base/mixins.scss +++ b/app/assets/stylesheets/base/mixins.scss @@ -109,7 +109,7 @@ font-size: 1.2em; } - blockquote p { + blockquote { color: #888; font-size: 15px; line-height: 1.5; diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index 1419a9cded9..961ac793de2 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -364,3 +364,12 @@ table { margin-top: 8px; } } + +.profiler-results { + top: 50px !important; + + .profiler-button, + .profiler-controls { + border-color: #EEE !important; + } +} diff --git a/app/assets/stylesheets/generic/gfm.scss b/app/assets/stylesheets/generic/gfm.scss index 8fac5e534fa..bd9200ace23 100644 --- a/app/assets/stylesheets/generic/gfm.scss +++ b/app/assets/stylesheets/generic/gfm.scss @@ -19,3 +19,7 @@ height: 14em; } } + +.gfm-commit, .gfm-commit_range { + font-family: $monospace_font; +} diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 8faae893a51..31e2ad86691 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -69,6 +69,7 @@ header { float: left; height: $header-height; width: $sidebar_width; + overflow: hidden; transition-duration: .3s; a { @@ -169,10 +170,6 @@ header { @mixin collapsed-header { .header-logo { width: $sidebar_collapsed_width; - - h3 { - display: none; - } } .header-content { diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index a49775daf8b..24c828e0d97 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -44,20 +44,18 @@ .project-home-panel { padding-left: 0 !important; - .project-home-row { - .project-home-desc { - margin-right: 0 !important; - float: none !important; - } - - .project-repo-buttons { - position: static; - margin-top: 15px; - width: 100%; - float: none; - text-align: left; - } + .project-avatar { + display: block; } + + .project-repo-buttons, + .git-clone-holder { + display: none; + } + } + + .project-stats { + display: none; } .container .title { diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss index f9abceb7f3e..cb8ecba8dee 100644 --- a/app/assets/stylesheets/generic/sidebar.scss +++ b/app/assets/stylesheets/generic/sidebar.scss @@ -29,42 +29,44 @@ &.navbar-collapse { padding: 0px !important; } -} - -.nav-sidebar li a .count { - float: right; - background: #eee; - padding: 0px 8px; - @include border-radius(6px); -} -.nav-sidebar li { - &.separate-item { - padding-top: 10px; - margin-top: 10px; - } - - a { - color: $gray; - display: block; - text-decoration: none; - padding: 8px 15px; - font-size: 14px; - line-height: 20px; - padding-left: 16px; + li { + width: $sidebar_width; - &:hover { - text-decoration: none; + &.separate-item { + padding-top: 10px; + margin-top: 10px; } - &:active, &:focus { + a { + padding: 7px 15px; + font-size: 13px; + line-height: 18px; + color: $gray; + display: block; text-decoration: none; - } + padding-left: 16px; + + &:hover { + text-decoration: none; + } - i { - width: 20px; - color: $gray-light; - margin-right: 23px; + &:active, &:focus { + text-decoration: none; + } + + i { + width: 20px; + color: $gray-light; + margin-right: 23px; + } + + .count { + float: right; + background: #eee; + padding: 0px 8px; + @include border-radius(6px); + } } } } @@ -112,15 +114,7 @@ width: $sidebar_collapsed_width; li a { - font-size: 14px; - padding: 8px 15px; - text-align: left; padding-left: 16px; - - - & > span { - display: none; - } } } @@ -130,9 +124,7 @@ } .sidebar-user { - .username { - display: none; - } + width: $sidebar_collapsed_width; } } } @@ -182,12 +174,13 @@ .sidebar-user { position: absolute; bottom: 0; - width: 100%; + width: $sidebar_width; padding: 10px; overflow: hidden; + transition-duration: .3s; .username { margin-top: 5px; - width: 230px; + width: $sidebar_width - 2 * 10px; } } diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index 66767cb13cb..2db4213159a 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -17,6 +17,14 @@ pre { background: #333; color: $background-color; } + + &.plain-readme { + background: none; + border: none; + padding: 0; + margin: 0; + font-size: 14px; + } } .monospace { diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss index 7ab01187a02..7e86a0fe4b9 100644 --- a/app/assets/stylesheets/generic/zen.scss +++ b/app/assets/stylesheets/generic/zen.scss @@ -63,43 +63,24 @@ } } - // Make the placeholder text in the standard textarea the same color as the - // background, effectively hiding it - - .zen-backdrop textarea::-webkit-input-placeholder { - color: white; - } - - .zen-backdrop textarea:-moz-placeholder { - color: white; - } - - .zen-backdrop textarea::-moz-placeholder { - color: white; - } - - .zen-backdrop textarea:-ms-input-placeholder { - color: white; - } - // Make the color of the placeholder text in the Zenned-out textarea darker, // so it becomes visible input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder { - color: #999; + color: #A8A8A8; } input:checked ~ .zen-backdrop textarea:-moz-placeholder { - color: #999; + color: #A8A8A8; opacity: 1; } input:checked ~ .zen-backdrop textarea::-moz-placeholder { - color: #999; + color: #A8A8A8; opacity: 1; } input:checked ~ .zen-backdrop textarea:-ms-input-placeholder { - color: #999; + color: #A8A8A8; } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index ed938f86b35..3572f33e91f 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -145,9 +145,3 @@ h2.issue-title { .issue-form .select2-container { width: 250px !important; } - -.issues-holder { - .issue-info { - margin-left: 20px; - } -} diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 42b8ecabb38..4da65b28743 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -72,13 +72,28 @@ ul.notes { .note { display: block; position:relative; + .note-body { overflow: auto; + .note-text { overflow: auto; word-wrap: break-word; @include md-typography; + // Reset ul style types since we're nested inside a ul already + & > ul { + list-style-type: disc; + + ul { + list-style-type: circle; + + ul { + list-style-type: square; + } + } + } + // Reduce left padding of first task list ul element ul.task-list:first-child { padding-left: 10px; @@ -94,6 +109,7 @@ ul.notes { } } } + .note-header { padding-bottom: 3px; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index e19b2eafa43..5f415f2d78f 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -15,48 +15,31 @@ } .project-home-panel { - margin-top: 10px; - margin-bottom: 15px; - position: relative; - padding-left: 65px; - min-height: 50px; + text-align: center; + margin-bottom: 20px; .project-identicon-holder { - position: absolute; - left: 0; - top: -14px; + margin-bottom: 15px; - .avatar { - width: 50px; - height: 50px; + .avatar, .identicon { + margin: 0 auto; + float: none; } .identicon { - font-size: 26px; - line-height: 50px; + @include border-radius(50%); } } - .project-home-row { - @extend .clearfix; - margin-bottom: 15px; - - &.project-home-row-top { - margin-bottom: 15px; + .lead { + p { + display: inline; } + } - .project-home-desc { - color: $gray; - float: left; - font-size: 16px; - line-height: 1.3; - margin-right: 250px; - - // Render Markdown-generated HTML inline for this block - p { - display: inline; - } - } + .git-clone-holder { + max-width: 600px; + margin: 0 auto; } .visibility-level-label { @@ -67,22 +50,22 @@ } .project-repo-buttons { - margin-top: -3px; - position: absolute; - right: 0; - width: 265px; - text-align: right; + margin-top: 25px; + margin-bottom: 25px; .btn { + @extend .btn-info; + + margin-left: 10px; font-weight: bold; font-size: 14px; line-height: 16px; + padding: 8px 12px; .count { - padding-left: 10px; - border-left: 1px solid #ccc; + padding-left: 7px; display: inline-block; - margin-left: 10px; + margin-left: 7px; } } } @@ -307,3 +290,15 @@ table.table.protected-branches-list tr.no-border { float: left; margin-right: 10px; } + +.project-stats { + text-align: center; + + ul.nav-pills { display:inline-block; } + li { display:inline; } + a { float:left; } +} + +pre.light-well { + border-color: #f1f1f1; +} diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss index 7cabeaefb93..dc47b108100 100644 --- a/app/assets/stylesheets/themes/gitlab-theme.scss +++ b/app/assets/stylesheets/themes/gitlab-theme.scss @@ -72,7 +72,7 @@ &.active a { color: #FFF; - font-weight: bold; + background: $color-dark; &.no-highlight { border: none; diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index f616ccf5684..da5f5bb83fa 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -23,7 +23,8 @@ class Admin::ProjectsController < Admin::ApplicationController end def transfer - ::Projects::TransferService.new(@project, current_user, params.dup).execute + namespace = Namespace.find_by(id: params[:new_namespace_id]) + ::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace) @project.reload redirect_to admin_namespace_project_path(@project.namespace, @project) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index ec29c320654..770fe00af51 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -47,6 +47,20 @@ class Admin::UsersController < Admin::ApplicationController end end + def unlock + if user.unlock_access! + redirect_to :back, alert: "Successfully unlocked" + else + redirect_to :back, alert: "Error occurred. User was not unlocked" + end + end + + def disable_two_factor + user.disable_two_factor! + redirect_to admin_user_path(user), + notice: 'Two-factor Authentication has been disabled for this user' + end + def create opts = { force_random_password: true, diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a657d3c54ee..362b03e0d5e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -56,7 +56,7 @@ 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? - if current_user.nil? && controller_name == 'dashboard' && action_name == 'show' + if current_user.nil? && root_path == request.path redirect_to current_application_settings.home_page_url and return end end @@ -183,7 +183,10 @@ class ApplicationController < ActionController::Base headers['X-XSS-Protection'] = '1; mode=block' headers['X-UA-Compatible'] = 'IE=edge' headers['X-Content-Type-Options'] = 'nosniff' - headers['Strict-Transport-Security'] = 'max-age=31536000' if Gitlab.config.gitlab.https + # Enabling HSTS for non-standard ports would send clients to the wrong port + if Gitlab.config.gitlab.https and Gitlab.config.gitlab.port == 443 + headers['Strict-Transport-Security'] = 'max-age=31536000' + end end def add_gon_variables @@ -265,6 +268,7 @@ class ApplicationController < ActionController::Base params[:scope] = 'all' if params[:scope].blank? params[:state] = 'opened' if params[:state].blank? + @sort = params[:sort] @filter_params = params.dup if @project diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 11af9895261..52e9c58b47c 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -1,22 +1,35 @@ class AutocompleteController < ApplicationController + skip_before_action :authenticate_user!, only: [:users] + def users - @users = - if params[:project_id].present? - project = Project.find(params[:project_id]) + begin + @users = + if params[:project_id].present? + project = Project.find(params[:project_id]) - if can?(current_user, :read_project, project) - project.team.users - end - elsif params[:group_id] - group = Group.find(params[:group_id]) + if can?(current_user, :read_project, project) + project.team.users + end + elsif params[:group_id] + group = Group.find(params[:group_id]) - if can?(current_user, :read_group, group) - group.users + if can?(current_user, :read_group, group) + group.users + end + elsif current_user + User.all end - else - User.all + rescue ActiveRecord::RecordNotFound + if current_user + return render json: {}, status: 404 end + end + + if @users.nil? && current_user.nil? + authenticate_user! + end + @users ||= User.none @users = @users.search(params[:search]) if params[:search].present? @users = @users.active @users = @users.page(params[:page]).per(PER_PAGE) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 2e381822e42..901c1cdddcb 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -121,6 +121,8 @@ class GroupsController < Groups::ApplicationController def determine_layout if [:new, :create].include?(action_name.to_sym) 'application' + elsif [:edit, :update, :projects].include?(action_name.to_sym) + 'group_settings' else 'group' end diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 3ab6def511c..4193ac11399 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -4,7 +4,12 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio layout 'profile' def destroy - Doorkeeper::AccessToken.revoke_all_for(params[:id], current_resource_owner) + if params[:token_id].present? + current_resource_owner.oauth_authorized_tokens.find(params[:token_id]).revoke + else + Doorkeeper::AccessToken.revoke_all_for(params[:id], current_resource_owner) + end + redirect_to applications_profile_url, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy]) end end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 765adaf2128..fd51b380da2 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -28,6 +28,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Do additional LDAP checks for the user filter and EE features if @user.allowed? + log_audit_event(gl_user, with: :ldap) sign_in_and_redirect(gl_user) else flash[:alert] = "Access denied for your LDAP account." @@ -47,6 +48,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController if current_user # Add new authentication method current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider']) + log_audit_event(current_user, with: oauth['provider']) redirect_to profile_account_path, notice: 'Authentication method updated' else @user = Gitlab::OAuth::User.new(oauth) @@ -54,6 +56,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Only allow properly saved users to login. if @user.persisted? && @user.valid? + log_audit_event(@user.gl_user, with: oauth['provider']) sign_in_and_redirect(@user.gl_user) else error_message = @@ -83,4 +86,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def oauth @oauth ||= request.env['omniauth.auth'] end + + def log_audit_event(user, options = {}) + AuditEventService.new(user, user, options). + for_authentication.security_event + end end diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index 03845f1e1ec..f9af0871cf1 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -29,13 +29,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController end def destroy - current_user.update_attributes({ - two_factor_enabled: false, - encrypted_otp_secret: nil, - encrypted_otp_secret_iv: nil, - encrypted_otp_secret_salt: nil, - otp_backup_codes: nil - }) + current_user.disable_two_factor! redirect_to profile_account_path end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index b4af9e490ed..26a4de15462 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -11,7 +11,8 @@ class ProfilesController < Profiles::ApplicationController def applications @applications = current_user.oauth_applications @authorized_tokens = current_user.oauth_authorized_tokens - @authorized_apps = @authorized_tokens.map(&:application).uniq + @authorized_anonymous_tokens = @authorized_tokens.reject(&:application) + @authorized_apps = @authorized_tokens.map(&:application).uniq - [nil] end def update @@ -37,8 +38,11 @@ class ProfilesController < Profiles::ApplicationController redirect_to profile_account_path end - def history - @events = current_user.recent_events.page(params[:page]).per(PER_PAGE) + def audit_log + @events = AuditEvent.where(entity_type: "User", entity_id: current_user.id). + order("created_at DESC"). + page(params[:page]). + per(PER_PAGE) end def update_username diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 696011b94b9..117ae3aaa3d 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -32,7 +32,7 @@ class Projects::BranchesController < Projects::ApplicationController end def destroy - DeleteBranchService.new(project, current_user).execute(params[:id]) + status = DeleteBranchService.new(project, current_user).execute(params[:id]) @branch_name = params[:id] respond_to do |format| @@ -40,7 +40,7 @@ class Projects::BranchesController < Projects::ApplicationController redirect_to namespace_project_branches_path(@project.namespace, @project) end - format.js + format.js { render status: status[:return_code] } end end end diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index a060ea6f998..0b6f7f5c91e 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -1,6 +1,9 @@ class Projects::GraphsController < Projects::ApplicationController + include ExtractsPath + # Authorize before_action :require_non_empty_project + before_action :assign_ref_vars before_action :authorize_download_code! def show @@ -13,7 +16,7 @@ class Projects::GraphsController < Projects::ApplicationController end def commits - @commits = @project.repository.commits(nil, nil, 2000, 0, true) + @commits = @project.repository.commits(@ref, nil, 2000, 0, true) @commits_graph = Gitlab::Graphs::Commits.new(@commits) @commits_per_week_days = @commits_graph.commits_per_week_days @commits_per_time = @commits_graph.commits_per_time @@ -23,7 +26,7 @@ class Projects::GraphsController < Projects::ApplicationController private def fetch_graph - @commits = @project.repository.commits(nil, nil, 6000, 0, true) + @commits = @project.repository.commits(@ref, nil, 6000, 0, true) @log = [] @commits.each do |commit| diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 7d168aa827b..bfafdeeb1fb 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -6,10 +6,10 @@ class Projects::IssuesController < Projects::ApplicationController before_action :authorize_read_issue! # Allow write(create) issue - before_action :authorize_write_issue!, only: [:new, :create] + before_action :authorize_create_issue!, only: [:new, :create] # Allow modify issue - before_action :authorize_modify_issue!, only: [:edit, :update] + before_action :authorize_update_issue!, only: [:edit, :update] # Allow issues bulk update before_action :authorize_admin_issues!, only: [:bulk_update] @@ -55,6 +55,7 @@ class Projects::IssuesController < Projects::ApplicationController end def show + @participants = @issue.participants(current_user, @project) @note = @project.notes.new(noteable: @issue) @notes = @issue.notes.inc_author.fresh @noteable = @issue @@ -121,8 +122,8 @@ class Projects::IssuesController < Projects::ApplicationController end end - def authorize_modify_issue! - return render_404 unless can?(current_user, :modify_issue, @issue) + def authorize_update_issue! + return render_404 unless can?(current_user, :update_issue, @issue) end def authorize_admin_issues! diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 51ecbfd561a..d1265198318 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -14,10 +14,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :authorize_read_merge_request! # Allow write(create) merge_request - before_action :authorize_write_merge_request!, only: [:new, :create] + before_action :authorize_create_merge_request!, only: [:new, :create] # Allow modify merge_request - before_action :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] + before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :sort] def index terms = params['issue_search'] @@ -218,8 +218,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController @closes_issues ||= @merge_request.closes_issues end - def authorize_modify_merge_request! - return render_404 unless can?(current_user, :modify_merge_request, @merge_request) + def authorize_update_merge_request! + return render_404 unless can?(current_user, :update_merge_request, @merge_request) end def authorize_admin_merge_request! @@ -246,6 +246,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_show_vars + @participants = @merge_request.participants(current_user, @project) + # Build a note object for comment form @note = @project.notes.new(noteable: @merge_request) @notes = @merge_request.mr_and_commit_notes.inc_author.fresh diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index f3e521adb69..c4a87e9dbd8 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -1,7 +1,7 @@ class Projects::NotesController < Projects::ApplicationController # Authorize before_action :authorize_read_note! - before_action :authorize_write_note!, only: [:create] + before_action :authorize_create_note!, only: [:create] before_action :authorize_admin_note!, only: [:update, :destroy] before_action :find_current_user_notes, except: [:destroy, :delete_attachment] diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 01ca1537c0e..d83561cf32a 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -8,17 +8,21 @@ class Projects::RefsController < Projects::ApplicationController def switch respond_to do |format| format.html do - new_path = if params[:destination] == "tree" - namespace_project_tree_path(@project.namespace, @project, - (@id)) - elsif params[:destination] == "blob" - namespace_project_blob_path(@project.namespace, @project, - (@id)) - elsif params[:destination] == "graph" - namespace_project_network_path(@project.namespace, @project, @id, @options) - else - namespace_project_commits_path(@project.namespace, @project, @id) - end + new_path = + case params[:destination] + when "tree" + namespace_project_tree_path(@project.namespace, @project, @id) + when "blob" + namespace_project_blob_path(@project.namespace, @project, @id) + when "graph" + namespace_project_network_path(@project.namespace, @project, @id, @options) + when "graphs" + namespace_project_graph_path(@project.namespace, @project, @id) + when "graphs_commits" + commits_namespace_project_graph_path(@project.namespace, @project, @id) + else + namespace_project_commits_path(@project.namespace, @project, @id) + end redirect_to new_path end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index dc18bbd8d5b..1e435be8275 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -7,7 +7,8 @@ class Projects::ServicesController < Projects::ApplicationController :colorize_messages, :channels, :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, - :notify, :color] + :notify, :color, + :server_host, :server_port, :default_irc_uri] # 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 3d75abcc29d..64306637423 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -6,10 +6,10 @@ class Projects::SnippetsController < Projects::ApplicationController before_action :authorize_read_project_snippet! # Allow write(create) snippet - before_action :authorize_write_project_snippet!, only: [:new, :create] + before_action :authorize_create_project_snippet!, only: [:new, :create] # Allow modify snippet - before_action :authorize_modify_project_snippet!, only: [:edit, :update] + before_action :authorize_update_project_snippet!, only: [:edit, :update] # Allow destroy snippet before_action :authorize_admin_project_snippet!, only: [:destroy] @@ -75,8 +75,8 @@ class Projects::SnippetsController < Projects::ApplicationController @snippet ||= @project.snippets.find(params[:id]) end - def authorize_modify_project_snippet! - return render_404 unless can?(current_user, :modify_project_snippet, @snippet) + def authorize_update_project_snippet! + return render_404 unless can?(current_user, :update_project_snippet, @snippet) end def authorize_admin_project_snippet! diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 36ef86e1909..50512cb6dc3 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -2,7 +2,7 @@ require 'project_wiki' class Projects::WikisController < Projects::ApplicationController before_action :authorize_read_wiki! - before_action :authorize_write_wiki!, only: [:edit, :create, :history] + before_action :authorize_create_wiki!, only: [:edit, :create, :history] before_action :authorize_admin_wiki!, only: :destroy before_action :load_project_wiki include WikiHelper @@ -28,7 +28,7 @@ class Projects::WikisController < Projects::ApplicationController ) end else - return render('empty') unless can?(current_user, :write_wiki, @project) + return render('empty') unless can?(current_user, :create_wiki, @project) @page = WikiPage.new(@project_wiki) @page.title = params[:id] @@ -43,7 +43,7 @@ class Projects::WikisController < Projects::ApplicationController def update @page = @project_wiki.find_page(params[:id]) - return render('empty') unless can?(current_user, :write_wiki, @project) + return render('empty') unless can?(current_user, :create_wiki, @project) if @page.update(content, format, message) redirect_to( diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index be5968cd7b0..b191819a117 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -6,7 +6,7 @@ class ProjectsController < ApplicationController # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] - before_action :event_filter, only: :show + before_action :event_filter, only: [:show, :activity] layout :determine_layout @@ -52,10 +52,21 @@ class ProjectsController < ApplicationController end def transfer - transfer_params = params.permit(:new_namespace_id) - ::Projects::TransferService.new(project, current_user, transfer_params).execute - if @project.errors[:namespace_id].present? - flash[:alert] = @project.errors[:namespace_id].first + namespace = Namespace.find_by(id: params[:new_namespace_id]) + ::Projects::TransferService.new(project, current_user).execute(namespace) + + if @project.errors[:new_namespace].present? + flash[:alert] = @project.errors[:new_namespace].first + end + end + + def activity + respond_to do |format| + format.html + format.json do + load_events + pager_json('events/_events', @events.count) + end end end @@ -65,15 +76,12 @@ class ProjectsController < ApplicationController return end - @show_star = !(current_user && current_user.starred?(@project)) - respond_to do |format| format.html do if @project.repository_exists? if @project.empty_repo? render 'projects/empty' else - @last_push = current_user.recent_push(@project.id) if current_user render :show end else @@ -81,11 +89,6 @@ class ProjectsController < ApplicationController end end - format.json do - load_events - pager_json('events/_events', @events.count) - end - format.atom do load_events render layout: false @@ -147,11 +150,14 @@ class ProjectsController < ApplicationController def toggle_star current_user.toggle_star(@project) @project.reload - render json: { star_count: @project.star_count } + + render json: { + html: view_to_html_string("projects/buttons/_star") + } end def markdown_preview - text = params[:text] + text = params[:text] ext = Gitlab::ReferenceExtractor.new(@project, current_user) ext.analyze(text) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 7577fc96d6d..89629bc0581 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -37,6 +37,8 @@ class SessionsController < Devise::SessionsController resource.update_attributes(reset_password_token: nil, reset_password_sent_at: nil) end + authenticated_with = user_params[:otp_attempt] ? "two-factor" : "standard" + log_audit_event(current_user, with: authenticated_with) end end @@ -95,4 +97,9 @@ class SessionsController < Devise::SessionsController user.valid_otp?(user_params[:otp_attempt]) || user.invalidate_otp_backup_code!(user_params[:otp_attempt]) end + + def log_audit_event(user, options = {}) + AuditEventService.new(user, user, options). + for_authentication.security_event + end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index cf672c5c093..8e7e45c781f 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -2,7 +2,7 @@ class SnippetsController < ApplicationController before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] # Allow modify snippet - before_action :authorize_modify_snippet!, only: [:edit, :update] + before_action :authorize_update_snippet!, only: [:edit, :update] # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] @@ -87,8 +87,8 @@ class SnippetsController < ApplicationController end end - def authorize_modify_snippet! - return render_404 unless can?(current_user, :modify_personal_snippet, @snippet) + def authorize_update_snippet! + return render_404 unless can?(current_user, :update_personal_snippet, @snippet) end def authorize_admin_snippet! diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 2eccc0ee31f..ab89aa2c53a 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -10,7 +10,7 @@ # state: 'open' or 'closed' or 'all' # group_id: integer # project_id: integer -# milestone_id: integer +# milestone_title: string # assignee_id: integer # search: string # label_name: string @@ -76,7 +76,7 @@ class IssuableFinder return @milestones if defined?(@milestones) @milestones = - if milestones? && params[:milestone_title] != NONE + if milestones? && params[:milestone_title] != Milestone::None.title Milestone.where(title: params[:milestone_title]) else nil diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 0b46db4b1c3..a803b66c502 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -213,6 +213,10 @@ module ApplicationHelper Haml::Helpers.preserve(markdown(file_content)) elsif asciidoc?(file_name) asciidoc(file_content) + elsif plain?(file_name) + content_tag :pre, class: 'plain-readme' do + file_content + end else GitHub::Markup.render(file_name, file_content). force_encoding(file_content.encoding).html_safe @@ -221,6 +225,10 @@ module ApplicationHelper simple_format(file_content) end + def plain?(filename) + Gitlab::MarkupHelper.plain?(filename) + end + def markup?(filename) Gitlab::MarkupHelper.markup?(filename) end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 9aabe01f60e..eb3f72a307d 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -98,6 +98,29 @@ module GitlabMarkdownHelper end end + MARKDOWN_TIPS = [ + "End a line with two or more spaces for a line-break, or soft-return", + "Inline code can be denoted by `surrounding it with backticks`", + "Blocks of code can be denoted by three backticks ``` or four leading spaces", + "Emoji can be added by :emoji_name:, for example :thumbsup:", + "Notify other participants using @user_name", + "Notify a specific group using @group_name", + "Notify the entire team using @all", + "Reference an issue using a hash, for example issue #123", + "Reference a merge request using an exclamation point, for example MR !123", + "Italicize words or phrases using *asterisks* or _underscores_", + "Bold words or phrases using **double asterisks** or __double underscores__", + "Strikethrough words or phrases using ~~two tildes~~", + "Make a bulleted list using + pluses, - minuses, or * asterisks", + "Denote blockquotes using > at the beginning of a line", + "Make a horizontal line using three or more hyphens ---, asterisks ***, or underscores ___" + ].freeze + + # Returns a random markdown tip for use as a textarea placeholder + def random_markdown_tip + MARKDOWN_TIPS.sample + end + private # Return +text+, truncated to +max_chars+ characters, excluding any HTML diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 9703c8d9e9c..d0fae255a04 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -17,6 +17,10 @@ module GitlabRoutingHelper namespace_project_path(project.namespace, project, *args) end + def activity_project_path(project, *args) + activity_namespace_project_path(project.namespace, project, *args) + end + def edit_project_path(project, *args) edit_namespace_project_path(project.namespace, project, *args) end @@ -52,4 +56,12 @@ module GitlabRoutingHelper def project_snippet_url(entity, *args) namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args) end + + def toggle_subscription_path(entity, *args) + if entity.is_a?(Issue) + toggle_subscription_namespace_project_issue_path(entity.project.namespace, entity.project, entity) + else + toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity) + end + end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 3569ac2af63..b067cb54a43 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -19,14 +19,6 @@ module GroupsHelper end end - def group_settings_page? - if current_controller?('groups') - current_action?('edit') || current_action?('projects') - else - false - end - end - def group_icon(group) if group.is_a?(String) group = Group.find_by(path: group) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 93e33ebefd8..132a893e532 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -29,6 +29,8 @@ module MilestonesHelper end.active grouped_milestones = Milestones::GroupService.new(milestones).execute + grouped_milestones.unshift(Milestone::None) + options_from_collection_for_select(grouped_milestones, 'title', 'title', params[:milestone_title]) end end diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index 997b91de077..2fdca13ed40 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -13,7 +13,7 @@ module OauthHelper def enabled_social_providers enabled_oauth_providers.select do |name| - [:twitter, :gitlab, :github, :bitbucket, :google_oauth2].include?(name.to_sym) + [:saml, :twitter, :gitlab, :github, :bitbucket, :google_oauth2].include?(name.to_sym) end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index ec65e473919..aa15398cbed 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -84,53 +84,6 @@ module ProjectsHelper @project.milestones.active.order("due_date, title ASC") end - def link_to_toggle_star(title, starred) - cls = 'star-btn btn btn-sm btn-default' - - toggle_text = - if starred - ' Unstar' - else - ' Star' - end - - toggle_html = content_tag('span', class: 'toggle') do - icon('star') + toggle_text - end - - count_html = content_tag('span', class: 'count') do - @project.star_count.to_s - end - - link_opts = { - title: title, - class: cls, - method: :post, - remote: true, - data: { type: 'json' } - } - - path = toggle_star_namespace_project_path(@project.namespace, @project) - - content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do - link_to(path, link_opts) do - toggle_html + ' ' + count_html - end - end - end - - def link_to_toggle_fork - html = content_tag('span') do - icon('code-fork') + ' Fork' - end - - count_html = content_tag(:span, class: 'count') do - @project.forks_count.to_s - end - - html + count_html - end - def project_for_deploy_key(deploy_key) if deploy_key.projects.include?(@project) @project @@ -139,6 +92,16 @@ module ProjectsHelper end end + def can_change_visibility_level?(project, current_user) + return false unless can?(current_user, :change_visibility_level, project) + + if project.forked? + project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE + else + true + end + end + private def get_project_nav_tabs(project, current_user) @@ -306,4 +269,21 @@ module ProjectsHelper def leave_project_message(project) "Are you sure you want to leave \"#{project.name}\" project?" end + + def new_readme_path + ref = @repository.root_ref if @repository + ref ||= 'master' + + namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md') + end + + def last_push_event + if current_user + current_user.recent_push(@project.id) + end + end + + def readme_cache_key + [@project.id, @project.commit.sha, "readme"].join('-') + end end diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index 6def7793dc3..b3f50ceebe4 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -63,7 +63,7 @@ module SubmoduleHelper namespace = components.pop.gsub(/^\.\.$/, '') if namespace.empty? - namespace = @project.namespace.name + namespace = @project.namespace.path end [ diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 00d4c7f1051..b52cd23aba2 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -86,4 +86,10 @@ module VisibilityLevelHelper def default_snippet_visibility current_application_settings.default_snippet_visibility end + + def skip_level?(form_model, level) + form_model.is_a?(Project) && + form_model.forked? && + !Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level) + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index a5db22040e0..d3631d49ec6 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -68,6 +68,7 @@ class Ability def project_abilities(user, project) rules = [] key = "/user/#{user.id}/project/#{project.id}" + RequestStore.store[key] ||= begin team = project.team @@ -144,9 +145,9 @@ class Ability :read_project_member, :read_merge_request, :read_note, - :write_project, - :write_issue, - :write_note + :create_project, + :create_issue, + :create_note ] end @@ -154,27 +155,27 @@ class Ability project_guest_rules + [ :download_code, :fork_project, - :write_project_snippet + :create_project_snippet, + :update_issue, + :admin_issue, + :admin_label, ] end def project_dev_rules project_report_rules + [ - :write_merge_request, - :write_wiki, - :modify_issue, - :admin_issue, - :admin_label, + :create_merge_request, + :create_wiki, :push_code ] end def project_archived_rules [ - :write_merge_request, + :create_merge_request, :push_code, :push_code_to_protected_branches, - :modify_merge_request, + :update_merge_request, :admin_merge_request ] end @@ -182,10 +183,8 @@ class Ability def project_master_rules project_dev_rules + [ :push_code_to_protected_branches, - :modify_issue, - :modify_project_snippet, - :modify_merge_request, - :admin_issue, + :update_project_snippet, + :update_merge_request, :admin_milestone, :admin_project_snippet, :admin_project_member, @@ -245,30 +244,40 @@ class Ability rules.flatten end - [:issue, :note, :project_snippet, :personal_snippet, :merge_request].each do |name| + + [:issue, :merge_request].each do |name| define_method "#{name}_abilities" do |user, subject| - if subject.author == user || user.is_admin? - rules = [ + rules = [] + + if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user) + rules += [ :"read_#{name}", - :"write_#{name}", - :"modify_#{name}", - :"admin_#{name}" + :"update_#{name}", ] - rules.push(:change_visibility_level) if subject.is_a?(Snippet) - rules - elsif subject.respond_to?(:assignee) && subject.assignee == user - [ + end + + rules += project_abilities(user, subject.project) + rules + end + end + + [:note, :project_snippet, :personal_snippet].each do |name| + define_method "#{name}_abilities" do |user, subject| + rules = [] + + if subject.author == user + rules += [ :"read_#{name}", - :"write_#{name}", - :"modify_#{name}", + :"update_#{name}", + :"admin_#{name}" ] - else - if subject.respond_to?(:project) && subject.project - project_abilities(user, subject.project) - else - [] - end end + + if subject.respond_to?(:project) && subject.project + rules += project_abilities(user, subject.project) + end + + rules end end @@ -277,13 +286,16 @@ class Ability target_user = subject.user group = subject.group can_manage = group_abilities(user, group).include?(:admin_group) + if can_manage && (user != target_user) - rules << :modify_group_member + rules << :update_group_member rules << :destroy_group_member end + if !group.last_owner?(user) && (can_manage || (user == target_user)) rules << :destroy_group_member end + rules end @@ -300,8 +312,8 @@ class Ability def named_abilities(name) [ :"read_#{name}", - :"write_#{name}", - :"modify_#{name}", + :"create_#{name}", + :"update_#{name}", :"admin_#{name}" ] end diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb new file mode 100644 index 00000000000..967ffd46db0 --- /dev/null +++ b/app/models/audit_event.rb @@ -0,0 +1,19 @@ +class AuditEvent < ActiveRecord::Base + serialize :details, Hash + + belongs_to :user, foreign_key: :author_id + + validates :author_id, presence: true + validates :entity_id, presence: true + validates :entity_type, presence: true + + after_initialize :initialize_details + + def initialize_details + self.details = {} if details.nil? + end + + def author_name + self.user.name + end +end diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 9f667f47e0d..7c9597333dd 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -14,7 +14,7 @@ # # participant :author, :assignee, :mentioned_users, :notes # end -# +# # issue = Issue.last # users = issue.participants # # `users` will contain the issue's author, its assignee, @@ -35,11 +35,13 @@ module Participable end end + # Be aware that this method makes a lot of sql queries. + # Save result into variable if you are going to reuse it inside same request def participants(current_user = self.author, project = self.project) participants = self.class.participant_attrs.flat_map do |attr| meth = method(attr) - value = + value = if meth.arity == 1 || meth.arity == -1 meth.call(current_user) else @@ -59,7 +61,7 @@ module Participable end private - + def participants_for(value, current_user = nil, project = nil) case value when User diff --git a/app/models/event.rb b/app/models/event.rb index c9a88ffa8e0..78f16c6304e 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -44,7 +44,7 @@ class Event < ActiveRecord::Base after_create :reset_project_activity # Scopes - scope :recent, -> { order("created_at DESC") } + scope :recent, -> { order(created_at: :desc) } scope :code_push, -> { where(action: PUSHED) } scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent } scope :with_associations, -> { includes(project: :namespace) } diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 7ecdaf6b2e0..53b3fc10ccb 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -205,7 +205,14 @@ class MergeRequest < ActiveRecord::Base end def check_if_can_be_merged - if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged? + can_be_merged = + if for_fork? + Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged? + else + project.repository.can_be_merged?(source_branch, target_branch) + end + + if can_be_merged mark_as_mergeable else mark_as_unmergeable diff --git a/app/models/milestone.rb b/app/models/milestone.rb index e0c5fec97b7..d28f3c8d3f9 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -14,6 +14,10 @@ # class Milestone < ActiveRecord::Base + # Represents a "No Milestone" state used for filtering Issues and Merge + # Requests that have no milestone assigned. + None = Struct.new(:title).new('No Milestone') + include InternalId include Sortable diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 19b5859d5c9..c284e19fe50 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -76,6 +76,7 @@ class GitlabCiService < CiService params = { id: new_project.id, name_with_namespace: new_project.name_with_namespace, + path_with_namespace: new_project.path_with_namespace, web_url: new_project.web_url, default_branch: new_project.default_branch, ssh_url_to_repo: new_project.ssh_url_to_repo diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 89f312e8c98..d24aa317cf3 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -21,26 +21,11 @@ require 'uri' class IrkerService < Service + prop_accessor :server_host, :server_port, :default_irc_uri prop_accessor :colorize_messages, :recipients, :channels validates :recipients, presence: true, if: :activated? - validate :check_recipients_count, if: :activated? before_validation :get_channels - after_initialize :initialize_settings - - # Writer for RSpec tests - attr_writer :settings - - def initialize_settings - # See the documentation (doc/project_services/irker.md) for possible values - # here - @settings ||= { - server_ip: 'localhost', - server_port: 6659, - max_channels: 3, - default_irc_uri: nil - } - end def title 'Irker (IRC gateway)' @@ -51,20 +36,6 @@ class IrkerService < Service 'gateway.' end - def help - msg = 'Recipients have to be specified with a full URI: '\ - 'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\ - 'the channel to be a nickname instead, append ",isnick" to the channel '\ - 'name; if the channel is protected by a secret password, append '\ - '"?key=secretpassword" to the URI.' - - unless @settings[:default_irc].nil? - msg += ' Note that a default IRC URI is provided by this service\'s '\ - "administrator: #{default_irc}. You can thus just give a channel name." - end - msg - end - def to_param 'irker' end @@ -77,30 +48,45 @@ class IrkerService < Service return unless supported_events.include?(data[:object_kind]) IrkerWorker.perform_async(project_id, channels, - colorize_messages, data, @settings) + colorize_messages, data, settings) + end + + def settings + { server_host: server_host.present? ? server_host : 'localhost', + server_port: server_port.present? ? server_port : 6659 + } end def fields [ + { type: 'text', name: 'server_host', placeholder: 'localhost', + help: 'Irker daemon hostname (defaults to localhost)' }, + { type: 'text', name: 'server_port', placeholder: 6659, + help: 'Irker daemon port (defaults to 6659)' }, + { type: 'text', name: 'default_irc_uri', title: 'Default IRC URI', + help: 'A default IRC URI to prepend before each recipient (optional)', + placeholder: 'irc://irc.network.net:6697/' }, { type: 'textarea', name: 'recipients', - placeholder: 'Recipients/channels separated by whitespaces' }, + placeholder: 'Recipients/channels separated by whitespaces', + help: 'Recipients have to be specified with a full URI: '\ + 'irc[s]://irc.network.net[:port]/#channel. Special cases: if '\ + 'you want the channel to be a nickname instead, append ",isnick" to ' \ + 'the channel name; if the channel is protected by a secret password, ' \ + ' append "?key=secretpassword" to the URI. Note that if you specify a ' \ + ' default IRC URI to prepend before each recipient, you can just give ' \ + ' a channel name.' }, { type: 'checkbox', name: 'colorize_messages' }, ] end - private - - def check_recipients_count - return true if recipients.nil? || recipients.empty? - - if recipients.split(/\s+/).count > max_chans - errors.add(:recipients, "are limited to #{max_chans}") - end + def help + ' NOTE: Irker does NOT have built-in authentication, which makes it' \ + ' vulnerable to spamming IRC channels if it is hosted outside of a ' \ + ' firewall. Please make sure you run the daemon within a secured network ' \ + ' to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html.' end - def max_chans - @settings[:max_channels] - end + private def get_channels return true unless :activated? @@ -114,40 +100,35 @@ class IrkerService < Service def map_recipients self.channels = recipients.split(/\s+/).map do |recipient| - format_channel default_irc_uri, recipient + format_channel(recipient) end channels.reject! &:nil? end - def default_irc_uri - default_irc = @settings[:default_irc_uri] - if !(default_irc.nil? || default_irc[-1] == '/') - default_irc += '/' - end - default_irc - end - - def format_channel(default_irc, recipient) - cnt = 0 - url = nil + def format_channel(recipient) + uri = nil # Try to parse the chan as a full URI begin - uri = URI.parse(recipient) - raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0 + uri = consider_uri(URI.parse(recipient)) rescue URI::InvalidURIError - unless default_irc.nil? - cnt += 1 - recipient = "#{default_irc}#{recipient}" - retry if cnt == 1 + end + + unless uri.present? and default_irc_uri.nil? + begin + new_recipient = URI.join(default_irc_uri, '/', recipient).to_s + uri = consider_uri(URI.parse(new_recipient)) + rescue + Rails.logger.error("Unable to create a valid URL from #{default_irc_uri} and #{recipient}") end - else - url = consider_uri uri end - url + + uri end def consider_uri(uri) + return nil if uri.scheme.nil? + # Authorize both irc://domain.com/#chan and irc://domain.com/chan if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? # Do not authorize irc://domain.com/ diff --git a/app/models/repository.rb b/app/models/repository.rb index b32e8847bb5..6262b5c4c92 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -8,7 +8,7 @@ class Repository @project = project if path_with_namespace - @raw_repository = Gitlab::Git::Repository.new(path_to_repo) + @raw_repository = Gitlab::Git::Repository.new(path_to_repo) @raw_repository.autocrlf = :input end @@ -173,7 +173,9 @@ class Repository end def blob_at(sha, path) - Gitlab::Git::Blob.find(self, sha, path) + unless Gitlab::Git.blank_ref?(sha) + Gitlab::Git::Blob.find(self, sha, path) + end end def blob_by_oid(oid) @@ -412,8 +414,6 @@ class Repository Gitlab::Git::Blob.remove(raw_repository, options) end - private - def user_to_comitter(user) { email: user.email, @@ -422,6 +422,51 @@ class Repository } end + def can_be_merged?(source_branch, target_branch) + our_commit = rugged.branches[target_branch].target + their_commit = rugged.branches[source_branch].target + + if our_commit && their_commit + !rugged.merge_commits(our_commit, their_commit).conflicts? + end + end + + def search_files(query, ref) + offset = 2 + args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref}) + Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/) + end + + def parse_search_result(result) + ref = nil + filename = nil + startline = 0 + + lines = result.lines + lines.each_with_index do |line, index| + if line =~ /^.*:.*:\d+:/ + ref, filename, startline = line.split(':') + startline = startline.to_i - index + break + end + end + + data = lines.map do |line| + line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') + end + + data = data.join("") + + OpenStruct.new( + filename: filename, + ref: ref, + startline: startline, + data: data + ) + end + + private + def cache @cache ||= RepositoryCache.new(path_with_namespace) end diff --git a/app/models/security_event.rb b/app/models/security_event.rb new file mode 100644 index 00000000000..d131c11cb6c --- /dev/null +++ b/app/models/security_event.rb @@ -0,0 +1,2 @@ +class SecurityEvent < AuditEvent +end diff --git a/app/models/user.rb b/app/models/user.rb index dc84f5141d8..317257a2500 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -322,6 +322,16 @@ class User < ActiveRecord::Base @reset_token end + def disable_two_factor! + update_attributes( + two_factor_enabled: false, + encrypted_otp_secret: nil, + encrypted_otp_secret_iv: nil, + encrypted_otp_secret_salt: nil, + otp_backup_codes: nil + ) + end + def namespace_uniq namespace_name = self.username existing_namespace = Namespace.by_path(namespace_name) diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb new file mode 100644 index 00000000000..a7f090655e1 --- /dev/null +++ b/app/services/audit_event_service.rb @@ -0,0 +1,25 @@ +class AuditEventService + def initialize(author, entity, details = {}) + @author, @entity, @details = author, entity, details + end + + def for_authentication + @details = { + with: @details[:with], + target_id: @author.id, + target_type: "User", + target_details: @author.name, + } + + self + end + + def security_event + SecurityEvent.create( + author_id: @author.id, + entity_id: @entity.id, + entity_type: @entity.class.name, + details: @details + ) + end +end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 1d99223cfe6..f1ef5ca84fe 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -26,4 +26,12 @@ class IssuableBaseService < BaseService issuable, issuable.project, current_user, branch_type, old_branch, new_branch) end + + def filter_params + unless can?(current_user, :admin_issue, project) + params.delete(:milestone_id) + params.delete(:label_ids) + params.delete(:assignee_id) + end + end end diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb index eb07413ee94..de8387c4900 100644 --- a/app/services/issues/bulk_update_service.rb +++ b/app/services/issues/bulk_update_service.rb @@ -10,7 +10,7 @@ module Issues issues = Issue.where(id: issues_ids) issues.each do |issue| - next unless can?(current_user, :modify_issue, issue) + next unless can?(current_user, :update_issue, issue) Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue) end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index d5c17906a55..1ea4b72216c 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -1,6 +1,7 @@ module Issues class CreateService < Issues::BaseService def execute + filter_params label_params = params[:label_ids] issue = project.issues.new(params.except(:label_ids)) issue.author = current_user diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 6af942a5ca4..f848ecedd6b 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -1,26 +1,20 @@ module Issues class UpdateService < Issues::BaseService def execute(issue) - state = params[:state_event] - - case state + case params.delete(:state_event) when 'reopen' Issues::ReopenService.new(project, current_user, {}).execute(issue) when 'close' Issues::CloseService.new(project, current_user, {}).execute(issue) - when 'task_check' - issue.update_nth_task(params[:task_num].to_i, true) - when 'task_uncheck' - issue.update_nth_task(params[:task_num].to_i, false) end params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE + filter_params old_labels = issue.labels.to_a - if params.present? && issue.update_attributes(params.except(:state_event, - :task_num)) + if params.present? && issue.update_attributes(params) issue.reset_events_cache if issue.labels != old_labels diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb index cdedf48b0c0..df793fc997d 100644 --- a/app/services/merge_requests/auto_merge_service.rb +++ b/app/services/merge_requests/auto_merge_service.rb @@ -5,17 +5,20 @@ module MergeRequests # mark merge request as merged and execute all hooks and notifications # Called when you do merge via GitLab UI class AutoMergeService < BaseMergeService + attr_reader :merge_request, :commit_message + def execute(merge_request, commit_message) + @commit_message = commit_message + @merge_request = merge_request + merge_request.lock_mr - if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) + if merge! merge_request.merge - create_merge_event(merge_request, current_user) create_note(merge_request) notification_service.merge_mr(merge_request, current_user) execute_hooks(merge_request, 'merge') - true else merge_request.unlock_mr @@ -26,5 +29,39 @@ module MergeRequests merge_request.mark_as_unmergeable false end + + def merge! + if merge_request.for_fork? + Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) + else + # Merge local branches using rugged instead of satellites + if sha = commit + after_commit(sha, merge_request.target_branch) + end + end + end + + def commit + committer = repository.user_to_comitter(current_user) + + options = { + message: commit_message, + author: committer, + committer: committer + } + + repository.merge(merge_request.source_branch, merge_request.target_branch, options) + end + + def after_commit(sha, branch) + commit = repository.commit(sha) + full_ref = 'refs/heads/' + branch + old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA + GitPushService.new.execute(project, current_user, old_sha, sha, full_ref) + end + + def repository + project.repository + end end end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index ca8d80f6c0c..f431c5d5534 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -1,6 +1,7 @@ module MergeRequests class CreateService < MergeRequests::BaseService def execute + filter_params label_params = params[:label_ids] merge_request = MergeRequest.new(params.except(:label_ids)) merge_request.source_project = project diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 4f6c6cba9a9..e5c5368f5d6 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -11,27 +11,20 @@ module MergeRequests params.except!(:target_project_id) params.except!(:source_branch) - state = params[:state_event] - - case state + case params.delete(:state_event) when 'reopen' MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request) when 'close' MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request) - when 'task_check' - merge_request.update_nth_task(params[:task_num].to_i, true) - when 'task_uncheck' - merge_request.update_nth_task(params[:task_num].to_i, false) end params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE + filter_params old_labels = merge_request.labels.to_a - if params.present? && merge_request.update_attributes( - params.except(:state_event, :task_num) - ) + if params.present? && merge_request.update_attributes(params) merge_request.reset_events_cache if merge_request.labels != old_labels diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 489e03bd5ef..f43c0ef70e9 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -11,19 +11,16 @@ module Projects include Gitlab::ShellAdapter class TransferError < StandardError; end - def execute - namespace_id = params[:new_namespace_id] - namespace = Namespace.find_by(id: namespace_id) - - if allowed_transfer?(current_user, project, namespace) - transfer(project, namespace) + def execute(new_namespace) + if allowed_transfer?(current_user, project, new_namespace) + transfer(project, new_namespace) else - project.errors.add(:namespace, 'is invalid') + project.errors.add(:new_namespace, 'is invalid') false end rescue Projects::TransferService::TransferError => ex project.reload - project.errors.add(:namespace_id, ex.message) + project.errors.add(:new_namespace, ex.message) false end diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb index 9d181c2d2ab..e9328bb7323 100644 --- a/app/services/update_snippet_service.rb +++ b/app/services/update_snippet_service.rb @@ -9,9 +9,9 @@ class UpdateSnippetService < BaseService def execute # check that user is allowed to set specified visibility_level new_visibility = params[:visibility_level] + if new_visibility && new_visibility.to_i != snippet.visibility_level - unless can?(current_user, :change_visibility_level, snippet) && - Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) + unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) deny_visibility_level(snippet, new_visibility) return snippet end diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml index 6405a69fad3..2bf1689cbc6 100644 --- a/app/views/admin/deploy_keys/index.html.haml +++ b/app/views/admin/deploy_keys/index.html.haml @@ -16,8 +16,7 @@ - @deploy_keys.each do |deploy_key| %tr %td - = link_to admin_deploy_key_path(deploy_key) do - %strong= deploy_key.title + %strong= deploy_key.title %td %code.key-fingerprint= deploy_key.fingerprint %td diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml index b405aa6e8e3..0525552ebf8 100644 --- a/app/views/admin/identities/_form.html.haml +++ b/app/views/admin/identities/_form.html.haml @@ -12,7 +12,7 @@ .form-group = f.label :extern_uid, "Identifier", class: 'control-label' .col-sm-10 - = f.text_field :extern_uid, required: true, class: 'form-control', required: true + = f.text_field :extern_uid, class: 'form-control', required: true .form-actions = f.submit 'Save changes', class: "btn btn-save" diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 9c1bec7c84d..b0d31170704 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -93,6 +93,8 @@ = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success" - else = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning" + - if user.access_locked? + = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success", data: { confirm: 'Are you sure?' } - if user.can_be_removed? = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove" = paginate @users, theme: "gitlab" diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 2662b3569ec..33730ff05df 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -43,6 +43,7 @@ %strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'} - if @user.two_factor_enabled? Enabled + = link_to 'Disable', disable_two_factor_admin_user_path(@user), data: {confirm: 'Are you sure?'}, method: :patch, class: 'btn btn-xs btn-remove pull-right', title: 'Disable Two-factor Authentication' - else Disabled @@ -131,6 +132,14 @@ %li Owned groups will be left %br = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning" + - if @user.access_locked? + .panel.panel-info + .panel-heading + This account has been locked + .panel-body + %p This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account. + %br + = link_to 'Unlock user', unlock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } .panel.panel-danger .panel-heading diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 0dd2edbb1bc..94318d1bcf5 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -17,5 +17,5 @@ = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do %i.fa.fa-rss - = render 'shared/issuable_filter', type: :issues + = render 'shared/issuable/filter', type: :issues = render 'shared/issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 61d2fbe538c..90611d562b0 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -7,5 +7,5 @@ List all merge requests from all projects you have access to. %hr .append-bottom-20 - = render 'shared/issuable_filter', type: :merge_requests + = render 'shared/issuable/filter', type: :merge_requests = render 'shared/merge_requests' diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml index 4bba72167e3..bfa95ce79a7 100644 --- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml +++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml @@ -1,4 +1,9 @@ - submit_btn_css ||= 'btn btn-link btn-remove' -= form_tag oauth_authorized_application_path(application) do +- if defined?(token) + - path = oauth_authorized_application_path(0, token_id: token) +- else + - path = oauth_authorized_application_path(application) + += form_tag path do %input{:name => "_method", :type => "hidden", :value => "delete"}/ - = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-sm'
\ No newline at end of file + = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-sm' diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 02b1dec753c..b8409f64665 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -3,14 +3,16 @@ .event-item-timestamp #{time_ago_with_tooltip(event.created_at)} - = cache [event, current_user] do - = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' - - - if event.push? - = render "events/event/push", event: event - - elsif event.commented? - = render "events/event/note", event: event - - elsif event.created_project? + - if event.created_project? + = cache [event, current_user] do + = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' = render "events/event/created_project", event: event - - else - = render "events/event/common", event: event
\ No newline at end of file + - else + = cache event do + = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' + - if event.push? + = render "events/event/push", event: event + - elsif event.commented? + = render "events/event/note", event: event + - else + = render "events/event/common", event: event diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index aa13ed85b53..2ff4b7e23ea 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,4 +1,3 @@ -- page_title "Settings" .panel.panel-default .panel-heading %strong= @group.name diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml index ec39a755f0f..b460e0ff59e 100644 --- a/app/views/groups/group_members/_group_member.html.haml +++ b/app/views/groups/group_members/_group_member.html.haml @@ -32,7 +32,7 @@ %span.pull-right %strong= member.human_access - if show_controls - - if can?(current_user, :modify_group_member, member) + - if can?(current_user, :update_group_member, member) = button_tag class: "btn-xs btn js-toggle-button", title: 'Edit access level', type: 'button' do %i.fa.fa-pencil-square-o diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index e0756e909be..f0d90782556 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -21,5 +21,5 @@ = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do %i.fa.fa-rss - = render 'shared/issuable_filter', type: :issues + = render 'shared/issuable/filter', type: :issues = render 'shared/issues' diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 3d9e857cc52..ca85a158707 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -10,5 +10,5 @@ To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. %hr .append-bottom-20 - = render 'shared/issuable_filter', type: :merge_requests + = render 'shared/issuable/filter', type: :merge_requests = render 'shared/merge_requests' diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 825acb0ae3e..e809d99ba71 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -80,6 +80,12 @@ .key g .key p %td + Go to the project's home page + %tr + %td.shortcut + .key g + .key e + %td Go to the project's activity feed %tr %td.shortcut diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 5edc03129d2..db7dbf9bfe3 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,5 +1,5 @@ - page_title @group.name - header_title @group.name, group_path(@group) -- sidebar "group" +- sidebar "group" unless sidebar = render template: "layouts/application" diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml new file mode 100644 index 00000000000..e303a561628 --- /dev/null +++ b/app/views/layouts/group_settings.html.haml @@ -0,0 +1,4 @@ +- page_title "Settings" +- sidebar "group_settings" + += render template: "layouts/group" diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 9f1654b25b4..fc74d0205c6 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,9 +1,17 @@ %ul.nav.nav-sidebar + = nav_link do + = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'} do + = icon('caret-square-o-left fw') + %span + Back to Dashboard + + %li.separate-item + = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: 'Home', data: {placement: 'right'} do = icon('dashboard fw') %span - Activity + Group - if current_user = nav_link(controller: [:group, :milestones]) do = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do @@ -29,25 +37,9 @@ = icon('users fw') %span Members - - if can?(current_user, :admin_group, @group) - = nav_link(html_options: { class: "#{"active" if group_settings_page?} separate-item" }) do - = link_to edit_group_path(@group), title: 'Settings', class: 'tab no-highlight', data: {placement: 'right'} do + = nav_link(html_options: { class: "separate-item" }) do + = link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do = icon ('cogs fw') %span Settings - = icon ('angle-down fw') - - - if group_settings_page? - %ul.sidebar-subnav - = nav_link(path: 'groups#edit') do - = link_to edit_group_path(@group), title: 'Group', data: {placement: 'right'} do - = icon('pencil-square-o') - %span - Group Settings - = nav_link(path: 'groups#projects') do - = link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do - = icon('folder') - %span - Projects - diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml new file mode 100644 index 00000000000..72ada771ca4 --- /dev/null +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -0,0 +1,20 @@ +%ul.nav.nav-sidebar + = nav_link do + = link_to group_path(@group), title: 'Back to group', data: {placement: 'right'} do + = icon('caret-square-o-left fw') + %span + Back to group + + %li.separate-item + + %ul.sidebar-subnav + = nav_link(path: 'groups#edit') do + = link_to edit_group_path(@group), title: 'Group Settings', data: {placement: 'right'} do + = icon ('pencil-square-o fw') + %span + Group Settings + = nav_link(path: 'groups#projects') do + = link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do + = icon('folder fw') + %span + Projects diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 914e1b83d1f..00d3cb300d4 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,4 +1,12 @@ %ul.nav.nav-sidebar + = nav_link do + = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'} do + = icon('caret-square-o-left fw') + %span + Back to Dashboard + + %li.separate-item + = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile', data: {placement: 'right'} do = icon('user fw') @@ -44,8 +52,8 @@ = icon('image fw') %span Preferences - = nav_link(path: 'profiles#history') do - = link_to history_profile_path, title: 'History', data: {placement: 'right'} do + = nav_link(path: 'profiles#audit_log') do + = link_to audit_log_profile_path, title: 'Audit Log', data: {placement: 'right'} do = icon('history fw') %span - History + Audit Log diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index cbcf560d0af..a92d618284d 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,9 +1,29 @@ -%ul.project-navigation.nav.nav-sidebar +%ul.nav.nav-sidebar + - if @project.group + = nav_link do + = link_to group_path(@project.group), title: 'Back to group', data: {placement: 'right'} do + = icon('caret-square-o-left fw') + %span + Back to Group + - else + = nav_link do + = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'} do + = icon('caret-square-o-left fw') + %span + Back to Dashboard + + %li.separate-item + = nav_link(path: 'projects#show', html_options: {class: 'home'}) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do - = icon('dashboard fw') + = icon('home fw') %span Project + = nav_link(path: 'projects#activity') do + = link_to activity_project_path(@project), title: 'Project Activity', class: 'shortcuts-project-activity', data: {placement: 'right'} do + = icon('dashboard fw') + %span + Activity - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do = link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do @@ -86,7 +106,7 @@ - if project_nav_tab? :settings = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do - = link_to edit_project_path(@project), title: 'Settings', class: 'stat-tab tab no-highlight', data: {placement: 'right'} do + = link_to edit_project_path(@project), title: 'Settings', data: {placement: 'right'} do = icon('cogs fw') %span Settings diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 633c6ae6bfb..9c86d3c09b2 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -1,4 +1,4 @@ -%ul.project-navigation.nav.nav-sidebar +%ul.nav.nav-sidebar = nav_link do = link_to project_path(@project), title: 'Back to project', data: {placement: 'right'} do = icon('caret-square-o-left fw') @@ -7,9 +7,9 @@ %li.separate-item - %ul.project-settings-nav.sidebar-subnav + %ul.sidebar-subnav = nav_link(path: 'projects#edit') do - = link_to edit_project_path(@project), title: 'Project', class: 'stat-tab tab', data: {placement: 'right'} do + = link_to edit_project_path(@project), title: 'Project Settings', data: {placement: 'right'} do = icon('pencil-square-o fw') %span Project Settings @@ -32,5 +32,5 @@ = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do = icon('lock fw') %span - Protected branches + Protected Branches diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml new file mode 100644 index 00000000000..c19ac429d52 --- /dev/null +++ b/app/views/profiles/_event_table.html.haml @@ -0,0 +1,16 @@ +%table.table#audits + %thead + %tr + %th Action + %th When + + %tbody + - events.each do |event| + %tr + %td + %span + Signed in with + %b= event.details[:with] + authentication + %td #{time_ago_in_words event.created_at} ago += paginate events, theme: "gitlab" diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml index 2c4f0804f0b..3a3e6e1b1c4 100644 --- a/app/views/profiles/applications.html.haml +++ b/app/views/profiles/applications.html.haml @@ -56,5 +56,14 @@ %td= token.created_at %td= token.scopes %td= render 'doorkeeper/authorized_applications/delete_form', application: app + - @authorized_anonymous_tokens.each do |token| + %tr + %td + Anonymous + %div.help-block + %em Authorization was granted by entering your username and password in the application. + %td= token.created_at + %td= token.scopes + %td= render 'doorkeeper/authorized_applications/delete_form', token: token - else - %p.light You dont have any authorized applications + %p.light You don't have any authorized applications diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml new file mode 100644 index 00000000000..698d6037428 --- /dev/null +++ b/app/views/profiles/audit_log.html.haml @@ -0,0 +1,5 @@ +- page_title "Audit Log" +%h3.page-title Audit Log +%p.light History of authentications + += render 'event_table', events: @events
\ No newline at end of file diff --git a/app/views/profiles/history.html.haml b/app/views/profiles/history.html.haml deleted file mode 100644 index b414fb69f4e..00000000000 --- a/app/views/profiles/history.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -- page_title "History" -%h3.page-title - Your Account History -%p.light - All events created by your account are listed below. -%hr -.profile_history - = render @events -%hr -= paginate @events, theme: "gitlab" - diff --git a/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml deleted file mode 100644 index c9c17110d2b..00000000000 --- a/app/views/projects/_aside.html.haml +++ /dev/null @@ -1,108 +0,0 @@ -.clearfix - - unless @project.empty_repo? - .panel.panel-default - .panel-heading - = visibility_level_icon(@project.visibility_level) - = "#{visibility_level_label(@project.visibility_level).capitalize} project" - - .panel-body - - if @repository.changelog || @repository.license || @repository.contribution_guide - %ul.nav.nav-pills - - if @repository.changelog - %li.hidden-xs - = link_to changelog_url(@project) do - Changelog - - if @repository.license - %li - = link_to license_url(@project) do - License - - if @repository.contribution_guide - %li - = link_to contribution_guide_url(@project) do - Contribution guide - - .actions - - if can? current_user, :write_issue, @project - = link_to url_for_new_issue(@project, only_path: true), title: "New Issue", class: 'btn btn-sm append-right-10' do - = icon("exclamation-circle fw") - New Issue - - - if can? current_user, :write_merge_request, @project - = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-sm", title: "New Merge Request" do - = icon("plus fw") - New Merge Request - - - if forked_from_project = @project.forked_from_project - .panel-footer - = icon("code-fork fw") - Forked from - .pull-right - = link_to forked_from_project.namespace.try(:name), project_path(forked_from_project) - - - - @project.ci_services.each do |ci_service| - - if ci_service.active? && ci_service.respond_to?(:builds_path) - .panel-footer - = icon("check fw") - = ci_service.title - .pull-right - - if ci_service.respond_to?(:status_img_path) - = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do - = image_tag ci_service.status_img_path, alt: "build status", class: 'ci-status-image' - - else - = link_to 'view builds', ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' - - - - unless @project.empty_repo? - .panel.panel-default - .panel-heading - = icon("folder-o fw") - Repository - .panel-body - %ul.nav.nav-pills - %li - = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do - = pluralize(number_with_delimiter(@repository.commit_count), 'commit') - %li - = link_to namespace_project_branches_path(@project.namespace, @project) do - = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch') - %li - = link_to namespace_project_tags_path(@project.namespace, @project) do - = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag') - - .actions - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-sm append-right-10' do - %i.fa.fa-exchange - Compare code - - - if can?(current_user, :download_code, @project) - = render 'projects/repositories/download_archive', split_button: true, btn_class: 'btn-group-sm' - - if version = @repository.version - .panel-footer - = icon("clock-o fw") - Version - .pull-right - = link_to version_url(@project) do - = @repository.blob_by_oid(version.id).data - - = render "shared/clone_panel" - - - if @project.archived? - %br - .alert.alert-warning - %h4 - = icon("exclamation-triangle fw") - Archived project! - %p Repository is read-only - - - if current_user - - access = user_max_access_in_project(current_user, @project) - - if access - .light-well.light.prepend-top-20 - %small - You have #{access} access to this project. - - if @project.project_member_by_id(current_user) - %br - = link_to leave_namespace_project_project_members_path(@project.namespace, @project), - data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do - Leave this project diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 076afb11a9d..95c84c96c41 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,37 +1,26 @@ - empty_repo = @project.empty_repo? .project-home-panel.clearfix{:class => ("empty-project" if empty_repo)} .project-identicon-holder - = project_icon(@project, alt: '', class: 'avatar project-avatar') - .project-home-row.project-home-row-top - .project-home-desc - - if @project.description.present? - = markdown(@project.description, pipeline: :description) - - if can?(current_user, :admin_project, @project) - – - = link_to 'Edit', edit_namespace_project_path - - elsif !empty_repo && @repository.readme - - readme = @repository.readme - – - = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do - = readme.name - .project-repo-buttons - .inline.star.js-toggler-container{class: @show_star ? 'on' : ''} - - if current_user - = link_to_toggle_star('Star this project.', false) - = link_to_toggle_star('Unstar this project.', true) - - else - = link_to new_user_session_path, class: 'btn star-btn has_tooltip', title: 'You must sign in to star a project' do - %span - = icon('star') - Star - %span.count - = @project.star_count - - unless empty_repo - - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace - .inline.fork-buttons.prepend-left-10 - - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-sm btn-default' do - = link_to_toggle_fork - - else - = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-sm btn-default' do - = link_to_toggle_fork + = project_icon(@project, alt: '', class: 'project-avatar avatar s90') + .project-home-desc.lead + - if @project.description.present? + = markdown(@project.description, pipeline: :description) + + + .project-repo-buttons + = render 'projects/buttons/star' + + - unless empty_repo + = render 'projects/buttons/fork' + + - if forked_from_project = @project.forked_from_project + = link_to project_path(forked_from_project), class: 'btn' do + = icon("code-fork fw") + Forked from + = forked_from_project.namespace.try(:name) + + - if can? current_user, :download_code, @project + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do + %i.fa.fa-download + + = render "shared/clone_panel" diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml new file mode 100644 index 00000000000..30622d8a910 --- /dev/null +++ b/app/views/projects/_last_push.html.haml @@ -0,0 +1,14 @@ +- if event = last_push_event + - if show_last_push_widget?(event) + .hidden-xs.center + .slead + %span You pushed to + = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do + %strong= event.ref_name + branch + #{time_ago_with_tooltip(event.created_at)} + + %div + = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do + Create Merge Request + %hr diff --git a/app/views/projects/_section.html.haml b/app/views/projects/_section.html.haml deleted file mode 100644 index d7b06197f67..00000000000 --- a/app/views/projects/_section.html.haml +++ /dev/null @@ -1,36 +0,0 @@ -%ul.nav.nav-tabs - %li.active - = link_to '#tab-activity', 'data-toggle' => 'tab' do - = icon("tachometer") - Activity - - if @repository.readme - %li - = link_to '#tab-readme', 'data-toggle' => 'tab' do - = icon("file-text-o") - Readme -.tab-content - .tab-pane.active#tab-activity - .hidden-xs - = render "events/event_last_push", event: @last_push - - - if current_user - %ul.nav.nav-pills.event_filter.pull-right - %li - = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do - %i.fa.fa-rss - - = render 'shared/event_filter' - %hr - .content_list - = spinner - - - if readme = @repository.readme - .tab-pane#tab-readme - %article.readme-holder#README - .clearfix - %small.pull-right - = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do - %i.fa.fa-file - = readme.name - .wiki - = render_readme(readme) diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index cf1c55ecca6..6a41cdbc907 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -2,7 +2,7 @@ %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' } .zen-backdrop - classes << ' js-gfm-input markdown-area' - = f.text_area attr, class: classes, placeholder: 'Leave a comment' + = f.text_area attr, class: classes, placeholder: '' = link_to nil, class: 'zen-enter-link', tabindex: '-1' do %i.fa.fa-expand Edit in fullscreen diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml new file mode 100644 index 00000000000..b486cd4ded4 --- /dev/null +++ b/app/views/projects/activity.html.haml @@ -0,0 +1,12 @@ += render 'projects/last_push' +.hidden-xs + - if current_user + %ul.nav.nav-pills.event_filter.pull-right + %li + = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do + %i.fa.fa-rss + + = render 'shared/event_filter' + %hr +.content_list += spinner diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index b8d8451880a..cae5ff01099 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -9,13 +9,10 @@ %strong= @ref .modal-body - = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal' do + = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-requires-input' do = render 'shared/commit_message_container', params: params, placeholder: 'Removed this file because...' .form-group .col-sm-offset-2.col-sm-10 = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" - -:javascript - disableButtonIfEmptyField('#commit_message', '.btn-remove-file') diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index e78181f8801..a12cd660fc1 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -11,10 +11,9 @@ %i.fa.fa-eye = editing_preview_title(@blob.name) - = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: "form-horizontal") do + = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input') do = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data - = render 'shared/commit_message_container', params: params, - placeholder: "Update #{@blob.name}" + = render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}" .form-group.branch = label_tag 'branch', class: 'control-label' do @@ -25,8 +24,7 @@ = hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] - = render 'projects/commit_button', ref: @ref, - cancel_path: @after_edit_path + = render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path :javascript blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index f7ddf74b4fc..7c2a4fece94 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -1,7 +1,7 @@ - page_title "New File", @ref %h3.page-title New file .file-editor - = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file') do + = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do = render 'projects/blob/editor', ref: @ref = render 'shared/commit_message_container', params: params, placeholder: 'Add new file' diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index a1d464bac59..bd2fc43633c 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -1,4 +1,7 @@ - page_title @blob.path, @ref + += render 'projects/last_push' + %div.tree-ref-holder = render 'shared/ref_switcher', destination: 'blob', path: @path diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index cac5dc91afd..29e82b93883 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -6,7 +6,7 @@ %h3.page-title %i.fa.fa-code-fork New branch -= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal" do += form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do .form-group = label_tag :branch_name, 'Name for new branch', class: 'control-label' .col-sm-10 @@ -20,7 +20,6 @@ = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel' :javascript - disableButtonIfAnyEmptyField($("#new-branch-form"), ".form-control", ".btn-create"); var availableTags = #{@project.repository.ref_names.to_json}; $("#ref").autocomplete({ diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml new file mode 100644 index 00000000000..f0483c79edc --- /dev/null +++ b/app/views/projects/buttons/_fork.html.haml @@ -0,0 +1,13 @@ +- if current_user && can?(current_user, :fork_project, @project) + - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 + = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn' do + = icon('code-fork') + Fork + %span.count + = @project.forks_count + - else + = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do + = icon('code-fork') + Fork + %span.count + = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml new file mode 100644 index 00000000000..b5f14b43bfd --- /dev/null +++ b/app/views/projects/buttons/_star.html.haml @@ -0,0 +1,22 @@ +- if current_user + = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do + = icon('star') + - if current_user.starred?(@project) + Unstar + - else + Star + %span.count + = @project.star_count + + :coffeescript + $('.project-home-panel .toggle-star').on 'ajax:success', (e, data, status, xhr) -> + $(@).replaceWith(data.html) + .on 'ajax:error', (e, xhr, status, error) -> + new Flash('Star toggle failed. Try again later.', 'alert') + +- else + = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do + = icon('star') + Star + %span.count + = @project.star_count diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index f9106564a27..74f8d8b15cf 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -1,33 +1,34 @@ -%li.commit.js-toggle-container - .commit-row-title - %strong.str-truncated - = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" - - if commit.description? - %a.text-expander.js-toggle-button ... +- if @note_counts + - note_count = @note_counts.fetch(commit.id, 0) +- else + - notes = commit.notes + - note_count = notes.user.count - .pull-right - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" += cache [project.id, commit.id, note_count] do + %li.commit.js-toggle-container + .commit-row-title + %strong.str-truncated + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" + - if commit.description? + %a.text-expander.js-toggle-button ... - .notes_count - - if @note_counts - - note_count = @note_counts.fetch(commit.id, 0) - - else - - notes = commit.notes - - note_count = notes.user.count + .pull-right + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" - - if note_count > 0 - %span.light - %i.fa.fa-comments - = note_count + .notes_count + - if note_count > 0 + %span.light + %i.fa.fa-comments + = note_count - - if commit.description? - .commit-row-description.js-toggle-content - %pre - = preserve(gfm(escape_once(commit.description))) + - if commit.description? + .commit-row-description.js-toggle-content + %pre + = preserve(gfm(escape_once(commit.description))) - .commit-row-info - = commit_author_link(commit, avatar: true, size: 24) - authored - .committed_ago - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} - = link_to_browse_code(project, commit) + .commit-row-info + = commit_author_link(commit, avatar: true, size: 24) + authored + .committed_ago + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} + = link_to_browse_code(project, commit) diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 9682100a54c..55054a31977 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -15,9 +15,8 @@ Create Merge Request - if current_user && current_user.private_token - = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'prepend-left-10 btn' do + = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'prepend-left-10 btn' do = icon("rss") - Commits Feed %ul.breadcrumb.repo-breadcrumb diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index a0e904cfd8b..3019893d12c 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,16 +1,16 @@ -= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline' do += form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do .clearfix.append-bottom-20 - if params[:to] && params[:from] = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'} .form-group .input-group.inline-input-group %span.input-group-addon from - = text_field_tag :from, params[:from], class: "form-control" + = text_field_tag :from, params[:from], class: "form-control", required: true = "..." .form-group .input-group.inline-input-group %span.input-group-addon to - = text_field_tag :to, params[:to], class: "form-control" + = text_field_tag :to, params[:to], class: "form-control", required: true = button_tag "Compare", class: "btn btn-create commits-compare-btn" - if create_mr_button? @@ -26,5 +26,3 @@ source: availableTags, minLength: 1 }); - - disableButtonIfEmptyField('#to', '.commits-compare-btn'); diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index ec8974c5475..52c1e03040c 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -20,6 +20,3 @@ Failed to collect changes %p Maybe diff is really big and operation failed with timeout. Try to get diff locally - -:coffeescript - $('.files .diff-header').stick_in_parent(offset_top: $('.navbar').height()) diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index cb41dd852d3..37fd1b1ec8a 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -18,7 +18,7 @@ - elsif type_left == 'old' || type_left.nil? %td.old_line{id: line_code_left, class: "#{type_left}"} = link_to raw(line_number_left), "##{line_code_left}", id: line_code_left - - if @comments_allowed && can?(current_user, :write_note, @project) + - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(line_code_left, 'old') %td.line_content{class: "parallel noteable_line #{type_left} #{line_code_left}", "line_code" => line_code_left }= raw line_content_left @@ -31,7 +31,7 @@ %td.new_line{id: new_line_code, class: "#{new_line_class}", data: { linenumber: line_number_right }} = link_to raw(line_number_right), "##{new_line_code}", id: new_line_code - - if @comments_allowed && can?(current_user, :write_note, @project) + - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(line_code_right, 'new') %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}= raw line_content_right diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index a6373181b45..ed4c601bcdb 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -16,7 +16,7 @@ - else %td.old_line = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code - - if @comments_allowed && can?(current_user, :write_note, @project) + - if @comments_allowed && can?(current_user, :create_note, @project) = link_to_new_diff_note(line_code) %td.new_line{data: {linenumber: line.new_pos}} = link_to raw(type == "old" ? " " : line.new_pos) , "##{line_code}", id: line_code diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index bd0b7376ba7..da3d4be84ba 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -1,6 +1,6 @@ .alert.alert-warning %h4 - Too many changes. + Too many changes to show. .pull-right - unless diff_hard_limit_enabled? = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-sm btn-warning" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 3fecd25c324..e8e65d87f47 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -29,7 +29,7 @@ .col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'}) - = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project), form_model: @project + = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project .form-group = f.label :tag_list, "Tags", class: 'control-label' @@ -176,7 +176,7 @@ .form-group = label_tag :new_namespace_id, nil, class: 'control-label' do %span Namespace - .col-sm-10 + .col-sm-9 .form-group = select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' } %ul diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 8080a904978..dfe45a3802d 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -4,30 +4,30 @@ = render "home_panel" -.center.well - %h3 +.center.light-well + %h3.page-title The repository for this project is empty - %h4 - You can - = link_to namespace_project_new_blob_path(@project.namespace, @project, 'master'), class: 'btn btn-new btn-lg' do - add a file - or do a push via the command line. + %p + If you already have files you can push them using command line instructions below. + %br + Otherwise you can start with + = link_to "adding README", new_readme_path, class: 'underlined-link' + file to this project. -.well - = render "shared/clone_panel" -%h4 - %strong Command line instructions +.prepend-top-20 +%h3.page-title + Command line instructions %div.git-empty %fieldset - %legend Git global setup - %pre.dark + %h5 Git global setup + %pre.light-well :preserve git config --global user.name "#{git_user_name}" git config --global user.email "#{git_user_email}" %fieldset - %legend Create a new repository - %pre.dark + %h5 Create a new repository + %pre.light-well :preserve git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')} cd #{@project.path} @@ -37,8 +37,8 @@ git push -u origin master %fieldset - %legend Existing folder or Git repository - %pre.dark + %h5 Existing folder or Git repository + %pre.light-well :preserve cd existing_folder git init diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index 254a76e108b..141acbdcf72 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -1,9 +1,11 @@ - page_title "Commit statistics" +.tree-ref-holder + = render 'shared/ref_switcher', destination: 'graphs_commits' = render 'head' %p.lead Commit statistics for - %strong #{@repository.root_ref} + %strong #{@ref} #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} .row diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 3a8dc89f84c..ecdd0eaf52f 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,5 +1,8 @@ - page_title "Contributor statistics" +.tree-ref-holder + = render 'shared/ref_switcher', destination: 'graphs' = render 'head' + .loading-graph .center %h3.page-title @@ -11,7 +14,7 @@ .header.clearfix %h3#date_header.page-title %p.light - Commits to #{@project.default_branch}, excluding merge commits. Limited by 6,000 commits + Commits to #{@ref}, excluding merge commits. Limited by 6,000 commits %input#brush_change{:type => "hidden"} .graphs #contributors-master @@ -35,4 +38,3 @@ $(".stat-graph").fadeIn(); $(".loading-graph").hide(); dataType: "json" - diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 48858fa32da..f61ae957208 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -1,5 +1,5 @@ - content_for :note_actions do - - if can?(current_user, :modify_issue, @issue) + - if can?(current_user, :update_issue, @issue) - if @issue.closed? = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue' - else @@ -12,8 +12,8 @@ .votes-holder.pull-right #votes= render 'votes/votes_block', votable: @issue .participants - %span= pluralize(@issue.participants(current_user).count, 'participant') - - @issue.participants(current_user).each do |participant| + %span= pluralize(@participants.count, 'participant') + - @participants.each do |participant| = link_to_member(@project, participant, name: false, size: 24) .voting_notes#notes= render 'projects/notes/notes_with_form' %aside.col-md-3 @@ -23,7 +23,7 @@ = cross_project_reference(@project, @issue) %hr .context - = render partial: 'issue_context', locals: { issue: @issue } + = render 'shared/issuable/context', issuable: @issue - if @issue.labels.any? .issuable-context-title diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 8d2564be55e..f39bb7d2574 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -3,7 +3,7 @@ %hr = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f| - = render 'projects/issuable_form', f: f, issuable: @issue + = render 'shared/issuable/form', f: f, issuable: @issue :javascript $('.assign-to-me-link').on('click', function(e){ diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 2c296cab977..1b45bb1af0c 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -1,44 +1,45 @@ %li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue) } - - if controller.controller_name == 'issues' + - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) .issue-check - = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) + = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue" - .issue-title - %span.issue-title-text - = link_to_gfm issue.title, issue_path(issue), class: "row_title" - .issue-labels - - issue.labels.each do |label| - = link_to_label(label, project: issue.project) - .pull-right.light - - if issue.closed? - %span - CLOSED - - if issue.assignee - = link_to_member(@project, issue.assignee, name: false) - - note_count = issue.notes.user.count - - if note_count > 0 + = cache issue do + .issue-title + %span.issue-title-text + = link_to_gfm issue.title, issue_path(issue), class: "row_title" + .issue-labels + - issue.labels.each do |label| + = link_to_label(label, project: issue.project) + .pull-right.light + - if issue.closed? + %span + CLOSED + - if issue.assignee + = link_to_member(@project, issue.assignee, name: false) + - note_count = issue.notes.user.count + - if note_count > 0 + + %span + %i.fa.fa-comments + = note_count + - else + + %span.issue-no-comments + %i.fa.fa-comments + = 0 + + .issue-info + = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe + - if issue.votes_count > 0 + = render 'votes/votes_inline', votable: issue + - if issue.milestone %span - %i.fa.fa-comments - = note_count - - else - - %span.issue-no-comments - %i.fa.fa-comments - = 0 - - .issue-info - = "##{issue.iid} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe - - if issue.votes_count > 0 - = render 'votes/votes_inline', votable: issue - - if issue.milestone - - %span - %i.fa.fa-clock-o - = issue.milestone.title - - if issue.tasks? - %span.task-status - = issue.task_status + %i.fa.fa-clock-o + = issue.milestone.title + - if issue.tasks? + %span.task-status + = issue.task_status - .pull-right.issue-updated-at - %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')} + .pull-right.issue-updated-at + %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')} diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml deleted file mode 100644 index 323f5c84a85..00000000000 --- a/app/views/projects/issues/_issue_context.html.haml +++ /dev/null @@ -1,46 +0,0 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @issue], remote: true, html: {class: 'edit-issue inline-update js-issue-update'} do |f| - %div.prepend-top-20 - .issuable-context-title - %label - Assignee: - - if issue.assignee - %strong= link_to_member(@project, @issue.assignee, size: 24) - - else - none - - if can?(current_user, :modify_issue, @issue) - = users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id, null_user: true, first_user: true) - - %div.prepend-top-20.clearfix - .issuable-context-title - %label - Milestone: - - if issue.milestone - %span.back-to-milestone - = link_to namespace_project_milestone_path(@project.namespace, @project, @issue.milestone) do - %strong - %i.fa.fa-clock-o - = @issue.milestone.title - - else - none - - if can?(current_user, :modify_issue, @issue) - = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'}) - = hidden_field_tag :issue_context - = f.submit class: 'btn' - - - if current_user - %div.prepend-top-20.clearfix - .issuable-context-title - %label - Subscription: - %button.btn.btn-block.subscribe-button{:type => 'button'} - %i.fa.fa-eye - %span= @issue.subscribed?(current_user) ? "Unsubscribe" : "Subscribe" - - subscribtion_status = @issue.subscribed?(current_user) ? "subscribed" : "unsubscribed" - .subscription-status{"data-status" => subscribtion_status} - .description-block.unsubscribed{class: ( "hidden" if @issue.subscribed?(current_user) )} - You're not receiving notifications from this thread. - .description-block.subscribed{class: ( "hidden" unless @issue.subscribed?(current_user) )} - You're receiving notifications because you're subscribed to this thread. - -:coffeescript - new Subscription("#{toggle_subscription_namespace_project_issue_path(@issue.project.namespace, @project, @issue)}") diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 1d5597602d1..d06225f5488 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -11,14 +11,14 @@ = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do %i.fa.fa-rss - = render 'shared/issuable_search_form', path: namespace_project_issues_path(@project.namespace, @project) + = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) - - if can? current_user, :write_issue, @project + - if can? current_user, :create_issue, @project = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do %i.fa.fa-plus New Issue - = render 'shared/issuable_filter', type: :issues + = render 'shared/issuable/filter', type: :issues .issues-holder = render "issues" diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index ee1b2a08bc4..54d33a5ddd1 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,6 +1,6 @@ - page_title "#{@issue.title} (##{@issue.iid})", "Issues" .issue - .issue-details + .issue-details.issuable-details %h4.page-title .issue-box{ class: issue_box_class(@issue) } - if @issue.closed? @@ -12,11 +12,11 @@ · created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)} .pull-right - - if can?(current_user, :write_issue, @project) + - if can?(current_user, :create_issue, @project) = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do = icon('plus') New Issue - - if can?(current_user, :modify_issue, @issue) + - if can?(current_user, :update_issue, @issue) - if @issue.closed? = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen' - else @@ -31,7 +31,7 @@ = gfm escape_once(@issue.title) %div - if @issue.description.present? - .description{class: can?(current_user, :modify_issue, @issue) ? 'js-task-list-container' : ''} + .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''} .wiki = preserve do = markdown(@issue.description) diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml index 1d38662bff8..b7735aaf3c1 100644 --- a/app/views/projects/issues/update.js.haml +++ b/app/views/projects/issues/update.js.haml @@ -1,17 +1,3 @@ -- if params[:status_only] - - if @issue.valid? - :plain - $("##{dom_id(@issue)}").fadeOut(); -- elsif params[:issue_context] - $('.context').html("#{escape_javascript(render partial: 'issue_context', locals: { issue: @issue })}"); - $('.context').effect('highlight'); - - if @issue.milestone - $('.milestone-nav-link').replaceWith("<span class='milestone-nav-link'>| <span class='light'>Milestone</span> #{escape_javascript(link_to @issue.milestone.title, namespace_project_milestone_path(@issue.project.namespace, @issue.project, @issue.milestone))}</span>") - - else - $('.milestone-nav-link').html('') - - -$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}) -$('.edit-issue.inline-update input[type="submit"]').hide(); -new UsersSelect() +$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @issue)}"); +$('.context').effect('highlight') new Issue(); diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index d791ed3410c..534c545329b 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f| -if @label.errors.any? .row .col-sm-offset-2.col-sm-10 diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 7fa1ee53f76..c6ebfa281a1 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -6,5 +6,5 @@ = pluralize label.open_issues_count, 'open issue' - if can? current_user, :admin_label, @project - = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn' - = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm' + = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index eb3dba6858d..f855dfec321 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -1,5 +1,5 @@ - content_for :note_actions do - - if can?(current_user, :modify_merge_request, @merge_request) + - if can?(current_user, :update_merge_request, @merge_request) - if @merge_request.open? = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" - if @merge_request.closed? @@ -20,7 +20,7 @@ = cross_project_reference(@project, @merge_request) %hr .context - = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request } + = render 'shared/issuable/context', issuable: @merge_request - if @merge_request.labels.any? .issuable-context-title diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index be73f087449..9cf389dbe38 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -1,9 +1,8 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f| .merge-request-form-info - = render 'projects/issuable_form', f: f, issuable: @merge_request + = render 'shared/issuable/form', f: f, issuable: @merge_request :javascript - disableButtonIfEmptyField("#merge_request_title", ".btn-save"); $('.assign-to-me-link').on('click', function(e){ $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index e611b23bca6..ff9c0cdb283 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -1,6 +1,6 @@ %p.lead Compare branches for new Merge Request -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline" } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f| .hide.alert.alert-danger.mr-compare-errors .merge-request-branches.row .col-md-6 @@ -8,9 +8,9 @@ .panel-heading %strong Source branch .panel-body - = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted? }) + = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true }) - = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'}) + = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2', required: true}) .panel-footer .mr_source_commit @@ -20,9 +20,9 @@ %strong Target branch .panel-body - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project] - = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? }) + = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true }) - = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'}) + = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2', required: true}) .panel-footer .mr_target_commit diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 6792104569b..633a54f3620 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -9,9 +9,9 @@ %span.pull-right = link_to 'Change branches', mr_change_branches_path(@merge_request) %hr -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f| .merge-request-form-info - = render 'projects/issuable_form', f: f, issuable: @merge_request + = render 'shared/issuable/form', f: f, issuable: @merge_request = f.hidden_field :source_project_id = f.hidden_field :source_branch = f.hidden_field :target_project_id diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 9dc4a47258e..b6d9b135c70 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,6 +1,6 @@ - page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" .merge-request{'data-url' => merge_request_path(@merge_request)} - .merge-request-details + .merge-request-details.issuable-details = render "projects/merge_requests/show/mr_title" %hr = render "projects/merge_requests/show/mr_box" diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index fa591b0537e..72fbe2e27a7 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,13 +1,14 @@ - page_title "Merge Requests" += render 'projects/last_push' .append-bottom-10 .pull-right - = render 'shared/issuable_search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) + = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) - - if can? current_user, :write_merge_request, @project + - if can? current_user, :create_merge_request, @project .pull-left.hidden-xs = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new", title: "New Merge Request" do %i.fa.fa-plus New Merge Request - = render 'shared/issuable_filter', type: :merge_requests + = render 'shared/issuable/filter', type: :merge_requests .merge-requests-holder = render 'merge_requests' diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml deleted file mode 100644 index 1d0e2e350b0..00000000000 --- a/app/views/projects/merge_requests/show/_context.html.haml +++ /dev/null @@ -1,48 +0,0 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update js-merge-request-update'} do |f| - %div.prepend-top-20 - .issuable-context-title - %label - Assignee: - - if @merge_request.assignee - %strong= link_to_member(@project, @merge_request.assignee, size: 24) - - else - none - .issuable-context-selectbox - - if can?(current_user, :modify_merge_request, @merge_request) - = users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id, project: @target_project, null_user: true) - - %div.prepend-top-20.clearfix - .issuable-context-title - %label - Milestone: - - if @merge_request.milestone - %span.back-to-milestone - = link_to namespace_project_milestone_path(@project.namespace, @project, @merge_request.milestone) do - %strong - = icon('clock-o') - = @merge_request.milestone.title - - else - none - .issuable-context-selectbox - - if can?(current_user, :modify_merge_request, @merge_request) - = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'}) - = hidden_field_tag :merge_request_context - = f.submit class: 'btn' - - - if current_user - %div.prepend-top-20.clearfix - .issuable-context-title - %label - Subscription: - %button.btn.btn-block.subscribe-button{:type => 'button'} - = icon('eye') - %span= @merge_request.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' - - subscribtion_status = @merge_request.subscribed?(current_user) ? 'subscribed' : 'unsubscribed' - .subscription-status{data: {status: subscribtion_status}} - .description-block.unsubscribed{class: ( 'hidden' if @merge_request.subscribed?(current_user) )} - You're not receiving notifications from this thread. - .description-block.subscribed{class: ( 'hidden' unless @merge_request.subscribed?(current_user) )} - You're receiving notifications because you're subscribed to this thread. - -:coffeescript - new Subscription("#{toggle_subscription_namespace_project_merge_request_path(@merge_request.project.namespace, @project, @merge_request)}") diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index b3470ba37d6..e3cd4346872 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -3,7 +3,7 @@ %div - if @merge_request.description.present? - .description{class: can?(current_user, :modify_merge_request, @merge_request) ? 'js-task-list-container' : ''} + .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''} .wiki = preserve do = markdown(@merge_request.description) diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 83baf157a92..4e8144b4de2 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -7,7 +7,7 @@ created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)} .issue-btn-group.pull-right - - if can?(current_user, :modify_merge_request, @merge_request) + - if can?(current_user, :update_merge_request, @merge_request) - if @merge_request.open? = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/projects/merge_requests/show/_participants.html.haml index 9c93fa55fe6..c67afe963e7 100644 --- a/app/views/projects/merge_requests/show/_participants.html.haml +++ b/app/views/projects/merge_requests/show/_participants.html.haml @@ -1,4 +1,4 @@ .participants - %span #{@merge_request.participants(current_user).count} participants - - @merge_request.participants(current_user).each do |participant| + %span #{@participants.count} participants + - @participants.each do |participant| = link_to_member(@project, participant, name: false, size: 24) diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml index b4df1d20737..25583b2cc6f 100644 --- a/app/views/projects/merge_requests/update.js.haml +++ b/app/views/projects/merge_requests/update.js.haml @@ -1,8 +1,3 @@ -- if params[:merge_request_context] - $('.context').html("#{escape_javascript(render partial: 'projects/merge_requests/show/context', locals: { issue: @issue })}"); - $('.context').effect('highlight'); - - new UsersSelect() - - $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}); - merge_request = new MergeRequest(); +$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @merge_request)}"); +$('.context').effect('highlight') +merge_request = new MergeRequest(); diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index 41aa66dd76b..f5bacaf280a 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -1,4 +1,4 @@ -= form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form' } do |f| += form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f| = hidden_field_tag :authenticity_token, form_authenticity_token .accept-merge-holder.clearfix.js-toggle-container .accept-action @@ -25,8 +25,6 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" :coffeescript - disableButtonIfEmptyField '#commit_message', '.accept_merge_request' - $('.accept-mr-form').on 'ajax:before', -> btn = $('.accept_merge_request') btn.disable() diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 5650607f31f..b93462e5bdf 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -5,7 +5,7 @@ %hr -= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form'} do |f| += form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-requires-input'} do |f| -if @milestone.errors.any? .alert.alert-danger %ul @@ -16,7 +16,7 @@ .form-group = f.label :title, "Title", class: "control-label" .col-sm-10 - = f.text_field :title, maxlength: 255, class: "form-control" + = f.text_field :title, maxlength: 255, class: "form-control", required: true %p.hint Required .form-group.milestone-description = f.label :description, "Description", class: "control-label" @@ -45,7 +45,6 @@ :javascript - disableButtonIfEmptyField("#milestone_title", ".btn-save"); $( ".datepicker" ).datepicker({ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 5c85092a045..5947498e379 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -62,7 +62,7 @@ %span.badge= @users.count .pull-right - - if can?(current_user, :write_issue, @project) + - if can?(current_user, :create_issue, @project) = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do %i.fa.fa-plus New Issue diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index c67a7d256a8..a88cf167511 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -4,8 +4,8 @@ .controls = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f| = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha' - = button_tag class: 'btn btn-success btn-search-sha' do - %i.fa.fa-search + = button_tag class: 'btn btn-success' do + = icon('search') .inline.prepend-left-20 .checkbox.light = label_tag :filter_ref do @@ -16,8 +16,6 @@ = spinner nil, true :javascript - disableButtonIfEmptyField('#extended_sha1', '.btn-search-sha') - network_graph = new Network({ url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}', commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}', diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index e56d8615132..d49eacb30dd 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -5,13 +5,13 @@ = render 'projects/errors' .project-edit-content - = form_for @project, html: { class: 'new_project form-horizontal' } do |f| + = form_for @project, html: { class: 'new_project form-horizontal js-requires-input' } do |f| .form-group.project-name-holder = f.label :path, class: 'control-label' do %strong Project path .col-sm-10 .input-group - = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true + = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true, required: true .input-group-addon \.git @@ -85,7 +85,7 @@ %li The import will time out after 4 minutes. For big repositories, use a clone/push combination. %li - To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}. + To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}. %hr.prepend-botton-10 diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index a663950f031..7472b33bb53 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -5,8 +5,8 @@ = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field' .comment-hints.clearfix - .pull-left Comments are parsed with #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }} - .pull-right Attach files by dragging & dropping or #{link_to 'selecting them', '#', class: 'markdown-selector', tabindex: -1 }. + .pull-left #{link_to 'Markdown ', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }} + .pull-right #{link_to 'Attach a file', '#', class: 'markdown-selector', tabindex: -1 } .note-form-actions .buttons diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 3fb044d736e..64f98741d45 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -12,8 +12,14 @@ classes: 'note_text js-note-text' .comment-hints.clearfix - .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} - .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. + .pull-left + = link_to "Markdown ", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 } + tip: + = random_markdown_tip + .pull-right + = link_to '#', class: 'markdown-selector', tabindex: -1 do + Attach a file + = icon('paperclip') .error-alert .note-form-actions diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 0a77f200f56..5478a887f91 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -21,7 +21,7 @@ - if member %span.note-role.label = member.human_access - + - if note.system = link_to user_path(note.author) do = image_tag avatar_icon(note.author_email), class: 'avatar s16', alt: '' @@ -56,9 +56,10 @@ .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} - .note-text - = preserve do - = markdown(note.note, {no_header_anchors: true}) + = cache [note, 'markdown'] do + .note-text + = preserve do + = markdown(note.note, {no_header_anchors: true}) = render 'projects/notes/edit_form', note: note - if note.attachment.url diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index a202e74a892..04222b8f7c4 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -3,7 +3,7 @@ .js-notes-busy .js-main-target-form -- if can? current_user, :write_note, @project +- if can? current_user, :create_note, @project = render "projects/notes/form", view: params[:view] :javascript diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 2259dea0865..5c2ac484ceb 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -6,11 +6,75 @@ = render 'shared/no_ssh' = render 'shared/no_password' += render 'projects/last_push' = render "home_panel" -= render 'shared/show_aside' -.row - %section.col-md-8 - = render 'section' - %aside.col-md-4.project-side - = render 'aside' +.project-stats + %ul.nav.nav-pills + %li + = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do + = pluralize(number_with_delimiter(@repository.commit_count), 'commit') + %li + = link_to namespace_project_branches_path(@project.namespace, @project) do + = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch') + %li + = link_to namespace_project_tags_path(@project.namespace, @project) do + = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag') + - if @repository.changelog + %li + = link_to changelog_url(@project) do + Changelog + - if @repository.license + %li + = link_to license_url(@project) do + License + - if @repository.contribution_guide + %li + = link_to contribution_guide_url(@project) do + Contribution guide + +- if @project.archived? + .text-warning.center.prepend-top-20 + %p + = icon("exclamation-triangle fw") + Archived project! Repository is read-only + +%hr +%section + - if readme = @repository.readme + %article.readme-holder#README + .clearfix + .pull-right + + - if can?(current_user, :push_code, @project) + = link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do + %i.fa.fa-pencil + .wiki + = cache(readme_cache_key) do + = render_readme(readme) + - else + %h3.page-title + This project does not have README yet + - if can?(current_user, :push_code, @project) + %p.slead + A + %code README + file contains information about other files in a repository and is commonly + distributed with computer software, forming part of its documentation. + %br + We recommend you to + = link_to "add README", new_readme_path, class: 'underlined-link' + file to the repository and GitLab will render it here instead of this message. + + + +- if current_user + - access = user_max_access_in_project(current_user, @project) + - if access + %hr + %p.light + You have #{access} access to this project. + - if @project.project_member_by_id(current_user) + = link_to leave_namespace_project_project_members_path(@project.namespace, @project), + data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do + Leave this project diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index da9401bd8c1..30081673ffc 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,7 +1,7 @@ - page_title "Snippets" %h3.page-title Snippets - - if can? current_user, :write_project_snippet, @project + - if can? current_user, :create_project_snippet, @project = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Snippet" do Add new snippet diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index 5725d804df3..8cbb813c758 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -28,7 +28,7 @@ = @snippet.file_name .file-actions .btn-group - - if can?(current_user, :modify_project_snippet, @snippet) + - if can?(current_user, :update_project_snippet, @snippet) = link_to "edit", edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", title: 'Edit Snippet' = link_to "raw", raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" - if can?(current_user, :admin_project_snippet, @snippet) diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 04590f65b27..c9e59428e78 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -2,7 +2,9 @@ = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") - + += render 'projects/last_push' + .tree-ref-holder = render 'shared/ref_switcher', destination: 'tree', path: @path diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 633214a4e86..788bb8cf1e2 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -2,7 +2,7 @@ - if (@page && @page.persisted?) = link_to history_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do Page History - - if can?(current_user, :write_wiki, @project) + - if can?(current_user, :create_wiki, @project) = link_to edit_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do %i.fa.fa-pencil-square-o Edit diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 693c3facb32..804a1b52dbe 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -10,7 +10,7 @@ %i.fa.fa-download Git Access - - if can?(current_user, :write_wiki, @project) + - if can?(current_user, :create_wiki, @project) .pull-right = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do %i.fa.fa-plus diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index 84e9be82c44..58f58eff54d 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,3 +1,4 @@ +- blob = @project.repository.parse_search_result(blob) .blob-result .file-holder .file-title diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index f9c5810e3d0..c03438eb952 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -1,3 +1,4 @@ +- wiki_blob = @project.repository.parse_search_result(wiki_blob) .blob-result .file-holder .file-title diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 6de2aed29ed..07672359dba 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -20,7 +20,7 @@ :"data-container" => "body"} = gitlab_config.protocol.upcase = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control input-sm", readonly: true - - if project.kind_of?(Project) && project.empty_repo? + - if project.kind_of?(Project) .input-group-addon .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" } = visibility_level_icon(project.visibility_level) diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml index 02416125a72..ebe2eb0433d 100644 --- a/app/views/shared/_visibility_radios.html.haml +++ b/app/views/shared/_visibility_radios.html.haml @@ -1,4 +1,5 @@ - Gitlab::VisibilityLevel.values.each do |level| + - next if skip_level?(form_model, level) .radio - restricted = restricted_visibility_levels.include?(level) = form.label "#{model_method}_#{level}" do diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml new file mode 100644 index 00000000000..46990895d33 --- /dev/null +++ b/app/views/shared/issuable/_context.html.haml @@ -0,0 +1,50 @@ += form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| + %div.prepend-top-20 + .issuable-context-title + %label + Assignee: + - if issuable.assignee + %strong= link_to_member(@project, issuable.assignee, size: 24) + - else + none + .issuable-context-selectbox + - if can?(current_user, :admin_issue, @project) + = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true) + + %div.prepend-top-20.clearfix + .issuable-context-title + %label + Milestone: + - if issuable.milestone + %span.back-to-milestone + = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do + %strong + = icon('clock-o') + = issuable.milestone.title + - else + none + .issuable-context-selectbox + - if can?(current_user, :admin_issue, @project) + = f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'}) + = hidden_field_tag :issuable_context + = f.submit class: 'btn hide' + + - if current_user + - subscribed = issuable.subscribed?(current_user) + %div.prepend-top-20.clearfix + .issuable-context-title + %label + Subscription: + %button.btn.btn-block.subscribe-button{:type => 'button'} + = icon('eye') + %span= subscribed ? 'Unsubscribe' : 'Subscribe' + - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' + .subscription-status{data: {status: subscribtion_status}} + .description-block.unsubscribed{class: ( 'hidden' if subscribed )} + You're not receiving notifications from this thread. + .description-block.subscribed{class: ( 'hidden' unless subscribed )} + You're receiving notifications because you're subscribed to this thread. + +:coffeescript + new Subscription("#{toggle_subscription_path(issuable)}") + new IssuableContext() diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index a355eb62813..0e8da8de723 100644 --- a/app/views/shared/_issuable_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -29,11 +29,10 @@ .issues-details-filters = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do - - if controller.controller_name == 'issues' + - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) .check-all-holder = check_box_tag "check_all_issues", nil, false, - class: "check_all_issues left", - disabled: !can?(current_user, :modify_issue, @project) + class: "check_all_issues left" .issues-other-filters .filter-item.inline = users_select_tag(:assignee_id, selected: params[:assignee_id], @@ -44,11 +43,15 @@ placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true) .filter-item.inline.milestone-filter - = select_tag('milestone_title', projects_milestones_options, class: "select2 trigger-submit", prompt: 'Milestone') + = select_tag('milestone_title', projects_milestones_options, + class: 'select2 trigger-submit', include_blank: true, + data: {placeholder: 'Milestone'}) - if @project .filter-item.inline.labels-filter - = select_tag('label_name', project_labels_options(@project), class: "select2 trigger-submit", prompt: 'Label') + = select_tag('label_name', project_labels_options(@project), + class: 'select2 trigger-submit', include_blank: true, + data: {placeholder: 'Label'}) .pull-right = render 'shared/sort_dropdown' @@ -64,6 +67,8 @@ = button_tag "Update issues", class: "btn update_selected_issues btn-save" :coffeescript + new UsersSelect() + $('form.filter-form').on 'submit', (event) -> event.preventDefault() Turbolinks.visit @.action + '&' + $(@).serialize() diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/shared/issuable/_form.html.haml index 496fad34dc2..e434e1b6b98 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -37,47 +37,48 @@ .clearfix .error-alert -%hr -.form-group - .issue-assignee - = f.label :assignee_id, class: 'control-label' do - %i.fa.fa-user - Assign to - .col-sm-10 - = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", - placeholder: 'Select a user', class: 'custom-form-control', null_user: true, - selected: issuable.assignee_id, project: @target_project || @project) - - = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' -.form-group - .issue-milestone - = f.label :milestone_id, class: 'control-label' do - %i.fa.fa-clock-o - Milestone + %hr +- if can?(current_user, :admin_issue, @project) + .form-group + .issue-assignee + = f.label :assignee_id, class: 'control-label' do + %i.fa.fa-user + Assign to + .col-sm-10 + = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", + placeholder: 'Select a user', class: 'custom-form-control', null_user: true, + selected: issuable.assignee_id, project: @target_project || @project) + + = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' + .form-group + .issue-milestone + = f.label :milestone_id, class: 'control-label' do + %i.fa.fa-clock-o + Milestone + .col-sm-10 + - if milestone_options(issuable).present? + = f.select(:milestone_id, milestone_options(issuable), + { include_blank: 'Select milestone' }, { class: 'select2' }) + - else + .prepend-top-10 + %span.light No open milestones available. + + - if can? current_user, :admin_milestone, issuable.project + = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank + .form-group + = f.label :label_ids, class: 'control-label' do + %i.fa.fa-tag + Labels .col-sm-10 - - if milestone_options(issuable).present? - = f.select(:milestone_id, milestone_options(issuable), - { include_blank: 'Select milestone' }, { class: 'select2' }) + - if issuable.project.labels.any? + = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, + { selected: issuable.label_ids }, multiple: true, class: 'select2' - else .prepend-top-10 - %span.light No open milestones available. + %span.light No labels yet. - - if can? current_user, :admin_milestone, issuable.project - = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank -.form-group - = f.label :label_ids, class: 'control-label' do - %i.fa.fa-tag - Labels - .col-sm-10 - - if issuable.project.labels.any? - = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, - { selected: issuable.label_ids }, multiple: true, class: 'select2' - - else - .prepend-top-10 - %span.light No labels yet. - - - if can? current_user, :admin_label, issuable.project - = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank + - if can? current_user, :admin_label, issuable.project + = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank - if issuable.is_a?(MergeRequest) %hr diff --git a/app/views/shared/_issuable_search_form.html.haml b/app/views/shared/issuable/_search_form.html.haml index 58c3de64b77..58c3de64b77 100644 --- a/app/views/shared/_issuable_search_form.html.haml +++ b/app/views/shared/issuable/_search_form.html.haml diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 70a95abde6f..089e8122918 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -36,7 +36,7 @@ = @snippet.file_name .file-actions .btn-group - - if can?(current_user, :modify_personal_snippet, @snippet) + - if can?(current_user, :update_personal_snippet, @snippet) = link_to "edit", edit_snippet_path(@snippet), class: "btn btn-sm", title: 'Edit Snippet' = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" - if can?(current_user, :admin_personal_snippet, @snippet) diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 15d53499e03..43d847831d6 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -9,7 +9,7 @@ .row %section.col-md-8 .header-with-avatar - = link_to avatar_icon(@user.email), target: '_blank' do + = link_to avatar_icon(@user.email, 400), target: '_blank' do = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' %h3 = @user.name diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 84a54656df2..2d44d8d4dc6 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -19,7 +19,7 @@ class IrkerWorker branch = "\x0305#{branch}\x0f" end - # Firsts messages are for branch creation/deletion + # First messages are for branch creation/deletion send_branch_updates push_data, project, repo_name, committer, branch # Next messages are for commits @@ -34,7 +34,7 @@ class IrkerWorker def init_perform(set, chans, colors) @colors = colors @channels = chans - start_connection set['server_ip'], set['server_port'] + start_connection set['server_host'], set['server_port'] end def start_connection(irker_server, irker_port) diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb index 5312fd8e89a..1d958904e8f 100644 --- a/config/initializers/6_rack_profiler.rb +++ b/config/initializers/6_rack_profiler.rb @@ -5,6 +5,6 @@ if Rails.env.development? Rack::MiniProfilerRails.initialize!(Rails.application) Rack::MiniProfiler.config.position = 'right' - Rack::MiniProfiler.config.start_hidden = true + Rack::MiniProfiler.config.start_hidden = false Rack::MiniProfiler.config.skip_paths << '/teaspoon' end diff --git a/config/routes.rb b/config/routes.rb index 33f55dde476..055d59a0c93 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -158,6 +158,8 @@ Gitlab::Application.routes.draw do put :team_update put :block put :unblock + put :unlock + patch :disable_two_factor delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' end end @@ -207,7 +209,7 @@ Gitlab::Application.routes.draw do # resource :profile, only: [:show, :update] do member do - get :history + get :audit_log get :applications put :reset_private_token @@ -313,6 +315,7 @@ Gitlab::Application.routes.draw do post :toggle_star post :markdown_preview get :autocomplete_sources + get :activity end scope module: :projects do diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb index bba2fc4b186..b25d0dfc701 100644 --- a/db/fixtures/development/01_admin.rb +++ b/db/fixtures/development/01_admin.rb @@ -5,7 +5,7 @@ Gitlab::Seeder.quiet do s.email = 'admin@example.com' s.notification_email = 'admin@example.com' s.username = 'root' - s.password = '5iveL!fe' + s.password = 'password' s.admin = true s.projects_limit = 100 s.confirmed_at = DateTime.now diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index 1c8740f6ba9..1af8dfc0ef0 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -1,5 +1,5 @@ if ENV['GITLAB_ROOT_PASSWORD'].blank? - password = '5iveL!fe' + password = 'password' expire_time = Time.now else password = ENV['GITLAB_ROOT_PASSWORD'] diff --git a/db/migrate/20141118150935_add_audit_event.rb b/db/migrate/20141118150935_add_audit_event.rb new file mode 100644 index 00000000000..07383c6bbc7 --- /dev/null +++ b/db/migrate/20141118150935_add_audit_event.rb @@ -0,0 +1,22 @@ +class AddAuditEvent < ActiveRecord::Migration + def change + create_table :audit_events do |t| + t.integer :author_id, null: false + t.string :type, null: false + + # "Namespace" where the change occurs + # eg. On a project, group or user + t.integer :entity_id, null: false + t.string :entity_type, null: false + + # Details for the event + t.text :details + + t.timestamps + end + + add_index :audit_events, :author_id + add_index :audit_events, :type + add_index :audit_events, [:entity_id, :entity_type] + end +end diff --git a/db/schema.rb b/db/schema.rb index 3a5af6a76d4..fb0982b10fd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -38,6 +38,20 @@ ActiveRecord::Schema.define(version: 20150620233230) do t.integer "session_expire_delay", default: 10080, null: false end + create_table "audit_events", force: true do |t| + t.integer "author_id", null: false + t.string "type", null: false + t.integer "entity_id", null: false + t.string "entity_type", null: false + t.text "details" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "audit_events", ["author_id"], name: "index_audit_events_on_author_id", using: :btree + add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree + add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree + create_table "broadcast_messages", force: true do |t| t.text "message", null: false t.datetime "starts_at" diff --git a/doc/README.md b/doc/README.md index 424b9e9a47e..0524fda3ed6 100644 --- a/doc/README.md +++ b/doc/README.md @@ -4,7 +4,7 @@ - [API](api/README.md) Automate GitLab via a simple and powerful API. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. -- [GitLab Basics](gitlab_basics/README.md) Find step by step how to start working on your commandline and on GitLab. +- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). - [Markdown](markdown/markdown.md) GitLab's advanced formatting system. - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. diff --git a/doc/api/README.md b/doc/api/README.md index ca58c184543..f369c3fd978 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -4,7 +4,7 @@ - [Users](users.md) - [Session](session.md) -- [Projects](projects.md) +- [Projects](projects.md) including setting Webhooks - [Project Snippets](project_snippets.md) - [Services](services.md) - [Repositories](repositories.md) @@ -20,6 +20,7 @@ - [System Hooks](system_hooks.md) - [Groups](groups.md) - [Namespaces](namespaces.md) +- [Settings](settings.md) ## Clients diff --git a/doc/api/milestones.md b/doc/api/milestones.md index a6828728264..cba4a7c484c 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -87,3 +87,5 @@ Parameters: - `id` (required) - The ID of a project - `milestone_id` (required) - The ID of a project milestone + +You can close multiple issues with one Merge Request by following the [issue closing pattern documentation](http://doc.gitlab.com/ce/customization/issue_closing.html). diff --git a/doc/api/projects.md b/doc/api/projects.md index 17c014019ea..10533c73a31 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -4,16 +4,16 @@ ### Project visibility level Project in GitLab has be either private, internal or public. -You can determine it by `visibility_level` field in project. +You can determine it by `visibility_level` field in project. Constants for project visibility levels are next: -* Private. `visibility_level` is `0`. +* Private. `visibility_level` is `0`. Project access must be granted explicitly for each user. * Internal. `visibility_level` is `10`. The project can be cloned by any logged in user. - + * Public. `visibility_level` is `20`. The project can be cloned without any authentication. @@ -362,7 +362,7 @@ Parameters: - `public` (optional) - if `true` same as setting visibility_level = 20 - `visibility_level` (optional) -On success, method returns 200 with the updated project. If parameters are +On success, method returns 200 with the updated project. If parameters are invalid, 400 is returned. ### Fork project @@ -479,6 +479,9 @@ rely on the returned JSON structure. ## Hooks +Also called Project Hooks and Webhooks. +These are different for [System Hooks](system_hooks.md) that are system wide. + ### List project hooks Get a list of project hooks. diff --git a/doc/api/settings.md b/doc/api/settings.md new file mode 100644 index 00000000000..d1b93a09c02 --- /dev/null +++ b/doc/api/settings.md @@ -0,0 +1,88 @@ +# Application settings + +This API allows you to read and modify GitLab instance application settings. + + +## Get current application settings: + +``` +GET /application/settings +``` + +```json +{ + "id": 1, + "default_projects_limit": 10, + "signup_enabled": true, + "signin_enabled": true, + "gravatar_enabled": true, + "sign_in_text": "", + "created_at": "2015-06-12T15:51:55.432Z", + "updated_at": "2015-06-30T13:22:42.210Z", + "home_page_url": "", + "default_branch_protection": 2, + "twitter_sharing_enabled": true, + "restricted_visibility_levels": [], + "max_attachment_size": 10, + "session_expire_delay": 10080, + "default_project_visibility": 0, + "default_snippet_visibility": 0, + "restricted_signup_domains": [], + "user_oauth_applications": true, + "after_sign_out_path": "" +} +``` + +## Change application settings: + + + +``` +PUT /application/settings +``` + +Parameters: + +- `default_projects_limit` - project limit per user +- `signup_enabled` - enable registration +- `signin_enabled` - enable login via GitLab account +- `gravatar_enabled` - enable gravatar +- `sign_in_text` - text on login page +- `home_page_url` - redirect to this URL when not logged in +- `default_branch_protection` - determine if developers can push to master +- `twitter_sharing_enabled` - allow users to share project creation in twitter +- `restricted_visibility_levels` - restrict certain visibility levels +- `max_attachment_size` - limit attachment size +- `session_expire_delay` - session lifetime +- `default_project_visibility` - what visibility level new project receives +- `default_snippet_visibility` - what visibility level new snippet receives +- `restricted_signup_domains` - force people to use only corporate emails for signup +- `user_oauth_applications` - allow users to create oauth applicaitons +- `after_sign_out_path` - where redirect user after logout + +All parameters are optional. You can send only one that you want to change. + + +```json +{ + "id": 1, + "default_projects_limit": 10, + "signup_enabled": true, + "signin_enabled": true, + "gravatar_enabled": true, + "sign_in_text": "", + "created_at": "2015-06-12T15:51:55.432Z", + "updated_at": "2015-06-30T13:22:42.210Z", + "home_page_url": "", + "default_branch_protection": 2, + "twitter_sharing_enabled": true, + "restricted_visibility_levels": [], + "max_attachment_size": 10, + "session_expire_delay": 10080, + "default_project_visibility": 0, + "default_snippet_visibility": 0, + "restricted_signup_domains": [], + "user_oauth_applications": true, + "after_sign_out_path": "" +} +``` diff --git a/doc/api/users.md b/doc/api/users.md index 8b04282f160..5dca77b5c7b 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -396,3 +396,31 @@ Parameters: - `id` (required) - SSH key ID Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found. + +## Block user + +Blocks the specified user. Available only for admin. + +``` +PUT /users/:uid/block +``` + +Parameters: + +- `uid` (required) - id of specified user + +Will return `200 OK` on success, or `404 User Not Found` is user cannot be found. + +## Unblock user + +Unblocks the specified user. Available only for admin. + +``` +PUT /users/:uid/unblock +``` + +Parameters: + +- `uid` (required) - id of specified user + +Will return `200 OK` on success, or `404 User Not Found` is user cannot be found. diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md new file mode 100644 index 00000000000..3e4400b544b --- /dev/null +++ b/doc/gitlab-basics/README.md @@ -0,0 +1,15 @@ +# GitLab basics + +Step-by-step guides on the basics of working with Git and GitLab. + +* [Start using Git on the commandline](start-using-git.md) + +* [Create and add your SSH Keys](create-your-ssh-keys.md) + +* [Command Line basic commands](command-line-commands.md) + +* [Basic Git commands](basic-git-commands.md) + +* [Create a project](create-project.md) + +* [Create a group](create-group.md) diff --git a/doc/gitlab-basics/basic-git-commands.md b/doc/gitlab-basics/basic-git-commands.md new file mode 100644 index 00000000000..ed210ba5420 --- /dev/null +++ b/doc/gitlab-basics/basic-git-commands.md @@ -0,0 +1,59 @@ +# Basic Git commands + +* Go to the master branch to pull the latest changes from there +``` +git checkout master +``` + +* Download the latest changes in the project, so that you work on an up-to-date copy (this is important to do every time you work on a project), while you setup tracking branches +``` +git pull REMOTE NAME-OF-BRANCH -u +``` +(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch) + +* Create a branch (remember that spaces won't be recognized, you need to use a hyphen or underscore) +``` +git checkout -b NAME-OF-BRANCH +``` + +* Work on a branch that has already been created +``` +git checkout NAME-OF-BRANCH +``` + +* To see the changes you've made (it's important to be aware of what's happening and what's the status of your changes) +``` +git status +``` + +* Add changes to commit (you'll be able to see your changes in red when you type "git status") +``` +git add CHANGES IN RED +git commit -m "DESCRIBE THE INTENTION OF THE COMMIT" +``` + +* Send changes to gitlab.com +``` +git push origin NAME-OF-BRANCH +``` + +* Throw away all changes in the Git repository, but leave unstaged things +``` +git checkout . +``` + +* Delete all changes in the Git repository, including untracked files +``` +git clean -f +``` + +* Remove all the changes that you don't want to send to gitlab.com +``` +git add NAME-OF-FILE -all +``` + +* Merge created branch with master branch. You need to be in the created branch +``` +git checkout NAME-OF-BRANCH +git merge master +``` diff --git a/doc/gitlab_basics/basicsimages/add_new_merge_request.png b/doc/gitlab-basics/basicsimages/add_new_merge_request.png Binary files differindex 9d93b217a59..9d93b217a59 100644 --- a/doc/gitlab_basics/basicsimages/add_new_merge_request.png +++ b/doc/gitlab-basics/basicsimages/add_new_merge_request.png diff --git a/doc/gitlab_basics/basicsimages/add_sshkey.png b/doc/gitlab-basics/basicsimages/add_sshkey.png Binary files differindex 2dede97aa40..2dede97aa40 100644 --- a/doc/gitlab_basics/basicsimages/add_sshkey.png +++ b/doc/gitlab-basics/basicsimages/add_sshkey.png diff --git a/doc/gitlab_basics/basicsimages/branch_info.png b/doc/gitlab-basics/basicsimages/branch_info.png Binary files differindex c5e38b552a5..c5e38b552a5 100644 --- a/doc/gitlab_basics/basicsimages/branch_info.png +++ b/doc/gitlab-basics/basicsimages/branch_info.png diff --git a/doc/gitlab_basics/basicsimages/branch_name.png b/doc/gitlab-basics/basicsimages/branch_name.png Binary files differindex 06e77f5eea9..06e77f5eea9 100644 --- a/doc/gitlab_basics/basicsimages/branch_name.png +++ b/doc/gitlab-basics/basicsimages/branch_name.png diff --git a/doc/gitlab_basics/basicsimages/branches.png b/doc/gitlab-basics/basicsimages/branches.png Binary files differindex c18fa83b968..c18fa83b968 100644 --- a/doc/gitlab_basics/basicsimages/branches.png +++ b/doc/gitlab-basics/basicsimages/branches.png diff --git a/doc/gitlab-basics/basicsimages/click-on-new-group.png b/doc/gitlab-basics/basicsimages/click-on-new-group.png Binary files differnew file mode 100644 index 00000000000..94b6d5756d3 --- /dev/null +++ b/doc/gitlab-basics/basicsimages/click-on-new-group.png diff --git a/doc/gitlab_basics/basicsimages/commit_changes.png b/doc/gitlab-basics/basicsimages/commit_changes.png Binary files differindex 81588336f37..81588336f37 100644 --- a/doc/gitlab_basics/basicsimages/commit_changes.png +++ b/doc/gitlab-basics/basicsimages/commit_changes.png diff --git a/doc/gitlab_basics/basicsimages/commit_message.png b/doc/gitlab-basics/basicsimages/commit_message.png Binary files differindex 0df2c32653c..0df2c32653c 100644 --- a/doc/gitlab_basics/basicsimages/commit_message.png +++ b/doc/gitlab-basics/basicsimages/commit_message.png diff --git a/doc/gitlab_basics/basicsimages/commits.png b/doc/gitlab-basics/basicsimages/commits.png Binary files differindex 7e606539077..7e606539077 100644 --- a/doc/gitlab_basics/basicsimages/commits.png +++ b/doc/gitlab-basics/basicsimages/commits.png diff --git a/doc/gitlab_basics/basicsimages/compare_braches.png b/doc/gitlab-basics/basicsimages/compare_braches.png Binary files differindex 7eebaed9075..7eebaed9075 100644 --- a/doc/gitlab_basics/basicsimages/compare_braches.png +++ b/doc/gitlab-basics/basicsimages/compare_braches.png diff --git a/doc/gitlab_basics/basicsimages/create_file.png b/doc/gitlab-basics/basicsimages/create_file.png Binary files differindex 688e355cca2..688e355cca2 100644 --- a/doc/gitlab_basics/basicsimages/create_file.png +++ b/doc/gitlab-basics/basicsimages/create_file.png diff --git a/doc/gitlab_basics/basicsimages/create_group.png b/doc/gitlab-basics/basicsimages/create_group.png Binary files differindex 57da898abdc..57da898abdc 100644 --- a/doc/gitlab_basics/basicsimages/create_group.png +++ b/doc/gitlab-basics/basicsimages/create_group.png diff --git a/doc/gitlab_basics/basicsimages/edit_file.png b/doc/gitlab-basics/basicsimages/edit_file.png Binary files differindex afa68760108..afa68760108 100644 --- a/doc/gitlab_basics/basicsimages/edit_file.png +++ b/doc/gitlab-basics/basicsimages/edit_file.png diff --git a/doc/gitlab_basics/basicsimages/file_located.png b/doc/gitlab-basics/basicsimages/file_located.png Binary files differindex 1def489d16b..1def489d16b 100644 --- a/doc/gitlab_basics/basicsimages/file_located.png +++ b/doc/gitlab-basics/basicsimages/file_located.png diff --git a/doc/gitlab_basics/basicsimages/file_name.png b/doc/gitlab-basics/basicsimages/file_name.png Binary files differindex 9ac2f1c355f..9ac2f1c355f 100644 --- a/doc/gitlab_basics/basicsimages/file_name.png +++ b/doc/gitlab-basics/basicsimages/file_name.png diff --git a/doc/gitlab_basics/basicsimages/find_file.png b/doc/gitlab-basics/basicsimages/find_file.png Binary files differindex 98639149a39..98639149a39 100644 --- a/doc/gitlab_basics/basicsimages/find_file.png +++ b/doc/gitlab-basics/basicsimages/find_file.png diff --git a/doc/gitlab_basics/basicsimages/find_group.png b/doc/gitlab-basics/basicsimages/find_group.png Binary files differindex 5ac33c7e953..5ac33c7e953 100644 --- a/doc/gitlab_basics/basicsimages/find_group.png +++ b/doc/gitlab-basics/basicsimages/find_group.png diff --git a/doc/gitlab_basics/basicsimages/fork.png b/doc/gitlab-basics/basicsimages/fork.png Binary files differindex b1f94938613..b1f94938613 100644 --- a/doc/gitlab_basics/basicsimages/fork.png +++ b/doc/gitlab-basics/basicsimages/fork.png diff --git a/doc/gitlab_basics/basicsimages/group_info.png b/doc/gitlab-basics/basicsimages/group_info.png Binary files differindex e78d84e4d80..e78d84e4d80 100644 --- a/doc/gitlab_basics/basicsimages/group_info.png +++ b/doc/gitlab-basics/basicsimages/group_info.png diff --git a/doc/gitlab_basics/basicsimages/groups.png b/doc/gitlab-basics/basicsimages/groups.png Binary files differindex b8104343afa..b8104343afa 100644 --- a/doc/gitlab_basics/basicsimages/groups.png +++ b/doc/gitlab-basics/basicsimages/groups.png diff --git a/doc/gitlab_basics/basicsimages/https.png b/doc/gitlab-basics/basicsimages/https.png Binary files differindex 2a31b4cf751..2a31b4cf751 100644 --- a/doc/gitlab_basics/basicsimages/https.png +++ b/doc/gitlab-basics/basicsimages/https.png diff --git a/doc/gitlab_basics/basicsimages/image_file.png b/doc/gitlab-basics/basicsimages/image_file.png Binary files differindex 1061d9c5082..1061d9c5082 100644 --- a/doc/gitlab_basics/basicsimages/image_file.png +++ b/doc/gitlab-basics/basicsimages/image_file.png diff --git a/doc/gitlab_basics/basicsimages/issue_title.png b/doc/gitlab-basics/basicsimages/issue_title.png Binary files differindex 7b69c705392..7b69c705392 100644 --- a/doc/gitlab_basics/basicsimages/issue_title.png +++ b/doc/gitlab-basics/basicsimages/issue_title.png diff --git a/doc/gitlab_basics/basicsimages/issues.png b/doc/gitlab-basics/basicsimages/issues.png Binary files differindex 9354d05319e..9354d05319e 100644 --- a/doc/gitlab_basics/basicsimages/issues.png +++ b/doc/gitlab-basics/basicsimages/issues.png diff --git a/doc/gitlab_basics/basicsimages/key.png b/doc/gitlab-basics/basicsimages/key.png Binary files differindex 321805cda98..321805cda98 100644 --- a/doc/gitlab_basics/basicsimages/key.png +++ b/doc/gitlab-basics/basicsimages/key.png diff --git a/doc/gitlab_basics/basicsimages/merge_requests.png b/doc/gitlab-basics/basicsimages/merge_requests.png Binary files differindex 7601d40de47..7601d40de47 100644 --- a/doc/gitlab_basics/basicsimages/merge_requests.png +++ b/doc/gitlab-basics/basicsimages/merge_requests.png diff --git a/doc/gitlab_basics/basicsimages/new_issue.png b/doc/gitlab-basics/basicsimages/new_issue.png Binary files differindex 94e7503dd8b..94e7503dd8b 100644 --- a/doc/gitlab_basics/basicsimages/new_issue.png +++ b/doc/gitlab-basics/basicsimages/new_issue.png diff --git a/doc/gitlab_basics/basicsimages/new_merge_request.png b/doc/gitlab-basics/basicsimages/new_merge_request.png Binary files differindex 9120d2b1ab1..9120d2b1ab1 100644 --- a/doc/gitlab_basics/basicsimages/new_merge_request.png +++ b/doc/gitlab-basics/basicsimages/new_merge_request.png diff --git a/doc/gitlab_basics/basicsimages/new_project.png b/doc/gitlab-basics/basicsimages/new_project.png Binary files differindex ac255270a66..ac255270a66 100644 --- a/doc/gitlab_basics/basicsimages/new_project.png +++ b/doc/gitlab-basics/basicsimages/new_project.png diff --git a/doc/gitlab_basics/basicsimages/newbranch.png b/doc/gitlab-basics/basicsimages/newbranch.png Binary files differindex da1a6b604ea..da1a6b604ea 100644 --- a/doc/gitlab_basics/basicsimages/newbranch.png +++ b/doc/gitlab-basics/basicsimages/newbranch.png diff --git a/doc/gitlab_basics/basicsimages/paste_sshkey.png b/doc/gitlab-basics/basicsimages/paste_sshkey.png Binary files differindex 9880ddfead1..9880ddfead1 100644 --- a/doc/gitlab_basics/basicsimages/paste_sshkey.png +++ b/doc/gitlab-basics/basicsimages/paste_sshkey.png diff --git a/doc/gitlab_basics/basicsimages/profile_settings.png b/doc/gitlab-basics/basicsimages/profile_settings.png Binary files differindex 5f2e7a7e10c..5f2e7a7e10c 100644 --- a/doc/gitlab_basics/basicsimages/profile_settings.png +++ b/doc/gitlab-basics/basicsimages/profile_settings.png diff --git a/doc/gitlab_basics/basicsimages/project_info.png b/doc/gitlab-basics/basicsimages/project_info.png Binary files differindex 6c06ff351fa..6c06ff351fa 100644 --- a/doc/gitlab_basics/basicsimages/project_info.png +++ b/doc/gitlab-basics/basicsimages/project_info.png diff --git a/doc/gitlab_basics/basicsimages/public_file_link.png b/doc/gitlab-basics/basicsimages/public_file_link.png Binary files differindex 1a60a3d880a..1a60a3d880a 100644 --- a/doc/gitlab_basics/basicsimages/public_file_link.png +++ b/doc/gitlab-basics/basicsimages/public_file_link.png diff --git a/doc/gitlab-basics/basicsimages/select-group.png b/doc/gitlab-basics/basicsimages/select-group.png Binary files differnew file mode 100644 index 00000000000..d02c2255ff2 --- /dev/null +++ b/doc/gitlab-basics/basicsimages/select-group.png diff --git a/doc/gitlab-basics/basicsimages/select-group2.png b/doc/gitlab-basics/basicsimages/select-group2.png Binary files differnew file mode 100644 index 00000000000..fd40bce499b --- /dev/null +++ b/doc/gitlab-basics/basicsimages/select-group2.png diff --git a/doc/gitlab_basics/basicsimages/select_branch.png b/doc/gitlab-basics/basicsimages/select_branch.png Binary files differindex 3475b2df576..3475b2df576 100644 --- a/doc/gitlab_basics/basicsimages/select_branch.png +++ b/doc/gitlab-basics/basicsimages/select_branch.png diff --git a/doc/gitlab_basics/basicsimages/select_project.png b/doc/gitlab-basics/basicsimages/select_project.png Binary files differindex 6d5aa439124..6d5aa439124 100644 --- a/doc/gitlab_basics/basicsimages/select_project.png +++ b/doc/gitlab-basics/basicsimages/select_project.png diff --git a/doc/gitlab_basics/basicsimages/settings.png b/doc/gitlab-basics/basicsimages/settings.png Binary files differindex 9bf9c5a0d39..9bf9c5a0d39 100644 --- a/doc/gitlab_basics/basicsimages/settings.png +++ b/doc/gitlab-basics/basicsimages/settings.png diff --git a/doc/gitlab_basics/basicsimages/shh_keys.png b/doc/gitlab-basics/basicsimages/shh_keys.png Binary files differindex d7ef4dafe77..d7ef4dafe77 100644 --- a/doc/gitlab_basics/basicsimages/shh_keys.png +++ b/doc/gitlab-basics/basicsimages/shh_keys.png diff --git a/doc/gitlab_basics/basicsimages/submit_new_issue.png b/doc/gitlab-basics/basicsimages/submit_new_issue.png Binary files differindex 18944417085..18944417085 100644 --- a/doc/gitlab_basics/basicsimages/submit_new_issue.png +++ b/doc/gitlab-basics/basicsimages/submit_new_issue.png diff --git a/doc/gitlab_basics/basicsimages/title_description_mr.png b/doc/gitlab-basics/basicsimages/title_description_mr.png Binary files differindex e08eb628414..e08eb628414 100644 --- a/doc/gitlab_basics/basicsimages/title_description_mr.png +++ b/doc/gitlab-basics/basicsimages/title_description_mr.png diff --git a/doc/gitlab_basics/basicsimages/white_space.png b/doc/gitlab-basics/basicsimages/white_space.png Binary files differindex 6363a09360e..6363a09360e 100644 --- a/doc/gitlab_basics/basicsimages/white_space.png +++ b/doc/gitlab-basics/basicsimages/white_space.png diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md new file mode 100644 index 00000000000..a596bf20c74 --- /dev/null +++ b/doc/gitlab-basics/command-line-commands.md @@ -0,0 +1,72 @@ +# Command Line basic commands + +## Start working on your project + +* In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to [GitLab.com](https://gitlab.com) + +* When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen + +![Select a project](basicsimages/select_project.png) + +* To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step) + +![Copy the HTTPS or SSH](basicsimages/https.png) + +## On the command line + +* To clone your project, go to your computer's shell and type the following command +``` +git clone PASTE HTTPS OR SSH HERE +``` + +* A clone of the project will be created in your computer + +* Go into a project, directory or file to work in it +``` +cd NAME-OF-PROJECT-OR-FILE +``` + +* Go back one directory or file +``` +cd ../ +``` + +* To see what’s in the directory that you are in +``` +ls +``` + +* Create a directory +``` +mkdir NAME-OF-YOUR-DIRECTORY +``` + +* Create a README.md or file in directory +``` +touch README.md +nano README.md +#### ADD YOUR INFORMATION +#### Press: control + X +#### Type: Y +#### Press: enter +``` + +* Remove a file +``` +rm NAME-OF-FILE +``` + +* Remove a directory and all of its contents +``` +rm -rf NAME-OF-DIRECTORY +``` + +* View history in the command line +``` +history +``` + +* Carry out commands for which the account you are using lacks authority. (You will be asked for an administrator’s password) +``` +sudo +``` diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md new file mode 100644 index 00000000000..8e168395ff7 --- /dev/null +++ b/doc/gitlab-basics/create-group.md @@ -0,0 +1,43 @@ +# How to create a group in GitLab + +## Create a group + +Your projects in [GitLab.com](https://gitlab.com) can be organized in 2 different ways: +under your own namespace for single projects, such as ´your-name/project-1'; or under groups. +If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects. + +To create a group, follow the instructions below: + +Sign in to [GitLab.com](https://gitlab.com). + +When you are on your Dashboard, click on "Groups" on the left menu of your screen: + +![Go to groups](basicsimages/select-group2.png) + +Click on "New group" on the top right side of your screen: + +![New group](basicsimages/click-on-new-group.png) + +Fill out the information required: + +1. Add a group path or group name (you can't add spaces, so you can use hyphens or underscores) + +1. Add details or a group description + +1. You can choose a group avatar if you'd like + +1. Click on "create group" + +![Group information](basicsimages/group_info.png) + +## Add a project to a group + +There are 2 different ways to add a new project to a group: + +* Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md) + +![New project](basicsimages/new_project.png) + +* When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen + +![Create a group](basicsimages/create_group.png) diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md new file mode 100644 index 00000000000..e3963f66010 --- /dev/null +++ b/doc/gitlab-basics/create-project.md @@ -0,0 +1,23 @@ +# How to create a project in GitLab + +## Create a project + +* Sign in to [GitLab.com](https://gitlab.com) + +* Go to your Dashboard and click on "new project" on the right side of your screen + +![Create a project](basicsimages/new_project.png) + +* Fill out the required information + +1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores) + +1. Your project's description + +1. Select a [visibility level](https://gitlab.com/help/public_access/public_access) + +1. You can also [import your existing projects](http://doc.gitlab.com/ce/workflow/importing/README.html) + +1. Click on "create project" + +!![Project information](basicsimages/project_info.png) diff --git a/doc/gitlab_basics/create_your_ssh_keys.md b/doc/gitlab-basics/create-your-ssh-keys.md index 1e7f7c28513..cb699588cac 100644 --- a/doc/gitlab_basics/create_your_ssh_keys.md +++ b/doc/gitlab-basics/create-your-ssh-keys.md @@ -6,7 +6,7 @@ You need to connect your computer to your GitLab account through SSH Keys. They * Create an account on GitLab. Sign up and check your email for your confirmation link -* After you confirm, go to [gitlab.com](https://about.gitlab.com/) and sign in to your account +* After you confirm, go to [GitLab.com](https://about.gitlab.com/) and sign in to your account ## Add your SSH Key @@ -28,7 +28,7 @@ You need to connect your computer to your GitLab account through SSH Keys. They ## To generate an SSH Key on your commandline -* Go to your [commandline](start_using_git.md) and follow the [instructions](https://gitlab.com/help/ssh/README) to generate it +* Go to your [commandline](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate it * Copy the SSH Key that your commandline created and paste it on the "Key" box on the GitLab page. The title will be added automatically diff --git a/doc/gitlab_basics/start_using_git.md b/doc/gitlab-basics/start-using-git.md index f01a2f77eec..21d93ed2e4d 100644 --- a/doc/gitlab_basics/start_using_git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -1,6 +1,6 @@ # Start using Git on the commandline -If you want to start using a Git and GitLab, make sure that you have created an account on [gitlab.com](https://about.gitlab.com/) +If you want to start using a Git and GitLab, make sure that you have created an account on [GitLab.com](https://about.gitlab.com/) ## Open a shell @@ -40,7 +40,7 @@ git config --global user.name ADD YOUR USERNAME * Then verify that you have the correct username -``` +``` git config --global user.name ``` @@ -52,7 +52,7 @@ git config --global user.email ADD YOUR EMAIL * To verify that you entered your email correctly, type -``` +``` git config --global user.email ``` @@ -62,6 +62,6 @@ git config --global user.email * To view the information that you entered, type -``` +``` git config --global --list ``` diff --git a/doc/gitlab_basics/README.md b/doc/gitlab_basics/README.md deleted file mode 100644 index de56c3ab4fc..00000000000 --- a/doc/gitlab_basics/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# GitLab basics - -Step-by-step guides on the basics of working with Git and GitLab. - -* [Start using Git on the commandline](start_using_git.md) - -* [Create and add your SSH Keys](create_your_ssh_keys.md) diff --git a/doc/install/installation.md b/doc/install/installation.md index 8b918cba133..cf58abea4eb 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -404,7 +404,7 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in: root - 5iveL!fe + password **Important Note:** On login you'll be prompted to change the password. diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 7a3216dd2d2..1efc1f7bddf 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -57,6 +57,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim - 16 cores supports up to 10,000 users - 32 cores supports up to 20,000 users - 64 cores supports up to 40,000 users +- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/) ### Memory @@ -64,15 +65,17 @@ You need at least 2GB of addressable memory (RAM + swap) to install and use GitL With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage. - 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advise. -- 1GB RAM + 1GB swap supports up to 100 users -- **2GB RAM** is the **recommended** memory size and supports up to 500 users -- 4GB RAM supports up to 2,000 users -- 8GB RAM supports up to 5,000 users -- 16GB RAM supports up to 10,000 users -- 32GB RAM supports up to 20,000 users -- 64GB RAM supports up to 40,000 users - -Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. +- 1GB RAM + 1GB swap supports up to 100 users but it will be slow +- **2GB RAM** is the **recommended** memory size and supports up to 100 users +- 4GB RAM supports up to 1,000 users +- 8GB RAM supports up to 2,000 users +- 16GB RAM supports up to 4,000 users +- 32GB RAM supports up to 8,000 users +- 64GB RAM supports up to 16,000 users +- 128GB RAM supports up to 32,000 users +- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/) + +Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those. ## Unicorn Workers @@ -106,4 +109,8 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o - Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) - Safari 7+ (known problem: required fields in html5 do not work) - Opera (Latest released version) -- IE 10+
\ No newline at end of file +- IE 10+ + +### Common UI problems with IE + +If you experience UI issues with Internet Explorer, please make sure that you have the `Compatibility View` mode disabled.
\ No newline at end of file diff --git a/doc/integration/README.md b/doc/integration/README.md index 286bd34a0bd..6d856951d4e 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -7,6 +7,7 @@ See the documentation below for details on how to configure these services. - [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc. - [LDAP](ldap.md) Set up sign in via LDAP - [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth. +- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider - [Slack](slack.md) Integrate with the Slack chat service - [OAuth2 provider](oauth_provider.md) OAuth2 application creation - [Gmail](gitlab_buttons_in_gmail.md) Adds GitLab actions to messages diff --git a/doc/integration/saml.md b/doc/integration/saml.md index a8cc5c8f74a..4aa6dbe758a 100644 --- a/doc/integration/saml.md +++ b/doc/integration/saml.md @@ -75,3 +75,8 @@ At a minimum the IdP *must* provide a claim containing the user's email address, On the sign in page there should now be a SAML button below the regular sign in form. Click the icon to begin the authentication process. If everything goes well the user will be returned to GitLab and will be signed in. +## Troubleshooting + +If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page, this likely indicates that GitLab could not get the email address for the SAML user. + +Make sure the IdP provides a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username. diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 9c7f723c06d..322111ae9e1 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -343,6 +343,36 @@ Strikethrough uses two tildes. ~~Scratch this.~~ - Or minuses + Or pluses +If a list item contains multiple paragraphs, +each subsequent paragraph should be indented with four spaces. + +```no-highlight +1. First ordered list item + + Second paragraph of first item. +2. Another item +``` + +1. First ordered list item + + Second paragraph of first item. +2. Another item + +If the second paragraph isn't indented with four spaces, +the second list item will be incorrectly labeled as `1`. + +```no-highlight +1. First ordered list item + + Second paragraph of first item. +2. Another item +``` + +1. First ordered list item + + Second paragraph of first item. +2. Another item + ## Links There are two ways to create links, inline-style and reference-style. diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 8cfa7f9c876..70b7e17795d 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -15,6 +15,8 @@ If a user is a GitLab administrator they receive all permissions. | Pull project code | | ✓ | ✓ | ✓ | ✓ | | Download project | | ✓ | ✓ | ✓ | ✓ | | Create code snippets | | ✓ | ✓ | ✓ | ✓ | +| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ | +| Manage labels | | ✓ | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | | Push to non-protected branches | | | ✓ | ✓ | ✓ | @@ -22,8 +24,6 @@ If a user is a GitLab administrator they receive all permissions. | Remove non-protected branches | | | ✓ | ✓ | ✓ | | Add tags | | | ✓ | ✓ | ✓ | | Write a wiki | | | ✓ | ✓ | ✓ | -| Manage issue tracker | | | ✓ | ✓ | ✓ | -| Manage labels | | | ✓ | ✓ | ✓ | | Create new milestones | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md index 9875bebf66b..25c0c3ad2a6 100644 --- a/doc/project_services/irker.md +++ b/doc/project_services/irker.md @@ -9,38 +9,43 @@ See the project homepage for further info: https://gitlab.com/esr/irker ## Needed setup You will first need an Irker daemon. You can download the Irker code from its -gitorious repository on https://gitorious.org/irker: `git clone -git@gitorious.org:irker/irker.git`. Once you have downloaded the code, you can -run the python script named `irkerd`. This script is the gateway script, it acts -both as an IRC client, for sending messages to an IRC server obviously, and as a -TCP server, for receiving messages from the GitLab service. +repository on https://gitlab.com/esr/irker: -If the Irker server runs on the same machine, you are done. If not, you will -need to follow the firsts steps of the next section. +``` +git clone https://gitlab.com/esr/irker.git +``` -## Optional setup +Once you have downloaded the code, you can run the python script named `irkerd`. +This script is the gateway script, it acts both as an IRC client, for sending +messages to an IRC server obviously, and as a TCP server, for receiving messages +from the GitLab service. -In the `app/models/project_services/irker_service.rb` file, you can modify some -options in the `initialize_settings` method: -- **server_ip** (defaults to `localhost`): the server IP address where the -`irkerd` daemon runs; -- **server_port** (defaults to `6659`): the server port of the `irkerd` daemon; -- **max_channels** (defaults to `3`): the maximum number of recipients the -client is authorized to join, per project; -- **default_irc_uri** (no default) : if this option is set, it has to be in the -format `irc[s]://domain.name` and will be prepend to each and every channel -provided by the user which is not a full URI. +If the Irker server runs on the same machine, you are done. If not, you will +need to follow the firsts steps of the next section. -If the Irker server and the GitLab application do not run on the same host, you -will **need** to setup at least the **server_ip** option. +## Complete these steps in GitLab: + +1. Navigate to the project you want to configure for notifications. +1. Select "Settings" in the top navigation. +1. Select "Services" in the left navigation. +1. Click "Irker". +1. Select the "Active" checkbox. +1. Enter the server host address where `irkerd` runs (defaults to `localhost`) +in the `Server host` field on the Web page +1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the +`Server port` field on the Web page. +1. Optional: if `Default IRC URI` is set, it has to be in the format +`irc[s]://domain.name` and will be prepend to each and every channel provided +by the user which is not a full URI. +1. Specify the recipients (e.g. #channel1, user1, etc.) +1. Save or optionally click "Test Settings". ## Note on Irker recipients Irker accepts channel names of the form `chan` and `#chan`, both for the `#chan` channel. If you want to send messages in query, you will need to add -`,isnick` avec the channel name, in this form: `Aorimn,isnick`. In this latter +`,isnick` after the channel name, in this form: `Aorimn,isnick`. In this latter case, `Aorimn` is treated as a nick and no more as a channel name. Irker can also join password-protected channels. Users need to append `?key=thesecretpassword` to the chan name. - diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index ae2d465e0c1..39a13b14fba 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -19,7 +19,7 @@ sudo gitlab-rake gitlab:backup:create sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` -Also you can choose what should be backed up by adding environment variable SKIP. Available options: db, +Also you can choose what should be backed up by adding environment variable SKIP. Available options: db, uploads (attachments), repositories. Use a comma to specify several options at the same time. ``` @@ -300,6 +300,25 @@ Example: LVM snapshots + rsync If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server. It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use. -### Note -This documentation is for GitLab CE. -We backup GitLab.com and make sure your data is secure, but you can't use these methods to export / backup your data yourself from GitLab.com.
\ No newline at end of file +## Troubleshooting + +### Restoring database backup using omnibus packages outputs warnings +If you are using backup restore procedures you might encounter the following warnings: + +``` +psql:/var/opt/gitlab/backups/db/database.sql:22: ERROR: must be owner of extension plpgsql +psql:/var/opt/gitlab/backups/db/database.sql:2931: WARNING: no privileges could be revoked for "public" (two occurences) +psql:/var/opt/gitlab/backups/db/database.sql:2933: WARNING: no privileges were granted for "public" (two occurences) + +``` + +Be advised that, backup is successfully restored in spite of these warnings. + +The rake task runs this as the `gitlab` user which does not have the superuser access to the database. When restore is initiated it will also run as `gitlab` user but it will also try to alter the objects it does not have access to. +Those objects have no influence on the database backup/restore but they give this annoying warning. + +For more information see similar questions on postgresql issue tracker[here](http://www.postgresql.org/message-id/201110220712.30886.adrian.klaver@gmail.com) and [here](http://www.postgresql.org/message-id/2039.1177339749@sss.pgh.pa.us) as well as [stack overflow](http://stackoverflow.com/questions/4368789/error-must-be-owner-of-language-plpgsql). + +## Note +This documentation is for GitLab CE. +We backup GitLab.com and make sure your data is secure, but you can't use these methods to export / backup your data yourself from GitLab.com. diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 7fb22938690..3bc92187218 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -66,10 +66,11 @@ Xth: (2 working days before the 22nd) Xth: (1 working day before the 22nd) -- [ ] Create CE, EE, CI stable versions (#LINK) -- [ ] Create Omnibus tags and build packages -- [ ] Update GitLab.com with the stable version (#LINK) -- [ ] Update ci.gitLab.com with the stable version (#LINK) +- [ ] Merge CE stable into EE stable +- [ ] Create (hopefully final) CE, EE, CI release candidates (#LINK) +- [ ] Create Omnibus tags and build packages for the latest release candidates +- [ ] Update GitLab.com with the latest RC (#LINK) +- [ ] Update ci.gitLab.com with the latest RC (#LINK) 22nd before 12AM CET: @@ -77,7 +78,11 @@ Release before 12AM CET / 3AM PST, to make sure the majority of our users get the new version on the 22nd and there is sufficient time in the European workday to quickly fix any issues. -- [ ] Release CE, EE and CI (#LINK) +- [ ] Merge CE stable into EE stable (#LINK) +- [ ] Create the 'x.y.0' tag with the [release tools](https://dev.gitlab.org/gitlab/release-tools) (#LINK) +- [ ] BEFORE 11AM CET Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK) +- [ ] BEFORE 12AM CET Publish the release blog post (#LINK) +- [ ] Tweet about the release (blog post) (#LINK) - [ ] Schedule a second tweet of the release announcement at 6PM CET / 9AM PST ``` diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 2c43cf59c1f..8ef3e0d55cc 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -57,12 +57,15 @@ sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter. sudo -u git -H mkdir db sudo -u git -H python mysql-postgresql-converter/db_converter.py gitlabhq_production.mysql db/database.sql +# Compress database backup +sudo -u git -H gzip db/database.sql + # Replace the MySQL dump in TIMESTAMP_gitlab_backup.tar. # Warning: if you forget to replace TIMESTAMP below, tar will create a new file # 'TIMESTAMP_gitlab_backup.tar' without giving an error. -sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql +sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql.gz # Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab # installation. Remember to recreate the indexes after the import. diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index aad2c63817d..3efa92cb868 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -3,6 +3,8 @@ It takes just a couple of steps to import your existing GitHub projects to GitLab. Keep in mind that it is possible only if
GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
+If you want to import from a GitHub Enterprise instance, you need to use GitLab Enterprise; please see the [EE docs for the GitHub integration](http://doc.gitlab.com/ee/integration/github.html).
+
* Sign in to GitLab.com and go to your dashboard.
* To get to the importer page, you need to go to the "New project" page.
@@ -15,4 +17,4 @@ GitHub support is enabled on your GitLab instance. You can read more about GitHu * To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data.
### Note
-When you import your projects from GitHub, it is not possible to keep your labels and milestones and issue numbers won't match. We are working on improving this in the near future.
\ No newline at end of file +When you import your projects from GitHub, it is not possible to keep your labels and milestones and issue numbers won't match. We are working on improving this in the near future.
diff --git a/doc_styleguide.md b/doc_styleguide.md index db30a737f14..656bb1d17ff 100644 --- a/doc_styleguide.md +++ b/doc_styleguide.md @@ -4,20 +4,21 @@ This styleguide recommends best practices to improve documentation and to keep i ## Text -* Make sure that the documentation is added in the correct directory and that there's a link to it somewhere useful. - -* Add only one H1 or title in each document, by adding '#' at the begining of it (when using markdown). For subtitles, use '##', '###' and so on. - -* Do not duplicate information. - -* Be brief and clear. - -* Whenever it applies, add documents in alphabetical order. - -## When adding images to a document - -* Create a directory to store the images with the specific name of the document where the images belong. It could be in the same directory where the .md document that you're working on is located. - -* Images should have a specific, non-generic name that will differentiate them. - -* Keep all file names in lower case.
\ No newline at end of file +- Split up long lines, this makes it much easier to review and edit. Only +double line breaks are shown as a full line break in markdown. 80 characters +is a good line length. +- For subtitles, make sure to start with the largest and go down, meaning: +`#` for the title, `##` for subtitles and `###` for subtitles of the subtitles, etc. +- Make sure that the documentation is added in the correct directory and that there's a link to it somewhere useful. +- Add only one H1 or title in each document, by adding '#' at the begining of it (when using markdown). +For subtitles, use '##', '###' and so on. +- Do not duplicate information. +- Be brief and clear. +- Whenever it applies, add documents in alphabetical order. + +## Images + +- Create a directory to store the images with the specific name of the document where the images belong. +It could be in the same directory where the .md document that you're working on is located. +- Images should have a specific, non-generic name that will differentiate them. +- Keep all file names in lower case.
\ No newline at end of file diff --git a/docker/single/Dockerfile b/docker/Dockerfile index a6cbf131237..86f6c896a6d 100644 --- a/docker/single/Dockerfile +++ b/docker/Dockerfile @@ -7,7 +7,9 @@ RUN apt-get update -q \ ca-certificates \ openssh-server \ wget \ - apt-transport-https + apt-transport-https \ + vim \ + nano # Download & Install GitLab # If you run GitLab Enterprise Edition point it to a location where you have downloaded it. @@ -23,12 +25,21 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \ && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \ && mkdir -p /var/run/sshd +# Prepare default configuration +RUN ( \ + echo "" && \ + echo "# Docker options" && \ + echo "# Prevent Postgres from trying to allocate 25% of total memory" && \ + echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb + # Expose web & ssh -EXPOSE 80 22 +EXPOSE 443 80 22 + +# Define data volumes +VOLUME ["/etc/gitlab", "/var/opt/gitlab", "/var/log/gitlab"] # Copy assets COPY assets/wrapper /usr/local/bin/ -COPY assets/gitlab.rb /etc/gitlab/ # Wrapper to handle signal, trigger runit and reconfigure GitLab CMD ["/usr/local/bin/wrapper"] diff --git a/docker/README.md b/docker/README.md index fb3bde5016d..293ca79f915 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,9 +1,6 @@ # GitLab Docker images -## What is GitLab? - -GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster. -Learn more on [https://about.gitlab.com](https://about.gitlab.com) +The GitLab docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/). ## After starting a container @@ -11,152 +8,160 @@ After starting a container you can go to [http://localhost:8080/](http://localho It might take a while before the docker container is responding to queries. -You can check the status with something like `sudo docker logs -f 7c10172d7705`. +You can check the status with something like `sudo docker logs -f gitlab`. -You can login to the web interface with username `root` and password `5iveL!fe`. +You can login to the web interface with username `root` and password `password`. Next time, you can just use docker start and stop to run the container. -## How to build the docker images +## Run the image -This guide will also let you know how to build docker images yourself. -Please run all the commands from the GitLab repo root directory. -People using boot2docker should run all the commands without sudo. +Run the image: +```bash +sudo docker run --detach \ + --publish 8443:443 --publish 8080:80 --publish 2222:22 \ + --name gitlab \ + --restart always \ + --volume /srv/gitlab/config:/etc/gitlab \ + --volume /srv/gitlab/logs:/var/log/gitlab \ + --volume /srv/gitlab/data:/var/opt/gitlab \ + gitlab/gitlab-ce:latest +``` -## Choosing between the single and the app and data images +This will download and start GitLab CE container and publish ports needed to access SSH, HTTP and HTTPS. +All GitLab data will be stored as subdirectories of `/srv/gitlab/`. +The container will automatically `restart` after system reboot. -Normally docker uses a single image for one applications. -But GitLab stores repositories and uploads in the filesystem. -This means that upgrades of a single image are hard. -That is why we recommend using separate app and data images. -We'll first describe how to use a single image. -After that we'll describe how to use the app and data images. +After this you can login to the web interface as explained above in 'After starting a container'. -## Single image +## Where is the data stored? -Get a published image from Dockerhub: +The GitLab container uses host mounted volumes to store persistent data: +- `/srv/gitlab/data` mounted as `/var/opt/gitlab` in the container is used for storing *application data* +- `/srv/gitlab/logs` mounted as `/var/log/gitlab` in the container is used for storing *logs* +- `/srv/gitlab/config` mounted as `/etc/gitlab` in the container is used for storing *configuration* -```bash -sudo docker pull sytse/gitlab-ce:7.10.1 -``` +You can fine tune these directories to meet your requirements. -Run the image: +### Configure GitLab + +This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`. +To access GitLab configuration, you can start an bash in a new the context of running container, you will be able to browse all directories and use your favorite text editor: ```bash -sudo docker run --detach --publish 8080:80 --publish 2222:22 sytse/gitlab-ce:7.10.1 +sudo docker exec -it gitlab /bin/bash ``` -After this you can login to the web interface as explained above in 'After starting a container'. - -Build the image: - +You can also edit just `/etc/gitlab/gitlab.rb`: ```bash -sudo docker build --tag sytse/gitlab-ce:7.10.1 docker/single/ +sudo docker exec -it gitlab vi /etc/gitlab/gitlab.rb ``` -Publish the image to Dockerhub: +**You should set the `external_url` to point to a valid URL.** -```bash -sudo docker push sytse/gitlab-ce -``` +**You may also be interesting in [Enabling HTTPS](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/nginx.md#enable-https).** + +**To receive e-mails from GitLab you have to configure the [SMTP settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md), +because Docker image doesn't have a SMTP server.** -Diagnosing commands: +**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab: ```bash -sudo docker run -i -t sytse/gitlab-ce:7.10.1 -sudo docker run -ti -e TERM=linux --name gitlab-ce-troubleshoot --publish 8080:80 --publish 2222:22 sytse/gitlab-ce:7.10.1 bash /usr/local/bin/wrapper +sudo docker restart gitlab ``` -## App and data images +For more options for configuring the container please check [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration). -### Get published images from Dockerhub +## Diagnose potential problems +Read container logs: ```bash -sudo docker pull sytse/gitlab-data -sudo docker pull sytse/gitlab-app:7.10.1 +sudo docker logs gitlab ``` -### Run the images - +Enter running container: ```bash -sudo docker run --name gitlab-data sytse/gitlab-data /bin/true -sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data sytse/gitlab-app:7.10.1 +sudo docker exec -it gitlab /bin/bash ``` -After this you can login to the web interface as explained above in 'After starting a container'. - -### Build images +From within container you can administrer GitLab container as you would normally administer Omnibus installation: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md. -Build your own based on the Omnibus packages with the following commands. +### Upgrade GitLab to newer version +To upgrade GitLab to new version you have to do: +1. pull new image, ```bash -sudo docker build --tag gitlab-data docker/data/ -sudo docker build --tag gitlab-app:7.10.1 docker/app/ +sudo docker stop gitlab ``` -After this run the images: - +1. stop running container, ```bash -sudo docker run --name gitlab-data gitlab-data /bin/true -sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1 +sudo docker rm gitlab ``` -We assume using a data volume container, this will simplify migrations and backups. -This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it. - -The directories on data container are: - -- `/var/opt/gitlab` for application data -- `/var/log/gitlab` for logs -- `/etc/gitlab` for configuration - -### Configure GitLab - -This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`. - -To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor: +1. remove existing container, +```bash +sudo docker pull gitlab/gitlab-ce:latest +``` +1. create the container once again with previously specified options. ```bash -sudo docker run -ti -e TERM=linux --rm --volumes-from gitlab-data ubuntu -vi /etc/gitlab/gitlab.rb +sudo docker run --detach \ + --publish 8443:443 --publish 8080:80 --publish 2222:22 \ + --name gitlab \ + --restart always \ + --volume /srv/gitlab/config:/etc/gitlab \ + --volume /srv/gitlab/logs:/var/log/gitlab \ + --volume /srv/gitlab/data:/var/opt/gitlab \ + gitlab/gitlab-ce:latest ``` -**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab. +On the first run GitLab will reconfigure and update itself. -You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration). +### Run GitLab CE on public IP address -### Upgrade GitLab with app and data images +You can make Docker to use your IP address and forward all traffic to the GitLab CE container. +You can do that by modifying the `--publish` ([Binding container ports to the host](https://docs.docker.com/articles/networking/#binding-ports)): -To upgrade GitLab to new versions, stop running container, create new docker image and container from that image. +> --publish=[] : Publish a container᾿s port or a range of ports to the host format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort -It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated GitLab repo root directory: +To expose GitLab CE on IP 1.1.1.1: ```bash -sudo docker stop gitlab-app -sudo docker rm gitlab-app -sudo docker build --tag gitlab-app:7.10.1 docker/app/ -sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1 +sudo docker run --detach \ + --publish 1.1.1.1:443:443 --publish 1.1.1.1:80:80 --publish 1.1.1.1:22:22 \ + --name gitlab \ + --restart always \ + --volume /srv/gitlab/config:/etc/gitlab \ + --volume /srv/gitlab/logs:/var/log/gitlab \ + --volume /srv/gitlab/data:/var/opt/gitlab \ + gitlab/gitlab-ce:latest ``` -On the first run GitLab will reconfigure and update itself. If everything runs OK don't forget to cleanup the app image: +You can then access GitLab instance at http://1.1.1.1/ and https://1.1.1.1/. + +### Build the image + +This guide will also let you know how to build docker image yourself. +Please run the command from the GitLab repo root directory. +People using boot2docker should run all the commands without sudo. ```bash -sudo docker rmi gitlab-app:7.8.1 +sudo docker build --tag gitlab/gitlab-ce:latest docker/ ``` -### Publish images to Dockerhub +### Publish the image to Dockerhub - Ensure the containers are running - Login to Dockerhub with `sudo docker login` -- Run the following (replace '7.10.1' with the version you're using and 'Sytse Sijbrandij' with your name): ```bash -sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app sytse/gitlab-app:7.10.1 -sudo docker push sytse/gitlab-app:7.10.1 -sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-data sytse/gitlab-data -sudo docker push sytse/gitlab-data +sudo docker login +sudo docker push gitlab/gitlab-ce:latest ``` ## Troubleshooting -Please see the [troubleshooting](troubleshooting.md) file in this directory.
\ No newline at end of file +Please see the [troubleshooting](troubleshooting.md) file in this directory. + +Note: We use `fig.yml` to have compatibility with fig and because docker-compose also supports it. diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile deleted file mode 100644 index fe3f7f0bcd2..00000000000 --- a/docker/app/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM ubuntu:14.04 - -# Install required packages -RUN apt-get update -q \ - && DEBIAN_FRONTEND=noninteractive apt-get install -qy --no-install-recommends \ - ca-certificates \ - openssh-server \ - wget \ - apt-transport-https - -# Download & Install GitLab -# If you run GitLab Enterprise Edition point it to a location where you have downloaded it. -RUN echo "deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/gitlab_gitlab-ce.list -RUN wget -q -O - https://packages.gitlab.com/gpg.key | apt-key add - -RUN apt-get update && apt-get install -yq --no-install-recommends gitlab-ce - -# Manage SSHD through runit -RUN mkdir -p /opt/gitlab/sv/sshd/supervise \ - && mkfifo /opt/gitlab/sv/sshd/supervise/ok \ - && printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \ - && chmod a+x /opt/gitlab/sv/sshd/run \ - && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \ - && mkdir -p /var/run/sshd - -# Expose web & ssh -EXPOSE 80 22 - -# Copy assets -COPY assets/wrapper /usr/local/bin/ - -# Wrapper to handle signal, trigger runit and reconfigure GitLab -CMD ["/usr/local/bin/wrapper"]
\ No newline at end of file diff --git a/docker/app/assets/wrapper b/docker/app/assets/wrapper deleted file mode 100755 index 9e6e7a05903..00000000000 --- a/docker/app/assets/wrapper +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -function sigterm_handler() { - echo "SIGTERM signal received, try to gracefully shutdown all services..." - gitlab-ctl stop -} - -trap "sigterm_handler; exit" TERM - -function entrypoint() { - # Default is to run runit and reconfigure GitLab - gitlab-ctl reconfigure & - /opt/gitlab/embedded/bin/runsvdir-start & - wait -} - -entrypoint diff --git a/docker/single/assets/wrapper b/docker/assets/wrapper index 966b2cab4a1..966b2cab4a1 100755 --- a/docker/single/assets/wrapper +++ b/docker/assets/wrapper diff --git a/docker/data/Dockerfile b/docker/data/Dockerfile deleted file mode 100644 index ea0175c4aa2..00000000000 --- a/docker/data/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM busybox - -# Declare volumes -VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"] -# Copy assets -COPY assets/gitlab.rb /etc/gitlab/ - -CMD /bin/sh diff --git a/docker/data/assets/gitlab.rb b/docker/data/assets/gitlab.rb deleted file mode 100644 index 7fddf309c01..00000000000 --- a/docker/data/assets/gitlab.rb +++ /dev/null @@ -1,37 +0,0 @@ -# External URL should be your Docker instance. -# By default, this example is the "standard" boot2docker IP. -# Always use port 80 here to force the internal nginx to bind port 80, -# even if you intend to use another port in Docker. -external_url "http://192.168.59.103/" - -# Prevent Postgres from trying to allocate 25% of total memory -postgresql['shared_buffers'] = '1MB' - -# Configure GitLab to redirect PostgreSQL logs to the data volume -postgresql['log_directory'] = '/var/log/gitlab/postgresql' - -# Some configuration of GitLab -# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration -gitlab_rails['gitlab_email_from'] = 'gitlab@example.com' -gitlab_rails['gitlab_support_email'] = 'support@example.com' -gitlab_rails['time_zone'] = 'Europe/Paris' - -# SMTP settings -# You must use an external server, the Docker container does not install an SMTP server -gitlab_rails['smtp_enable'] = true -gitlab_rails['smtp_address'] = "smtp.example.com" -gitlab_rails['smtp_port'] = 587 -gitlab_rails['smtp_user_name'] = "user" -gitlab_rails['smtp_password'] = "password" -gitlab_rails['smtp_domain'] = "example.com" -gitlab_rails['smtp_authentication'] = "plain" -gitlab_rails['smtp_enable_starttls_auto'] = true - -# Enable LDAP authentication -# gitlab_rails['ldap_enabled'] = true -# gitlab_rails['ldap_host'] = 'ldap.example.com' -# gitlab_rails['ldap_port'] = 389 -# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain' -# gitlab_rails['ldap_allow_username_or_email_login'] = false -# gitlab_rails['ldap_uid'] = 'uid' -# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com' diff --git a/docker/fig.yml b/docker/fig.yml new file mode 100644 index 00000000000..989551cbfe2 --- /dev/null +++ b/docker/fig.yml @@ -0,0 +1,2 @@ +app: + build: . diff --git a/docker/marathon.json b/docker/marathon.json new file mode 100644 index 00000000000..9b2091a8c22 --- /dev/null +++ b/docker/marathon.json @@ -0,0 +1,31 @@ +{ + "id": "/gitlab", + "ports": [0,0], + "cpus": 2, + "mem": 2048.0, + "disk": 10240.0, + "container": { + "type": "DOCKER", + "docker": { + "network": "HOST", + "image": "gitlab/gitlab-ce:latest" + }, + "volumes": [ + { + "containerPath": "/etc/gitlab", + "hostPath": "/var/data/etc/gitlab", + "mode": "RW" + }, + { + "containerPath": "/var/opt/gitlab", + "hostPath": "/var/data/opt/gitlab", + "mode": "RW" + }, + { + "containerPath": "/var/log/gitlab", + "hostPath": "/var/data/log/gitlab", + "mode": "RW" + } + ] + } +}
\ No newline at end of file diff --git a/docker/single/assets/gitlab.rb b/docker/single/assets/gitlab.rb deleted file mode 100644 index ef84e7832d6..00000000000 --- a/docker/single/assets/gitlab.rb +++ /dev/null @@ -1,37 +0,0 @@ -# External URL should be your Docker instance. -# By default, GitLab will use the Docker container hostname. -# Always use port 80 here to force the internal nginx to bind port 80, -# even if you intend to use another port in Docker. -# external_url "http://192.168.59.103/" - -# Prevent Postgres from trying to allocate 25% of total memory -postgresql['shared_buffers'] = '1MB' - -# Configure GitLab to redirect PostgreSQL logs to the data volume -postgresql['log_directory'] = '/var/log/gitlab/postgresql' - -# Some configuration of GitLab -# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration -gitlab_rails['gitlab_email_from'] = 'gitlab@example.com' -gitlab_rails['gitlab_support_email'] = 'support@example.com' -gitlab_rails['time_zone'] = 'Europe/Paris' - -# SMTP settings -# You must use an external server, the Docker container does not install an SMTP server -gitlab_rails['smtp_enable'] = true -gitlab_rails['smtp_address'] = "smtp.example.com" -gitlab_rails['smtp_port'] = 587 -gitlab_rails['smtp_user_name'] = "user" -gitlab_rails['smtp_password'] = "password" -gitlab_rails['smtp_domain'] = "example.com" -gitlab_rails['smtp_authentication'] = "plain" -gitlab_rails['smtp_enable_starttls_auto'] = true - -# Enable LDAP authentication -# gitlab_rails['ldap_enabled'] = true -# gitlab_rails['ldap_host'] = 'ldap.example.com' -# gitlab_rails['ldap_port'] = 389 -# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain' -# gitlab_rails['ldap_allow_username_or_email_login'] = false -# gitlab_rails['ldap_uid'] = 'uid' -# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com' diff --git a/docker/single/marathon.json b/docker/single/marathon.json deleted file mode 100644 index d23c2b84e0e..00000000000 --- a/docker/single/marathon.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "/gitlab", - "ports": [0,0], - "cpus": 2, - "mem": 2048.0, - "disk": 10240.0, - "container": { - "type": "DOCKER", - "docker": { - "network": "HOST", - "image": "sytse/gitlab-ce:7.10.1" - } - } -}
\ No newline at end of file diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md index 5827f2185db..63482547daa 100644 --- a/docker/troubleshooting.md +++ b/docker/troubleshooting.md @@ -9,24 +9,19 @@ postgresql['log_directory'] = '/var/log/gitlab/postgresql' # Commands ```bash -sudo docker build --tag gitlab_image docker/ +sudo docker build --tag gitlab/gitlab-ce:latest docker/ -sudo docker rm -f gitlab_app -sudo docker rm -f gitlab_data +sudo docker rm -f gitlab -sudo docker run --name gitlab_data gitlab_image /bin/true +sudo docker exec -it gitlab vim /etc/gitlab/gitlab.rb -sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb +sudo docker exec gitlab tail -f /var/log/gitlab/reconfigure.log -sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image +sudo docker exec gitlab tail -f /var/log/gitlab/postgresql/current -sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log +sudo docker exec gitlab cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers -sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current - -sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers - -sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb +sudo docker exec gitlab cat /etc/gitlab/gitlab.rb ``` # Interactively @@ -37,7 +32,16 @@ sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab # - we run interactively (-t -i) # - we define TERM=linux because it allows to use arrow keys in vi (!!!) # - we choose another startup command (bash) -sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash +sudo docker run --ti \ + -e TERM=linux + --publish 80443:443 --publish 8080:80 --publish 2222:22 \ + --name gitlab \ + --restart always \ + --volume /srv/gitlab/config:/etc/gitlab \ + --volume /srv/gitlab/logs:/var/log/gitlab \ + --volume /srv/gitlab/data:/var/opt/gitlab \ + gitlab/gitlab-ce:latest \ + bash # Configure GitLab to redirect PostgreSQL logs echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb @@ -64,10 +68,17 @@ free -m # Cleanup -Remove ALL docker containers and images (also non GitLab ones): +Remove ALL docker containers and images (also non GitLab ones). +**Be careful, because the `-v` also removes volumes attached to the images.** -``` -docker rm $(docker ps -a -q) +```bash +# Remove all containers with attached volumes +docker rm -v $(docker ps -a -q) + +# Remove all images docker rmi $(docker images -q) + +# Remove GitLab persistent data +rm -rf /srv/gitlab ``` diff --git a/features/groups.feature b/features/groups.feature index 415e43d6ae7..299e846edb0 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -4,6 +4,10 @@ Feature: Groups And "John Doe" is owner of group "Owned" And "John Doe" is guest of group "Guest" + Scenario: I should have back to group button + When I visit group "Owned" page + Then I should see back to dashboard button + @javascript Scenario: I should see group "Owned" dashboard list When I visit group "Owned" page diff --git a/features/profile/active_tab.feature b/features/profile/active_tab.feature index 1fa4ac88ddc..788b7895d72 100644 --- a/features/profile/active_tab.feature +++ b/features/profile/active_tab.feature @@ -23,7 +23,7 @@ Feature: Profile Active Tab Then the active main tab should be Preferences And no other main tabs should be active - Scenario: On Profile History - Given I visit profile history page - Then the active main tab should be History + Scenario: On Profile Audit Log + Given I visit Audit Log page + Then the active main tab should be Audit Log And no other main tabs should be active diff --git a/features/profile/profile.feature b/features/profile/profile.feature index 0dd0afde8b1..7a1345f2b37 100644 --- a/features/profile/profile.feature +++ b/features/profile/profile.feature @@ -63,7 +63,7 @@ Feature: Profile Scenario: I visit history tab Given I have activity - When I visit profile history page + When I visit Audit Log page Then I should see my activity Scenario: I visit my user page diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature index ad1160e3343..10bd6fec803 100644 --- a/features/project/forked_merge_requests.feature +++ b/features/project/forked_merge_requests.feature @@ -26,7 +26,6 @@ Feature: Project Forked Merge Requests #And I save the merge request #Then I should see the edited merge request - @javascript Scenario: I cannot submit an invalid merge request Given I visit project "Forked Shop" merge requests page And I click link "New Merge Request" diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index bf84e2f8e87..28cc43ef710 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -184,3 +184,16 @@ Feature: Project Issues Then I should see that I am subscribed When I click button "Unsubscribe" Then I should see that I am unsubscribed + + Scenario: I submit new unassigned issue as guest + Given I logout + Given public project "Community" + When I visit project "Community" page + And I visit project "Community" issues page + And I click link "New Issue" + And I should not see assignee field + And I should not see milestone field + And I should not see labels field + And I submit new issue "500 error on profile" + Then I should see issue "500 error on profile" + diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 35ffa9fc6e1..947f668e432 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -63,7 +63,7 @@ Feature: Project Merge Requests Scenario: I comment on a merge request diff Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" - And I switch to the diff tab + And I click on the Changes tab And I leave a comment like "Line is wrong" on diff And I switch to the merge request's comments tab Then I should see a discussion has started on diff @@ -114,7 +114,7 @@ Feature: Project Merge Requests Scenario: I hide comments on a merge request diff with comments in a single file Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" - And I switch to the diff tab + And I click on the Changes tab And I leave a comment like "Line is wrong" on line 39 of the second file And I click link "Hide inline discussion" of the second file Then I should not see a comment like "Line is wrong here" in the second file @@ -123,7 +123,7 @@ Feature: Project Merge Requests Scenario: I show comments on a merge request diff with comments in a single file Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" - And I switch to the diff tab + And I click on the Changes tab And I leave a comment like "Line is wrong" on line 39 of the second file Then I should see a comment like "Line is wrong" in the second file @@ -131,7 +131,7 @@ Feature: Project Merge Requests Scenario: I hide comments on a merge request diff with comments in multiple files Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" - And I switch to the diff tab + And I click on the Changes tab And I leave a comment like "Line is correct" on line 12 of the first file And I leave a comment like "Line is wrong" on line 39 of the second file And I click link "Hide inline discussion" of the second file @@ -142,7 +142,7 @@ Feature: Project Merge Requests Scenario: I show comments on a merge request diff with comments in multiple files Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" - And I switch to the diff tab + And I click on the Changes tab And I leave a comment like "Line is correct" on line 12 of the first file And I leave a comment like "Line is wrong" on line 39 of the second file And I click link "Hide inline discussion" of the second file @@ -154,7 +154,7 @@ Feature: Project Merge Requests Scenario: I unfold diff Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" - And I switch to the diff tab + And I click on the Changes tab And I unfold diff Then I should see additional file lines @@ -162,7 +162,7 @@ Feature: Project Merge Requests Scenario: I show comments on a merge request side-by-side diff with comments in multiple files Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" - And I switch to the diff tab + And I click on the Changes tab And I leave a comment like "Line is correct" on line 12 of the first file And I leave a comment like "Line is wrong" on line 39 of the second file And I click Side-by-side Diff tab @@ -172,7 +172,7 @@ Feature: Project Merge Requests Scenario: I view diffs on a merge request Given project "Shop" have "Bug NS-05" open merge request with diffs inside And I visit merge request page "Bug NS-05" - And I click on the Changes tab via Javascript + And I click on the Changes tab Then I should see the proper Inline and Side-by-side links # Description preview diff --git a/features/project/project.feature b/features/project/project.feature index 56ae5c78d01..089ffcba14a 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -18,9 +18,22 @@ Feature: Project Then I should see the default project avatar And I should not see the "Remove avatar" button + Scenario: I should have back to group button + And project "Shop" belongs to group + And I visit project "Shop" page + Then I should see back to group button + + Scenario: I should have back to group button + And I visit project "Shop" page + Then I should see back to dashboard button + + Scenario: I should have readme on page + And I visit project "Shop" page + Then I should see project "Shop" README + @javascript Scenario: I should see project activity - When I visit project "Shop" page + When I visit project "Shop" activity page Then I should see project "Shop" activity feed Scenario: I visit edit project @@ -38,24 +51,12 @@ Feature: Project And change project path settings Then I should see project with new path settings - Scenario: I should see project readme and version - When I visit project "Shop" page - And I should see project "Shop" version - Scenario: I should change project default branch When I visit edit project "Shop" page And change project default branch And I save project Then I should see project default branch changed - @javascript - Scenario: I should have default tab per my preference - And I own project "Forum" - When I select project "Forum" README tab - Then I should see project "Forum" README - And I visit project "Shop" page - Then I should see project "Shop" README - Scenario: I tag a project When I visit edit project "Shop" page Then I should see project settings diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature index cfb68bf1f50..0f71c32380b 100644 --- a/features/project/shortcuts.feature +++ b/features/project/shortcuts.feature @@ -3,7 +3,7 @@ Feature: Project Shortcuts Background: Given I sign in as a user And I own a project - And I visit my project's home page + And I visit my project's commits page @javascript Scenario: Navigate to files tab @@ -12,6 +12,7 @@ Feature: Project Shortcuts @javascript Scenario: Navigate to commits tab + Given I visit my project's files page Given I press "g" and "c" Then the active main tab should be Commits @@ -46,7 +47,11 @@ Feature: Project Shortcuts Then the active main tab should be Wiki @javascript - Scenario: Navigate to project feed - Given I visit my project's files page + Scenario: Navigate to project home Given I press "g" and "p" Then the active main tab should be Home + + @javascript + Scenario: Navigate to project feed + Given I press "g" and "e" + Then the active main tab should be Activity diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 2812c5473e9..46e1f4d0990 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -5,6 +5,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps include SharedUser include Select2Helper + step 'I should see back to dashboard button' do + expect(page).to have_content 'Back to Dashboard' + end + step 'gitlab user "Mike"' do create(:user, name: "Mike") end diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb index 79e3b55f6e1..4724a326277 100644 --- a/features/steps/profile/active_tab.rb +++ b/features/steps/profile/active_tab.rb @@ -19,7 +19,7 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps ensure_active_main_tab('Preferences') end - step 'the active main tab should be History' do - ensure_active_main_tab('History') + step 'the active main tab should be Audit Log' do + ensure_active_main_tab('Audit Log') end end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 11e1163c352..2b6b8b167f6 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -115,7 +115,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step 'I should see my activity' do - expect(page).to have_content "#{current_user.name} closed issue" + expect(page).to have_content "Signed in with standard authentication" end step 'my password is expired' do diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index fabbc1d3d81..9e96fa5ba49 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -20,8 +20,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps end step 'I click the "Edit" tab' do - page.within '.project-settings-nav' do - click_link('Project') + page.within '.sidebar-subnav' do + click_link('Project Settings') end end diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 78812c52026..58c16d59d05 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -42,6 +42,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps click_button "Compare branches" + expect(page).to have_content "New merge request" fill_in "merge_request_title", with: "Merge Request On Forked Project" end diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index 6873c043e19..239392eab96 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -19,12 +19,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'I should see that I am subscribed' do - expect(find(".subscribe-button span").text).to eq "Unsubscribe" + expect(find('.subscribe-button span')).to have_content 'Unsubscribe' end step 'I should see that I am unsubscribed' do - sleep 0.2 - expect(find(".subscribe-button span").text).to eq "Subscribe" + expect(find('.subscribe-button span')).to have_content 'Subscribe' end step 'I click link "Closed"' do @@ -195,6 +194,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end end + When "I visit project \"Community\" issues page" do + project = Project.find_by(name: 'Community') + visit namespace_project_issues_path(project.namespace, project) + end + When "I visit empty project's issues page" do project = Project.find_by(name: 'Empty Project') visit namespace_project_issues_path(project.namespace, project) @@ -262,6 +266,24 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end end + step 'I should not see labels field' do + page.within '.issue-form' do + expect(page).not_to have_content("Labels") + end + end + + step 'I should not see milestone field' do + page.within '.issue-form' do + expect(page).not_to have_content("Milestone") + end + end + + step 'I should not see assignee field' do + page.within '.issue-form' do + expect(page).not_to have_content("Assign to") + end + end + def filter_issue(text) fill_in 'issue_search', with: text end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index f11edb659d5..a1a26abd8ca 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -58,11 +58,11 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I should see that I am subscribed' do - expect(find(".subscribe-button span").text).to eq "Unsubscribe" + expect(find('.subscribe-button span')).to have_content 'Unsubscribe' end step 'I should see that I am unsubscribed' do - expect(find(".subscribe-button span")).to have_content("Subscribe") + expect(find('.subscribe-button span')).to have_content 'Subscribe' end step 'I click button "Unsubscribe"' do @@ -118,25 +118,17 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps author: project.users.first) end - step 'I switch to the diff tab' do - visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) - end - - step 'I click on the Changes tab via Javascript' do + step 'I click on the Changes tab' do page.within '.merge-request-tabs' do click_link 'Changes' end - sleep 2 + # Waits for load + expect(page).to have_css('.tab-content #diffs.active') end step 'I should see the proper Inline and Side-by-side links' do - buttons = page.all('#commit-diff-viewtype') - expect(buttons.count).to eq(2) - - buttons.each do |b| - expect(b['href']).not_to have_content('json') - end + expect(page).to have_css('#commit-diff-viewtype', count: 2) end step 'I switch to the merge request\'s comments tab' do @@ -301,6 +293,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I unfold diff' do + expect(page).to have_css('.js-unfold') + first('.js-unfold').click end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index b4a0ba1e27f..0404fd5e594 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -86,13 +86,15 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I should see project "Forum" README' do - expect(page).to have_link 'README.md' - expect(page).to have_content 'Sample repo for testing gitlab features' + page.within('#README') do + expect(page).to have_content 'Sample repo for testing gitlab features' + end end step 'I should see project "Shop" README' do - expect(page).to have_link 'README.md' - expect(page).to have_content 'testme' + page.within('#README') do + expect(page).to have_content 'testme' + end end step 'I add project tags' do @@ -114,4 +116,18 @@ class Spinach::Features::Project < Spinach::FeatureSteps step 'I should not see "Snippets" button' do expect(page).not_to have_link 'Snippets' end + + step 'project "Shop" belongs to group' do + group = create(:group) + @project.namespace = group + @project.save! + end + + step 'I should see back to dashboard button' do + expect(page).to have_content 'Back to Dashboard' + end + + step 'I should see back to group button' do + expect(page).to have_content 'Back to Group' + end end diff --git a/features/steps/project/project_shortcuts.rb b/features/steps/project/project_shortcuts.rb index a10e7bf78ee..49e9c5520bb 100644 --- a/features/steps/project/project_shortcuts.rb +++ b/features/steps/project/project_shortcuts.rb @@ -33,4 +33,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps find('body').native.send_key('g') find('body').native.send_key('w') end + + step 'I press "g" and "e"' do + find('body').native.send_key('g') + find('body').native.send_key('e') + end end diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 398c9bf5756..95879b9544d 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -187,7 +187,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I click on "add a file" link' do - click_link 'add a file' + click_link 'adding README' # Remove pre-receive hook so we can push without auth FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive')) diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb index 8b50bfcef04..bd2e0619cdd 100644 --- a/features/steps/project/star.rb +++ b/features/steps/project/star.rb @@ -5,7 +5,7 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps include SharedUser step "The project has no stars" do - expect(page).not_to have_content '.star-buttons' + expect(page).not_to have_content '.toggle-star' end step "The project has 0 stars" do diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 4cc01443c8b..88a98a37807 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -127,8 +127,8 @@ module SharedPaths visit profile_preferences_path end - step 'I visit profile history page' do - visit history_profile_path + step 'I visit Audit Log page' do + visit audit_log_profile_path end # ---------------------------------------- @@ -251,6 +251,10 @@ module SharedPaths visit namespace_project_path(project.namespace, project) end + step 'I visit project "Shop" activity page' do + visit activity_namespace_project_path(project.namespace, project) + end + step 'I visit project "Forked Shop" merge requests page' do visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project) end diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb index 3b94b7d8621..c67e5e4a06a 100644 --- a/features/steps/shared/project_tab.rb +++ b/features/steps/shared/project_tab.rb @@ -49,4 +49,8 @@ module SharedProjectTab expect(page).to have_content('Back to project') end end + + step 'the active main tab should be Activity' do + ensure_active_main_tab('Activity') + end end diff --git a/lib/api/api.rb b/lib/api/api.rb index d2a35c78fc1..eebd44ea5b6 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -49,5 +49,6 @@ module API mount Namespaces mount Branches mount Labels + mount Settings end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 14a8f929d76..31202fa8c1f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -277,5 +277,27 @@ module API class BroadcastMessage < Grape::Entity expose :message, :starts_at, :ends_at, :color, :font end + + class ApplicationSetting < Grape::Entity + expose :id + expose :default_projects_limit + expose :signup_enabled + expose :signin_enabled + expose :gravatar_enabled + expose :sign_in_text + expose :created_at + expose :updated_at + expose :home_page_url + expose :default_branch_protection + expose :twitter_sharing_enabled + expose :restricted_visibility_levels + expose :max_attachment_size + expose :session_expire_delay + expose :default_project_visibility + expose :default_snippet_visibility + expose :restricted_signup_domains + expose :user_oauth_applications + expose :after_sign_out_path + end end end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index e88b6e31775..024aeec2e14 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -74,9 +74,9 @@ module API # POST /groups/:id/projects/:project_id post ":id/projects/:project_id" do authenticated_as_admin! - group = Group.find(params[:id]) + group = Group.find_by(id: params[:id]) project = Project.find(params[:project_id]) - result = ::Projects::TransferService.new(project, current_user, namespace_id: group.id).execute + result = ::Projects::TransferService.new(project, current_user).execute(group) if result present group diff --git a/lib/api/issues.rb b/lib/api/issues.rb index c8db93eb778..6e7a7672070 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -144,7 +144,7 @@ module API # PUT /projects/:id/issues/:issue_id put ":id/issues/:issue_id" do issue = user_project.issues.find(params[:issue_id]) - authorize! :modify_issue, issue + authorize! :update_issue, issue attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] # Validate label names in advance @@ -157,7 +157,7 @@ module API if issue.valid? # Find or create labels and attach to issue. Labels are valid because # we already checked its name, so there can't be an error here - unless params[:labels].nil? + if params[:labels] && can?(current_user, :admin_issue, user_project) issue.remove_labels # Create and add labels to the new created issue issue.add_labels_by_names(params[:labels].split(',')) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d835dce2ded..aa43e1dffd9 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -109,7 +109,7 @@ module API # POST /projects/:id/merge_requests # post ":id/merge_requests" do - authorize! :write_merge_request, user_project + authorize! :create_merge_request, user_project required_attributes! [:source_branch, :target_branch, :title] attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description] @@ -149,7 +149,7 @@ module API put ":id/merge_request/:merge_request_id" do attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description] merge_request = user_project.merge_requests.find(params[:merge_request_id]) - authorize! :modify_merge_request, merge_request + authorize! :update_merge_request, merge_request # Ensure source_branch is not specified if params[:source_branch].present? diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 54f2555903f..22ce3c6a066 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -46,7 +46,7 @@ module API # Example Request: # POST /projects/:id/snippets post ":id/snippets" do - authorize! :write_project_snippet, user_project + authorize! :create_project_snippet, user_project required_attributes! [:title, :file_name, :code, :visibility_level] attrs = attributes_for_keys [:title, :file_name, :visibility_level] @@ -74,7 +74,7 @@ module API # PUT /projects/:id/snippets/:snippet_id put ":id/snippets/:snippet_id" do @snippet = user_project.snippets.find(params[:snippet_id]) - authorize! :modify_project_snippet, @snippet + authorize! :update_project_snippet, @snippet attrs = attributes_for_keys [:title, :file_name, :visibility_level] attrs[:content] = params[:code] if params[:code].present? @@ -98,7 +98,7 @@ module API delete ":id/snippets/:snippet_id" do begin @snippet = user_project.snippets.find(params[:snippet_id]) - authorize! :modify_project_snippet, @snippet + authorize! :update_project_snippet, @snippet @snippet.destroy rescue not_found!('Snippet') diff --git a/lib/api/settings.rb b/lib/api/settings.rb new file mode 100644 index 00000000000..c885fcd7ea3 --- /dev/null +++ b/lib/api/settings.rb @@ -0,0 +1,35 @@ +module API + class Settings < Grape::API + before { authenticated_as_admin! } + + helpers do + def current_settings + @current_setting ||= + (ApplicationSetting.current || ApplicationSetting.create_from_defaults) + end + end + + # Get current applicaiton settings + # + # Example Request: + # GET /application/settings + get "application/settings" do + present current_settings, with: Entities::ApplicationSetting + end + + # Modify applicaiton settings + # + # Example Request: + # PUT /application/settings + put "application/settings" do + attributes = current_settings.attributes.keys - ["id"] + attrs = attributes_for_keys(attributes) + + if current_settings.update_attributes(attrs) + present current_settings, with: Entities::ApplicationSetting + else + render_validation_error!(current_settings) + end + end + end +end diff --git a/lib/api/users.rb b/lib/api/users.rb index 9b268cfe8bc..c468371d3d4 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -199,6 +199,36 @@ module API not_found!('User') end end + + # Block user. Available only for admin + # + # Example Request: + # PUT /users/:id/block + put ':id/block' do + authenticated_as_admin! + user = User.find_by(id: params[:id]) + + if user + user.block + else + not_found!('User') + end + end + + # Unblock user. Available only for admin + # + # Example Request: + # PUT /users/:id/unblock + put ':id/unblock' do + authenticated_as_admin! + user = User.find_by(id: params[:id]) + + if user + user.activate + else + not_found!('User') + end + end end resource :user do diff --git a/lib/backup/database.rb b/lib/backup/database.rb index 9ab6aca276d..b8aa6b9ff2f 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -18,23 +18,30 @@ module Backup when "postgresql" then $progress.print "Dumping PostgreSQL database #{config['database']} ... " pg_env - system('pg_dump', config['database'], out: db_file_name) + # Pass '--clean' to include 'DROP TABLE' statements in the DB dump. + system('pg_dump', '--clean', config['database'], out: db_file_name) end report_success(success) abort 'Backup failed' unless success + + $progress.print 'Compressing database ... ' + success = system('gzip', db_file_name) + report_success(success) + abort 'Backup failed: compress error' unless success end def restore + $progress.print 'Decompressing database ... ' + success = system('gzip', '-d', db_file_name_gz) + report_success(success) + abort 'Restore failed: decompress error' unless success + success = case config["adapter"] when /^mysql/ then $progress.print "Restoring MySQL database #{config['database']} ... " system('mysql', *mysql_args, config['database'], in: db_file_name) when "postgresql" then $progress.print "Restoring PostgreSQL database #{config['database']} ... " - # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE - # statements like MySQL. - Rake::Task["gitlab:db:drop_all_tables"].invoke - Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke pg_env system('psql', config['database'], '-f', db_file_name) end @@ -48,6 +55,10 @@ module Backup File.join(db_dir, 'database.sql') end + def db_file_name_gz + File.join(db_dir, 'database.sql.gz') + end + def mysql_args args = { 'host' => '--host', diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index 6e4ed01e079..3f420553d42 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -55,12 +55,16 @@ module ExtractsPath valid_refs = @project.repository.ref_names valid_refs.select! { |v| id.start_with?("#{v}/") } - if valid_refs.length != 1 + if valid_refs.length == 0 # No exact ref match, so just try our best pair = id.match(/([^\/]+)(.*)/).captures else + # There is a distinct possibility that multiple refs prefix the ID. + # Use the longest match to maximize the chance that we have the + # right ref. + best_match = valid_refs.max_by(&:length) # Partition the string into the ref and the path, ignoring the empty first value - pair = id.partition(valid_refs.first)[1..-1] + pair = id.partition(best_match)[1..-1] end end diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index 8ba97184e69..8672cbc0ec4 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -1,7 +1,7 @@ module Gitlab class GitAccessWiki < GitAccess def change_access_check(change) - if user.can?(:write_wiki, project) + if user.can?(:create_wiki, project) build_status_object(true) else build_status_object(false, "You are not allowed to write to this project's wiki.") diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 23832b3233c..98039a76dcd 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -11,7 +11,9 @@ module Gitlab def execute #Issues && Comments - client.list_issues(project.import_source, state: :all).each do |issue| + client.list_issues(project.import_source, state: :all, + sort: :created, + direction: :asc).each do |issue| if issue.pull_request.nil? body = @formatter.author_line(issue.user.login, issue.body) diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index fa9c0975bb8..889decc9b48 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -54,7 +54,7 @@ module Gitlab current_user: current_user ) - pipeline = HTML::Pipeline.new(filters) + @pipeline ||= HTML::Pipeline.new(filters) context = { # SanitizationFilter @@ -79,7 +79,7 @@ module Gitlab project_wiki: @project_wiki } - result = pipeline.call(text, context) + result = @pipeline.call(text, context) save_options = 0 if options[:xhtml] diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb index f99be969d3e..b1991e2e285 100644 --- a/lib/gitlab/markup_helper.rb +++ b/lib/gitlab/markup_helper.rb @@ -33,6 +33,16 @@ module Gitlab filename.downcase.end_with?(*%w(.adoc .ad .asciidoc)) end + # Public: Determines if the given filename is plain text. + # + # filename - Filename string to check + # + # Returns boolean + def plain?(filename) + filename.downcase.end_with?('.txt') || + filename.downcase == 'readme' + end + def previewable?(filename) markup?(filename) end diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index 582fc759efd..335dc44be19 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -47,6 +47,10 @@ module Gitlab def valid_level?(level) options.has_value?(level) end + + def allowed_fork_levels(origin_level) + [PRIVATE, INTERNAL, PUBLIC].select{ |level| level <= origin_level } + end end def private? diff --git a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake deleted file mode 100644 index e9cf0a9b5e8..00000000000 --- a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake +++ /dev/null @@ -1,10 +0,0 @@ -namespace :gitlab do - namespace :db do - task drop_all_postgres_sequences: :environment do - connection = ActiveRecord::Base.connection - connection.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';").each do |sequence| - connection.execute("DROP SEQUENCE #{sequence['relname']}") - end - end - end -end diff --git a/lib/tasks/gitlab/db/drop_all_tables.rake b/lib/tasks/gitlab/db/drop_all_tables.rake deleted file mode 100644 index a66030ab93a..00000000000 --- a/lib/tasks/gitlab/db/drop_all_tables.rake +++ /dev/null @@ -1,10 +0,0 @@ -namespace :gitlab do - namespace :db do - task drop_all_tables: :environment do - connection = ActiveRecord::Base.connection - connection.tables.each do |table| - connection.drop_table(table) - end - end - end -end diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake index cbb3d61af04..4d4e746503a 100644 --- a/lib/tasks/gitlab/test.rake +++ b/lib/tasks/gitlab/test.rake @@ -6,7 +6,7 @@ namespace :gitlab do %W(rake rubocop), %W(rake spinach), %W(rake spec), - %W(rake jasmine:ci) + %W(rake teaspoon) ] cmds.each do |cmd| diff --git a/lib/tasks/jasmine.rake b/lib/tasks/jasmine.rake deleted file mode 100644 index ac307a9e929..00000000000 --- a/lib/tasks/jasmine.rake +++ /dev/null @@ -1,12 +0,0 @@ -# Since we no longer explicitly require the 'jasmine' gem, we lost the -# `jasmine:ci` task used by GitLab CI jobs. -# -# This provides a simple alias to run the `spec:javascript` task from the -# 'jasmine-rails' gem. -task jasmine: ['jasmine:ci'] - -namespace :jasmine do - task :ci do - Rake::Task['teaspoon'].invoke - end -end diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake index b9f9c72c91f..c5666d49e61 100644 --- a/lib/tasks/test.rake +++ b/lib/tasks/test.rake @@ -9,5 +9,5 @@ unless Rails.env.production? require 'coveralls/rake/task' Coveralls::RakeTask.new desc "GitLab | Run all tests on CI with simplecov" - task :test_ci => [:rubocop, :brakeman, 'jasmine:ci', :spinach, :spec, 'coveralls:push'] + task :test_ci => [:rubocop, :brakeman, 'teaspoon', :spinach, :spec, 'coveralls:push'] end diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index 5525ab77435..dca5e1c5db3 100755 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -4,7 +4,7 @@ if [ -f /.dockerinit ]; then dpkg -i phantomjs_1.9.0-1+b1_amd64.deb apt-get update -qq - apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs + apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client cp config/database.yml.mysql config/database.yml sed -i 's/username:.*/username: root/g' config/database.yml diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index f27e861e175..6f4c8987637 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -21,4 +21,47 @@ describe Admin::UsersController do expect { User.find(user.id) }.to raise_exception(ActiveRecord::RecordNotFound) end end + + describe 'PUT unlock/:id' do + let(:user) { create(:user) } + + before do + request.env["HTTP_REFERER"] = "/" + user.lock_access! + end + + it 'unlocks user' do + put :unlock, id: user.username + user.reload + expect(user.access_locked?).to be_falsey + end + end + + describe 'PATCH disable_two_factor' do + let(:user) { create(:user) } + + it 'disables 2FA for the user' do + expect(user).to receive(:disable_two_factor!) + allow(subject).to receive(:user).and_return(user) + + go + end + + it 'redirects back' do + go + + expect(response).to redirect_to(admin_user_path(user)) + end + + it 'displays an alert' do + go + + expect(flash[:notice]). + to eq 'Two-factor Authentication has been disabled for this user' + end + + def go + patch :disable_two_factor, id: user.to_param + end + end end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 9ad9cb41cc1..1230017c270 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -9,15 +9,27 @@ describe AutocompleteController do before do sign_in(user) project.team << [user, :master] - - get(:users, project_id: project.id) end let(:body) { JSON.parse(response.body) } - it { expect(body).to be_kind_of(Array) } - it { expect(body.size).to eq 1 } - it { expect(body.first["username"]).to eq user.username } + describe 'GET #users with project ID' do + before do + get(:users, project_id: project.id) + end + + it { expect(body).to be_kind_of(Array) } + it { expect(body.size).to eq 1 } + it { expect(body.first["username"]).to eq user.username } + end + + describe 'GET #users with unknown project' do + before do + get(:users, project_id: 'unknown') + end + + it { expect(response.status).to eq(404) } + end end context 'group members' do @@ -26,15 +38,27 @@ describe AutocompleteController do before do sign_in(user) group.add_owner(user) - - get(:users, group_id: group.id) end let(:body) { JSON.parse(response.body) } - it { expect(body).to be_kind_of(Array) } - it { expect(body.size).to eq 1 } - it { expect(body.first["username"]).to eq user.username } + describe 'GET #users with group ID' do + before do + get(:users, group_id: group.id) + end + + it { expect(body).to be_kind_of(Array) } + it { expect(body.size).to eq 1 } + it { expect(body.first["username"]).to eq user.username } + end + + describe 'GET #users with unknown group ID' do + before do + get(:users, group_id: 'unknown') + end + + it { expect(response.status).to eq(404) } + end end context 'all users' do @@ -48,4 +72,52 @@ describe AutocompleteController do it { expect(body).to be_kind_of(Array) } it { expect(body.size).to eq User.count } end + + context 'unauthenticated user' do + let(:public_project) { create(:project, :public) } + let(:body) { JSON.parse(response.body) } + + describe 'GET #users with public project' do + before do + public_project.team << [user, :guest] + get(:users, project_id: public_project.id) + end + + it { expect(body).to be_kind_of(Array) } + it { expect(body.size).to eq 1 } + end + + describe 'GET #users with project' do + before do + get(:users, project_id: project.id) + end + + it { expect(response.status).to eq(302) } + end + + describe 'GET #users with unknown project' do + before do + get(:users, project_id: 'unknown') + end + + it { expect(response.status).to eq(302) } + end + + describe 'GET #users with inaccessible group' do + before do + project.team << [user, :guest] + get(:users, group_id: user.namespace.id) + end + + it { expect(response.status).to eq(302) } + end + + describe 'GET #users with no project' do + before do + get(:users) + end + + it { expect(response.status).to eq(302) } + end + end end diff --git a/spec/controllers/branches_controller_spec.rb b/spec/controllers/branches_controller_spec.rb index db3b64babcd..bd4c946b64b 100644 --- a/spec/controllers/branches_controller_spec.rb +++ b/spec/controllers/branches_controller_spec.rb @@ -55,4 +55,30 @@ describe Projects::BranchesController do it { is_expected.to render_template('new') } end end + + describe "POST destroy" do + render_views + + before do + post :destroy, + format: :js, + id: branch, + namespace_id: project.namespace.to_param, + project_id: project.to_param + end + + context "valid branch name, valid source" do + let(:branch) { "feature" } + + it { expect(response.status).to eq(200) } + it { expect(subject).to render_template('destroy') } + end + + context "invalid branch name, valid ref" do + let(:branch) { "no-branch" } + + it { expect(response.status).to eq(404) } + it { expect(subject).to render_template('destroy') } + end + end end diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb index aa09f1a758d..f54706e3aa3 100644 --- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb +++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb @@ -105,19 +105,12 @@ describe Profiles::TwoFactorAuthsController do end describe 'DELETE destroy' do - let(:user) { create(:user, :two_factor) } - let!(:codes) { user.generate_otp_backup_codes! } + let(:user) { create(:user, :two_factor) } - it 'clears all 2FA-related fields' do - expect(user).to be_two_factor_enabled - expect(user.otp_backup_codes).not_to be_nil - expect(user.encrypted_otp_secret).not_to be_nil + it 'disables two factor' do + expect(user).to receive(:disable_two_factor!) delete :destroy - - expect(user).not_to be_two_factor_enabled - expect(user.otp_backup_codes).to be_nil - expect(user.encrypted_otp_secret).to be_nil end it 'redirects to profile_account_path' do diff --git a/spec/factories.rb b/spec/factories.rb index 578a2e4dc69..05e3211d551 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -32,6 +32,7 @@ FactoryGirl.define do before(:create) do |user| user.two_factor_enabled = true user.otp_secret = User.generate_otp_secret(32) + user.generate_otp_backup_codes! end end diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb new file mode 100644 index 00000000000..71be66303d2 --- /dev/null +++ b/spec/features/admin/admin_disables_two_factor_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +feature 'Admin disables 2FA for a user', feature: true do + scenario 'successfully', js: true do + login_as(:admin) + user = create(:user, :two_factor) + + edit_user(user) + page.within('.two-factor-status') do + click_link 'Disable' + end + + page.within('.two-factor-status') do + expect(page).to have_content 'Disabled' + expect(page).not_to have_button 'Disable' + end + end + + scenario 'for a user without 2FA enabled' do + login_as(:admin) + user = create(:user) + + edit_user(user) + + page.within('.two-factor-status') do + expect(page).not_to have_button 'Disable' + end + end + + def edit_user(user) + visit admin_user_path(user) + end +end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index edc1c63a0aa..891df65216d 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Group' do +feature 'Group', feature: true do describe 'description' do let(:group) { create(:group) } let(:path) { group_path(group) } diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb new file mode 100644 index 00000000000..f600f8684ac --- /dev/null +++ b/spec/features/issues/filter_by_milestone_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +feature 'Issue filtering by Milestone', feature: true do + include Select2Helper + + let(:project) { create(:project, :public) } + let(:milestone) { create(:milestone, project: project) } + + scenario 'filters by no Milestone', js: true do + create(:issue, project: project) + create(:issue, project: project, milestone: milestone) + + visit_issues(project) + filter_by_milestone(Milestone::None.title) + + expect(page).to have_css('.issue-title', count: 1) + end + + scenario 'filters by a specific Milestone', js: true do + create(:issue, project: project, milestone: milestone) + create(:issue, project: project) + + visit_issues(project) + filter_by_milestone(milestone.title) + + expect(page).to have_css('.issue-title', count: 1) + end + + def visit_issues(project) + visit namespace_project_issues_path(project.namespace, project) + end + + def filter_by_milestone(title) + select2(title, from: '#milestone_title') + end +end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 158a5c0c29c..32fd4065bb4 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -92,22 +92,6 @@ describe 'Issues', feature: true do let(:issue) { @issue } - it 'should allow filtering by issues with no specified milestone' do - visit namespace_project_issues_path(project.namespace, project, milestone_title: IssuableFinder::NONE) - - expect(page).not_to have_content 'foobar' - expect(page).to have_content 'barbaz' - expect(page).to have_content 'gitlab' - end - - it 'should allow filtering by a specified milestone' do - visit namespace_project_issues_path(project.namespace, project, milestone_title: issue.milestone.title) - - expect(page).to have_content 'foobar' - expect(page).not_to have_content 'barbaz' - expect(page).not_to have_content 'gitlab' - end - it 'should allow filtering by issues with no specified assignee' do visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE) @@ -218,7 +202,7 @@ describe 'Issues', feature: true do it 'with dropdown menu' do visit namespace_project_issue_path(project.namespace, project, issue) - find('.edit-issue.inline-update #issue_assignee_id'). + find('.context #issue_assignee_id'). set project.team.members.first.id click_button 'Update Issue' @@ -257,7 +241,7 @@ describe 'Issues', feature: true do it 'with dropdown menu' do visit namespace_project_issue_path(project.namespace, project, issue) - find('.edit-issue.inline-update'). + find('.context'). select(milestone.title, from: 'issue_milestone_id') click_button 'Update Issue' diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 046a9f6191d..cef432e512b 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Login' do +feature 'Login', feature: true do describe 'with two-factor authentication' do context 'with valid username/password' do let(:user) { create(:user, :two_factor) } diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 902968cebcb..b8199aa5e61 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -32,7 +32,7 @@ require 'erb' # # See the MarkdownFeature class for setup details. -describe 'GitLab Markdown' do +describe 'GitLab Markdown', feature: true do include ActionView::Helpers::TagHelper include ActionView::Helpers::UrlHelper include Capybara::Node::Matchers diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb new file mode 100644 index 00000000000..f70214e1122 --- /dev/null +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +feature 'Merge Request filtering by Milestone', feature: true do + include Select2Helper + + let(:project) { create(:project, :public) } + let(:milestone) { create(:milestone, project: project) } + + scenario 'filters by no Milestone', js: true do + create(:merge_request, :with_diffs, source_project: project) + create(:merge_request, :simple, source_project: project, milestone: milestone) + + visit_merge_requests(project) + filter_by_milestone(Milestone::None.title) + + expect(page).to have_css('.merge-request-title', count: 1) + end + + scenario 'filters by a specific Milestone', js: true do + create(:merge_request, :with_diffs, source_project: project, milestone: milestone) + create(:merge_request, :simple, source_project: project) + + visit_merge_requests(project) + filter_by_milestone(milestone.title) + + expect(page).to have_css('.merge-request-title', count: 1) + end + + def visit_merge_requests(project) + visit namespace_project_merge_requests_path(project.namespace, project) + end + + def filter_by_milestone(title) + select2(title, from: '#milestone_title') + end +end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 12ab4b844d8..d7cb3b2e86e 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Comments' do +describe 'Comments', feature: true do include RepoHelpers describe 'On a merge request', js: true, feature: true do @@ -223,8 +223,7 @@ describe 'Comments' do sample_compare.changes.last[:line_code] end - def click_diff_line(data = nil) - data ||= line_code - find("button[data-line-code=\"#{data}\"]").click + def click_diff_line(data = line_code) + page.find(%Q{button[data-line-code="#{data}"]}, visible: false).click end end diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb index a34efce09ef..2b6311e4fd7 100644 --- a/spec/features/password_reset_spec.rb +++ b/spec/features/password_reset_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Password reset' do +feature 'Password reset', feature: true do def forgot_password click_on 'Forgot your password?' fill_in 'Email', with: user.email diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index 9fe2e610555..c80253fead8 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -9,8 +9,7 @@ describe 'Profile account page', feature: true do describe 'when signup is enabled' do before do - allow_any_instance_of(ApplicationSetting). - to receive(:signup_enabled?).and_return(true) + stub_application_setting(signup_enabled: true) visit profile_account_path end @@ -24,8 +23,7 @@ describe 'Profile account page', feature: true do describe 'when signup is disabled' do before do - allow_any_instance_of(ApplicationSetting). - to receive(:signup_enabled?).and_return(false) + stub_application_setting(signup_enabled: false) visit profile_account_path end diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index 03e78c533db..9bc6145dda4 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'Profile > Preferences' do +describe 'Profile > Preferences', feature: true do let(:user) { create(:user) } before do diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index f8eea70ec4a..c8d44efdc8b 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Project' do +feature 'Project', feature: true do describe 'description' do let(:project) { create(:project) } let(:path) { namespace_project_path(project.namespace, project) } diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index 8f7a9606262..bcabc2d53ac 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -45,8 +45,8 @@ describe "Profile access", feature: true do it { is_expected.to be_denied_for :visitor } end - describe "GET /profile/history" do - subject { history_profile_path } + describe "GET /profile/audit_log" do + subject { audit_log_profile_path } it { is_expected.to be_allowed_for @u1 } it { is_expected.to be_allowed_for :admin } diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 8d1bfd25223..4649e58cb1a 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -138,6 +138,18 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for :visitor } end + describe "GET /:project_path/issues/:id/edit" do + let(:issue) { create(:issue, project: project) } + subject { edit_namespace_project_issue_path(project.namespace, project, issue) } + + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 9021ff33186..2866bf0355b 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -138,6 +138,18 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for :visitor } end + describe "GET /:project_path/issues/:id/edit" do + let(:issue) { create(:issue, project: project) } + subject { edit_namespace_project_issue_path(project.namespace, project, issue) } + + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 6ec190ed777..554c96bcdc5 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -143,6 +143,18 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :visitor } end + describe "GET /:project_path/issues/:id/edit" do + let(:issue) { create(:issue, project: project) } + subject { edit_namespace_project_issue_path(project.namespace, project, issue) } + + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } + end + describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 2099fc40cca..fca3c77fc64 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Task Lists' do +feature 'Task Lists', feature: true do include Warden::Test::Helpers let(:project) { create(:project) } @@ -52,7 +52,7 @@ feature 'Task Lists' do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") expect(page).to have_selector("#{container} .js-task-list-field") - expect(page).to have_selector('form.js-issue-update') + expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end @@ -128,7 +128,7 @@ feature 'Task Lists' do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") expect(page).to have_selector("#{container} .js-task-list-field") - expect(page).to have_selector('form.js-merge-request-update') + expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index a4c3dfe9205..efcb8a31abe 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Users' do +feature 'Users', feature: true do scenario 'GET /users/sign_in creates a new user account' do visit new_user_session_path fill_in 'user_name', with: 'Name Surname' diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 8fd3d8f407b..742420f550e 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -2,157 +2,177 @@ require 'spec_helper' describe ApplicationHelper do describe 'current_controller?' do - before do - allow(controller).to receive(:controller_name).and_return('foo') - end - it 'returns true when controller matches argument' do - expect(current_controller?(:foo)).to be_truthy + stub_controller_name('foo') + + expect(helper.current_controller?(:foo)).to eq true end it 'returns false when controller does not match argument' do - expect(current_controller?(:bar)).not_to be_truthy + stub_controller_name('foo') + + expect(helper.current_controller?(:bar)).to eq false end - it 'should take any number of arguments' do - expect(current_controller?(:baz, :bar)).not_to be_truthy - expect(current_controller?(:baz, :bar, :foo)).to be_truthy + it 'takes any number of arguments' do + stub_controller_name('foo') + + expect(helper.current_controller?(:baz, :bar)).to eq false + expect(helper.current_controller?(:baz, :bar, :foo)).to eq true + end + + def stub_controller_name(value) + allow(helper.controller).to receive(:controller_name).and_return(value) end end describe 'current_action?' do - before do - allow(self).to receive(:action_name).and_return('foo') + it 'returns true when action matches' do + stub_action_name('foo') + + expect(helper.current_action?(:foo)).to eq true end - it 'returns true when action matches argument' do - expect(current_action?(:foo)).to be_truthy + it 'returns false when action does not match' do + stub_action_name('foo') + + expect(helper.current_action?(:bar)).to eq false end - it 'returns false when action does not match argument' do - expect(current_action?(:bar)).not_to be_truthy + it 'takes any number of arguments' do + stub_action_name('foo') + + expect(helper.current_action?(:baz, :bar)).to eq false + expect(helper.current_action?(:baz, :bar, :foo)).to eq true end - it 'should take any number of arguments' do - expect(current_action?(:baz, :bar)).not_to be_truthy - expect(current_action?(:baz, :bar, :foo)).to be_truthy + def stub_action_name(value) + allow(helper).to receive(:action_name).and_return(value) end end describe 'project_icon' do - avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') + let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') } it 'should return an url for the avatar' do - project = create(:project) - project.avatar = File.open(avatar_file_path) - project.save! - avatar_url = "http://localhost/uploads/project/avatar/#{ project.id }/banana_sample.gif" - expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to eq( - "<img alt=\"Banana sample\" src=\"#{avatar_url}\" />" - ) + project = create(:project, avatar: File.open(avatar_file_path)) + + avatar_url = "http://localhost/uploads/project/avatar/#{project.id}/banana_sample.gif" + expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s). + to eq "<img alt=\"Banana sample\" src=\"#{avatar_url}\" />" end it 'should give uploaded icon when present' do project = create(:project) - project.save! allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true) avatar_url = 'http://localhost' + namespace_project_avatar_path(project.namespace, project) - expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to match( + expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to match( image_tag(avatar_url)) end end describe 'avatar_icon' do - avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') + let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') } it 'should return an url for the avatar' do - user = create(:user) - user.avatar = File.open(avatar_file_path) - user.save! - expect(avatar_icon(user.email).to_s). - to match("/uploads/user/avatar/#{ user.id }/banana_sample.gif") + user = create(:user, avatar: File.open(avatar_file_path)) + + expect(helper.avatar_icon(user.email).to_s). + to match("/uploads/user/avatar/#{user.id}/banana_sample.gif") end it 'should return an url for the avatar with relative url' do - allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab') - allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url)) + stub_config_setting(relative_url_root: '/gitlab') + # Must be stubbed after the stub above, and separately + stub_config_setting(url: Settings.send(:build_gitlab_url)) - user = create(:user) - user.avatar = File.open(avatar_file_path) - user.save! - expect(avatar_icon(user.email).to_s). - to match("/gitlab/uploads/user/avatar/#{ user.id }/banana_sample.gif") + user = create(:user, avatar: File.open(avatar_file_path)) + + expect(helper.avatar_icon(user.email).to_s). + to match("/gitlab/uploads/user/avatar/#{user.id}/banana_sample.gif") end - it 'should call gravatar_icon when no avatar is present' do - user = create(:user, email: 'test@example.com') - user.save! - expect(avatar_icon(user.email).to_s).to eq('http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=40&d=identicon') + it 'should call gravatar_icon when no User exists with the given email' do + expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20) + + helper.avatar_icon('foo@example.com', 20) end end describe 'gravatar_icon' do let(:user_email) { 'user@email.com' } - it 'should return a generic avatar path when Gravatar is disabled' do - allow_any_instance_of(ApplicationSetting).to receive(:gravatar_enabled?).and_return(false) - expect(gravatar_icon(user_email)).to match('no_avatar.png') - end + context 'with Gravatar disabled' do + before do + stub_application_setting(gravatar_enabled?: false) + end - it 'should return a generic avatar path when email is blank' do - expect(gravatar_icon('')).to match('no_avatar.png') + it 'returns a generic avatar' do + expect(helper.gravatar_icon(user_email)).to match('no_avatar.png') + end end - it 'should return default gravatar url' do - allow(Gitlab.config.gitlab).to receive(:https).and_return(false) - url = 'http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118' - expect(gravatar_icon(user_email)).to match(url) - end + context 'with Gravatar enabled' do + before do + stub_application_setting(gravatar_enabled?: true) + end - it 'should use SSL when appropriate' do - allow(Gitlab.config.gitlab).to receive(:https).and_return(true) - expect(gravatar_icon(user_email)).to match('https://secure.gravatar.com') - end + it 'returns a generic avatar when email is blank' do + expect(helper.gravatar_icon('')).to match('no_avatar.png') + end - it 'should return custom gravatar path when gravatar_url is set' do - allow(self).to receive(:request).and_return(double(:ssl? => false)) - allow(Gitlab.config.gravatar). - to receive(:plain_url). - and_return('http://example.local/?s=%{size}&hash=%{hash}') - url = 'http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118' - expect(gravatar_icon(user_email, 20)).to eq(url) - end + it 'returns a valid Gravatar URL' do + stub_config_setting(https: false) - it 'should accept a custom size' do - allow(self).to receive(:request).and_return(double(:ssl? => false)) - expect(gravatar_icon(user_email, 64)).to match(/\?s=64/) - end + expect(helper.gravatar_icon(user_email)). + to match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') + end - it 'should use default size when size is wrong' do - allow(self).to receive(:request).and_return(double(:ssl? => false)) - expect(gravatar_icon(user_email, nil)).to match(/\?s=40/) - end + it 'uses HTTPs when configured' do + stub_config_setting(https: true) + + expect(helper.gravatar_icon(user_email)). + to match('https://secure.gravatar.com') + end + + it 'should return custom gravatar path when gravatar_url is set' do + stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}') - it 'should be case insensitive' do - allow(self).to receive(:request).and_return(double(:ssl? => false)) - expect(gravatar_icon(user_email)). - to eq(gravatar_icon(user_email.upcase + ' ')) + expect(gravatar_icon(user_email, 20)). + to eq('http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118') + end + + it 'accepts a custom size argument' do + expect(helper.gravatar_icon(user_email, 64)).to include '?s=64' + end + + it 'defaults size to 40 when given an invalid size' do + expect(helper.gravatar_icon(user_email, nil)).to include '?s=40' + end + + it 'ignores case and surrounding whitespace' do + normal = helper.gravatar_icon('foo@example.com') + upcase = helper.gravatar_icon(' FOO@EXAMPLE.COM ') + + expect(normal).to eq upcase + end end end describe 'grouped_options_refs' do - # Override Rails' grouped_options_for_select helper since HTML is harder to work with - def grouped_options_for_select(options, *args) - options - end - - let(:options) { grouped_options_refs } + let(:options) { helper.grouped_options_refs } + let(:project) { create(:project) } before do - # Must be an instance variable - @project = create(:project) + assign(:project, project) + + # Override Rails' grouped_options_for_select helper to just return the + # first argument (`options`), since it's easier to work with than the + # generated HTML. + allow(helper).to receive(:grouped_options_for_select). + and_wrap_original { |_, *args| args.first } end it 'includes a list of branch names' do @@ -167,15 +187,16 @@ describe ApplicationHelper do it 'includes a specific commit ref if defined' do # Must be an instance variable - @ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8' + ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8' + assign(:ref, ref) expect(options[2][0]).to eq('Commit') - expect(options[2][1]).to eq([@ref]) + expect(options[2][1]).to eq([ref]) end it 'sorts tags in a natural order' do # Stub repository.tag_names to make sure we get some valid testing data - expect(@project.repository).to receive(:tag_names). + expect(project.repository).to receive(:tag_names). and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿', 'v1.0.9a', 'v2.0-rc1', 'v2.0rc2']) @@ -189,17 +210,17 @@ describe ApplicationHelper do let(:a_tag) { '<a href="#">Foo</a>' } it 'allows the a tag' do - expect(simple_sanitize(a_tag)).to eq(a_tag) + expect(helper.simple_sanitize(a_tag)).to eq(a_tag) end it 'allows the span tag' do input = '<span class="foo">Bar</span>' - expect(simple_sanitize(input)).to eq(input) + expect(helper.simple_sanitize(input)).to eq(input) end it 'disallows other tags' do input = "<strike><b>#{a_tag}</b></strike>" - expect(simple_sanitize(input)).to eq(a_tag) + expect(helper.simple_sanitize(input)).to eq(a_tag) end end @@ -207,7 +228,7 @@ describe ApplicationHelper do def element(*arguments) Time.zone = 'UTC' time = Time.zone.parse('2015-07-02 08:00') - element = time_ago_with_tooltip(time, *arguments) + element = helper.time_ago_with_tooltip(time, *arguments) Nokogiri::HTML::DocumentFragment.parse(element).first_element_child end @@ -257,21 +278,21 @@ describe ApplicationHelper do it 'should preserve encoding' do expect(content.encoding.name).to eq('UTF-8') - expect(render_markup('foo.rst', content).encoding.name).to eq('UTF-8') + expect(helper.render_markup('foo.rst', content).encoding.name).to eq('UTF-8') end it "should delegate to #markdown when file name corresponds to Markdown" do - expect(self).to receive(:gitlab_markdown?).with('foo.md').and_return(true) - expect(self).to receive(:markdown).and_return('NOEL') + expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true) + expect(helper).to receive(:markdown).and_return('NOEL') - expect(render_markup('foo.md', content)).to eq('NOEL') + expect(helper.render_markup('foo.md', content)).to eq('NOEL') end it "should delegate to #asciidoc when file name corresponds to AsciiDoc" do - expect(self).to receive(:asciidoc?).with('foo.adoc').and_return(true) - expect(self).to receive(:asciidoc).and_return('NOEL') + expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true) + expect(helper).to receive(:asciidoc).and_return('NOEL') - expect(render_markup('foo.adoc', content)).to eq('NOEL') + expect(helper.render_markup('foo.adoc', content)).to eq('NOEL') end end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index bbb434638ce..a42ccb9b501 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -133,4 +133,11 @@ describe GitlabMarkdownHelper do helper.render_wiki_content(@wiki) end end + + describe 'random_markdown_tip' do + it 'returns a random Markdown tip' do + stub_const("#{described_class}::MARKDOWN_TIPS", ['Random tip']) + expect(random_markdown_tip).to eq 'Random tip' + end + end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 0f78725e3d9..beb9b4e438e 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -8,4 +8,48 @@ describe ProjectsHelper do expect(project_status_css_class("finished")).to eq("success") end end + + describe "can_change_visibility_level?" do + let(:project) { create(:project) } + + let(:fork_project) do + fork_project = create(:forked_project_with_submodules) + fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) + fork_project.save + + fork_project + end + + let(:user) { create(:user) } + + it "returns false if there are no approipriate permissions" do + allow(helper).to receive(:can?) { false } + + expect(helper.can_change_visibility_level?(project, user)).to be_falsey + end + + it "returns true if there are permissions and it is not fork" do + allow(helper).to receive(:can?) { true } + + expect(helper.can_change_visibility_level?(project, user)).to be_truthy + end + + context "forks" do + it "returns false if there are permissions and origin project is PRIVATE" do + allow(helper).to receive(:can?) { true } + + project.update visibility_level: Gitlab::VisibilityLevel::PRIVATE + + expect(helper.can_change_visibility_level?(fork_project, user)).to be_falsey + end + + it "returns true if there are permissions and origin project is INTERNAL" do + allow(helper).to receive(:can?) { true } + + project.update visibility_level: Gitlab::VisibilityLevel::INTERNAL + + expect(helper.can_change_visibility_level?(fork_project, user)).to be_truthy + end + end + end end diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index a7abf9d3839..10121759132 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -115,7 +115,7 @@ describe SubmoduleHelper do end context 'submodules with relative links' do - let(:group) { create(:group) } + let(:group) { create(:group, name: "Master Project", path: "master-project") } let(:project) { create(:project, group: group) } let(:commit_id) { sample_commit[:id] } diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb index 3840e64981f..c4f7693329c 100644 --- a/spec/helpers/visibility_level_helper_spec.rb +++ b/spec/helpers/visibility_level_helper_spec.rb @@ -72,4 +72,43 @@ describe VisibilityLevelHelper do end end end + + describe "skip_level?" do + describe "forks" do + let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + let(:fork_project) { create(:forked_project_with_submodules) } + + before do + fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) + fork_project.save + end + + it "skips levels" do + expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy + expect(skip_level?(fork_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey + expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey + end + end + + describe "non-forked project" do + let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + + it "skips levels" do + expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey + expect(skip_level?(project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey + expect(skip_level?(project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey + end + end + + describe "Snippet" do + let(:snippet) { create(:snippet, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + + it "skips levels" do + expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey + expect(skip_level?(snippet, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey + expect(skip_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey + end + end + + end end diff --git a/spec/javascripts/behaviors/requires_input_spec.js.coffee b/spec/javascripts/behaviors/requires_input_spec.js.coffee new file mode 100644 index 00000000000..61a17632173 --- /dev/null +++ b/spec/javascripts/behaviors/requires_input_spec.js.coffee @@ -0,0 +1,49 @@ +#= require behaviors/requires_input + +describe 'requiresInput', -> + fixture.preload('behaviors/requires_input.html') + + beforeEach -> + fixture.load('behaviors/requires_input.html') + + it 'disables submit when any field is required', -> + $('.js-requires-input').requiresInput() + + expect($('.submit')).toBeDisabled() + + it 'enables submit when no field is required', -> + $('*[required=required]').removeAttr('required') + + $('.js-requires-input').requiresInput() + + expect($('.submit')).not.toBeDisabled() + + it 'enables submit when all required fields are pre-filled', -> + $('*[required=required]').remove() + + $('.js-requires-input').requiresInput() + + expect($('.submit')).not.toBeDisabled() + + it 'enables submit when all required fields receive input', -> + $('.js-requires-input').requiresInput() + + $('#required1').val('input1').change() + expect($('.submit')).toBeDisabled() + + $('#optional1').val('input1').change() + expect($('.submit')).toBeDisabled() + + $('#required2').val('input2').change() + $('#required3').val('input3').change() + $('#required4').val('input4').change() + $('#required5').val('1').change() + + expect($('.submit')).not.toBeDisabled() + + it 'is called on page:load event', -> + spy = spyOn($.fn, 'requiresInput') + + $(document).trigger('page:load') + + expect(spy).toHaveBeenCalled() diff --git a/spec/javascripts/fixtures/behaviors/requires_input.html.haml b/spec/javascripts/fixtures/behaviors/requires_input.html.haml new file mode 100644 index 00000000000..c3f905e912e --- /dev/null +++ b/spec/javascripts/fixtures/behaviors/requires_input.html.haml @@ -0,0 +1,18 @@ +%form.js-requires-input + %input{type: 'text', id: 'required1', required: 'required'} + %input{type: 'text', id: 'required2', required: 'required'} + %input{type: 'text', id: 'required3', required: 'required', value: 'Pre-filled'} + %input{type: 'text', id: 'optional1'} + + %textarea{id: 'required4', required: 'required'} + %textarea{id: 'optional2'} + + %select{id: 'required5', required: 'required'} + %option Zero + %option{value: '1'} One + %select{id: 'optional3', required: 'required'} + %option Zero + %option{value: '1'} One + + %button.submit{type: 'submit', value: 'Submit'} + %input.submit{type: 'submit', value: 'Submit'} diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml index db5abe0cae3..7e8b2a64351 100644 --- a/spec/javascripts/fixtures/issues_show.html.haml +++ b/spec/javascripts/fixtures/issues_show.html.haml @@ -10,4 +10,4 @@ %textarea.js-task-list-field \- [ ] Task List Item -%form.js-issue-update{action: '/foo'} +%form.js-issuable-update{action: '/foo'} diff --git a/spec/javascripts/fixtures/merge_requests_show.html.haml b/spec/javascripts/fixtures/merge_requests_show.html.haml index c4329b8f94a..f0c622935f8 100644 --- a/spec/javascripts/fixtures/merge_requests_show.html.haml +++ b/spec/javascripts/fixtures/merge_requests_show.html.haml @@ -10,4 +10,4 @@ %textarea.js-task-list-field \- [ ] Task List Item -%form.js-merge-request-update{action: '/foo'} +%form.js-issuable-update{action: '/foo'} diff --git a/spec/javascripts/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee index a4735af0343..22ebc7039d1 100644 --- a/spec/javascripts/merge_request_spec.js.coffee +++ b/spec/javascripts/merge_request_spec.js.coffee @@ -1,7 +1,5 @@ #= require merge_request -window.disableButtonIfEmptyField = -> null - describe 'MergeRequest', -> describe 'task lists', -> fixture.preload('merge_requests_show.html') diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index f077c80d478..4439775f612 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -10,7 +10,8 @@ describe ExtractsPath do before do @project = project - repo = double(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0']) + repo = double(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0', + 'release/app', 'release/app/v1.0.0']) allow(project).to receive(:repository).and_return(repo) allow(project).to receive(:path_with_namespace). and_return('gitlab/gitlab-ci') @@ -54,11 +55,17 @@ describe ExtractsPath do it "falls back to a primitive split for an invalid ref" do expect(extract_ref('stable')).to eq(['stable', '']) end + + it "extracts the longest matching ref" do + expect(extract_ref('release/app/v1.0.0/README.md')).to eq( + ['release/app/v1.0.0', 'README.md']) + end end context "with a path" do it "extracts a valid branch" do - expect(extract_ref('foo/bar/baz/CHANGELOG')).to eq(['foo/bar/baz', 'CHANGELOG']) + expect(extract_ref('foo/bar/baz/CHANGELOG')).to eq( + ['foo/bar/baz', 'CHANGELOG']) end it "extracts a valid tag" do diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 5c72cfe1d6a..ee912bf12a2 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -43,7 +43,7 @@ describe ProjectMember do it { expect(@project_2.users).to include(@user_1) } it { expect(@project_2.users).to include(@user_2) } - it { expect(@abilities.allowed?(@user_1, :write_project, @project_2)).to be_truthy } + it { expect(@abilities.allowed?(@user_1, :create_project, @project_2)).to be_truthy } it { expect(@abilities.allowed?(@user_2, :read_project, @project_2)).to be_truthy } end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 9037992bb08..eba33dd510f 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -172,9 +172,9 @@ describe Note do @p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER) end - it { expect(@abilities.allowed?(@u1, :write_note, @p1)).to be_falsey } - it { expect(@abilities.allowed?(@u2, :write_note, @p1)).to be_truthy } - it { expect(@abilities.allowed?(@u3, :write_note, @p1)).to be_falsey } + it { expect(@abilities.allowed?(@u1, :create_note, @p1)).to be_falsey } + it { expect(@abilities.allowed?(@u2, :create_note, @p1)).to be_truthy } + it { expect(@abilities.allowed?(@u3, :create_note, @p1)).to be_falsey } end describe 'admin' do diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index 37690434ec8..7d483a44c53 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -38,22 +38,6 @@ describe IrkerService do let(:_recipients) { nil } it { should validate_presence_of :recipients } end - - context 'too many recipients' do - let(:_recipients) { 'a b c d' } - it 'should add an error if there is too many recipients' do - subject.send :check_recipients_count - expect(subject.errors).not_to be_blank - end - end - - context '3 recipients' do - let(:_recipients) { 'a b c' } - it 'should not add an error if there is 3 recipients' do - subject.send :check_recipients_count - expect(subject.errors).to be_blank - end - end end describe 'Execute' do @@ -62,7 +46,7 @@ describe IrkerService do let(:project) { create(:project) } let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } - let(:recipients) { '#commits' } + let(:recipients) { '#commits irc://test.net/#test ftp://bad' } let(:colorize_messages) { '1' } before do @@ -71,17 +55,12 @@ describe IrkerService do project: project, project_id: project.id, service_hook: true, - properties: { - 'recipients' => recipients, - 'colorize_messages' => colorize_messages - } - ) - irker.settings = { - server_ip: 'localhost', + server_host: 'localhost', server_port: 6659, - max_channels: 3, - default_irc_uri: 'irc://chat.freenode.net/' - } + default_irc_uri: 'irc://chat.freenode.net/', + recipients: recipients, + colorize_messages: colorize_messages) + irker.valid? @irker_server = TCPServer.new 'localhost', 6659 end @@ -97,11 +76,8 @@ describe IrkerService do conn.readlines.each do |line| msg = JSON.load(line.chomp("\n")) expect(msg.keys).to match_array(['to', 'privmsg']) - if msg['to'].is_a?(String) - expect(msg['to']).to eq 'irc://chat.freenode.net/#commits' - else - expect(msg['to']).to match_array(['irc://chat.freenode.net/#commits']) - end + expect(msg['to']).to match_array(["irc://chat.freenode.net/#commits", + "irc://test.net/#test"]) end conn.close end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f41e5a97ca3..d25351b0f0e 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -25,4 +25,50 @@ describe Repository do it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } end + + describe :blob_at do + context 'blank sha' do + subject { repository.blob_at(Gitlab::Git::BLANK_SHA, '.gitignore') } + + it { is_expected.to be_nil } + end + end + + describe :can_be_merged? do + context 'mergeable branches' do + subject { repository.can_be_merged?('feature', 'master') } + + it { is_expected.to be_truthy } + end + + context 'non-mergeable branches' do + subject { repository.can_be_merged?('feature_conflict', 'feature') } + + it { is_expected.to be_falsey } + end + end + + describe "search_files" do + let(:results) { repository.search_files('feature', 'master') } + subject { results } + + it { is_expected.to be_an Array } + + describe 'result' do + subject { results.first } + + it { is_expected.to be_an String } + it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") } + end + + describe 'parsing result' do + subject { repository.parse_search_result(results.first) } + + it { is_expected.to be_an OpenStruct } + it { expect(subject.filename).to eq('CHANGELOG') } + it { expect(subject.ref).to eq('master') } + it { expect(subject.startline).to eq(186) } + it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") } + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6d2423ae27a..16902317f10 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -217,6 +217,24 @@ describe User do end end + describe '#disable_two_factor!' do + it 'clears all 2FA-related fields' do + user = create(:user, :two_factor) + + expect(user).to be_two_factor_enabled + expect(user.encrypted_otp_secret).not_to be_nil + expect(user.otp_backup_codes).not_to be_nil + + user.disable_two_factor! + + expect(user).not_to be_two_factor_enabled + expect(user.encrypted_otp_secret).to be_nil + expect(user.encrypted_otp_secret_iv).to be_nil + expect(user.encrypted_otp_secret_salt).to be_nil + expect(user.otp_backup_codes).to be_nil + end + end + describe 'projects' do before do @user = create :user diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e9ff832603f..4178bb2e836 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -220,9 +220,7 @@ describe API::API, api: true do context 'when a visibility level is restricted' do before do @project = attributes_for(:project, { public: true }) - allow_any_instance_of(ApplicationSetting).to( - receive(:restricted_visibility_levels).and_return([20]) - ) + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) end it 'should not allow a non-admin to use a restricted visibility level' do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb new file mode 100644 index 00000000000..c815a8e1d73 --- /dev/null +++ b/spec/requests/api/settings_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe API::API, 'Settings', api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:admin) { create(:admin) } + + + describe "GET /application/settings" do + it "should return application settings" do + get api("/application/settings", admin) + expect(response.status).to eq(200) + expect(json_response).to be_an Hash + expect(json_response['default_projects_limit']).to eq(42) + expect(json_response['signin_enabled']).to be_truthy + end + end + + describe "PUT /application/settings" do + it "should update application settings" do + put api("/application/settings", admin), + default_projects_limit: 3, signin_enabled: false + expect(response.status).to eq(200) + expect(json_response['default_projects_limit']).to eq(3) + expect(json_response['signin_enabled']).to be_falsey + end + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 1a29058f3f1..c4dd1f76cf2 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -527,4 +527,55 @@ describe API::API, api: true do expect(response.status).to eq(401) end end + + describe 'PUT /user/:id/block' do + before { admin } + it 'should block existing user' do + put api("/users/#{user.id}/block", admin) + expect(response.status).to eq(200) + expect(user.reload.state).to eq('blocked') + end + + it 'should not be available for non admin users' do + put api("/users/#{user.id}/block", user) + expect(response.status).to eq(403) + expect(user.reload.state).to eq('active') + end + + it 'should return a 404 error if user id not found' do + put api('/users/9999/block', admin) + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 User Not Found') + end + end + + describe 'PUT /user/:id/unblock' do + before { admin } + it 'should unblock existing user' do + put api("/users/#{user.id}/unblock", admin) + expect(response.status).to eq(200) + expect(user.reload.state).to eq('active') + end + + it 'should unblock a blocked user' do + put api("/users/#{user.id}/block", admin) + expect(response.status).to eq(200) + expect(user.reload.state).to eq('blocked') + put api("/users/#{user.id}/unblock", admin) + expect(response.status).to eq(200) + expect(user.reload.state).to eq('active') + end + + it 'should not be available for non admin users' do + put api("/users/#{user.id}/unblock", user) + expect(response.status).to eq(403) + expect(user.reload.state).to eq('active') + end + + it 'should return a 404 error if user id not found' do + put api('/users/9999/block', admin) + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 User Not Found') + end + end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 0fda6202a11..dd045826692 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -108,8 +108,8 @@ describe ProfilesController, "routing" do expect(get("/profile/account")).to route_to('profiles/accounts#show') end - it "to #history" do - expect(get("/profile/history")).to route_to('profiles#history') + it "to #audit_log" do + expect(get("/profile/audit_log")).to route_to('profiles#audit_log') end it "to #reset_private_token" do diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb index 08689c15ca8..8edabe9450b 100644 --- a/spec/services/create_snippet_service_spec.rb +++ b/spec/services/create_snippet_service_spec.rb @@ -14,11 +14,7 @@ describe CreateSnippetService do context 'When public visibility is restricted' do before do - allow_any_instance_of(ApplicationSetting).to( - receive(:restricted_visibility_levels).and_return( - [Gitlab::VisibilityLevel::PUBLIC] - ) - ) + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 3373b97bfd4..62cef9db534 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -124,9 +124,7 @@ describe GitPushService do end it "when pushing a branch for the first time with default branch protection disabled" do - allow(ApplicationSetting.current_application_settings). - to receive(:default_branch_protection). - and_return(Gitlab::Access::PROTECTION_NONE) + stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE) expect(project).to receive(:execute_hooks) expect(project.default_branch).to eq("master") @@ -135,9 +133,7 @@ describe GitPushService do end it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do - allow(ApplicationSetting.current_application_settings). - to receive(:default_branch_protection). - and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH) + stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH) expect(project).to receive(:execute_hooks) expect(project.default_branch).to eq("master") diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 337dae592dd..97b206c9854 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -58,9 +58,7 @@ describe Projects::CreateService do context 'restricted visibility level' do before do - allow_any_instance_of(ApplicationSetting).to( - receive(:restricted_visibility_levels).and_return([20]) - ) + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) @opts.merge!( visibility_level: Gitlab::VisibilityLevel.options['Public'] diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 79acba78bda..bb7da33b12e 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -8,7 +8,7 @@ describe Projects::TransferService do context 'namespace -> namespace' do before do group.add_owner(user) - @result = transfer_project(project, user, new_namespace_id: group.id) + @result = transfer_project(project, user, group) end it { expect(@result).to be_truthy } @@ -17,7 +17,7 @@ describe Projects::TransferService do context 'namespace -> no namespace' do before do - @result = transfer_project(project, user, new_namespace_id: nil) + @result = transfer_project(project, user, nil) end it { expect(@result).to eq false } @@ -26,14 +26,14 @@ describe Projects::TransferService do context 'namespace -> not allowed namespace' do before do - @result = transfer_project(project, user, new_namespace_id: group.id) + @result = transfer_project(project, user, group) end it { expect(@result).to eq false } it { expect(project.namespace).to eq(user.namespace) } end - def transfer_project(project, user, params) - Projects::TransferService.new(project, user, params).execute + def transfer_project(project, user, new_namespace) + Projects::TransferService.new(project, user).execute(new_namespace) end end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 0dd6980a44f..b347fa15f87 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -47,9 +47,7 @@ describe Projects::UpdateService do context 'respect configured visibility restrictions setting' do before(:each) do - allow_any_instance_of(ApplicationSetting).to( - receive(:restricted_visibility_levels).and_return([20]) - ) + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) end context 'should be private when updated to private' do diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb index 841ef9bfed1..d7c516e3934 100644 --- a/spec/services/update_snippet_service_spec.rb +++ b/spec/services/update_snippet_service_spec.rb @@ -14,11 +14,7 @@ describe UpdateSnippetService do context 'When public visibility is restricted' do before do - allow_any_instance_of(ApplicationSetting).to( - receive(:restricted_visibility_levels).and_return( - [Gitlab::VisibilityLevel::PUBLIC] - ) - ) + stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) @snippet = create_snippet(@project, @user, @opts) @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 666d56079d7..682a8863bad 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,12 +13,12 @@ RSpec.configure do |config| config.use_instantiated_fixtures = false config.mock_with :rspec - config.include LoginHelpers, type: :feature - config.include LoginHelpers, type: :request - config.include FactoryGirl::Syntax::Methods config.include Devise::TestHelpers, type: :controller - + config.include LoginHelpers, type: :feature + config.include LoginHelpers, type: :request + config.include StubConfiguration config.include TestEnv + config.infer_spec_type_from_file_location! config.raise_errors_for_deprecations! diff --git a/spec/support/factory_girl.rb b/spec/support/factory_girl.rb new file mode 100644 index 00000000000..eec437fb3aa --- /dev/null +++ b/spec/support/factory_girl.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include FactoryGirl::Syntax::Methods +end diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb new file mode 100644 index 00000000000..e4004ec8f79 --- /dev/null +++ b/spec/support/stub_configuration.rb @@ -0,0 +1,40 @@ +module StubConfiguration + def stub_application_setting(messages) + add_predicates(messages) + + # Stubbing both of these because we're not yet consistent with how we access + # current application settings + allow_any_instance_of(ApplicationSetting).to receive_messages(messages) + allow(Gitlab::CurrentSettings.current_application_settings). + to receive_messages(messages) + end + + def stub_config_setting(messages) + allow(Gitlab.config.gitlab).to receive_messages(messages) + end + + def stub_gravatar_setting(messages) + allow(Gitlab.config.gravatar).to receive_messages(messages) + end + + private + + # Modifies stubbed messages to also stub possible predicate versions + # + # Examples: + # + # add_predicates(foo: true) + # # => {foo: true, foo?: true} + # + # add_predicates(signup_enabled?: false) + # # => {signup_enabled? false} + def add_predicates(messages) + # Only modify keys that aren't already predicates + keys = messages.keys.map(&:to_s).reject { |k| k.end_with?('?') } + + keys.each do |key| + predicate = key + '?' + messages[predicate.to_sym] = messages[key.to_sym] + end + end +end diff --git a/vendor/assets/javascripts/jquery.sticky-kit.min.js b/vendor/assets/javascripts/jquery.sticky-kit.min.js deleted file mode 100644 index e8bb207c5a5..00000000000 --- a/vendor/assets/javascripts/jquery.sticky-kit.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - Sticky-kit v1.1.1 | WTFPL | Leaf Corcoran 2014 | http://leafo.net -*/ -(function(){var k,e;k=this.jQuery||window.jQuery;e=k(window);k.fn.stick_in_parent=function(d){var v,y,n,p,h,C,s,G,q,H;null==d&&(d={});s=d.sticky_class;y=d.inner_scrolling;C=d.recalc_every;h=d.parent;p=d.offset_top;n=d.spacer;v=d.bottoming;null==p&&(p=0);null==h&&(h=void 0);null==y&&(y=!0);null==s&&(s="is_stuck");null==v&&(v=!0);G=function(a,d,q,z,D,t,r,E){var u,F,m,A,c,f,B,w,x,g,b;if(!a.data("sticky_kit")){a.data("sticky_kit",!0);f=a.parent();null!=h&&(f=f.closest(h));if(!f.length)throw"failed to find stick parent"; -u=m=!1;(g=null!=n?n&&a.closest(n):k("<div />"))&&g.css("position",a.css("position"));B=function(){var c,e,l;if(!E&&(c=parseInt(f.css("border-top-width"),10),e=parseInt(f.css("padding-top"),10),d=parseInt(f.css("padding-bottom"),10),q=f.offset().top+c+e,z=f.height(),m&&(u=m=!1,null==n&&(a.insertAfter(g),g.detach()),a.css({position:"",top:"",width:"",bottom:""}).removeClass(s),l=!0),D=a.offset().top-parseInt(a.css("margin-top"),10)-p,t=a.outerHeight(!0),r=a.css("float"),g&&g.css({width:a.outerWidth(!0), -height:t,display:a.css("display"),"vertical-align":a.css("vertical-align"),"float":r}),l))return b()};B();if(t!==z)return A=void 0,c=p,x=C,b=function(){var b,k,l,h;if(!E&&(null!=x&&(--x,0>=x&&(x=C,B())),l=e.scrollTop(),null!=A&&(k=l-A),A=l,m?(v&&(h=l+t+c>z+q,u&&!h&&(u=!1,a.css({position:"fixed",bottom:"",top:c}).trigger("sticky_kit:unbottom"))),l<D&&(m=!1,c=p,null==n&&("left"!==r&&"right"!==r||a.insertAfter(g),g.detach()),b={position:"",width:"",top:""},a.css(b).removeClass(s).trigger("sticky_kit:unstick")), -y&&(b=e.height(),t+p>b&&!u&&(c-=k,c=Math.max(b-t,c),c=Math.min(p,c),m&&a.css({top:c+"px"})))):l>D&&(m=!0,b={position:"fixed",top:c},b.width="border-box"===a.css("box-sizing")?a.outerWidth()+"px":a.width()+"px",a.css(b).addClass(s),null==n&&(a.after(g),"left"!==r&&"right"!==r||g.append(a)),a.trigger("sticky_kit:stick")),m&&v&&(null==h&&(h=l+t+c>z+q),!u&&h)))return u=!0,"static"===f.css("position")&&f.css({position:"relative"}),a.css({position:"absolute",bottom:d,top:"auto"}).trigger("sticky_kit:bottom")}, -w=function(){B();return b()},F=function(){E=!0;e.off("touchmove",b);e.off("scroll",b);e.off("resize",w);k(document.body).off("sticky_kit:recalc",w);a.off("sticky_kit:detach",F);a.removeData("sticky_kit");a.css({position:"",bottom:"",top:"",width:""});f.position("position","");if(m)return null==n&&("left"!==r&&"right"!==r||a.insertAfter(g),g.remove()),a.removeClass(s)},e.on("touchmove",b),e.on("scroll",b),e.on("resize",w),k(document.body).on("sticky_kit:recalc",w),a.on("sticky_kit:detach",F),setTimeout(b, -0)}};q=0;for(H=this.length;q<H;q++)d=this[q],G(k(d));return this}}).call(this); |