diff options
240 files changed, 2575 insertions, 962 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..7e800609e6c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGELOG merge=union
\ No newline at end of file diff --git a/CHANGELOG b/CHANGELOG index 12d6f5830bf..9bb75fdf884 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,4 @@ -Note: The upcoming release contains empty lines to reduce the number of merge conflicts, scroll down to see past releases. - -v 7.8.0 +v 7.8.0 (unreleased) - Replace highlight.js with rouge-fork rugments (Stefan Tatschner) - Make project search case insensitive (Hannes Rosenögger) - Include issue/mr participants in list of recipients for reassign/close/reopen emails @@ -9,62 +7,48 @@ v 7.8.0 - Cleaner UI for web editor - Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger) - Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen) - - - - + - View note image attachments in new tab when clicked instead of downloading them + - Improve sorting logic in UI and API. Explicitly define what sorting method is used by default - Allow more variations for commit messages closing issues (Julien Bianchi and Hannes Rosenögger) - - - - + - Fix overflow at sidebar when have several itens + - Add notes for label changes in issue and merge requests - Show tags in commit view (Hannes Rosenögger) - Only count a user's vote once on a merge request or issue (Michael Clarke) - - - Increate font size when browse source files and diffs - Create new file in empty repository using GitLab UI - - - Ability to clone project using oauth2 token - - - Upgrade Sidekiq gem to version 3.3.0 - Stop git zombie creation during force push check - Show success/error messages for test setting button in services - Added Rubocop for code style checks - Fix commits pagination - - - Async load a branch information at the commit page - Disable blacklist validation for project names - Allow configuring protection of the default branch upon first push (Marco Wessel) - - - - + - Add gitlab.com importer + - Add an ability to login with gitlab.com - Add a commit calendar to the user profile (Hannes Rosenögger) - - - - - - + - Submit comment on command-enter + - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`. + - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" - Fix long broadcast message cut-off on left sidebar (Visay Keo) - Add Project Avatars (Steven Thonus and Hannes Rosenögger) - - - - - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation. - - - - + - Edit group members via API - Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks) - - - - - Add action property to merge request hook (Julien Bianchi) - - - - - - - - + - Remove duplicates from group milestone participants list. - Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger) - - - - - API: Access groups with their path (Julien Bianchi) - Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard) - - - - + - Allow notification email to be set separately from primary email. - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) - - - - + - Don't have Markdown preview fail for long comments/wiki pages. - When test web hook - show error message instead of 500 error page if connection to hook url was reset - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov) - Added persistent collapse button for left side nav bar (Jason Blanchard) + - Prevent losing unsaved comments by automatically restoring them when comment page is loaded again. + - Don't allow page to be scaled on mobile. v 7.7.2 - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d26cf567e36..f3d4d8ea9b6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ If you can, please submit a merge request with the fix or improvements including 1. Fork the project on GitLab Cloud 1. Create a feature branch 1. Write [tests](README.md#run-the-tests) and code -1. Add your changes to the [CHANGELOG](CHANGELOG) insert your line at a [random point](doc/workflow/gitlab_flow.md#do-not-order-commits-with-rebase) in the current version +1. Add your changes to the [CHANGELOG](CHANGELOG) 1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message 1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. Push the commit to your fork diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 8e8299dcc06..35cee72dcbf 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.4.2 +2.4.3 @@ -29,6 +29,7 @@ gem 'omniauth-twitter' gem 'omniauth-github' gem 'omniauth-shibboleth' gem 'omniauth-kerberos' +gem 'omniauth-gitlab' gem 'doorkeeper', '2.1.0' gem "rack-oauth2", "~> 1.0.5" @@ -151,6 +152,9 @@ gem "gemnasium-gitlab-service", "~> 0.2" # Slack integration gem "slack-notifier", "~> 1.0.0" +# Asana integration +gem 'asana', '~> 0.0.6' + # d3 gem "d3_rails", "~> 3.1.4" diff --git a/Gemfile.lock b/Gemfile.lock index 7f115d79de0..aef30046d33 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,6 +23,10 @@ GEM activemodel (= 4.1.1) activesupport (= 4.1.1) arel (~> 5.0.0) + activeresource (4.0.0) + activemodel (~> 4.0) + activesupport (~> 4.0) + rails-observers (~> 0.1.1) activesupport (4.1.1) i18n (~> 0.6, >= 0.6.9) json (~> 1.7, >= 1.7.7) @@ -36,6 +40,8 @@ GEM activerecord (>= 2.3.0) rake (>= 0.8.7) arel (5.0.1.20140414130214) + asana (0.0.6) + activeresource (>= 3.2.3) asciidoctor (0.1.4) ast (2.0.0) astrolabe (1.3.0) @@ -332,6 +338,9 @@ GEM omniauth-github (1.1.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) + omniauth-gitlab (1.0.0) + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.0) omniauth-google-oauth2 (0.2.5) omniauth (> 1.0) omniauth-oauth2 (~> 1.1) @@ -406,6 +415,8 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 4.1.1) sprockets-rails (~> 2.0) + rails-observers (0.1.2) + activemodel (~> 4.0) rails_autolink (1.1.6) rails (> 3.1) railties (4.1.1) @@ -624,6 +635,7 @@ DEPENDENCIES acts-as-taggable-on addressable annotate (~> 2.6.0.beta2) + asana (~> 0.0.6) asciidoctor (= 0.1.4) awesome_print better_errors @@ -689,6 +701,7 @@ DEPENDENCIES octokit (= 3.7.0) omniauth (~> 1.1.3) omniauth-github + omniauth-gitlab omniauth-google-oauth2 omniauth-kerberos omniauth-shibboleth diff --git a/README.md b/README.md index 393909ef7c0..8bfb301d1c7 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,19 @@ - Each project can also have an issue tracker and a wiki - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises - Completely free and open source (MIT Expat license) -- Powered by Ruby on Rails +- Powered by [Ruby on Rails](https://github.com/rails/rails) ## Editions There are two editions of GitLab. -GitLab [Community Edition](https://about.gitlab.com/features/) (CE) is available without any costs under an MIT license. +*GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license. -GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users. +*GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users. To get access to the EE and support please [become a subscriber](https://about.gitlab.com/pricing/). ## Canonical source -- The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. +The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. ## Code status @@ -48,42 +48,45 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a ## Requirements -- Ubuntu/Debian/CentOS/RHEL** +GitLab requires the following software: + +- Ubuntu/Debian/CentOS/RHEL - Ruby (MRI) 2.0 or 2.1 -- git 1.7.10+ -- redis 2.0+ +- Git 1.7.10+ +- Redis 2.0+ - MySQL or PostgreSQL -** More details are in the [requirements doc](doc/install/requirements.md). +Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems. ## Installation -Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options. -Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm). -You can access new installation with the login `root` and password `5iveL!fe`, after login you are required to set a unique password. +The recommended way to install GitLab is using the provided [Omnibus packages](https://about.gitlab.com/downloads/). Compared to a manual installation, this is faster and less error prone. Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager. + +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. ## Third-party applications -There are a lot of applications and API wrappers for GitLab. -Find them [on our website](https://about.gitlab.com/applications/). +There are a lot of [third-party applications integrating with GitLab](https://about.gitlab.com/applications/). These include GUI Git clients, mobile applications and API wrappers for various languages. -## New versions +## GitLab release cycle -Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457). +Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457). ## Upgrading -For updating the the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update). +For updating the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations 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. ## Install a development environment -We recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). -If you do not use the GitLab Development Development kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone. +To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). +If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone. One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file: cp config/unicorn.rb.example.development config/unicorn.rb -Instructions on how to start Gitlab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development). +Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development). ## Documentation diff --git a/app/assets/images/authbuttons/gitlab_32.png b/app/assets/images/authbuttons/gitlab_32.png Binary files differnew file mode 100644 index 00000000000..f3b78cb6efb --- /dev/null +++ b/app/assets/images/authbuttons/gitlab_32.png diff --git a/app/assets/images/authbuttons/gitlab_64.png b/app/assets/images/authbuttons/gitlab_64.png Binary files differnew file mode 100644 index 00000000000..ff2945fe89e --- /dev/null +++ b/app/assets/images/authbuttons/gitlab_64.png diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 4912c534b0e..9c97582e6dd 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -17,6 +17,7 @@ #= require jquery.blockUI #= require jquery.turbolinks #= require turbolinks +#= require autosave #= require bootstrap #= require select2 #= require raphael diff --git a/app/assets/javascripts/autosave.js.coffee b/app/assets/javascripts/autosave.js.coffee new file mode 100644 index 00000000000..3450f4b55f7 --- /dev/null +++ b/app/assets/javascripts/autosave.js.coffee @@ -0,0 +1,33 @@ +class @Autosave + constructor: (field, key) -> + @field = field + + key = key.join("/") if key.join? + @key = "autosave/#{key}" + + @field.data "autosave", this + + @restore() + + @field.on "input", => @save() + + restore: -> + return unless window.localStorage? + + text = window.localStorage.getItem @key + @field.val text if text?.length > 0 + @field.trigger "input" + + save: -> + return unless window.localStorage? + + text = @field.val() + if text?.length > 0 + window.localStorage.setItem @key, text + else + @reset() + + reset: -> + return unless window.localStorage? + + window.localStorage.removeItem @key
\ No newline at end of file diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index abb5bf519ee..d98d5482937 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -50,7 +50,7 @@ class @DropzoneInput preview.text "Nothing to preview." else preview.text "Loading..." - $.get($(this).data("url"), + $.post($(this).data("url"), md_text: mdText ).success (previewData) -> preview.html previewData diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee new file mode 100644 index 00000000000..e0e7771ab20 --- /dev/null +++ b/app/assets/javascripts/importer_status.js.coffee @@ -0,0 +1,35 @@ +class @ImporterStatus + constructor: (@jobs_url, @import_url) -> + this.initStatusPage() + this.setAutoUpdate() + + initStatusPage: -> + $(".js-add-to-import").click (event) => + new_namespace = null + tr = $(event.currentTarget).closest("tr") + id = tr.attr("id").replace("repo_", "") + if tr.find(".import-target input").length > 0 + new_namespace = tr.find(".import-target input").prop("value") + tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) + $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' + + $(".js-import-all").click (event) => + $(".js-add-to-import").each -> + $(this).click() + + setAutoUpdate: -> + setInterval (=> + $.get @jobs_url, (data) => + $.each data, (i, job) => + job_item = $("#project_" + job.id) + status_field = job_item.find(".job-status") + + if job.import_status == 'finished' + job_item.removeClass("active").addClass("success") + status_field.html('<span class="cgreen"><i class="fa fa-check"></i> done</span>') + else if job.import_status == 'started' + status_field.html("<i class='fa fa-spinner fa-spin'></i> started") + else + status_field.html(job.import_status) + + ), 4000
\ No newline at end of file diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index ac1353b8bb6..47c5ecdedf1 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -58,8 +58,9 @@ class @Notes $(document).on "visibilitychange", @visibilityChange @notes_forms = '.js-main-target-form textarea, .js-discussion-note-form textarea' - $(document).on('keypress', @notes_forms, (e)-> - if e.keyCode == 10 || (e.ctrlKey && e.keyCode == 13) + # Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown. + $(document).on('keydown', @notes_forms, (e) -> + if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13) $(@).parents('form').submit() ) @@ -170,6 +171,8 @@ class @Notes form.find(".js-md-write-button").click() form.find(".js-note-text").val("").trigger "input" + form.find(".js-note-text").data("autosave").reset() + ### Called when clicking the "Choose File" button. @@ -220,12 +223,22 @@ class @Notes # setup preview buttons form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left" previewButton = form.find(".js-md-preview-button") - form.find(".js-note-text").on "input", -> + + textarea = form.find(".js-note-text") + + textarea.on "input", -> if $(this).val().trim() isnt "" previewButton.removeClass("turn-off").addClass "turn-on" else previewButton.removeClass("turn-on").addClass "turn-off" + new Autosave textarea, [ + "Note" + form.find("#note_commit_id").val() + form.find("#note_line_code").val() + form.find("#note_noteable_type").val() + form.find("#note_noteable_id").val() + ] # remove notify commit author checkbox for non-commit notes form.find(".js-notify-commit-author").remove() if form.find("#note_noteable_type").val() isnt "Commit" @@ -233,7 +246,6 @@ class @Notes new DropzoneInput(form) form.show() - ### Called in response to the new note form being submitted @@ -407,6 +419,8 @@ class @Notes removeDiscussionNoteForm: (form)-> row = form.closest("tr") + form.find(".js-note-text").data("autosave").reset() + # show the reply button (will only work for replies) form.prev(".js-discussion-reply-button").show() if row.is(".js-temp-notes-holder") diff --git a/app/assets/stylesheets/generic/avatar.scss b/app/assets/stylesheets/generic/avatar.scss index b88cdd83937..700cc7e6947 100644 --- a/app/assets/stylesheets/generic/avatar.scss +++ b/app/assets/stylesheets/generic/avatar.scss @@ -15,7 +15,7 @@ &.s24 { margin-right: 4px; } } - &.avatar-tile { + &.group-avatar, &.project-avatar, &.avatar-tile { @include border-radius(0px); } diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index 1a7e96f1d0c..3db821fdf76 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -333,6 +333,10 @@ table { margin-bottom: 9px; } +.wiki .code { + overflow-x: auto; +} + .footer-links a { margin-right: 15px; } diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/generic/highlight.scss index e1ca86af816..0f8225d6823 100644 --- a/app/assets/stylesheets/generic/highlight.scss +++ b/app/assets/stylesheets/generic/highlight.scss @@ -59,6 +59,7 @@ box-shadow: none; background: $box_bg; padding: 1em; + overflow-x: auto; code { font-family: $monospace_font; diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss index 17c0cd81b93..77d403cc687 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/sections/dashboard.scss @@ -75,9 +75,6 @@ } } } -.project-avatar { - float: left; -} .project-description { overflow: hidden; diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss index 901733ef9ff..3a3644c12b7 100644 --- a/app/assets/stylesheets/sections/login.scss +++ b/app/assets/stylesheets/sections/login.scss @@ -46,6 +46,10 @@ .login-footer { margin-top: 10px; + + p:last-child { + margin-bottom: 0; + } } a.forgot { @@ -88,6 +92,7 @@ .devise-errors { h2 { + margin-top: 0; font-size: 14px; color: #a00; } diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 0a7671e3feb..3bb3779c294 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -32,7 +32,6 @@ .avatar { width: 70px; height: 70px; - @include border-radius(0px); } .identicon { diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index be19139c9b1..c491e5c7550 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -1,7 +1,7 @@ class Admin::DashboardController < Admin::ApplicationController def index - @projects = Project.order("created_at DESC").limit(10) - @users = User.order("created_at DESC").limit(10) - @groups = Group.order("created_at DESC").limit(10) + @projects = Project.limit(10) + @users = User.limit(10) + @groups = Group.limit(10) end end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 8c7d90a5d9f..65dc027c8eb 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -2,7 +2,8 @@ class Admin::GroupsController < Admin::ApplicationController before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update] def index - @groups = Group.order('name ASC') + @groups = Group.all + @groups = @groups.sort(@sort = params[:sort]) @groups = @groups.search(params[:name]) if params[:name].present? @groups = @groups.page(params[:page]).per(20) end diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb new file mode 100644 index 00000000000..e80cabd6e18 --- /dev/null +++ b/app/controllers/admin/services_controller.rb @@ -0,0 +1,51 @@ +class Admin::ServicesController < Admin::ApplicationController + before_filter :service, only: [:edit, :update] + + def index + @services = services_templates + end + + def edit + unless service.present? + redirect_to admin_application_settings_services_path, + alert: "Service is unknown or it doesn't exist" + end + end + + def update + if service.update_attributes(application_services_params[:service]) + redirect_to admin_application_settings_services_path, + notice: 'Application settings saved successfully' + else + render :edit + end + end + + private + + def services_templates + templates = [] + + Service.available_services_names.each do |service_name| + service_template = service_name.concat("_service").camelize.constantize + templates << service_template.where(template: true).first_or_create + end + + templates + end + + def service + @service ||= Service.where(id: params[:id], template: true).first + end + + def application_services_params + params.permit(:id, + service: [ + :title, :token, :type, :active, :api_key, :subdomain, + :room, :recipients, :project_url, :webhook, + :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, + :build_key, :server, :teamcity_url, :build_type, + :description, :issues_url, :new_issue_url, :restrict_to_branch + ]) + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index aea8545d38e..232f30b759d 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -2,16 +2,16 @@ class Admin::UsersController < Admin::ApplicationController before_filter :user, only: [:show, :edit, :update, :destroy] def index - @users = User.filter(params[:filter]) + @users = User.order_name_asc.filter(params[:filter]) @users = @users.search(params[:name]) if params[:name].present? @users = @users.sort(@sort = params[:sort]) - @users = @users.alphabetically.page(params[:page]) + @users = @users.page(params[:page]) end def show @personal_projects = user.personal_projects @joined_projects = user.projects.joined(@user) - @keys = user.keys.order('id DESC') + @keys = user.keys end def new @@ -102,6 +102,9 @@ class Admin::UsersController < Admin::ApplicationController email = user.emails.find(params[:email_id]) email.destroy + user.set_notification_email + user.save if user.notification_email_changed? + respond_to do |format| format.html { redirect_to :back, notice: "Successfully removed email." } format.js { render nothing: true } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 36e13706768..6553027b430 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -254,7 +254,7 @@ class ApplicationController < ActionController::Base end def set_filters_params - params[:sort] ||= 'newest' + params[:sort] ||= 'created_desc' params[:scope] = 'all' if params[:scope].blank? params[:state] = 'opened' if params[:state].blank? @@ -280,7 +280,7 @@ class ApplicationController < ActionController::Base author_id = @filter_params[:author_id] milestone_id = @filter_params[:milestone_id] - @sort = @filter_params[:sort].try(:humanize) + @sort = @filter_params[:sort] @assignees = User.where(id: collection.pluck(:assignee_id)) @authors = User.where(id: collection.pluck(:author_id)) @milestones = Milestone.where(id: collection.pluck(:milestone_id)) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index cd876024ba3..9e59264e418 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -9,7 +9,7 @@ class DashboardController < ApplicationController # If user needs more - point to Dashboard#projects page @projects_limit = 30 - @groups = current_user.authorized_groups.sort_by(&:human_name) + @groups = current_user.authorized_groups.order_name_asc @has_authorized_projects = @projects.count > 0 @projects_count = @projects.count @projects = @projects.limit(@projects_limit) diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index 7937454810d..9671245d3f4 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -5,7 +5,8 @@ class FilesController < ApplicationController if uploader.file_storage? if can?(current_user, :read_project, note.project) - send_file uploader.file.path, disposition: 'attachment' + disposition = uploader.image? ? 'inline' : 'attachment' + send_file uploader.file.path, disposition: disposition else not_found! end diff --git a/app/controllers/github_imports_controller.rb b/app/controllers/github_imports_controller.rb deleted file mode 100644 index b73e3f7ffac..00000000000 --- a/app/controllers/github_imports_controller.rb +++ /dev/null @@ -1,80 +0,0 @@ -class GithubImportsController < ApplicationController - before_filter :github_auth, except: :callback - - rescue_from Octokit::Unauthorized, with: :github_unauthorized - - def callback - token = client.auth_code.get_token(params[:code]).token - current_user.github_access_token = token - current_user.save - redirect_to status_github_import_url - end - - def status - @repos = octo_client.repos - octo_client.orgs.each do |org| - @repos += octo_client.repos(org.login) - end - - @already_added_projects = current_user.created_projects.where(import_type: "github") - already_added_projects_names = @already_added_projects.pluck(:import_source) - - @repos.reject!{|repo| already_added_projects_names.include? repo.full_name} - end - - def jobs - jobs = current_user.created_projects.where(import_type: "github").to_json(only: [:id, :import_status]) - render json: jobs - end - - def create - @repo_id = params[:repo_id].to_i - repo = octo_client.repo(@repo_id) - target_namespace = params[:new_namespace].presence || repo.owner.login - existing_namespace = Namespace.find_by("path = ? OR name = ?", target_namespace, target_namespace) - - if existing_namespace - if existing_namespace.owner == current_user - namespace = existing_namespace - else - @already_been_taken = true - @target_namespace = target_namespace - @project_name = repo.name - render and return - end - else - namespace = Group.create(name: target_namespace, path: target_namespace, owner: current_user) - namespace.add_owner(current_user) - end - - @project = Gitlab::Github::ProjectCreator.new(repo, namespace, current_user).execute - end - - private - - def client - @client ||= Gitlab::Github::Client.new.client - end - - def octo_client - Octokit.auto_paginate = true - @octo_client ||= Octokit::Client.new(access_token: current_user.github_access_token) - end - - def github_auth - if current_user.github_access_token.blank? - go_to_github_for_permissions - end - end - - def go_to_github_for_permissions - redirect_to client.auth_code.authorize_url({ - redirect_uri: callback_github_import_url, - scope: "repo, user, user:email" - }) - end - - def github_unauthorized - go_to_github_for_permissions - end -end diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb new file mode 100644 index 00000000000..4df171dbcfe --- /dev/null +++ b/app/controllers/import/base_controller.rb @@ -0,0 +1,21 @@ +class Import::BaseController < ApplicationController + + private + + def get_or_create_namespace + existing_namespace = Namespace.find_by("path = ? OR name = ?", @target_namespace, @target_namespace) + + if existing_namespace + if existing_namespace.owner == current_user + namespace = existing_namespace + else + @already_been_taken = true + return false + end + else + namespace = Group.create(name: @target_namespace, path: @target_namespace, owner: current_user) + namespace.add_owner(current_user) + namespace + end + end +end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb new file mode 100644 index 00000000000..c869c7c86f3 --- /dev/null +++ b/app/controllers/import/github_controller.rb @@ -0,0 +1,60 @@ +class Import::GithubController < Import::BaseController + before_filter :github_auth, except: :callback + + rescue_from Octokit::Unauthorized, with: :github_unauthorized + + def callback + token = client.get_token(params[:code]) + current_user.github_access_token = token + current_user.save + redirect_to status_import_github_url + end + + def status + @repos = client.repos + client.orgs.each do |org| + @repos += client.repos(org.login) + end + + @already_added_projects = current_user.created_projects.where(import_type: "github") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.reject!{ |repo| already_added_projects_names.include? repo.full_name } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "github").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id].to_i + repo = client.repo(@repo_id) + @target_namespace = params[:new_namespace].presence || repo.owner.login + @project_name = repo.name + + namespace = get_or_create_namespace || (render and return) + + @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user).execute + end + + private + + def client + @client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token) + end + + def github_auth + if current_user.github_access_token.blank? + go_to_github_for_permissions + end + end + + def go_to_github_for_permissions + redirect_to client.authorize_url(callback_import_github_url) + end + + def github_unauthorized + go_to_github_for_permissions + end +end diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb new file mode 100644 index 00000000000..a51ea36aff8 --- /dev/null +++ b/app/controllers/import/gitlab_controller.rb @@ -0,0 +1,57 @@ +class Import::GitlabController < Import::BaseController + before_filter :gitlab_auth, except: :callback + + rescue_from OAuth2::Error, with: :gitlab_unauthorized + + def callback + token = client.get_token(params[:code], callback_import_gitlab_url) + current_user.gitlab_access_token = token + current_user.save + redirect_to status_import_gitlab_url + end + + def status + @repos = client.projects + + @already_added_projects = current_user.created_projects.where(import_type: "gitlab") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.to_a.reject!{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "gitlab").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id].to_i + repo = client.project(@repo_id) + @target_namespace = params[:new_namespace].presence || repo["namespace"]["path"] + @project_name = repo["name"] + + namespace = get_or_create_namespace || (render and return) + + @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user).execute + end + + private + + def client + @client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token) + end + + def gitlab_auth + if current_user.gitlab_access_token.blank? + go_to_gitlab_for_permissions + end + end + + def go_to_gitlab_for_permissions + redirect_to client.authorize_url(callback_import_gitlab_url) + end + + def gitlab_unauthorized + go_to_gitlab_for_permissions + end +end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 442a1cf7518..bb9d65c9ed6 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -44,7 +44,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']) - redirect_to profile_path + redirect_to profile_account_path, notice: 'Authentication method updated' else @user = Gitlab::OAuth::User.new(oauth) @user.save diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb index f3f0e69b83a..4a65c978e5c 100644 --- a/app/controllers/profiles/emails_controller.rb +++ b/app/controllers/profiles/emails_controller.rb @@ -18,6 +18,9 @@ class Profiles::EmailsController < ApplicationController @email = current_user.emails.find(params[:id]) @email.destroy + current_user.set_notification_email + current_user.save if current_user.notification_email_changed? + respond_to do |format| format.html { redirect_to profile_emails_url } format.js { render nothing: true } diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 88414b13564..4e2bd0a9b4b 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -3,7 +3,7 @@ class Profiles::KeysController < ApplicationController skip_before_filter :authenticate_user!, only: [:get_keys] def index - @keys = current_user.keys.order('id DESC') + @keys = current_user.keys end def show diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index 638d1f9789b..433c19189af 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -2,6 +2,7 @@ class Profiles::NotificationsController < ApplicationController layout 'profile' def show + @user = current_user @notification = current_user.notification @project_members = current_user.project_members @group_members = current_user.group_members @@ -11,8 +12,7 @@ class Profiles::NotificationsController < ApplicationController type = params[:notification_type] @saved = if type == 'global' - current_user.notification_level = params[:notification_level] - current_user.save + current_user.update_attributes(user_params) elsif type == 'group' users_group = current_user.group_members.find(params[:notification_id]) users_group.notification_level = params[:notification_level] @@ -22,5 +22,23 @@ class Profiles::NotificationsController < ApplicationController project_member.notification_level = params[:notification_level] project_member.save end + + respond_to do |format| + format.html do + if @saved + flash[:notice] = "Notification settings saved" + else + flash[:alert] = "Failed to save new settings" + end + + redirect_to :back + end + + format.js + end + end + + def user_params + params.require(:user).permit(:notification_email, :notification_level) end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 0a85c36a758..b133afe44b5 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -13,7 +13,7 @@ class Projects::CommitsController < Projects::ApplicationController @commits = @repo.commits(@ref, @path, @limit, @offset) @note_counts = Note.where(commit_id: @commits.map(&:id)). - group(:commit_id).count + group(:commit_id).count respond_to do |format| format.html diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 6c7bde9c5d5..b61fef3b627 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -7,7 +7,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to :js, :html def index - @labels = @project.labels.order_by_name.page(params[:page]).per(20) + @labels = @project.labels.page(params[:page]).per(20) end def new diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 912f9eb5b6b..01be318ede2 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -23,7 +23,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def show @note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)). - group(:commit_id).count + group(:commit_id).count respond_to do |format| format.html diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 5b35cc90413..b3110eacc18 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -47,7 +47,7 @@ class Projects::ServicesController < Projects::ApplicationController :room, :recipients, :project_url, :webhook, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :build_key, :server, :teamcity_url, :build_type, - :description, :issues_url, :new_issue_url + :description, :issues_url, :new_issue_url, :restrict_to_branch ) end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ebe48265c63..462ab3d4749 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -102,7 +102,7 @@ class ProjectsController < ApplicationController note_type = params['type'] note_id = params['type_id'] autocomplete = ::Projects::AutocompleteService.new(@project) - participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id) + participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id) @suggestions = { emojis: autocomplete_emojis, diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 97aa2d9bdb4..38d116a4ee3 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,6 +1,10 @@ class RegistrationsController < Devise::RegistrationsController before_filter :signup_enabled? + def new + redirect_to(new_user_session_path) + end + def destroy current_user.destroy diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index 6fe15b41060..ab252821b52 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -10,18 +10,18 @@ class NotesFinder notes = case target_type when "commit" - project.notes.for_commit_id(target_id).not_inline.fresh + project.notes.for_commit_id(target_id).not_inline when "issue" - project.issues.find(target_id).notes.inc_author.fresh + project.issues.find(target_id).notes.inc_author when "merge_request" - project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh + project.merge_requests.find(target_id).mr_and_commit_notes.inc_author when "snippet", "project_snippet" - project.snippets.find(target_id).notes.fresh + project.snippets.find(target_id).notes else raise 'invalid target_type' end # Use overlapping intervals to avoid worrying about race conditions - notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP) + notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP).fresh end end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 19d688c4bb8..49063491abf 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -7,21 +7,34 @@ module LabelsHelper label_color = label.color || Label::DEFAULT_COLOR text_color = text_color_for_bg(label_color) - content_tag :span, class: 'label color-label', style: "background:#{label_color};color:#{text_color}" do + content_tag :span, class: 'label color-label', style: "background-color:#{label_color};color:#{text_color}" do label.name end end def suggested_colors [ - '#D9534F', - '#F0AD4E', + '#0033CC', '#428BCA', + '#44AD8E', + '#A8D695', '#5CB85C', + '#69D100', + '#004E00', '#34495E', '#7F8C8D', + '#A295D6', + '#5843AD', '#8E44AD', - '#FFECDB' + '#FFECDB', + '#AD4363', + '#D10069', + '#CC0033', + '#FF0000', + '#D9534F', + '#D1D100', + '#F0AD4E', + '#AD8D43' ] end diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index df18db71c84..c7bc9307a58 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -4,7 +4,7 @@ module OauthHelper end def default_providers - [:twitter, :github, :google_oauth2, :ldap] + [:twitter, :github, :gitlab, :google_oauth2, :ldap] end def enabled_oauth_providers @@ -13,7 +13,7 @@ module OauthHelper def enabled_social_providers enabled_oauth_providers.select do |name| - [:twitter, :github, :google_oauth2].include?(name.to_sym) + [:twitter, :gitlab, :github, :google_oauth2].include?(name.to_sym) end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 5cec6ae99d8..36463892ebf 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -253,4 +253,8 @@ module ProjectsHelper def github_import_enabled? enabled_oauth_providers.include?(:github) end + + def gitlab_import_enabled? + enabled_oauth_providers.include?(:gitlab) + end end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 492e065b713..bb12d43f397 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -1,4 +1,19 @@ module SortingHelper + def sort_options_hash + { + sort_value_name => sort_title_name, + sort_value_recently_updated => sort_title_recently_updated, + sort_value_oldest_updated => sort_title_oldest_updated, + sort_value_recently_created => sort_title_recently_created, + sort_value_oldest_created => sort_title_oldest_created, + sort_value_milestone_soon => sort_title_milestone_soon, + sort_value_milestone_later => sort_title_milestone_later, + sort_value_largest_repo => sort_title_largest_repo, + sort_value_recently_signin => sort_title_recently_signin, + sort_value_oldest_signin => sort_title_oldest_signin, + } + end + def sort_title_oldest_updated 'Oldest updated' end @@ -14,4 +29,68 @@ module SortingHelper def sort_title_recently_created 'Recently created' end + + def sort_title_milestone_soon + 'Milestone due soon' + end + + def sort_title_milestone_later + 'Milestone due later' + end + + def sort_title_name + 'Name' + end + + def sort_title_largest_repo + 'Largest repository' + end + + def sort_title_recently_signin + 'Recent sign in' + end + + def sort_title_oldest_signin + 'Oldest sign in' + end + + def sort_value_oldest_updated + 'updated_asc' + end + + def sort_value_recently_updated + 'updated_desc' + end + + def sort_value_oldest_created + 'created_asc' + end + + def sort_value_recently_created + 'created_desc' + end + + def sort_value_milestone_soon + 'milestone_due_asc' + end + + def sort_value_milestone_later + 'milestone_due_desc' + end + + def sort_value_name + 'name_asc' + end + + def sort_value_largest_repo + 'repository_size_desc' + end + + def sort_value_recently_signin + 'recent_sign_in' + end + + def sort_value_oldest_signin + 'oldest_sign_in' + end end diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb index 6d7f8eb4b02..ab5b0765352 100644 --- a/app/mailers/emails/profile.rb +++ b/app/mailers/emails/profile.rb @@ -4,20 +4,20 @@ module Emails @user = User.find(user_id) @target_url = user_url(@user) @token = token - mail(to: @user.email, subject: subject("Account was created for you")) + mail(to: @user.notification_email, subject: subject("Account was created for you")) end def new_email_email(email_id) @email = Email.find(email_id) @user = @email.user - mail(to: @user.email, subject: subject("Email was added to your account")) + mail(to: @user.notification_email, subject: subject("Email was added to your account")) end def new_ssh_key_email(key_id) @key = Key.find(key_id) @user = @key.user @target_url = user_url(@user) - mail(to: @user.email, subject: subject("SSH key was added to your account")) + mail(to: @user.notification_email, subject: subject("SSH key was added to your account")) end end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index d6edfd7059f..dc2ebc969c1 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -12,7 +12,7 @@ module Emails @user = User.find user_id @project = Project.find project_id @target_url = project_url(@project) - mail(to: @user.email, + mail(to: @user.notification_email, subject: subject("Project was moved")) end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 5ae07d771fa..46ead62f75f 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -60,7 +60,7 @@ class Notify < ActionMailer::Base # Returns a String containing the User's email address. def recipient(recipient_id) if recipient = User.find(recipient_id) - recipient.email + recipient.notification_email end end @@ -111,6 +111,7 @@ class Notify < ActionMailer::Base # See: mail_answer_thread def mail_new_thread(model, headers = {}, &block) headers['Message-ID'] = message_id(model) + headers['X-GitLab-Project'] = "#{@project.name} | " if @project mail(headers, &block) end @@ -125,6 +126,7 @@ class Notify < ActionMailer::Base def mail_answer_thread(model, headers = {}, &block) headers['In-Reply-To'] = message_id(model) headers['References'] = message_id(model) + headers['X-GitLab-Project'] = "#{@project.name} | " if @project if (headers[:subject]) headers[:subject].prepend('Re: ') diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 4d0c04bcc3d..05f5e979695 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -14,6 +14,8 @@ # class BroadcastMessage < ActiveRecord::Base + include Sortable + validates :message, presence: true validates :starts_at, presence: true validates :ends_at, presence: true diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index fb038a3cc3f..f5e23e9dc2d 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -29,6 +29,8 @@ module Issuable scope :only_opened, -> { with_state(:opened) } scope :only_reopened, -> { with_state(:reopened) } scope :closed, -> { with_state(:closed) } + scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } + scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } delegate :name, :email, @@ -55,13 +57,10 @@ module Issuable def sort(method) case method.to_s - when 'newest' then reorder("#{table_name}.created_at DESC") - when 'oldest' then reorder("#{table_name}.created_at ASC") - when 'recently_updated' then reorder("#{table_name}.updated_at DESC") - when 'last_updated' then reorder("#{table_name}.updated_at ASC") - when 'milestone_due_soon' then joins(:milestone).reorder("milestones.due_date ASC") - when 'milestone_due_later' then joins(:milestone).reorder("milestones.due_date DESC") - else reorder("#{table_name}.created_at DESC") + when 'milestone_due_asc' then order_milestone_due_asc + when 'milestone_due_desc' then order_milestone_due_desc + else + order_by(method) end end end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 66f83b932d4..50be458bf24 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -51,9 +51,12 @@ module Mentionable identifier = match.delete "@" if identifier == "all" users.push(*project.team.members.flatten) - else - id = User.find_by(username: identifier).try(:id) - users << User.find(id) unless id.blank? + elsif namespace = Namespace.find_by(path: identifier) + if namespace.type == "Group" + users.push(*namespace.users) + else + users << namespace.owner + end end end users.uniq @@ -64,9 +67,10 @@ module Mentionable return [] if text.blank? ext = Gitlab::ReferenceExtractor.new ext.analyze(text, p) - (ext.issues_for + - ext.merge_requests_for + - ext.commits_for).uniq - [local_reference] + + (ext.issues_for(p) + + ext.merge_requests_for(p) + + ext.commits_for(p)).uniq - [local_reference] end # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb new file mode 100644 index 00000000000..0ad2654867d --- /dev/null +++ b/app/models/concerns/sortable.rb @@ -0,0 +1,35 @@ +# == Sortable concern +# +# Set default scope for ordering objects +# +module Sortable + extend ActiveSupport::Concern + + included do + # By default all models should be ordered + # by created_at field starting from newest + default_scope { order(created_at: :desc, id: :desc) } + + scope :order_created_desc, -> { reorder(created_at: :desc, id: :desc) } + scope :order_created_asc, -> { reorder(created_at: :asc, id: :asc) } + scope :order_updated_desc, -> { reorder(updated_at: :desc, id: :desc) } + scope :order_updated_asc, -> { reorder(updated_at: :asc, id: :asc) } + scope :order_name_asc, -> { reorder(name: :asc) } + scope :order_name_desc, -> { reorder(name: :desc) } + end + + module ClassMethods + def order_by(method) + case method.to_s + when 'name_asc' then order_name_asc + when 'name_desc' then order_name_desc + when 'updated_asc' then order_updated_asc + when 'updated_desc' then order_updated_desc + when 'created_asc' then order_created_asc + when 'created_desc' then order_created_desc + else + all + end + end + end +end diff --git a/app/models/email.rb b/app/models/email.rb index 57f476bd519..556b0e9586e 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -10,6 +10,8 @@ # class Email < ActiveRecord::Base + include Sortable + belongs_to :user validates :user_id, presence: true diff --git a/app/models/event.rb b/app/models/event.rb index 2a6c690ab91..9a42d380f87 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -15,6 +15,7 @@ # class Event < ActiveRecord::Base + include Sortable default_scope { where.not(author_id: nil) } CREATED = 1 diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb new file mode 100644 index 00000000000..50efcb32f1b --- /dev/null +++ b/app/models/external_issue.rb @@ -0,0 +1,25 @@ +class ExternalIssue + def initialize(issue_identifier, project) + @issue_identifier, @project = issue_identifier, project + end + + def to_s + @issue_identifier.to_s + end + + def id + @issue_identifier.to_s + end + + def iid + @issue_identifier.to_s + end + + def ==(other) + other.is_a?(self.class) && (to_s == other.to_s) + end + + def project + @project + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 042b79a7850..d6ec0be6081 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -28,6 +28,16 @@ class Group < Namespace after_create :post_create_hook after_destroy :post_destroy_hook + class << self + def search(query) + where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%") + end + + def sort(method) + order_by(method) + end + end + def human_name name end @@ -88,20 +98,4 @@ class Group < Namespace def system_hook_service SystemHooksService.new end - - class << self - def search(query) - where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%") - end - - def sort(method) - case method.to_s - when "newest" then reorder("namespaces.created_at DESC") - when "oldest" then reorder("namespaces.created_at ASC") - when "recently_updated" then reorder("namespaces.updated_at DESC") - when "last_updated" then reorder("namespaces.updated_at ASC") - else reorder("namespaces.path, namespaces.name ASC") - end - end - end end diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb index 33915313789..7e4f16ebf16 100644 --- a/app/models/group_milestone.rb +++ b/app/models/group_milestone.rb @@ -66,15 +66,15 @@ class GroupMilestone end def issues - @group_issues ||= milestones.map { |milestone| milestone.issues }.flatten.group_by(&:state) + @group_issues ||= milestones.map(&:issues).flatten.group_by(&:state) end def merge_requests - @group_merge_requests ||= milestones.map { |milestone| milestone.merge_requests }.flatten.group_by(&:state) + @group_merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) end def participants - milestones.map { |milestone| milestone.participants.uniq }.reject(&:empty?).flatten + @group_participants ||= milestones.map(&:participants).flatten.compact.uniq end def opened_issues diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index c8fa9c50918..defef7216f2 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -16,6 +16,7 @@ # class WebHook < ActiveRecord::Base + include Sortable include HTTParty default_value_for :push_events, true diff --git a/app/models/identity.rb b/app/models/identity.rb index 80e0e3a8a23..b2c3792d1ce 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -9,6 +9,7 @@ # class Identity < ActiveRecord::Base + include Sortable belongs_to :user validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider } diff --git a/app/models/issue.rb b/app/models/issue.rb index 8a9e969248c..19e43ebd788 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -24,6 +24,7 @@ class Issue < ActiveRecord::Base include Issuable include InternalId include Taskable + include Sortable ActsAsTaggableOn.strict_case_match = true diff --git a/app/models/key.rb b/app/models/key.rb index d2d1af68822..e2e59296eed 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -15,6 +15,7 @@ require 'digest/md5' class Key < ActiveRecord::Base + include Sortable include Gitlab::Popen belongs_to :user diff --git a/app/models/label.rb b/app/models/label.rb index 2b2b02e0645..9d7099c5652 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -28,7 +28,7 @@ class Label < ActiveRecord::Base format: { with: /\A[^&\?,&]+\z/ }, uniqueness: { scope: :project_id } - scope :order_by_name, -> { reorder("labels.title ASC") } + default_scope { order(title: :asc) } alias_attribute :name, :title diff --git a/app/models/member.rb b/app/models/member.rb index 671ef466baa..fe3d2f40e87 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -14,6 +14,7 @@ # class Member < ActiveRecord::Base + include Sortable include Notifiable include Gitlab::Access diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ad2e8d7879b..f758126cfeb 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -28,6 +28,7 @@ class MergeRequest < ActiveRecord::Base include Issuable include Taskable include InternalId + include Sortable belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index a71122d5e07..acac1ca4cf7 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -14,6 +14,8 @@ require Rails.root.join("app/models/commit") class MergeRequestDiff < ActiveRecord::Base + include Sortable + # Prevent store of diff # if commits amount more then 200 COMMITS_SAFE_SIZE = 200 diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 8fd3e56d2ee..9bbb2bafb98 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -15,6 +15,7 @@ class Milestone < ActiveRecord::Base include InternalId + include Sortable belongs_to :project has_many :issues diff --git a/app/models/namespace.rb b/app/models/namespace.rb index e7fd3024750..ba0b2b71cf9 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -14,6 +14,7 @@ # class Namespace < ActiveRecord::Base + include Sortable include Gitlab::ShellAdapter has_many :projects, dependent: :destroy diff --git a/app/models/note.rb b/app/models/note.rb index 0b988cc3e0f..ccd9783e7d4 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -49,7 +49,7 @@ class Note < ActiveRecord::Base scope :not_inline, ->{ where(line_code: [nil, '']) } scope :system, ->{ where(system: true) } scope :common, ->{ where(noteable_type: ["", nil]) } - scope :fresh, ->{ order("created_at ASC, id ASC") } + scope :fresh, ->{ order(created_at: :asc, id: :asc) } scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author, ->{ includes(:author) } @@ -121,6 +121,36 @@ class Note < ActiveRecord::Base }) end + def create_labels_change_note(noteable, project, author, added_labels, removed_labels) + labels_count = added_labels.count + removed_labels.count + added_labels = added_labels.map{ |label| "~#{label.id}" }.join(' ') + removed_labels = removed_labels.map{ |label| "~#{label.id}" }.join(' ') + message = '' + + if added_labels.present? + message << "added #{added_labels}" + end + + if added_labels.present? && removed_labels.present? + message << ' and ' + end + + if removed_labels.present? + message << "removed #{removed_labels}" + end + + message << ' ' << 'label'.pluralize(labels_count) + body = "_#{message.capitalize}_" + + create( + noteable: noteable, + project: project, + author: author, + note: body, + system: true + ) + end + def create_new_commits_note(noteable, project, author, commits) commits_text = ActionController::Base.helpers.pluralize(commits.size, 'new commit') body = "Added #{commits_text}:\n\n" diff --git a/app/models/project.rb b/app/models/project.rb index 390e1457ca1..56e1aa29040 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -33,6 +33,7 @@ require 'carrierwave/orm/activerecord' require 'file_size_validator' class Project < ActiveRecord::Base + include Sortable include Gitlab::ShellAdapter include Gitlab::VisibilityLevel include Gitlab::ConfigHelper @@ -53,7 +54,7 @@ class Project < ActiveRecord::Base attr_accessor :new_default_branch # Relations - belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' + belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' belongs_to :namespace @@ -68,6 +69,7 @@ class Project < ActiveRecord::Base has_one :hipchat_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy has_one :assembla_service, dependent: :destroy + has_one :asana_service, dependent: :destroy has_one :gemnasium_service, dependent: :destroy has_one :slack_service, dependent: :destroy has_one :buildbox_service, dependent: :destroy @@ -86,7 +88,7 @@ class Project < ActiveRecord::Base has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' # Merge requests from source project should be kept when source project was removed has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest - has_many :issues, -> { order 'issues.state DESC, issues.created_at DESC' }, dependent: :destroy + has_many :issues, dependent: :destroy has_many :labels, dependent: :destroy has_many :services, dependent: :destroy has_many :events, dependent: :destroy @@ -139,14 +141,16 @@ class Project < ActiveRecord::Base mount_uploader :avatar, AttachmentUploader # Scopes + scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } + scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } + scope :sorted_by_names, -> { joins(:namespace).reorder('namespaces.name ASC, projects.name ASC') } + scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) } scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped } scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) } scope :in_team, ->(team) { where('projects.id IN (:ids)', ids: team.projects.map(&:id)) } scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } scope :in_group_namespace, -> { joins(:group) } - scope :sorted_by_activity, -> { reorder('projects.last_activity_at DESC') } - scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :public_only, -> { where(visibility_level: Project::PUBLIC) } @@ -228,13 +232,10 @@ class Project < ActiveRecord::Base end def sort(method) - case method.to_s - when 'newest' then reorder('projects.created_at DESC') - when 'oldest' then reorder('projects.created_at ASC') - when 'recently_updated' then reorder('projects.updated_at DESC') - when 'last_updated' then reorder('projects.updated_at ASC') - when 'largest_repository' then reorder('projects.repository_size DESC') - else reorder('namespaces.path, projects.name ASC') + if method == 'repository_size_desc' + reorder(repository_size: :desc, id: :desc) + else + order_by(method) end end end @@ -320,7 +321,7 @@ class Project < ActiveRecord::Base end def default_issue_tracker - gitlab_issue_tracker_service ||= create_gitlab_issue_tracker_service + gitlab_issue_tracker_service || create_gitlab_issue_tracker_service end def issues_tracker @@ -352,18 +353,28 @@ class Project < ActiveRecord::Base end def build_missing_services - available_services_names.each do |service_name| - service = services.find { |service| service.to_param == service_name } + services_templates = Service.where(template: true) + + Service.available_services_names.each do |service_name| + service = find_service(services, service_name) # If service is available but missing in db - # we should create an instance. Ex `create_gitlab_ci_service` - service = self.send :"create_#{service_name}_service" if service.nil? + if service.nil? + # We should check if template for the service exists + template = find_service(services_templates, service_name) + + if template.nil? + # If no template, we should create an instance. Ex `create_gitlab_ci_service` + service = self.send :"create_#{service_name}_service" + else + Service.create_from_template(self.id, template) + end + end end end - def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla - emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker) + def find_service(list, name) + list.find { |service| service.to_param == name } end def gitlab_ci? diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb new file mode 100644 index 00000000000..66b72572b9c --- /dev/null +++ b/app/models/project_services/asana_service.rb @@ -0,0 +1,117 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# + +require 'asana' + +class AsanaService < Service + prop_accessor :api_key, :restrict_to_branch + validates :api_key, presence: true, if: :activated? + + def title + 'Asana' + end + + def description + 'Asana - Teamwork without email' + end + + def help + 'This service adds commit messages as comments to Asana tasks. +Once enabled, commit messages are checked for Asana task URLs +(for example, `https://app.asana.com/0/123456/987654`) or task IDs +starting with # (for example, `#987654`). Every task ID found will +get the commit comment added to it. + +You can also close a task with a message containing: `fix #123456`. + +You can find your Api Keys here: +http://developer.asana.com/documentation/#api_keys' + end + + def to_param + 'asana' + end + + def fields + [ + { + type: 'text', + name: 'api_key', + placeholder: 'User API token. User must have access to task, +all comments will be attributed to this user.' + }, + { + type: 'text', + name: 'restrict_to_branch', + placeholder: 'Comma-separated list of branches which will be +automatically inspected. Leave blank to include all branches.' + } + ] + end + + def execute(push) + Asana.configure do |client| + client.api_key = api_key + end + + user = push[:user_name] + branch = push[:ref].gsub('refs/heads/', '') + + branch_restriction = restrict_to_branch.to_s + + # check the branch restriction is poplulated and branch is not included + if branch_restriction.length > 0 && branch_restriction.index(branch) == nil + return + end + + project_name = project.name_with_namespace + push_msg = user + ' pushed to branch ' + branch + ' of ' + project_name + + push[:commits].each do |commit| + check_commit(' ( ' + commit[:url] + ' ): ' + commit[:message], push_msg) + end + end + + def check_commit(message, push_msg) + task_list = [] + close_list = [] + + message.split("\n").each do |line| + # look for a task ID or a full Asana url + task_list.concat(line.scan(/#(\d+)/)) + task_list.concat(line.scan(/https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)/)) + # look for a word starting with 'fix' followed by a task ID + close_list.concat(line.scan(/(fix\w*)\W*#(\d+)/i)) + end + + # post commit to every taskid found + task_list.each do |taskid| + task = Asana::Task.find(taskid[0]) + + if task + task.create_story(text: push_msg + ' ' + message) + end + end + + # close all tasks that had 'fix(ed/es/ing) #:id' in them + close_list.each do |taskid| + task = Asana::Task.find(taskid.last) + + if task + task.modify(completed: true) + end + end + end +end diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 0b90a14f39c..cf7598f35eb 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class AssemblaService < Service diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 745609e5911..df68803152f 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class BambooService < CiService diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb index 0ab67b79fe4..058c890ae45 100644 --- a/app/models/project_services/buildbox_service.rb +++ b/app/models/project_services/buildbox_service.rb @@ -5,13 +5,13 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # - require "addressable/uri" class BuildboxService < CiService diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 3116c311052..14b6b87a0b7 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class CampfireService < Service diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index b1d5e49ede3..5a26c25b3c3 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # # Base class for CI services diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index 5845e2d3525..b29d1c86881 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class CustomIssueTrackerService < IssueTrackerService diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index b9071b98295..86693ad0c7e 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class EmailsOnPushService < Service diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 86705f5dabd..13e2dfceb1a 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # require "flowdock-git-hook" diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 18fdd204ecd..a2c87ae88f1 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # require "gemnasium/gitlab_service" diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 248f749b310..f4b463e8199 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class GitlabCiService < CiService diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 25e399883b7..b1eab24df19 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class GitlabIssueTrackerService < IssueTrackerService diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index c4c563b3cca..003e06a4c80 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class HipchatService < Service diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index b19c02bab44..51b2fb3dcc7 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class IssueTrackerService < Service @@ -77,12 +78,14 @@ class IssueTrackerService < Service end def set_project_url - id = self.project.issues_tracker_id + if self.project + id = self.project.issues_tracker_id - if id - issues_tracker['project_url'].gsub(":issues_tracker_id", id) - else - issues_tracker['project_url'] + if id + issues_tracker['project_url'].gsub(":issues_tracker_id", id) + end end + + issues_tracker['project_url'] end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 7a32b0e8c2c..a159c287485 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class JiraService < IssueTrackerService diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index 09e114f9cca..287812c57a5 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class PivotaltrackerService < Service diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index a9b23f97ba0..3a3af59390a 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class PushoverService < Service diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index 547b2401832..e1dc10415e0 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class RedmineService < IssueTrackerService diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 963f5440b6f..297d8bbb5d4 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class SlackService < Service diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 287f5c0e84e..c4b6ef5d9a9 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # class TeamcityService < CiService diff --git a/app/models/service.rb b/app/models/service.rb index 15948e63e41..f87d875c10a 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -5,16 +5,17 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text -# +# template :boolean default(FALSE) # To add new service you should build a class inherited from Service # and implement a set of methods class Service < ActiveRecord::Base + include Sortable serialize :properties, JSON default_value_for :active, false @@ -24,7 +25,7 @@ class Service < ActiveRecord::Base belongs_to :project has_one :service_hook - validates :project_id, presence: true + validates :project_id, presence: true, unless: Proc.new { |service| service.template? } scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } @@ -32,6 +33,10 @@ class Service < ActiveRecord::Base active end + def template? + template + end + def category :common end @@ -93,7 +98,15 @@ class Service < ActiveRecord::Base self.category == :issue_tracker end - def self.issue_tracker_service_list - Service.select(&:issue_tracker?).map{ |s| s.to_param } + def self.available_services_names + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla asana + emails_on_push gemnasium slack pushover buildbox bamboo teamcity jira redmine custom_issue_tracker) + end + + def self.create_from_template(project_id, template) + service = template.dup + service.template = false + service.project_id = project_id + service if service.save end end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index a3222d29892..82c1ab94446 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -16,6 +16,7 @@ # class Snippet < ActiveRecord::Base + include Sortable include Linguist::BlobHelper include Gitlab::VisibilityLevel diff --git a/app/models/user.rb b/app/models/user.rb index 552a37c9533..3a7dfabeafe 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -43,12 +43,14 @@ # website_url :string(255) default(""), not null # last_credential_check_at :datetime # github_access_token :string(255) +# notification_email :string(255) # require 'carrierwave/orm/activerecord' require 'file_size_validator' class User < ActiveRecord::Base + include Sortable include Gitlab::ConfigHelper include TokenAuthenticatable extend Gitlab::ConfigHelper @@ -114,6 +116,7 @@ class User < ActiveRecord::Base # validates :name, presence: true validates :email, presence: true, email: { strict_mode: true }, uniqueness: true + validates :notification_email, presence: true, email: { strict_mode: true } validates :bio, length: { maximum: 255 }, allow_blank: true validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :username, @@ -127,10 +130,12 @@ class User < ActiveRecord::Base validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :avatar_type, if: ->(user) { user.avatar_changed? } validate :unique_email, if: ->(user) { user.email_changed? } + validate :owns_notification_email, if: ->(user) { user.notification_email_changed? } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } before_validation :generate_password, on: :create before_validation :sanitize_attrs + before_validation :set_notification_email, if: ->(user) { user.email_changed? } before_save :ensure_authentication_token after_save :ensure_namespace_correct @@ -176,7 +181,6 @@ class User < ActiveRecord::Base scope :admins, -> { where(admin: true) } scope :blocked, -> { with_state(:blocked) } scope :active, -> { with_state(:active) } - scope :alphabetically, -> { order('name ASC') } scope :in_team, ->(team){ where(id: team.member_ids) } scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } @@ -199,11 +203,10 @@ class User < ActiveRecord::Base def sort(method) case method.to_s - when 'recent_sign_in' then reorder('users.last_sign_in_at DESC') - when 'oldest_sign_in' then reorder('users.last_sign_in_at ASC') - when 'recently_created' then reorder('users.created_at DESC') - when 'late_created' then reorder('users.created_at ASC') - else reorder("users.name ASC") + when 'recent_sign_in' then reorder(last_sign_in_at: :desc) + when 'oldest_sign_in' then reorder(last_sign_in_at: :asc) + else + order_by(method) end end @@ -286,11 +289,15 @@ class User < ActiveRecord::Base self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email) end + def owns_notification_email + self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email) + end + # Groups user has access to def authorized_groups @authorized_groups ||= begin group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) - Group.where(id: group_ids).order('namespaces.name ASC') + Group.where(id: group_ids) end end @@ -301,7 +308,7 @@ class User < ActiveRecord::Base project_ids = personal_projects.pluck(:id) project_ids.push(*groups_projects.pluck(:id)) project_ids.push(*projects.pluck(:id).uniq) - Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC') + Project.where(id: project_ids) end end @@ -431,6 +438,12 @@ class User < ActiveRecord::Base end end + def set_notification_email + if self.notification_email.blank? || !self.all_emails.include?(self.notification_email) + self.notification_email = self.email + end + end + def requires_ldap_check? if !Gitlab.config.ldap.enabled false @@ -504,6 +517,10 @@ class User < ActiveRecord::Base end end + def all_emails + [self.email, *self.emails.map(&:email)] + end + def hook_attrs { name: name, diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index e3371ec3c1b..5e1906ad2ae 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -10,4 +10,9 @@ class IssuableBaseService < BaseService Note.create_milestone_change_note( issuable, issuable.project, current_user, issuable.milestone) end + + def create_labels_note(issuable, added_labels, removed_labels) + Note.create_labels_change_note( + issuable, issuable.project, current_user, added_labels, removed_labels) + end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 83e413d7248..c61d67a7893 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -14,10 +14,17 @@ module Issues issue.update_nth_task(params[:task_num].to_i, false) end + old_labels = issue.labels.to_a + if params.present? && issue.update_attributes(params.except(:state_event, :task_num)) issue.reset_events_cache + if issue.labels != old_labels + create_labels_note( + issue, issue.labels - old_labels, old_labels - issue.labels) + end + if issue.previous_changes.include?('milestone_id') create_milestone_note(issue) end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 10c401756eb..870b50bb60d 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -23,11 +23,21 @@ module MergeRequests merge_request.update_nth_task(params[:task_num].to_i, false) end + old_labels = merge_request.labels.to_a + if params.present? && merge_request.update_attributes( params.except(:state_event, :task_num) ) merge_request.reset_events_cache + if merge_request.labels != old_labels + create_labels_note( + merge_request, + merge_request.labels - old_labels, + old_labels - merge_request.labels + ) + end + if merge_request.previous_changes.include?('milestone_id') create_milestone_note(merge_request) end diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index e3b33de8d02..0be50fed7cc 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -1,7 +1,8 @@ module Projects class ParticipantsService < BaseService - def initialize(project) - @project = project + def initialize(project, user) + @project = project + @user = user end def execute(note_type, note_id) @@ -12,7 +13,7 @@ module Projects [] end team_members = sorted(@project.team.members) - participants = all_members + team_members + participating + participants = all_members + groups + team_members + participating participants.uniq end @@ -37,6 +38,10 @@ module Projects users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } } end + def groups + @user.authorized_groups.sort_by(&:path).map { |group| { username: group.path, name: group.name } } + end + def all_members [{ username: "all", name: "Project and Group Members" }] end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index bf0ee49d2f4..ae0c70a79c7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -37,6 +37,7 @@ .form-group = f.label :sign_in_text, class: 'control-label' .col-sm-10 - = f.text_area :sign_in_text, class: 'form-control' + = f.text_area :sign_in_text, class: 'form-control', rows: 4 + .help-block Markdown enabled .form-actions = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 1d7fef43184..8ae9a1edea9 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -8,10 +8,31 @@ %hr = form_tag admin_groups_path, method: :get, class: 'form-inline' do + = hidden_field_tag :sort, @sort .form-group = text_field_tag :name, params[:name], class: "form-control input-mn-300" = button_tag "Search", class: "btn submit btn-primary" + .pull-right + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_recently_created + %b.caret + %ul.dropdown-menu + %li + = link_to admin_groups_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to admin_groups_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to admin_groups_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to admin_groups_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated + %hr %ul.bordered-list diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index aa59f38d213..36a4a2fb4af 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -47,24 +47,22 @@ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %span.light sort: - if @sort.present? - = @sort.humanize + = sort_options_hash[@sort] - else - Name + = sort_title_recently_created %b.caret %ul.dropdown-menu %li - = link_to admin_projects_path(sort: nil) do - Name - = link_to admin_projects_path(sort: 'newest') do + = link_to admin_projects_path(sort: sort_value_recently_created) do = sort_title_recently_created - = link_to admin_projects_path(sort: 'oldest') do + = link_to admin_projects_path(sort: sort_value_oldest_created) do = sort_title_oldest_created - = link_to admin_projects_path(sort: 'recently_updated') do + = link_to admin_projects_path(sort: sort_value_recently_updated) do = sort_title_recently_updated - = link_to admin_projects_path(sort: 'last_updated') do + = link_to admin_projects_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated - = link_to admin_projects_path(sort: 'largest_repository') do - Largest repository + = link_to admin_projects_path(sort: sort_value_largest_repo) do + = sort_title_largest_repo = link_to 'New Project', new_project_path, class: "btn btn-new" %ul.well-list - @projects.each do |project| diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml new file mode 100644 index 00000000000..d8242e37621 --- /dev/null +++ b/app/views/admin/services/_form.html.haml @@ -0,0 +1,36 @@ +%h3.page-title + = @service.title + +%p #{@service.description} template + += form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |f| + - if @service.errors.any? + #error_explanation + .alert.alert-danger + - @service.errors.full_messages.each do |msg| + %p= msg + + - @service.fields.each do |field| + - name = field[:name] + - value = @service.send(name) unless field[:type] == 'password' + - type = field[:type] + - placeholder = field[:placeholder] + - choices = field[:choices] + - default_choice = field[:default_choice] + + .form-group + = f.label name, class: "control-label" + .col-sm-10 + - if type == 'text' + = f.text_field name, class: "form-control", placeholder: placeholder + - elsif type == 'textarea' + = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder + - elsif type == 'checkbox' + = f.check_box name + - elsif type == 'select' + = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } + - elsif type == 'password' + = f.password_field name, class: 'form-control' + + .form-actions + = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml new file mode 100644 index 00000000000..bcc5832792f --- /dev/null +++ b/app/views/admin/services/edit.html.haml @@ -0,0 +1 @@ += render 'form' diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml new file mode 100644 index 00000000000..1d3e192a325 --- /dev/null +++ b/app/views/admin/services/index.html.haml @@ -0,0 +1,22 @@ +%h3.page-title Service templates +%p.light Service template allows you to set default values for project services + +%table.table + %thead + %tr + %th + %th Service + %th Desription + %th Last edit + - @services.sort_by(&:title).each do |service| + %tr + %td + = icon("copy", class: 'clgray') + %td + = link_to edit_admin_application_settings_service_path(service.id) do + %strong= service.title + %td + = service.description + %td.light + = time_ago_in_words service.updated_at + ago diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 8e1ecb41a85..6e15cec467b 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -36,22 +36,26 @@ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %span.light sort: - if @sort.present? - = @sort.humanize + = sort_options_hash[@sort] - else - Name + = sort_title_name %b.caret %ul.dropdown-menu %li - = link_to admin_users_path(sort: nil) do - Name - = link_to admin_users_path(sort: 'recent_sign_in') do - Recent sign in - = link_to admin_users_path(sort: 'oldest_sign_in') do - Oldest sign in - = link_to admin_users_path(sort: 'recently_created') do + = link_to admin_users_path(sort: sort_value_name) do + = sort_title_name + = link_to admin_users_path(sort: sort_value_recently_signin) do + = sort_title_recently_signin + = link_to admin_users_path(sort: sort_value_oldest_signin) do + = sort_title_oldest_signin + = link_to admin_users_path(sort: sort_value_recently_created) do = sort_title_recently_created - = link_to admin_users_path(sort: 'late_created') do + = link_to admin_users_path(sort: sort_value_oldest_created) do = sort_title_oldest_created + = link_to admin_users_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to admin_users_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated = link_to 'New User', new_admin_user_path, class: "btn btn-new" %ul.well-list diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml index e9f411725a3..f0fb2c1881b 100644 --- a/app/views/dashboard/_project.html.haml +++ b/app/views/dashboard/_project.html.haml @@ -1,6 +1,6 @@ = link_to project_path(project), class: dom_class(project) do .dash-project-avatar - = project_icon(project.to_param, alt: '', class: 'avatar s40') + = project_icon(project.to_param, alt: '', class: 'avatar project-avatar s40') .dash-project-access-icon = visibility_level_icon(project.visibility_level) %span.str-truncated diff --git a/app/views/dashboard/_projects_filter.html.haml b/app/views/dashboard/_projects_filter.html.haml index 0e990ccfab4..7b5d46072e3 100644 --- a/app/views/dashboard/_projects_filter.html.haml +++ b/app/views/dashboard/_projects_filter.html.haml @@ -82,19 +82,19 @@ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %span.light sort: - if @sort.present? - = @sort.humanize + = sort_options_hash[@sort] - else - Name + = sort_title_recently_created %b.caret %ul.dropdown-menu %li - = link_to projects_dashboard_filter_path(sort: nil) do - Name - = link_to projects_dashboard_filter_path(sort: 'newest') do + = link_to projects_dashboard_filter_path(sort: sort_value_recently_created) do = sort_title_recently_created - = link_to projects_dashboard_filter_path(sort: 'oldest') do + = link_to projects_dashboard_filter_path(sort: sort_value_oldest_created) do = sort_title_oldest_created - = link_to projects_dashboard_filter_path(sort: 'recently_updated') do + = link_to projects_dashboard_filter_path(sort: sort_value_recently_updated) do = sort_title_recently_updated - = link_to projects_dashboard_filter_path(sort: 'last_updated') do + = link_to projects_dashboard_filter_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated + = link_to projects_dashboard_filter_path(sort: sort_value_name) do + = sort_title_name diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index f60bcc72e1d..dba3025b3cc 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -11,8 +11,8 @@ - @projects.each do |project| %li.my-project-row %h4.project-title - .project-avatar - = project_icon(project.to_param, alt: '', class: 'avatar s60') + .pull-left + = project_icon(project.to_param, alt: '', class: 'avatar project-avatar s60') .project-access-icon = visibility_level_icon(project.visibility_level) = link_to project_path(project), class: dom_class(project) do diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 8d17f39eba2..970ba147111 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -7,7 +7,8 @@ = devise_error_messages! .clearfix.append-bottom-20 = f.email_field :email, placeholder: 'Email', class: "form-control", required: true - .clearfix.append-bottom-10 + .clearfix = f.submit "Resend confirmation instructions", class: 'btn btn-success' - .login-footer - = render 'devise/shared/sign_in_link' + +.clearfix.prepend-top-20 + = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 1326cc0aac9..0640739b5d7 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -10,9 +10,10 @@ = f.password_field :password, class: "form-control top", placeholder: "New password", required: true %div = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true - .clearfix.append-bottom-10 + .clearfix = f.submit "Change my password", class: "btn btn-primary" - .login-footer - %p - = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) - = render 'devise/shared/sign_in_link' + +.clearfix.prepend-top-20 + %p + = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) + = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index b8af1b8693a..e8820daf58f 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -7,7 +7,8 @@ = devise_error_messages! .clearfix.append-bottom-20 = f.email_field :email, placeholder: "Email", class: "form-control", required: true - .clearfix.append-bottom-10 + .clearfix = f.submit "Reset password", class: "btn-primary btn" - .login-footer - = render 'devise/shared/sign_in_link' + +.clearfix.prepend-top-20 + = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index d6a952f3dc5..c07e409d583 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -1,27 +1,7 @@ -.login-box - .login-heading - %h3 Sign up - .login-body - = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| - .devise-errors - = devise_error_messages! - %div - = f.text_field :name, class: "form-control top", placeholder: "Name", required: true - %div - = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true - %div - = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true - %div - = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true - %div - = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true - %div - = f.submit "Sign up", class: "btn-create btn" - .login-footer - %p - %span.light - Have an account? - %strong - = link_to "Sign in", new_session_path(resource_name) - %p - = link_to "Forgot your password?", new_password_path(resource_name) += render 'devise/shared/signup_box' + +.clearfix.prepend-top-20 + = render 'devise/shared/sign_in_link' + %p + %span.light Did not receive confirmation email? + = link_to "Send again", new_confirmation_path(resource_name)
\ No newline at end of file diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 5709c661288..8a6dc19ab64 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -11,7 +11,7 @@ = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true %div = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true - .form-group#password-strength + .form-group.append-bottom-20#password-strength = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true %div = f.submit "Sign up", class: "btn-create btn" diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 9b1d7d0416d..5cf514927af 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -1,6 +1,7 @@ .clearfix .pull-left = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| + = hidden_field_tag :sort, @sort .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search" .form-group @@ -11,21 +12,19 @@ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %span.light sort: - if @sort.present? - = @sort.humanize + = sort_options_hash[@sort] - else - Name + = sort_title_recently_created %b.caret %ul.dropdown-menu %li - = link_to explore_groups_path(sort: nil) do - Name - = link_to explore_groups_path(sort: 'newest') do + = link_to explore_groups_path(sort: sort_value_recently_created) do = sort_title_recently_created - = link_to explore_groups_path(sort: 'oldest') do + = link_to explore_groups_path(sort: sort_value_oldest_created) do = sort_title_oldest_created - = link_to explore_groups_path(sort: 'recently_updated') do + = link_to explore_groups_path(sort: sort_value_recently_updated) do = sort_title_recently_updated - = link_to explore_groups_path(sort: 'last_updated') do + = link_to explore_groups_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated %hr diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 02586077d8c..02d02912791 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -11,21 +11,19 @@ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %span.light sort: - if @sort.present? - = @sort.humanize + = sort_options_hash[@sort] - else - Name + = sort_title_recently_created %b.caret %ul.dropdown-menu %li - = link_to explore_projects_path(sort: nil) do - Name - = link_to explore_projects_path(sort: 'newest') do + = link_to explore_projects_path(sort: sort_value_recently_created) do = sort_title_recently_created - = link_to explore_projects_path(sort: 'oldest') do + = link_to explore_projects_path(sort: sort_value_oldest_created) do = sort_title_oldest_created - = link_to explore_projects_path(sort: 'recently_updated') do + = link_to explore_projects_path(sort: sort_value_recently_updated) do = sort_title_recently_updated - = link_to explore_projects_path(sort: 'last_updated') do + = link_to explore_projects_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated %hr diff --git a/app/views/github_imports/status.html.haml b/app/views/github_imports/status.html.haml deleted file mode 100644 index 52a1e16cd04..00000000000 --- a/app/views/github_imports/status.html.haml +++ /dev/null @@ -1,63 +0,0 @@ -%h3.page-title - %i.fa.fa-github - Import repositories from GitHub.com - -%p.light - Select projects you want to import. - -%hr -%table.table.import-jobs - %thead - %tr - %th From GitHub - %th To GitLab - %th Status - %tbody - - @already_added_projects.each do |project| - %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} - %td= project.import_source - %td - %strong= link_to project.name_with_namespace, project - %td.job-status - - if project.import_status == 'finished' - %span.cgreen - %i.fa.fa-check - done - - else - = project.human_import_status_name - - - @repos.each do |repo| - %tr{id: "repo_#{repo.id}"} - %td= repo.full_name - %td.import-target - = repo.full_name - %td.import-actions.job-status - = button_tag "Add", class: "btn btn-add-to-import" - - -:coffeescript - $(".btn-add-to-import").click () -> - new_namespace = null - tr = $(this).closest("tr") - id = tr.attr("id").replace("repo_", "") - if tr.find(".import-target input").length > 0 - new_namespace = tr.find(".import-target input").prop("value") - tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) - $.post "#{github_import_url}", {repo_id: id, new_namespace: new_namespace}, dataType: 'script' - - - setInterval (-> - $.get "#{jobs_github_import_path}", (data)-> - $.each data, (i, job) -> - job_item = $("#project_" + job.id) - status_field = job_item.find(".job-status") - - if job.import_status == 'finished' - job_item.removeClass("active").addClass("success") - status_field.html('<span class="cgreen"><i class="fa fa-check"></i> done</span>') - else if job.import_status == 'started' - status_field.html("<i class='fa fa-spinner fa-spin'></i> started") - else - status_field.html(job.import_status) - - ), 4000 diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index a963c59586e..c4eb00e8925 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -12,7 +12,7 @@ .form-group .col-sm-2 .col-sm-10 - = image_tag group_icon(@group.to_param), alt: '', class: 'avatar s160' + = image_tag group_icon(@group.to_param), alt: '', class: 'avatar group-avatar s160' %p.light - if @group.avatar? You can change your group avatar here diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 484bebca2d8..f2e591c1939 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,6 +1,6 @@ .dashboard %div - = image_tag group_icon(@group.path), class: "avatar avatar-tile s90" + = image_tag group_icon(@group.path), class: "avatar group-avatar s90" .clearfix %h2 = @group.name diff --git a/app/views/github_imports/create.js.haml b/app/views/import/base/create.js.haml index cd4c9fbf360..cd4c9fbf360 100644 --- a/app/views/github_imports/create.js.haml +++ b/app/views/import/base/create.js.haml diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml new file mode 100644 index 00000000000..84d9903fe15 --- /dev/null +++ b/app/views/import/github/status.html.haml @@ -0,0 +1,41 @@ +%h3.page-title + %i.fa.fa-github + Import repositories from GitHub.com + +%p.light + Select projects you want to import. +%hr +%p + = button_tag 'Import all projects', class: "btn btn-success js-import-all" + +%table.table.import-jobs + %thead + %tr + %th From GitHub + %th To GitLab + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td= project.import_source + %td + %strong= link_to project.path_with_namespace, project + %td.job-status + - if project.import_status == 'finished' + %span.cgreen + %i.fa.fa-check + done + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo.id}"} + %td= repo.full_name + %td.import-target + = repo.full_name + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + $ -> + new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}") diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml new file mode 100644 index 00000000000..d1e48dfad20 --- /dev/null +++ b/app/views/import/gitlab/status.html.haml @@ -0,0 +1,41 @@ +%h3.page-title + %i.fa.fa-github + Import repositories from GitLab.com + +%p.light + Select projects you want to import. +%hr +%p + = button_tag 'Import all projects', class: "btn btn-success js-import-all" + +%table.table.import-jobs + %thead + %tr + %th From GitLab.com + %th To GitLab private instance + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td= project.import_source + %td + %strong= link_to project.path_with_namespace, project + %td.job-status + - if project.import_status == 'finished' + %span.cgreen + %i.fa.fa-check + done + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo["id"]}"} + %td= repo["path_with_namespace"] + %td.import-target + = repo["path_with_namespace"] + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + $ -> + new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}") diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 17bcf8d3631..a6900f4a04b 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -18,7 +18,7 @@ = javascript_include_tag "application" = csrf_meta_tags = include_gon - %meta{name: 'viewport', content: 'width=device-width, initial-scale=1.0'} + %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} %meta{name: 'theme-color', content: '#474D57'} = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 4813a4f16f5..4f864926d08 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -46,6 +46,12 @@ %span Applications + = nav_link(controller: :application_settings) do + = link_to admin_application_settings_services_path, title: 'Service Templates' do + %i.fa.fa-copy + %span + Service Templates + = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = link_to admin_application_settings_path, title: 'Settings' do %i.fa.fa-cogs diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 6c2d5966cbe..8d572ddcd10 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,75 +1,89 @@ %ul.project-navigation.nav.nav-sidebar - = nav_link(path: 'projects#show', html_options: {class: "home"}) do - = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do - %i.fa.fa-dashboard - %span - Project - - if project_nav_tab? :files - = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do - = link_to project_tree_path(@project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree' do - %i.fa.fa-files-o + - if @project_settings_nav + = nav_link do + = link_to project_path(@project), title: 'Back to project', class: "" do + %i.fa.fa-angle-left %span - Files + Back to project - - if project_nav_tab? :commits - = nav_link(controller: %w(commit commits compare repositories tags branches)) do - = link_to project_commits_path(@project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits' do - %i.fa.fa-history + = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do + = link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do + %i.fa.fa-cogs %span - Commits + Settings + %i.fa.fa-angle-down - - if project_nav_tab? :network - = nav_link(controller: %w(network)) do - = link_to project_network_path(@project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network' do - %i.fa.fa-code-fork - %span - Network + = render 'projects/settings_nav' - - if project_nav_tab? :graphs - = nav_link(controller: %w(graphs)) do - = link_to project_graph_path(@project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs' do - %i.fa.fa-area-chart + - else + = nav_link(path: 'projects#show', html_options: {class: "home"}) do + = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do + %i.fa.fa-dashboard %span - Graphs + Project + - if project_nav_tab? :files + = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do + = link_to project_tree_path(@project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree' do + %i.fa.fa-files-o + %span + Files - - if project_nav_tab? :issues - = nav_link(controller: %w(issues milestones labels)) do - = link_to url_for_project_issues, title: 'Issues', class: 'shortcuts-issues' do - %i.fa.fa-exclamation-circle - %span - Issues - - if @project.default_issues_tracker? - %span.count.issue_counter= @project.issues.opened.count + - if project_nav_tab? :commits + = nav_link(controller: %w(commit commits compare repositories tags branches)) do + = link_to project_commits_path(@project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits' do + %i.fa.fa-history + %span + Commits - - if project_nav_tab? :merge_requests - = nav_link(controller: :merge_requests) do - = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do - %i.fa.fa-tasks - %span - Merge Requests - %span.count.merge_counter= @project.merge_requests.opened.count + - if project_nav_tab? :network + = nav_link(controller: %w(network)) do + = link_to project_network_path(@project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network' do + %i.fa.fa-code-fork + %span + Network - - if project_nav_tab? :wiki - = nav_link(controller: :wikis) do - = link_to project_wiki_path(@project, :home), title: 'Wiki', class: 'shortcuts-wiki' do - %i.fa.fa-book - %span - Wiki + - if project_nav_tab? :graphs + = nav_link(controller: %w(graphs)) do + = link_to project_graph_path(@project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs' do + %i.fa.fa-area-chart + %span + Graphs - - if project_nav_tab? :snippets - = nav_link(controller: :snippets) do - = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do - %i.fa.fa-file-text-o - %span - Snippets + - if project_nav_tab? :issues + = nav_link(controller: %w(issues milestones labels)) do + = link_to url_for_project_issues, title: 'Issues', class: 'shortcuts-issues' do + %i.fa.fa-exclamation-circle + %span + Issues + - if @project.default_issues_tracker? + %span.count.issue_counter= @project.issues.opened.count - - if project_nav_tab? :settings - = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do - = link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do - %i.fa.fa-cogs - %span - Settings - %i.fa.fa-angle-down + - if project_nav_tab? :merge_requests + = nav_link(controller: :merge_requests) do + = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do + %i.fa.fa-tasks + %span + Merge Requests + %span.count.merge_counter= @project.merge_requests.opened.count - - if @project_settings_nav - = render 'projects/settings_nav' + - if project_nav_tab? :wiki + = nav_link(controller: :wikis) do + = link_to project_wiki_path(@project, :home), title: 'Wiki', class: 'shortcuts-wiki' do + %i.fa.fa-book + %span + Wiki + + - if project_nav_tab? :snippets + = nav_link(controller: :snippets) do + = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do + %i.fa.fa-file-text-o + %span + Snippets + + - if project_nav_tab? :settings + = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do + = link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do + %i.fa.fa-cogs + %span + Settings + %i.fa.fa-angle-down diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index ca980db2f3c..0b30e772336 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -3,7 +3,11 @@ %p.light Your %b Primary Email - will be used for account notifications, avatar detection and web based operations, such as edits and merges. + will be used for avatar detection and web based operations, such as edits and merges. + %br + Your + %b Notification Email + will be used for account notifications. %br All email addresses will be used to identify your commits. diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 809953960bb..c83c73ffcf9 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -6,7 +6,7 @@ SSH keys allow you to establish a secure connection between your computer and GitLab %br Before you can add an SSH key you need to - = link_to "generate it", help_page_path("ssh", "ssh") + = link_to "generate it", help_page_path("ssh", "README") %hr = render 'key_table' diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml index c02b47b0ad5..ccec716d0c6 100644 --- a/app/views/profiles/keys/new.html.haml +++ b/app/views/profiles/keys/new.html.haml @@ -8,9 +8,9 @@ $('#key_key').on('focusout', function(){ var title = $('#key_title'), val = $('#key_key').val(), - key_mail = val.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+|\.[a-zA-Z0-9._-]+)/gi); + comment = val.match(/^\S+ \S+ (.+)$/); - if( key_mail && key_mail.length > 0 && title.val() == '' ){ - $('#key_title').val( key_mail ); + if( comment && comment.length > 1 && title.val() == '' ){ + $('#key_title').val( comment[1] ); } }); diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index bc6f76a2661..28bc5a426ac 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,40 +1,57 @@ %h3.page-title Notifications settings %p.light - GitLab uses the email specified in your profile for notifications + These are your global notification settings. %hr -= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications form-horizontal global-notifications-form' do + + += form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f| + -if @user.errors.any? + %div.alert.alert-danger + %ul + - @user.errors.full_messages.each do |msg| + %li= msg + = hidden_field_tag :notification_type, 'global' - = label_tag :notification_level, 'Notification level', class: 'control-label' - .col-sm-10 - .radio - = label_tag nil, class: '' do - = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit' - .level-title - Disabled - %p You will not get any notifications via email - - .radio - = label_tag nil, class: '' do - = radio_button_tag :notification_level, Notification::N_MENTION, @notification.mention?, class: 'trigger-submit' - .level-title - Mention - %p You will receive notifications only for comments in which you were @mentioned - - .radio - = label_tag nil, class: '' do - = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit' - .level-title - Participating - %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) - - .radio - = label_tag nil, class: '' do - = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit' - .level-title - Watch - %p You will receive all notifications from projects in which you participate + .form-group + = f.label :notification_email, class: "control-label" + .col-sm-10 + = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "form-control" + + .form-group + = f.label :notification_level, class: 'control-label' + .col-sm-10 + .radio + = f.label :notification_level, value: Notification::N_DISABLED do + = f.radio_button :notification_level, Notification::N_DISABLED + .level-title + Disabled + %p You will not get any notifications via email + + .radio + = f.label :notification_level, value: Notification::N_MENTION do + = f.radio_button :notification_level, Notification::N_MENTION + .level-title + Mention + %p You will receive notifications only for comments in which you were @mentioned + + .radio + = f.label :notification_level, value: Notification::N_PARTICIPATING do + = f.radio_button :notification_level, Notification::N_PARTICIPATING + .level-title + Participating + %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) + + .radio + = f.label :notification_level, value: Notification::N_WATCH do + = f.radio_button :notification_level, Notification::N_WATCH + .level-title + Watch + %p You will receive all notifications from projects in which you participate + + .form-actions + = f.submit 'Save changes', class: "btn btn-save" .clearfix %hr diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml index 02c9ef45f2b..99325e66119 100644 --- a/app/views/projects/_github_import_modal.html.haml +++ b/app/views/projects/_github_import_modal.html.haml @@ -6,17 +6,4 @@ %h3 GitHub OAuth import .modal-body You need to setup integration with GitHub first. - = link_to 'How to setup integration with GitHub', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md' - - -:javascript - $(function(){ - var import_modal = $('#github_import_modal').modal({modal: true, show:false}); - $('.how_to_import_link').bind("click", function(e){ - e.preventDefault(); - import_modal.show(); - }); - $('.modal-header .close').bind("click", function(){ - import_modal.hide(); - }) - }) + = link_to 'How to setup integration with GitHub', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md'
\ No newline at end of file diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml new file mode 100644 index 00000000000..e7503f023b1 --- /dev/null +++ b/app/views/projects/_gitlab_import_modal.html.haml @@ -0,0 +1,9 @@ +%div#gitlab_import_modal.modal.hide + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 GitLab OAuth import + .modal-body + You need to setup integration with GitLab first. + = link_to 'How to setup integration with GitLab', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md'
\ No newline at end of file diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 2ed49f83a7a..5697f9ea1af 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,7 +1,7 @@ - empty_repo = @project.empty_repo? .project-home-panel{:class => ("empty-project" if empty_repo)} .project-identicon-holder - = project_icon(@project.to_param, alt: '', class: 'avatar') + = project_icon(@project.to_param, alt: '', class: 'avatar project-avatar') .project-home-row .project-home-desc - if @project.description.present? diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 367bd8806db..737cda411bc 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -78,7 +78,7 @@ .col-sm-2 .col-sm-10 - if @project.avatar? - = project_icon(@project.to_param, alt: '', class: 'avatar s160') + = project_icon(@project.to_param, alt: '', class: 'avatar project-avatar s160') %p.light - if @project.avatar_in_git Project avatar in repository: #{ @project.avatar_in_git } diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index 72a01e1c271..c7380920b47 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -16,7 +16,7 @@ .col-sm-10 .input-group .input-group-addon.label-color-preview - = f.text_field :color, placeholder: "#AA33EE", class: "form-control" + = f.color_field :color, placeholder: "#AA33EE", class: "form-control" .help-block 6 character hex values starting with a # sign. %br diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 3e0f9cbd80b..61f6a66c386 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -44,7 +44,7 @@ .col-sm-2 .col-sm-10 - if github_import_enabled? - = link_to status_github_import_path do + = link_to status_import_github_path do %i.fa.fa-github Import projects from GitHub - else @@ -52,6 +52,19 @@ %i.fa.fa-github Import projects from GitHub = render 'github_import_modal' + + .project-import.form-group + .col-sm-2 + .col-sm-10 + - if gitlab_import_enabled? + = link_to status_import_gitlab_path do + %i.fa.fa-heart + Import projects from GitLab.com + - else + = link_to '#', class: 'how_to_import_link light' do + %i.fa.fa-heart + Import projects from GitLab.com + = render 'gitlab_import_modal' %hr.prepend-botton-10 @@ -79,3 +92,11 @@ %i.fa.fa-spinner.fa-spin Creating project & repository. %p Please wait a moment, this page will automatically refresh when ready. + +:coffeescript + $ -> + $('.how_to_import_link').bind 'click', (e) -> + e.preventDefault() + import_modal = $(this).parent().find(".modal").show() + $('.modal-header .close').bind 'click', -> + $(".modal").hide()
\ No newline at end of file diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 1151f22c7e8..ba270880881 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -19,7 +19,8 @@ - if @service.help.present? .bs-callout - = @service.help + = preserve do + = markdown @service.help .form-group = f.label :active, "Active", class: "control-label" diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml index 4f683258fac..cd97481bb6c 100644 --- a/app/views/shared/_issuable_filter.html.haml +++ b/app/views/shared/_issuable_filter.html.haml @@ -98,7 +98,7 @@ = link_to page_filter_path(label_name: nil) do Any - if @project.labels.any? - - @project.labels.order_by_name.each do |label| + - @project.labels.each do |label| %li = link_to page_filter_path(label_name: label.name) do = render_colored_label(label) diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 3e6a62380f3..ba14c8643cd 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -2,21 +2,21 @@ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} %span.light sort: - if @sort.present? - = @sort + = sort_options_hash[@sort] - else - Newest + = sort_title_recently_created %b.caret %ul.dropdown-menu.dropdown-menu-align-right %li - = link_to page_filter_path(sort: 'newest') do + = link_to page_filter_path(sort: sort_value_recently_created) do = sort_title_recently_created - = link_to page_filter_path(sort: 'oldest') do + = link_to page_filter_path(sort: sort_value_oldest_created) do = sort_title_oldest_created - = link_to page_filter_path(sort: 'recently_updated') do + = link_to page_filter_path(sort: sort_value_recently_updated) do = sort_title_recently_updated - = link_to page_filter_path(sort: 'last_updated') do + = link_to page_filter_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated - = link_to page_filter_path(sort: 'milestone_due_soon') do - Milestone due soon - = link_to page_filter_path(sort: 'milestone_due_later') do - Milestone due later + = link_to page_filter_path(sort: sort_value_milestone_soon) do + = sort_title_milestone_soon + = link_to page_filter_path(sort: sort_value_milestone_later) do + = sort_title_milestone_later diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index b66a8808f87..cb84570a6d5 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -1,4 +1,4 @@ .clearfix - groups.each do |group| = link_to group, class: 'profile-groups-avatars inline', title: group.name do - = image_tag group_icon(group.path), class: 'avatar avatar-tile s40' + = image_tag group_icon(group.path), class: 'avatar group-avatar s40' diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 0bcc42bc62c..5f9970d3795 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -10,11 +10,13 @@ class RepositoryImportWorker project.path_with_namespace, project.import_url) - if project.import_type == 'github' - result_of_data_import = Gitlab::Github::Importer.new(project).execute - else - result_of_data_import = true - end + result_of_data_import = if project.import_type == 'github' + Gitlab::GithubImport::Importer.new(project).execute + elsif project.import_type == 'gitlab' + Gitlab::GitlabImport::Importer.new(project).execute + else + true + end if result && result_of_data_import project.import_finish diff --git a/config/application.rb b/config/application.rb index 24ba219cf3a..bd4578848c5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -31,7 +31,7 @@ module Gitlab config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters.push(*[:password]) + config.filter_parameters.push(:password, :password_confirmation, :private_token) # Enable escaping HTML in JSON. config.active_support.escape_html_entities_in_json = true diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 59af49c0180..044b1f66b25 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -46,8 +46,6 @@ production: &base # Email server smtp settings are in config/initializers/smtp_settings.rb.sample - ## User settings - default_projects_limit: 10 # default_can_create_group: false # default: true # username_changing_enabled: false # default: true - User can change her username/namespace ## Default theme @@ -58,16 +56,6 @@ production: &base ## COLOR = 5 # default_theme: 2 # default: 2 - ## Users can create accounts - # This also allows normal users to sign up for accounts themselves - # default: true - By default users can sign up themselves - # signup_enabled: true - - ## Standard login settings - # The standard login can be disabled to force login via LDAP - # default: true - If set to false the standard login form won't be shown on the sign-in page - # signin_enabled: false - # Restrict setting visibility levels for non-admin users. # The default is to allow all levels. # restricted_visibility_levels: [ "public" ] @@ -227,6 +215,9 @@ production: &base # - { name: 'github', app_id: 'YOUR APP ID', # app_secret: 'YOUR APP SECRET', # args: { scope: 'user:email' } } + # - { name: 'gitlab', app_id: 'YOUR APP ID', + # app_secret: 'YOUR APP SECRET', + # args: { scope: 'api' } } @@ -293,11 +284,6 @@ production: &base # piwik_url: '_your_piwik_url' # piwik_site_id: '_your_piwik_site_id' - ## Text under sign-in page (Markdown enabled) - # sign_in_text: | - #  - # [Learn more about CompanyName](http://www.companydomain.com/) - rack_attack: git_basic_auth: # Whitelist requests from 127.0.0.1 for web proxies (NGINX/Apache) with incorrect headers diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 4e015f1646b..d7c1a8428ac 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -114,7 +114,7 @@ Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled']. Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? -Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil? +Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil? diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index e9b843e29b4..9da7ebf4290 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -27,7 +27,7 @@ Doorkeeper.configure do # Access token expiration time (default 2 hours). # If you want to disable expiration, set this to nil. - # access_token_expires_in 2.hours + access_token_expires_in nil # Reuse access token for the same resource owner within an application (disabled by default) # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383 diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample index 3711b03796e..e00923e7e0c 100644 --- a/config/initializers/smtp_settings.rb.sample +++ b/config/initializers/smtp_settings.rb.sample @@ -1,4 +1,4 @@ -# To enable smtp email delivery for your GitLab instance do next: +# To enable smtp email delivery for your GitLab instance do the following: # 1. Rename this file to smtp_settings.rb # 2. Edit settings inside this file # 3. Restart GitLab instance @@ -13,6 +13,7 @@ if Rails.env.production? password: "123456", domain: "gitlab.company.com", authentication: :login, - enable_starttls_auto: true + enable_starttls_auto: true, + openssl_verify_mode: 'none' } end diff --git a/config/routes.rb b/config/routes.rb index f0abd876ecd..65786d83566 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -51,15 +51,26 @@ Gitlab::Application.routes.draw do end get '/s/:username' => 'snippets#user_index', as: :user_snippets, constraints: { username: /.*/ } + # - # Github importer area + # Import # - resource :github_import, only: [:create, :new] do - get :status - get :callback - get :jobs + namespace :import do + resource :github, only: [:create, :new], controller: :github do + get :status + get :callback + get :jobs + end + + resource :gitlab, only: [:create, :new], controller: :gitlab do + get :status + get :callback + get :jobs + end end + + # # Explore area # @@ -120,7 +131,9 @@ Gitlab::Application.routes.draw do end end - resource :application_settings, only: [:show, :update] + resource :application_settings, only: [:show, :update] do + resources :services + end root to: 'dashboard#index' end @@ -209,7 +222,7 @@ Gitlab::Application.routes.draw do post :unarchive post :upload_image post :toggle_star - get :markdown_preview + post :markdown_preview get :autocomplete_sources end diff --git a/db/migrate/20150205211843_add_timestamps_to_identities.rb b/db/migrate/20150205211843_add_timestamps_to_identities.rb new file mode 100644 index 00000000000..77cddbfec3b --- /dev/null +++ b/db/migrate/20150205211843_add_timestamps_to_identities.rb @@ -0,0 +1,5 @@ +class AddTimestampsToIdentities < ActiveRecord::Migration + def change + add_timestamps(:identities) + end +end diff --git a/db/migrate/20150206181414_add_index_to_created_at.rb b/db/migrate/20150206181414_add_index_to_created_at.rb new file mode 100644 index 00000000000..fc624fca60d --- /dev/null +++ b/db/migrate/20150206181414_add_index_to_created_at.rb @@ -0,0 +1,16 @@ +class AddIndexToCreatedAt < ActiveRecord::Migration + def change + add_index "users", [:created_at, :id] + add_index "members", [:created_at, :id] + add_index "projects", [:created_at, :id] + add_index "issues", [:created_at, :id] + add_index "merge_requests", [:created_at, :id] + add_index "milestones", [:created_at, :id] + add_index "namespaces", [:created_at, :id] + add_index "notes", [:created_at, :id] + add_index "identities", [:created_at, :id] + add_index "keys", [:created_at, :id] + add_index "web_hooks", [:created_at, :id] + add_index "snippets", [:created_at, :id] + end +end diff --git a/db/migrate/20150206222854_add_notification_email_to_user.rb b/db/migrate/20150206222854_add_notification_email_to_user.rb new file mode 100644 index 00000000000..ab80f7e582f --- /dev/null +++ b/db/migrate/20150206222854_add_notification_email_to_user.rb @@ -0,0 +1,11 @@ +class AddNotificationEmailToUser < ActiveRecord::Migration + def up + add_column :users, :notification_email, :string + + execute "UPDATE users SET notification_email = email" + end + + def down + remove_column :users, :notification_email + end +end diff --git a/db/migrate/20150209222013_add_missing_index.rb b/db/migrate/20150209222013_add_missing_index.rb new file mode 100644 index 00000000000..a816c2e9e8c --- /dev/null +++ b/db/migrate/20150209222013_add_missing_index.rb @@ -0,0 +1,5 @@ +class AddMissingIndex < ActiveRecord::Migration + def change + add_index "services", [:created_at, :id] + end +end diff --git a/db/migrate/20150211172122_add_template_to_service.rb b/db/migrate/20150211172122_add_template_to_service.rb new file mode 100644 index 00000000000..b1bfbc45ee9 --- /dev/null +++ b/db/migrate/20150211172122_add_template_to_service.rb @@ -0,0 +1,5 @@ +class AddTemplateToService < ActiveRecord::Migration + def change + add_column :services, :template, :boolean, default: false + end +end diff --git a/db/migrate/20150211174341_allow_null_in_services_project_id.rb b/db/migrate/20150211174341_allow_null_in_services_project_id.rb new file mode 100644 index 00000000000..68f02812791 --- /dev/null +++ b/db/migrate/20150211174341_allow_null_in_services_project_id.rb @@ -0,0 +1,5 @@ +class AllowNullInServicesProjectId < ActiveRecord::Migration + def change + change_column :services, :project_id, :integer, null: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 0e4af3df7c2..f33766a1fe8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150125163100) do +ActiveRecord::Schema.define(version: 20150211174341) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -87,11 +87,14 @@ ActiveRecord::Schema.define(version: 20150125163100) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree create_table "identities", force: true do |t| - t.string "extern_uid" - t.string "provider" - t.integer "user_id" + t.string "extern_uid" + t.string "provider" + t.integer "user_id" + t.datetime "created_at" + t.datetime "updated_at" end + add_index "identities", ["created_at", "id"], name: "index_identities_on_created_at_and_id", using: :btree add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree create_table "issues", force: true do |t| @@ -111,6 +114,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree + add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree @@ -127,6 +131,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do t.string "fingerprint" end + add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree create_table "label_links", force: true do |t| @@ -162,6 +167,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do end add_index "members", ["access_level"], name: "index_members_on_access_level", using: :btree + add_index "members", ["created_at", "id"], name: "index_members_on_created_at_and_id", using: :btree add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree add_index "members", ["type"], name: "index_members_on_type", using: :btree add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree @@ -198,6 +204,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree + add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree @@ -217,6 +224,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do t.integer "iid" end + add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree @@ -232,6 +240,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do t.string "avatar" end + add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree @@ -254,6 +263,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree + add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree @@ -323,14 +333,15 @@ ActiveRecord::Schema.define(version: 20150125163100) do t.string "import_url" t.integer "visibility_level", default: 0, null: false t.boolean "archived", default: false, null: false - t.string "avatar" t.string "import_status" t.float "repository_size", default: 0.0 t.integer "star_count", default: 0, null: false + t.string "avatar" t.string "import_type" t.string "import_source" end + add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree @@ -349,13 +360,15 @@ ActiveRecord::Schema.define(version: 20150125163100) do create_table "services", force: true do |t| t.string "type" t.string "title" - t.integer "project_id", null: false + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" t.boolean "active", default: false, null: false t.text "properties" + t.boolean "template", default: false end + add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree create_table "snippets", force: true do |t| @@ -372,6 +385,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree + add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree @@ -426,7 +440,6 @@ ActiveRecord::Schema.define(version: 20150125163100) do t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" - t.datetime "last_credential_check_at" t.string "avatar" t.string "confirmation_token" t.datetime "confirmed_at" @@ -434,13 +447,16 @@ ActiveRecord::Schema.define(version: 20150125163100) do t.string "unconfirmed_email" t.boolean "hide_no_ssh_key", default: false t.string "website_url", default: "", null: false + t.datetime "last_credential_check_at" t.string "github_access_token" t.string "gitlab_access_token" + t.string "notification_email" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree + add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree @@ -471,6 +487,7 @@ ActiveRecord::Schema.define(version: 20150125163100) do t.boolean "tag_push_events", default: false end + add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree end diff --git a/doc/api/groups.md b/doc/api/groups.md index 9f01b550641..3c1858e697d 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -152,6 +152,20 @@ Parameters: - `user_id` (required) - The ID of a user to add - `access_level` (required) - Project access level +### Edit group team member + +Updates a group team member to a specified access level. + +``` +PUT /groups/:id/members/:user_id +``` + +Parameters: + +- `id` (required) - The ID of a group +- `user_id` (required) - The ID of a group member +- `access_level` (required) - Project access level + ### Remove user team member Removes user from user team. diff --git a/doc/api/issues.md b/doc/api/issues.md index 8d073c46d33..5a2f6a4c229 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -18,6 +18,8 @@ Parameters: - `state` (optional) - Return `all` issues or just those that are `opened` or `closed` - `labels` (optional) - Comma-separated list of label names +- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` ```json [ @@ -105,6 +107,8 @@ Parameters: - `state` (optional) - Return `all` issues or just those that are `opened` or `closed` - `labels` (optional) - Comma-separated list of label names - `milestone` (optional) - Milestone title +- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` ## Single issue diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index acae55d07ef..1f3fd26a241 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -2,7 +2,9 @@ ## List merge requests -Get all merge requests for this project. The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. +Get all merge requests for this project. +The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). +The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. ``` GET /projects/:id/merge_requests @@ -14,8 +16,8 @@ Parameters: - `id` (required) - The ID of a project - `state` (optional) - Return `all` requests or just those that are `merged`, `opened` or `closed` -- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields -- `sort` (optional) - Return requests sorted in `asc` or `desc` order +- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` ```json [ diff --git a/doc/api/projects.md b/doc/api/projects.md index 559d35d316a..454f6fa2e91 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -11,8 +11,8 @@ GET /projects Parameters: - `archived` (optional) - if passed, limit by archived status -- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields -- `sort` (optional) - Return requests sorted in `asc` or `desc` order +- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `search` (optional) - Return list of authorized projects according to a search criteria ```json @@ -98,6 +98,13 @@ Get a list of projects which are owned by the authenticated user. GET /projects/owned ``` +Parameters: + +- `archived` (optional) - if passed, limit by archived status +- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` +- `search` (optional) - Return list of authorized projects according to a search criteria + ### List ALL projects Get a list of all GitLab projects (admin only). @@ -106,6 +113,13 @@ Get a list of all GitLab projects (admin only). GET /projects/all ``` +Parameters: + +- `archived` (optional) - if passed, limit by archived status +- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` +- `search` (optional) - Return list of authorized projects according to a search criteria + ### Get single project Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user. diff --git a/doc/install/installation.md b/doc/install/installation.md index bfdebaf8466..bd81073c7eb 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -22,7 +22,9 @@ This is the official installation guide to set up a production server. To set up The following steps have been known to work. Please **use caution when you deviate** from this guide. Make sure you don't violate any assumptions GitLab makes about its environment. For example many people run into permission problems because they changed the location of directories or run services as the wrong user. -If you find a bug/error in this guide please **submit a merge request** following the [contributing guide](../../CONTRIBUTING.md). +If you find a bug/error in this guide please **submit a merge request** +following the +[contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md). ## Overview @@ -457,4 +459,4 @@ You can configure LDAP authentication in `config/gitlab.yml`. Please restart Git ### Using Custom Omniauth Providers -See the [omniauth integration document](doc/integration/omniauth.md) +See the [omniauth integration document](../integration/omniauth.md) diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md new file mode 100644 index 00000000000..b3b1d897225 --- /dev/null +++ b/doc/integration/gitlab.md @@ -0,0 +1,54 @@ +# GitLab OAuth2 OmniAuth Provider + +To enable the GitLab OmniAuth provider you must register your application with GitLab. GitLab will generate a client ID and secret key for you to use. + +1. Sign in to GitLab. + +1. Navigate to your settings. + +1. Select "Applications" in the left menu. + +1. Select "New application". + +1. Provide the required details. + - Name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive. + - Redirect URI: + + ``` + http://gitlab.example.com/import/gitlab/callback + http://gitlab.example.com/users/auth/gitlab/callback + ``` + + The first link is required for the importer and second for the authorization. + +1. Select "Submit". + +1. You should now see a Application ID and Secret. Keep this page open as you continue configuration. + +1. On your GitLab server, open the configuration file. + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for more details. + +1. Under `providers:` uncomment (or add) lines that look like the following: + + ``` + - { name: 'gitlab', app_id: 'YOUR APP ID', + app_secret: 'YOUR APP SECRET', + args: { scope: 'api' } } + ``` + +1. Change 'YOUR APP ID' to the Application ID from the GitLab application page. + +1. Change 'YOUR APP SECRET' to the secret from the GitLab application page. + +1. Save the configuration file. + +1. Restart GitLab for the changes to take effect. + +On the sign in page there should now be a GitLab icon below the regular sign in form. Click the icon to begin the authentication process. GitLab will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to your GitLab instance and will be signed in. diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 2568245e9c2..1096ea9656c 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -148,7 +148,7 @@ But let's throw in a <b>tag</b>. If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. - Consult the [Emoji Cheat Sheet](https://www.dropbox.com/s/b9xaqb977s6d8w1/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup: + Consult the [Emoji Cheat Sheet](https://s3.amazonaws.com/emoji-cheatsheet/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup: Sometimes you want to be a :ninja: and add some :glowing_star: to your :speech_balloon:. Well we have a gift for you: @@ -158,7 +158,7 @@ You can use it to point out a :bug: or warn about :speak_no_evil_monkey: patches If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. -Consult the [Emoji Cheat Sheet](https://www.dropbox.com/s/b9xaqb977s6d8w1/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup: +Consult the [Emoji Cheat Sheet](https://s3.amazonaws.com/emoji-cheatsheet/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup: ## Special GitLab References @@ -170,7 +170,7 @@ GFM will turn that reference into a link so you can navigate between them easily GFM will recognize the following: -- @foo : for team members +- @foo : for specific team members or groups - @all : for the whole team - #123 : for issues - !123 : for merge requests @@ -420,6 +420,8 @@ Quote break. You can also use raw HTML in your Markdown, and it'll mostly work pretty well. +Note that inline HTML is disabled in the default Gitlab configuration, although it is [possible](https://github.com/gitlabhq/gitlabhq/pull/8007/commits) for the system administrator to enable it. + ```no-highlight <dl> <dt>Definition list</dt> diff --git a/doc/release/howto_rc1.md b/doc/release/howto_rc1.md index e8e8c8a821d..c4156d25d5f 100644 --- a/doc/release/howto_rc1.md +++ b/doc/release/howto_rc1.md @@ -11,59 +11,7 @@ The RC1 release comes with the task to update the installation and upgrade docs. ### 2. Create update guides -1. Create: CE update guide from previous version. Like `7.3-to-7.4.md` -1. Create: CE to EE update guide in EE repository for latest version. -1. Update: `6.x-or-7.x-to-7.x.md` to latest version. -1. Create: CI update guide from previous version - -It's best to copy paste the previous guide and make changes where necessary. -The typical steps are listed below with any points you should specifically look at. - -#### 0. Any major changes? - -List any major changes here, so the user is aware of them before starting to upgrade. For instance: - -- Database updates -- Web server changes -- File structure changes - -#### 1. Stop server - -#### 2. Make backup - -#### 3. Do users need to update dependencies like `git`? - -- Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release. - -- Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release. - -#### 4. Get latest code - -#### 5. Does GitLab shell need to be updated? - -#### 6. Install libs, migrations, etc. - -#### 7. Any config files updated since last release? - -Check if any of these changed since last release: - -- [lib/support/nginx/gitlab](/lib/support/nginx/gitlab) -- [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl) -- <https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example> -- [config/gitlab.yml.example](/config/gitlab.yml.example) -- [config/unicorn.rb.example](/config/unicorn.rb.example) -- [config/database.yml.mysql](/config/database.yml.mysql) -- [config/database.yml.postgresql](/config/database.yml.postgresql) -- [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example) -- [config/resque.yml.example](/config/resque.yml.example) - -#### 8. Need to update init script? - -Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab) - -#### 9. Start application - -#### 10. Check application status +[Follow this guide](howto_update_guides.md) to create update guides. ### 3. Code quality indicators diff --git a/doc/release/howto_update_guides.md b/doc/release/howto_update_guides.md new file mode 100644 index 00000000000..23d0959c33d --- /dev/null +++ b/doc/release/howto_update_guides.md @@ -0,0 +1,55 @@ +# Create update guides + +1. Create: CE update guide from previous version. Like `7.3-to-7.4.md` +1. Create: CE to EE update guide in EE repository for latest version. +1. Update: `6.x-or-7.x-to-7.x.md` to latest version. +1. Create: CI update guide from previous version + +It's best to copy paste the previous guide and make changes where necessary. +The typical steps are listed below with any points you should specifically look at. + +#### 0. Any major changes? + +List any major changes here, so the user is aware of them before starting to upgrade. For instance: + +- Database updates +- Web server changes +- File structure changes + +#### 1. Stop server + +#### 2. Make backup + +#### 3. Do users need to update dependencies like `git`? + +- Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release. + +- Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release. + +#### 4. Get latest code + +#### 5. Does GitLab shell need to be updated? + +#### 6. Install libs, migrations, etc. + +#### 7. Any config files updated since last release? + +Check if any of these changed since last release: + +- [lib/support/nginx/gitlab](/lib/support/nginx/gitlab) +- [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl) +- <https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example> +- [config/gitlab.yml.example](/config/gitlab.yml.example) +- [config/unicorn.rb.example](/config/unicorn.rb.example) +- [config/database.yml.mysql](/config/database.yml.mysql) +- [config/database.yml.postgresql](/config/database.yml.postgresql) +- [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example) +- [config/resque.yml.example](/config/resque.yml.example) + +#### 8. Need to update init script? + +Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab) + +#### 9. Start application + +#### 10. Check application status diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 4297bc7e2b7..c9e6d3426bc 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -66,15 +66,12 @@ 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) 22nd: - [ ] Release CE, EE and CI (#LINK) -Xth: (1 working day after the 22nd) - -- [ ] Update GitLab.com with the stable version (#LINK) - ``` - - - @@ -90,20 +87,13 @@ asked if there is anything missing. There are three changelogs that need to be updated: CE, EE and CI. -Remove the Note text in the stable branches. - ## Create RC1 (CE, EE, CI) [Follow this How-to guide](howto_rc1.md) to create RC1. ## Prepare CHANGELOG for next release -Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version and add 70 empty -lines to it. We do this in order to avoid merge conflicts when merging the CHANGELOG. - -Make sure that the CHANGELOG im master contains the following disclaimer message: - -> Note: The upcoming release contains empty lines to reduce the number of merge conflicts, scroll down to see past releases. +Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version. ## QA diff --git a/doc/release/patch.md b/doc/release/patch.md index 2bd34b7d822..d8bb4aef0e2 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -18,12 +18,13 @@ Otherwise include it in the monthly release and note there was a regression fix 1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier 1. Fix the issue on a feature branch, do this on the private GitLab development server 1. If it is a security issue, then assign it to the release manager and apply a 'security' label -1. Build the package for GitLab.com and do a deploy 1. Consider creating and testing workarounds 1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch 1. Make sure that the build has passed and all tests are passing -1. In a separate commit in the stable branch update the CHANGELOG +1. In a separate commit in the master branch update the CHANGELOG 1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X" +1. Merge CE stable branch into EE stable branch + ### Bump version @@ -48,9 +49,8 @@ CE=false be rake release['x.x.x'] ### Release -1. Apply the patch to GitLab Cloud and the private GitLab development server 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) -1. Cherry-pick the changelog update back into master +1. Apply the patch to GitLab.com and the private GitLab development server 1. Create and publish a blog post -1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing and link to the blog post +1. Send tweets about the release from `@gitlab`, tweet should include the most important feature that the release is addressing and link to the blog post 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) diff --git a/doc/security/README.md b/doc/security/README.md index f88375f2afd..49dfa6eec76 100644 --- a/doc/security/README.md +++ b/doc/security/README.md @@ -2,4 +2,5 @@ - [Password length limits](password_length_limits.md) - [Rack attack](rack_attack.md) +- [Web Hooks and insecure internal web services](webhooks.md) - [Information exclusivity](information_exclusivity.md) diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md new file mode 100644 index 00000000000..1e9d33e87c3 --- /dev/null +++ b/doc/security/webhooks.md @@ -0,0 +1,13 @@ +# Web Hooks and insecure internal web services + +If you have non-GitLab web services running on your GitLab server or within its local network, these may be vulnerable to exploitation via Web Hooks. + +With [Web Hooks](../web_hooks/web_hooks.md), you and your project masters and owners can set up URLs to be triggered when specific things happen to projects. Normally, these requests are sent to external web services specifically set up for this purpose, that process the request and its attached data in some appropriate way. + +Things get hairy, however, when a Web Hook is set up with a URL that doesn't point to an external, but to an internal service, that may do something completely unintended when the web hook is triggered and the POST request is sent. + +Because Web Hook requests are made by the GitLab server itself, these have complete access to everything running on the server (http://localhost:123) or within the server's local network (http://192.168.1.12:345), even if these services are otherwise protected and inaccessible from the outside world. + +If a web service does not require authentication, Web Hooks can be used to trigger destructive commands by getting the GitLab server to make POST requests to endpoints like "http://localhost:123/some-resource/delete". + +To prevent this type of exploitation from happening, make sure that you are aware of every web service GitLab could potentially have access to, and that all of these are set up to require authentication for every potentially destructive command. Enabling authentication but leaving a default password is not enough.
\ No newline at end of file diff --git a/doc/update/2.6-to-3.0.md b/doc/update/2.6-to-3.0.md index 2044b659468..4827ef9501a 100644 --- a/doc/update/2.6-to-3.0.md +++ b/doc/update/2.6-to-3.0.md @@ -1,4 +1,5 @@ # From 2.6 to 3.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/2.6-to-3.0.md) for the most up to date instructions.* ## 1. Stop server & resque diff --git a/doc/update/2.9-to-3.0.md b/doc/update/2.9-to-3.0.md index 8af86b0dc98..f4a997a8c5e 100644 --- a/doc/update/2.9-to-3.0.md +++ b/doc/update/2.9-to-3.0.md @@ -1,4 +1,5 @@ # From 2.9 to 3.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/2.9-to-3.0.md) for the most up to date instructions.* ## 1. Stop server & resque diff --git a/doc/update/3.0-to-3.1.md b/doc/update/3.0-to-3.1.md index 3206df3499b..a30485c42f7 100644 --- a/doc/update/3.0-to-3.1.md +++ b/doc/update/3.0-to-3.1.md @@ -1,4 +1,5 @@ # From 3.0 to 3.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/3.0-to-3.1.md) for the most up to date instructions.* **IMPORTANT!** diff --git a/doc/update/3.1-to-4.0.md b/doc/update/3.1-to-4.0.md index 165f4e6a308..f1ef4df4744 100644 --- a/doc/update/3.1-to-4.0.md +++ b/doc/update/3.1-to-4.0.md @@ -1,4 +1,5 @@ # From 3.1 to 4.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/3.1-to-4.0.md) for the most up to date instructions.* ## Important changes diff --git a/doc/update/4.0-to-4.1.md b/doc/update/4.0-to-4.1.md index 4149ed6b08d..d89d5235917 100644 --- a/doc/update/4.0-to-4.1.md +++ b/doc/update/4.0-to-4.1.md @@ -1,4 +1,5 @@ # From 4.0 to 4.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.0-to-4.1.md) for the most up to date instructions.* ## Important changes diff --git a/doc/update/4.1-to-4.2.md b/doc/update/4.1-to-4.2.md index 5ee8e8781e9..6fe4412ff90 100644 --- a/doc/update/4.1-to-4.2.md +++ b/doc/update/4.1-to-4.2.md @@ -1,4 +1,5 @@ # From 4.1 to 4.2 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.1-to-4.2.md) for the most up to date instructions.* ## 1. Stop server & Resque diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md index 0a929591dec..f9faf65f952 100644 --- a/doc/update/4.2-to-5.0.md +++ b/doc/update/4.2-to-5.0.md @@ -1,4 +1,5 @@ # From 4.2 to 5.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.2-to-5.0.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md index 0e597abb1a9..9fbd1f88515 100644 --- a/doc/update/5.0-to-5.1.md +++ b/doc/update/5.0-to-5.1.md @@ -1,4 +1,5 @@ # From 5.0 to 5.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.0-to-5.1.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md index 6ef559ac9f9..cf9c4e4f770 100644 --- a/doc/update/5.1-to-5.2.md +++ b/doc/update/5.1-to-5.2.md @@ -1,4 +1,5 @@ # From 5.1 to 5.2 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-5.2.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md index 8ec56b266ca..97a98ede070 100644 --- a/doc/update/5.1-to-5.4.md +++ b/doc/update/5.1-to-5.4.md @@ -1,4 +1,5 @@ # From 5.1 to 5.4 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-5.4.md) for the most up to date instructions.* Also works starting from 5.2. diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md index ef412b45695..a3fdd92bd2f 100644 --- a/doc/update/5.1-to-6.0.md +++ b/doc/update/5.1-to-6.0.md @@ -1,4 +1,5 @@ # From 5.1 to 6.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-6.0.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md index 61ddf135641..27613aeda07 100644 --- a/doc/update/5.2-to-5.3.md +++ b/doc/update/5.2-to-5.3.md @@ -1,4 +1,5 @@ # From 5.2 to 5.3 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.2-to-5.3.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md index 8a0d43e3e64..577b9a585ff 100644 --- a/doc/update/5.3-to-5.4.md +++ b/doc/update/5.3-to-5.4.md @@ -1,4 +1,5 @@ # From 5.3 to 5.4 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.3-to-5.4.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md index ba8f8e39584..d18c3fe8586 100644 --- a/doc/update/5.4-to-6.0.md +++ b/doc/update/5.4-to-6.0.md @@ -1,4 +1,5 @@ # From 5.4 to 6.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.4-to-6.0.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md index 9d67a3bcb96..c5eba1c01c4 100644 --- a/doc/update/6.0-to-6.1.md +++ b/doc/update/6.0-to-6.1.md @@ -1,4 +1,5 @@ # From 6.0 to 6.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.0-to-6.1.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md index 11b124cf263..a534528108a 100644 --- a/doc/update/6.1-to-6.2.md +++ b/doc/update/6.1-to-6.2.md @@ -1,4 +1,5 @@ # From 6.1 to 6.2 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.1-to-6.2.md) for the most up to date instructions.* **You should update to 6.1 before installing 6.2 so all the necessary conversions are run.** diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md index e9b3bdd2f54..b08ebde0808 100644 --- a/doc/update/6.2-to-6.3.md +++ b/doc/update/6.2-to-6.3.md @@ -1,4 +1,5 @@ # From 6.2 to 6.3 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.2-to-6.3.md) for the most up to date instructions.* **Requires version: 6.1 or 6.2.** diff --git a/doc/update/6.3-to-6.4.md b/doc/update/6.3-to-6.4.md index 96c2895981d..951d92dfeb5 100644 --- a/doc/update/6.3-to-6.4.md +++ b/doc/update/6.3-to-6.4.md @@ -1,4 +1,5 @@ # From 6.3 to 6.4 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.3-to-6.4.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/6.4-to-6.5.md b/doc/update/6.4-to-6.5.md index 1624296fc3f..0dae9a9fe59 100644 --- a/doc/update/6.4-to-6.5.md +++ b/doc/update/6.4-to-6.5.md @@ -1,4 +1,5 @@ # From 6.4 to 6.5 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.4-to-6.5.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/6.5-to-6.6.md b/doc/update/6.5-to-6.6.md index 544eee17fec..c24e83eb006 100644 --- a/doc/update/6.5-to-6.6.md +++ b/doc/update/6.5-to-6.6.md @@ -1,4 +1,5 @@ # From 6.5 to 6.6 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.5-to-6.6.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/6.6-to-6.7.md b/doc/update/6.6-to-6.7.md index 77ac4d0bfa6..5622a7001ed 100644 --- a/doc/update/6.6-to-6.7.md +++ b/doc/update/6.6-to-6.7.md @@ -1,4 +1,5 @@ # From 6.6 to 6.7 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.6-to-6.7.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/6.7-to-6.8.md b/doc/update/6.7-to-6.8.md index 16f3439c998..4fb90639f16 100644 --- a/doc/update/6.7-to-6.8.md +++ b/doc/update/6.7-to-6.8.md @@ -1,4 +1,5 @@ # From 6.7 to 6.8 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.7-to-6.8.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/6.8-to-6.9.md b/doc/update/6.8-to-6.9.md index 9efb384ff59..b9b8b63f652 100644 --- a/doc/update/6.8-to-6.9.md +++ b/doc/update/6.8-to-6.9.md @@ -1,4 +1,5 @@ # From 6.8 to 6.9 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.8-to-6.9.md) for the most up to date instructions.* ### 0. Backup diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md index 1f3421a799b..236430b5951 100644 --- a/doc/update/6.9-to-7.0.md +++ b/doc/update/6.9-to-7.0.md @@ -1,4 +1,5 @@ # From 6.9 to 7.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.9-to-7.0.md) for the most up to date instructions.* ### 0. Backup diff --git a/doc/update/6.x-or-7.x-to-7.7.md b/doc/update/6.x-or-7.x-to-7.7.md index e9a0d3d4c65..8280cf2f38f 100644 --- a/doc/update/6.x-or-7.x-to-7.7.md +++ b/doc/update/6.x-or-7.x-to-7.7.md @@ -1,4 +1,5 @@ # From 6.x or 7.x to 7.7 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.4.md) for the most up to date instructions.* This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.7. @@ -199,7 +200,7 @@ If all items are green, then congratulations upgrade complete! When using Google omniauth login, changes of the Google account required. Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/). -More details can be found at the [integration documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/google.md). +More details can be found at the [integration documentation](../../../master/doc/integration/google.md). ## 12. Optional optimizations for GitLab setups with MySQL databases diff --git a/doc/update/7.0-to-7.1.md b/doc/update/7.0-to-7.1.md index 82bb5708734..a4e9be9946e 100644 --- a/doc/update/7.0-to-7.1.md +++ b/doc/update/7.0-to-7.1.md @@ -1,4 +1,5 @@ # From 7.0 to 7.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.0-to-7.1.md) for the most up to date instructions.* ### 0. Backup diff --git a/doc/update/7.1-to-7.2.md b/doc/update/7.1-to-7.2.md index 699111f0143..88cb63d7d41 100644 --- a/doc/update/7.1-to-7.2.md +++ b/doc/update/7.1-to-7.2.md @@ -1,4 +1,5 @@ # From 7.1 to 7.2 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.1-to-7.2.md) for the most up to date instructions.* ## Editable labels diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md index ebdd4ff60fa..18f77d6396e 100644 --- a/doc/update/7.2-to-7.3.md +++ b/doc/update/7.2-to-7.3.md @@ -1,4 +1,5 @@ # From 7.2 to 7.3 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.2-to-7.3.md) for the most up to date instructions.* ### 0. Backup diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md index 62bd98832c2..53e739c06fb 100644 --- a/doc/update/7.3-to-7.4.md +++ b/doc/update/7.3-to-7.4.md @@ -1,4 +1,5 @@ # From 7.3 to 7.4 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.3-to-7.4.md) for the most up to date instructions.* ### 0. Stop server @@ -68,14 +69,17 @@ git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gi sudo -u git -H editor config/unicorn.rb ``` -#### Change nginx https settings +#### Change Nginx HTTPS settings * HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting #### MySQL Databases: Update database.yml config file -* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql) +* Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql](/config/database.yml.mysql) +``` +sudo -u git -H editor config/database.yml +``` ### 5. Start application diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 229689392b8..6af940cca36 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -1,4 +1,5 @@ # Migrating GitLab from MySQL to Postgres +*Make sure you view this [guide from the `master` branch](../../../master/doc/update/mysql_to_postgresql.md) for the most up to date instructions.* If you are replacing MySQL with Postgres while keeping GitLab on the same server all you need to do is to export from MySQL, import into Postgres and rebuild the indexes as described below. If you are also moving GitLab to another server, or if you are switching to omnibus-gitlab, you may want to use a GitLab backup file. The second part of this documents explains the procedure to do this. diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index 629c46ad030..ad302492556 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -1,4 +1,5 @@ # Universal update guide for patch versions +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/patch_versions.md) for the most up to date instructions.* For example from 6.2.0 to 6.2.1, also see the [semantic versioning specification](http://semver.org/). diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md index 5016ee4baad..4ed35b2b562 100644 --- a/doc/update/upgrader.md +++ b/doc/update/upgrader.md @@ -1,4 +1,5 @@ # GitLab Upgrader +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/upgrader.md) for the most up to date instructions.* GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version. diff --git a/features/project/service.feature b/features/project/service.feature index 85939a5c9ca..d0600aca010 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -72,4 +72,9 @@ Feature: Project Services And I click jetBrains TeamCity CI service link And I fill jetBrains TeamCity CI settings Then I should see jetBrains TeamCity CI service settings saved - + + Scenario: Activate Asana service + When I visit project "Shop" services page + And I click Asana service link + And I fill Asana settings + Then I should see Asana service settings saved diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 5e45063b4b5..6bcec48be88 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -33,7 +33,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps end step 'I should be redirected to group page' do - current_path.should == admin_group_path(Group.last) + current_path.should == admin_group_path(Group.find_by(path: 'gitlab')) end When 'I select user "John Doe" from user list as "Reporter"' do diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 895ee7ba081..610e7fd3a48 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -83,7 +83,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'I should be redirected to group "Samurai" page' do - current_path.should == group_path(Group.last) + current_path.should == group_path(Group.find_by(name: 'Samurai')) end step 'I should see newly created group "Samurai"' do diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index ccef84cdcc5..a5484ad3a00 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -70,8 +70,8 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps find("#merge_request_source_branch").value.should have_content "new_design" find("#merge_request_target_branch").value.should have_content "master" find("#merge_request_title").value.should == "New Design" - verify_commit_link(".mr_target_commit",@project) - verify_commit_link(".mr_source_commit",@forked_project) + verify_commit_link(".mr_target_commit", @project) + verify_commit_link(".mr_source_commit", @forked_project) end step 'I update the merge request title' do @@ -114,7 +114,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps step 'I fill out an invalid "Merge Request On Forked Project" merge request' do select "Select branch", from: "merge_request_target_branch" find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s - find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s + find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s find(:select, "merge_request_source_branch", {}).value.should == "" find(:select, "merge_request_target_branch", {}).value.should == "" click_button "Compare branches" @@ -125,7 +125,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'the target repository should be the original repository' do - page.should have_select("merge_request_target_project_id", selected: project.path_with_namespace) + page.should have_select("merge_request_target_project_id", selected: @project.path_with_namespace) end # Verify a link is generated against the correct project diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 09e86447058..957a16d06a8 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -16,6 +16,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps page.should have_content 'Pushover' page.should have_content 'Atlassian Bamboo' page.should have_content 'JetBrains TeamCity' + page.should have_content 'Asana' end step 'I click gitlab-ci service link' do @@ -102,6 +103,22 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps find_field('Token').value.should == 'verySecret' end + step 'I click Asana service link' do + click_link 'Asana' + end + + step 'I fill Asana settings' do + check 'Active' + fill_in 'Api key', with: 'verySecret' + fill_in 'Restrict to branch', with: 'master' + click_button 'Save' + end + + step 'I should see Asana service settings saved' do + find_field('Api key').value.should == 'verySecret' + find_field('Restrict to branch').value.should == 'master' + end + step 'I click email on push service link' do click_link 'Emails on push' end diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 770e8162497..1fe01e55aa4 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -174,7 +174,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps click_link 'add a file' # Remove pre-receive hook so we can push without auth - FileUtils.rm(File.join(Project.last.repository.path, 'hooks', 'pre-receive')) + FileUtils.rm(File.join(@project.repository.path, 'hooks', 'pre-receive')) end private diff --git a/lib/api/entities.rb b/lib/api/entities.rb index fa76a54c2d8..8d0664386b4 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -270,5 +270,9 @@ module API class Contributor < Grape::Entity expose :name, :email, :commits, :additions, :deletions end + + class BroadcastMessage < Grape::Entity + expose :message, :starts_at, :ends_at, :color, :font + end end end diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb index 4373070083a..c9c9ccbcb2e 100644 --- a/lib/api/group_members.rb +++ b/lib/api/group_members.rb @@ -40,6 +40,30 @@ module API present member.user, with: Entities::GroupMember, group: group end + # Update group member + # + # Parameters: + # id (required) - The ID of a group + # user_id (required) - The ID of a group member + # access_level (required) - Project access level + # Example Request: + # PUT /groups/:id/members/:user_id + put ':id/members/:user_id' do + group = find_group(params[:id]) + authorize! :manage_group, group + required_attributes! [:access_level] + + team_member = group.group_members.find_by(user_id: params[:user_id]) + not_found!('User can not be found') if team_member.nil? + + if team_member.update_attributes(access_level: params[:access_level]) + @member = team_member.user + present @member, with: Entities::GroupMember, group: group + else + handle_member_errors team_member.errors + end + end + # Remove member. # # Parameters: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index be9e4280d65..a50ee4659a3 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -154,6 +154,22 @@ module API Gitlab::Access.options_with_owner.values.include? level.to_i end + def issuable_order_by + if params["order_by"] == 'updated_at' + 'updated_at' + else + 'created_at' + end + end + + def issuable_sort + if params["sort"] == 'asc' + :asc + else + :desc + end + end + # error helpers def forbidden!(reason = nil) @@ -222,5 +238,10 @@ module API def secret_token File.read(Rails.root.join('.gitlab_shell_secret')) end + + def handle_member_errors(errors) + error!(errors[:access_level], 422) if errors[:access_level].any? + not_found!(errors) + end end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 7a89a26facc..b5542c1874b 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -69,6 +69,14 @@ module API gitlab_rev: Gitlab::REVISION, } end + + get "/broadcast_message" do + if message = BroadcastMessage.current + present message, with: Entities::BroadcastMessage + else + not_found! + end + end end end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index d2828b24c36..ff062be6040 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -27,7 +27,9 @@ module API # Parameters: # state (optional) - Return "opened" or "closed" issues # labels (optional) - Comma-separated list of label names - + # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` + # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` + # # Example Requests: # GET /issues # GET /issues?state=opened @@ -39,8 +41,7 @@ module API issues = current_user.issues issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? - issues = issues.order('issues.id DESC') - + issues.reorder(issuable_order_by => issuable_sort) present paginate(issues), with: Entities::Issue end end @@ -53,6 +54,8 @@ module API # state (optional) - Return "opened" or "closed" issues # labels (optional) - Comma-separated list of label names # milestone (optional) - Milestone title + # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` + # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` # # Example Requests: # GET /projects/:id/issues @@ -67,11 +70,12 @@ module API issues = user_project.issues issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? + unless params[:milestone].nil? issues = filter_issues_milestone(issues, params[:milestone]) end - issues = issues.order('issues.id DESC') + issues.reorder(issuable_order_by => issuable_sort) present paginate(issues), with: Entities::Issue end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index a0ebd8d0c1b..25b7857f4b1 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -25,6 +25,8 @@ module API # Parameters: # id (required) - The ID of a project # state (optional) - Return requests "merged", "opened" or "closed" + # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` + # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` # # Example: # GET /projects/:id/merge_requests @@ -37,25 +39,18 @@ module API # get ":id/merge_requests" do authorize! :read_merge_request, user_project + merge_requests = user_project.merge_requests + + merge_requests = + case params["state"] + when "opened" then merge_requests.opened + when "closed" then merge_requests.closed + when "merged" then merge_requests.merged + else merge_requests + end - mrs = case params["state"] - when "opened" then user_project.merge_requests.opened - when "closed" then user_project.merge_requests.closed - when "merged" then user_project.merge_requests.merged - else user_project.merge_requests - end - - sort = case params["sort"] - when 'desc' then 'DESC' - else 'ASC' - end - - mrs = case params["order_by"] - when 'updated_at' then mrs.order("updated_at #{sort}") - else mrs.order("created_at #{sort}") - end - - present paginate(mrs), with: Entities::MergeRequest + merge_requests.reorder(issuable_order_by => issuable_sort) + present paginate(merge_requests), with: Entities::MergeRequest end # Show MR diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb index 1e890f9e199..73cf062155b 100644 --- a/lib/api/project_members.rb +++ b/lib/api/project_members.rb @@ -4,14 +4,6 @@ module API before { authenticate! } resource :projects do - helpers do - def handle_project_member_errors(errors) - if errors[:access_level].any? - error!(errors[:access_level], 422) - end - not_found!(errors) - end - end # Get a project team members # @@ -66,7 +58,7 @@ module API @member = team_member.user present @member, with: Entities::ProjectMember, project: user_project else - handle_project_member_errors team_member.errors + handle_member_errors team_member.errors end end @@ -89,7 +81,7 @@ module API @member = team_member.user present @member, with: Entities::ProjectMember, project: user_project else - handle_project_member_errors team_member.errors + handle_member_errors team_member.errors end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index d96288bb982..0677e85beab 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -11,6 +11,37 @@ module API attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true attrs end + + def filter_projects(projects) + # If the archived parameter is passed, limit results accordingly + if params[:archived].present? + projects = projects.where(archived: parse_boolean(params[:archived])) + end + + if params[:search].present? + projects = projects.search(params[:search]) + end + + projects.reorder(project_order_by => project_sort) + end + + def project_order_by + order_fields = %w(id name path created_at updated_at last_activity_at) + + if order_fields.include?(params['order_by']) + params['order_by'] + else + 'created_at' + end + end + + def project_sort + if params["sort"] == 'asc' + :asc + else + :desc + end + end end # Get a projects list for authenticated user @@ -19,25 +50,7 @@ module API # GET /projects get do @projects = current_user.authorized_projects - sort = params[:sort] == 'desc' ? 'desc' : 'asc' - - @projects = case params["order_by"] - when 'id' then @projects.reorder("id #{sort}") - when 'name' then @projects.reorder("name #{sort}") - when 'created_at' then @projects.reorder("created_at #{sort}") - when 'last_activity_at' then @projects.reorder("last_activity_at #{sort}") - else @projects - end - - # If the archived parameter is passed, limit results accordingly - if params[:archived].present? - @projects = @projects.where(archived: parse_boolean(params[:archived])) - end - - if params[:search].present? - @projects = @projects.search(params[:search]) - end - + @projects = filter_projects(@projects) @projects = paginate @projects present @projects, with: Entities::Project end @@ -47,16 +60,8 @@ module API # Example Request: # GET /projects/owned get '/owned' do - sort = params[:sort] == 'desc' ? 'desc' : 'asc' @projects = current_user.owned_projects - @projects = case params["order_by"] - when 'id' then @projects.reorder("id #{sort}") - when 'name' then @projects.reorder("name #{sort}") - when 'created_at' then @projects.reorder("created_at #{sort}") - when 'last_activity_at' then @projects.reorder("last_activity_at #{sort}") - else @projects - end - + @projects = filter_projects(@projects) @projects = paginate @projects present @projects, with: Entities::Project end @@ -67,16 +72,8 @@ module API # GET /projects/all get '/all' do authenticated_as_admin! - sort = params[:sort] == 'desc' ? 'desc' : 'asc' - - @projects = case params["order_by"] - when 'id' then Project.order("id #{sort}") - when 'name' then Project.order("name #{sort}") - when 'created_at' then Project.order("created_at #{sort}") - when 'last_activity_at' then Project.order("last_activity_at #{sort}") - else Project - end - + @projects = Project.all + @projects = filter_projects(@projects) @projects = paginate @projects present @projects, with: Entities::Project end diff --git a/lib/gitlab/github/client.rb b/lib/gitlab/github/client.rb deleted file mode 100644 index d6b936c649c..00000000000 --- a/lib/gitlab/github/client.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Gitlab - module Github - class Client - attr_reader :client - - def initialize - @client = ::OAuth2::Client.new( - config.app_id, - config.app_secret, - github_options - ) - end - - private - - def config - Gitlab.config.omniauth.providers.select{|provider| provider.name == "github"}.first - end - - def github_options - { - site: 'https://api.github.com', - authorize_url: 'https://github.com/login/oauth/authorize', - token_url: 'https://github.com/login/oauth/access_token' - } - end - end - end -end diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb new file mode 100644 index 00000000000..c9904fe8779 --- /dev/null +++ b/lib/gitlab/github_import/client.rb @@ -0,0 +1,57 @@ +module Gitlab + module GithubImport + class Client + attr_reader :client, :api + + def initialize(access_token) + @client = ::OAuth2::Client.new( + config.app_id, + config.app_secret, + github_options + ) + + if access_token + ::Octokit.auto_paginate = true + @api = ::Octokit::Client.new(access_token: access_token) + end + end + + def authorize_url(redirect_uri) + client.auth_code.authorize_url({ + redirect_uri: redirect_uri, + scope: "repo, user, user:email" + }) + end + + def get_token(code) + client.auth_code.get_token(code).token + end + + def method_missing(method, *args, &block) + if api.respond_to?(method) + api.send(method, *args, &block) + else + super(method, *args, &block) + end + end + + def respond_to?(method) + api.respond_to?(method) || super + end + + private + + def config + Gitlab.config.omniauth.providers.find{|provider| provider.name == "github"} + end + + def github_options + { + site: 'https://api.github.com', + authorize_url: 'https://github.com/login/oauth/authorize', + token_url: 'https://github.com/login/oauth/access_token' + } + end + end + end +end diff --git a/lib/gitlab/github/importer.rb b/lib/gitlab/github_import/importer.rb index 9f0fc6c4471..bc2b645b2d9 100644 --- a/lib/gitlab/github/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -1,24 +1,26 @@ module Gitlab - module Github + module GithubImport class Importer - attr_reader :project + attr_reader :project, :client def initialize(project) @project = project + @client = Client.new(project.creator.github_access_token) + @formatter = Gitlab::ImportFormatter.new end def execute - client = octo_client(project.creator.github_access_token) - #Issues && Comments client.list_issues(project.import_source, state: :all).each do |issue| if issue.pull_request.nil? - body = "*Created by: #{issue.user.login}*\n\n#{issue.body}" + + body = @formatter.author_line(issue.user.login, issue.body) if issue.comments > 0 - body += "\n\n\n**Imported comments:**\n" + body += @formatter.comments_header + client.issue_comments(project.import_source, issue.number).each do |c| - body += "\n\n*By #{c.user.login} on #{c.created_at}*\n\n#{c.body}" + body += @formatter.comment_to_md(c.user.login, c.created_at, c.body) end end @@ -34,13 +36,9 @@ module Gitlab private - def octo_client(access_token) - ::Octokit.auto_paginate = true - ::Octokit::Client.new(access_token: access_token) - end - def gl_user_id(project, github_id) - user = User.joins(:identities).find_by("identities.extern_uid = ?", github_id.to_s) + user = User.joins(:identities). + find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s) (user && user.id) || project.creator_id end end diff --git a/lib/gitlab/github/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index 7b04926071f..9439ca6cbf4 100644 --- a/lib/gitlab/github/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -1,5 +1,5 @@ module Gitlab - module Github + module GithubImport class ProjectCreator attr_reader :repo, :namespace, :current_user diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb new file mode 100644 index 00000000000..2206b68da99 --- /dev/null +++ b/lib/gitlab/gitlab_import/client.rb @@ -0,0 +1,82 @@ +module Gitlab + module GitlabImport + class Client + attr_reader :client, :api + + PER_PAGE = 100 + + def initialize(access_token) + @client = ::OAuth2::Client.new( + config.app_id, + config.app_secret, + github_options + ) + + if access_token + @api = OAuth2::AccessToken.from_hash(@client, access_token: access_token) + end + end + + def authorize_url(redirect_uri) + client.auth_code.authorize_url({ + redirect_uri: redirect_uri, + scope: "api" + }) + end + + def get_token(code, redirect_uri) + client.auth_code.get_token(code, redirect_uri: redirect_uri).token + end + + def issues(project_identifier) + lazy_page_iterator(PER_PAGE) do |page| + api.get("/api/v3/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed + end + end + + def issue_comments(project_identifier, issue_id) + lazy_page_iterator(PER_PAGE) do |page| + api.get("/api/v3/projects/#{project_identifier}/issues/#{issue_id}/notes?per_page=#{PER_PAGE}&page=#{page}").parsed + end + end + + def project(id) + api.get("/api/v3/projects/#{id}").parsed + end + + def projects + lazy_page_iterator(PER_PAGE) do |page| + api.get("/api/v3/projects?per_page=#{PER_PAGE}&page=#{page}").parsed + end + end + + private + + def lazy_page_iterator(per_page) + Enumerator.new do |y| + page = 1 + loop do + items = yield(page) + items.each do |item| + y << item + end + break if items.empty? || items.size < per_page + page += 1 + end + end + end + + def config + Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"} + end + + def github_options + { + site: 'https://gitlab.com/', + authorize_url: 'oauth/authorize', + token_url: 'oauth/token' + } + end + end + end +end diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb new file mode 100644 index 00000000000..5f9b14399a4 --- /dev/null +++ b/lib/gitlab/gitlab_import/importer.rb @@ -0,0 +1,50 @@ +module Gitlab + module GitlabImport + class Importer + attr_reader :project, :client + + def initialize(project) + @project = project + @client = Client.new(project.creator.gitlab_access_token) + @formatter = Gitlab::ImportFormatter.new + end + + def execute + project_identifier = URI.encode(project.import_source, '/') + + #Issues && Comments + issues = client.issues(project_identifier) + + issues.each do |issue| + body = @formatter.author_line(issue["author"]["name"], issue["description"]) + + comments = client.issue_comments(project_identifier, issue["id"]) + + if comments.any? + body += @formatter.comments_header + end + + comments.each do |comment| + body += @formatter.comment_to_md(comment["author"]["name"], comment["created_at"], comment["body"]) + end + + project.issues.create!( + description: body, + title: issue["title"], + state: issue["state"], + author_id: gl_user_id(project, issue["author"]["id"]) + ) + end + + true + end + + private + + def gl_user_id(project, gitlab_id) + user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'gitlab'", gitlab_id.to_s) + (user && user.id) || project.creator_id + end + end + end +end diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb new file mode 100644 index 00000000000..6424d56f8f1 --- /dev/null +++ b/lib/gitlab/gitlab_import/project_creator.rb @@ -0,0 +1,39 @@ +module Gitlab + module GitlabImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user + + def initialize(repo, namespace, current_user) + @repo = repo + @namespace = namespace + @current_user = current_user + end + + def execute + @project = Project.new( + name: repo["name"], + path: repo["path"], + description: repo["description"], + namespace: namespace, + creator: current_user, + visibility_level: repo["visibility_level"], + import_type: "gitlab", + import_source: repo["path_with_namespace"], + import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{current_user.gitlab_access_token}@") + ) + + if @project.save! + @project.reload + + if @project.import_failed? + @project.import_retry + else + @project.import_start + end + end + + @project + end + end + end +end diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb new file mode 100644 index 00000000000..ebb4b87f7e3 --- /dev/null +++ b/lib/gitlab/import_formatter.rb @@ -0,0 +1,15 @@ +module Gitlab + class ImportFormatter + def comment_to_md(author, date, body) + "\n\n*By #{author} on #{date}*\n\n#{body}" + end + + def comments_header + "\n\n\n**Imported comments:**\n" + end + + def author_line(author, body) + "*Created by: #{author}*\n\n#{body}" + end + end +end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index c0e83fb3078..fb0218a2778 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -92,7 +92,7 @@ module Gitlab allowed_tags = ActionView::Base.sanitized_allowed_tags sanitize text.html_safe, - attributes: allowed_attributes + %w(id class), + attributes: allowed_attributes + %w(id class style), tags: allowed_tags + %w(table tr td th) end @@ -128,6 +128,7 @@ module Gitlab (?<prefix>\W)? # Prefix ( # Reference @(?<user>#{NAME_STR}) # User name + |~(?<label>\d+) # Label ID |(?<issue>([A-Z\-]+-)\d+) # JIRA Issue ID |#{PROJ_STR}?\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID |#{PROJ_STR}?!(?<merge_request>\d+) # MR ID @@ -138,7 +139,7 @@ module Gitlab (?<suffix>\W)? # Suffix }x.freeze - TYPES = [:user, :issue, :merge_request, :snippet, :commit].freeze + TYPES = [:user, :issue, :label, :merge_request, :snippet, :commit].freeze def parse_references(text, project = @project) # parse reference links @@ -202,8 +203,28 @@ module Gitlab if identifier == "all" link_to("@all", project_url(project), options) - elsif User.find_by(username: identifier) - link_to("@#{identifier}", user_url(identifier), options) + elsif namespace = Namespace.find_by(path: identifier) + url = + if namespace.type == "Group" + group_url(identifier) + else + user_url(identifier) + end + + link_to("@#{identifier}", url, options) + end + end + + def reference_label(identifier, project = @project, _ = nil) + if label = project.labels.find_by(id: identifier) + options = html_options.merge( + class: "gfm gfm-label #{html_options[:class]}" + ) + link_to( + render_colored_label(label), + project_issues_path(project, label_name: label.name), + options + ) end end diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 99165950aef..7e5c991a222 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -1,12 +1,13 @@ module Gitlab # Extract possible GFM references from an arbitrary String for further processing. class ReferenceExtractor - attr_accessor :users, :issues, :merge_requests, :snippets, :commits + attr_accessor :users, :labels, :issues, :merge_requests, :snippets, :commits include Markdown def initialize - @users, @issues, @merge_requests, @snippets, @commits = [], [], [], [], [] + @users, @labels, @issues, @merge_requests, @snippets, @commits = + [], [], [], [], [], [] end def analyze(string, project) @@ -22,6 +23,12 @@ module Gitlab end.reject(&:nil?) end + def labels_for(project = nil) + labels.map do |entry| + project.labels.where(id: entry[:id]).first + end.reject(&:nil?) + end + def issues_for(project = nil) issues.map do |entry| if should_lookup?(project, entry[:project]) @@ -64,7 +71,7 @@ module Gitlab if entry_project.nil? false else - project.nil? || project.id == entry_project.id + project.nil? || entry_project.default_issues_tracker? end end end diff --git a/spec/controllers/github_imports_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index 26e7854fea3..f80b3884d88 100644 --- a/spec/controllers/github_imports_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GithubImportsController do +describe Import::GithubController do let(:user) { create(:user, github_access_token: 'asd123') } before do @@ -10,13 +10,13 @@ describe GithubImportsController do describe "GET callback" do it "updates access token" do token = "asdasd12345" - Gitlab::Github::Client.any_instance.stub_chain(:client, :auth_code, :get_token, :token).and_return(token) + Gitlab::GithubImport::Client.any_instance.stub(:get_token).and_return(token) Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "github") get :callback user.reload.github_access_token.should == token - controller.should redirect_to(status_github_import_url) + controller.should redirect_to(status_import_github_url) end end @@ -27,8 +27,8 @@ describe GithubImportsController do it "assigns variables" do @project = create(:project, import_type: 'github', creator_id: user.id) - controller.stub_chain(:octo_client, :repos).and_return([@repo]) - controller.stub_chain(:octo_client, :orgs).and_return([]) + controller.stub_chain(:client, :repos).and_return([@repo]) + controller.stub_chain(:client, :orgs).and_return([]) get :status @@ -38,8 +38,8 @@ describe GithubImportsController do it "does not show already added project" do @project = create(:project, import_type: 'github', creator_id: user.id, import_source: 'asd/vim') - controller.stub_chain(:octo_client, :repos).and_return([@repo]) - controller.stub_chain(:octo_client, :orgs).and_return([]) + controller.stub_chain(:client, :repos).and_return([@repo]) + controller.stub_chain(:client, :orgs).and_return([]) get :status @@ -55,9 +55,9 @@ describe GithubImportsController do it "takes already existing namespace" do namespace = create(:namespace, name: "john", owner: user) - Gitlab::Github::ProjectCreator.should_receive(:new).with(@repo, namespace, user). + Gitlab::GithubImport::ProjectCreator.should_receive(:new).with(@repo, namespace, user). and_return(double(execute: true)) - controller.stub_chain(:octo_client, :repo).and_return(@repo) + controller.stub_chain(:client, :repo).and_return(@repo) post :create, format: :js end diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb new file mode 100644 index 00000000000..36995091c69 --- /dev/null +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +describe Import::GitlabController do + let(:user) { create(:user, gitlab_access_token: 'asd123') } + + before do + sign_in(user) + end + + describe "GET callback" do + it "updates access token" do + token = "asdasd12345" + Gitlab::GitlabImport::Client.any_instance.stub_chain(:client, :auth_code, :get_token, :token).and_return(token) + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "gitlab") + + get :callback + + user.reload.gitlab_access_token.should == token + controller.should redirect_to(status_import_gitlab_url) + end + end + + describe "GET status" do + before do + @repo = OpenStruct.new(path: 'vim', path_with_namespace: 'asd/vim') + end + + it "assigns variables" do + @project = create(:project, import_type: 'gitlab', creator_id: user.id) + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([@repo]) + end + + it "does not show already added project" do + @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim') + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([]) + end + end + + describe "POST create" do + before do + @repo = { + path: 'vim', + path_with_namespace: 'asd/vim', + owner: {name: "john"}, + namespace: {path: "john"} + }.with_indifferent_access + end + + it "takes already existing namespace" do + namespace = create(:namespace, name: "john", owner: user) + Gitlab::GitlabImport::ProjectCreator.should_receive(:new).with(@repo, namespace, user). + and_return(double(execute: true)) + controller.stub_chain(:client, :project).and_return(@repo) + + post :create, format: :js + end + end +end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 82da19746f8..59c4ffb5624 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -32,14 +32,14 @@ describe "Admin::Users", feature: true do it "should apply defaults to user" do click_button "Create user" - user = User.last + user = User.find_by(username: 'bang') user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group end it "should create user with valid data" do click_button "Create user" - user = User.last + user = User.find_by(username: 'bang') user.name.should == "Big Bang" user.email.should == "bigbang@mail.com" end @@ -52,7 +52,7 @@ describe "Admin::Users", feature: true do it "should send valid email to user with email & password" do click_button "Create user" - user = User.last + user = User.find_by(username: 'bang') email = ActionMailer::Base.deliveries.last email.subject.should have_content("Account was created") email.text_part.body.should have_content(user.email) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index e6fa376f3eb..29aeb6a400a 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Issues", feature: true do + include SortingHelper + let(:project) { create(:project) } before do @@ -80,7 +82,7 @@ describe "Issues", feature: true do title: title) end - @issue = Issue.first # with title 'foobar' + @issue = Issue.find_by(title: 'foobar') @issue.milestone = create(:milestone, project: project) @issue.assignee = nil @issue.save @@ -130,14 +132,14 @@ describe "Issues", feature: true do let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') } it 'sorts by newest' do - visit project_issues_path(project, sort: 'newest') + visit project_issues_path(project, sort: sort_value_recently_created) first_issue.should include("foo") last_issue.should include("baz") end it 'sorts by oldest' do - visit project_issues_path(project, sort: 'oldest') + visit project_issues_path(project, sort: sort_value_oldest_created) first_issue.should include("baz") last_issue.should include("foo") @@ -146,7 +148,7 @@ describe "Issues", feature: true do it 'sorts by most recently updated' do baz.updated_at = Time.now + 100 baz.save - visit project_issues_path(project, sort: 'recently_updated') + visit project_issues_path(project, sort: sort_value_recently_updated) first_issue.should include("baz") end @@ -154,7 +156,7 @@ describe "Issues", feature: true do it 'sorts by least recently updated' do baz.updated_at = Time.now - 100 baz.save - visit project_issues_path(project, sort: 'last_updated') + visit project_issues_path(project, sort: sort_value_oldest_updated) first_issue.should include("baz") end @@ -168,13 +170,13 @@ describe "Issues", feature: true do end it 'sorts by recently due milestone' do - visit project_issues_path(project, sort: 'milestone_due_soon') + visit project_issues_path(project, sort: sort_value_milestone_soon) first_issue.should include("foo") end it 'sorts by least recently due milestone' do - visit project_issues_path(project, sort: 'milestone_due_later') + visit project_issues_path(project, sort: sort_value_milestone_later) first_issue.should include("bar") end @@ -191,7 +193,7 @@ describe "Issues", feature: true do end it 'sorts with a filter applied' do - visit project_issues_path(project, sort: 'oldest', assignee_id: user2.id) + visit project_issues_path(project, sort: sort_value_oldest_created, assignee_id: user2.id) first_issue.should include("bar") last_issue.should include("foo") diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 8b237199bcc..21a3a4bf937 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -1,19 +1,14 @@ require 'spec_helper' describe 'Users', feature: true do - describe "GET /users/sign_up" do - before do - ApplicationSetting.any_instance.stub(signup_enabled?: true) - end - + describe "GET /users/sign_in" do it "should create a new user account" do - visit new_user_registration_path + visit new_user_session_path fill_in "user_name", with: "Name Surname" fill_in "user_username", with: "Great" fill_in "user_email", with: "name@mail.com" - fill_in "user_password", with: "password1234" - fill_in "user_password_confirmation", with: "password1234" - expect { click_button "Sign up" }.to change {User.count}.by(1) + fill_in "user_password_sign_up", with: "password1234" + expect { click_button "Sign up" }.to change { User.count }.by(1) end end end diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 867455daf23..0a1f3fa351d 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -28,12 +28,102 @@ describe Gitlab::ClosingIssueExtractor do end it do + message = "Closing ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "closing ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Close ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "close ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Awesome commit (Fixes ##{iid1})" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do message = "Awesome commit (fixes ##{iid1})" subject.closed_by_message_in_project(message, project).should == [issue] end it do - message = "Awesome commit (fix ##{iid1})" + message = "Fixed ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "fixed ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Fixing ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "fixing ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Fix ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "fix ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Awesome commit (Resolves ##{iid1})" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Awesome commit (resolves ##{iid1})" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Resolved ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "resolved ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Resolving ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "resolving ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "Resolve ##{iid1}" + subject.closed_by_message_in_project(message, project).should == [issue] + end + + it do + message = "resolve ##{iid1}" subject.closed_by_message_in_project(message, project).should == [issue] end end diff --git a/spec/lib/gitlab/gitlab_import/project_creator.rb b/spec/lib/gitlab/gitlab_import/project_creator.rb new file mode 100644 index 00000000000..51f3534ed60 --- /dev/null +++ b/spec/lib/gitlab/gitlab_import/project_creator.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Gitlab::GitlabImport::ProjectCreator do + let(:user) { create(:user, gitlab_access_token: "asdffg") } + let(:repo) {{ + name: 'vim', + path: 'vim', + visibility_level: Gitlab::VisibilityLevel::PRIVATE, + path_with_namespace: 'asd/vim', + http_url_to_repo: "https://gitlab.com/asd/vim.git", + owner: {name: "john"}}.with_indifferent_access + } + let(:namespace){ create(:namespace) } + + it 'creates project' do + Project.any_instance.stub(:add_import_job) + + project_creator = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, user) + project_creator.execute + project = Project.last + + project.import_url.should == "https://oauth2:asdffg@gitlab.com/asd/vim.git" + project.visibility_level.should == Gitlab::VisibilityLevel::PRIVATE + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index a0c37587b23..c045f85052c 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -9,9 +9,14 @@ describe Notify do let(:recipient) { create(:user, email: 'recipient@example.com') } let(:project) { create(:project) } + before(:each) do + email = recipient.emails.create(email: "notifications@example.com") + recipient.update_attribute(:notification_email, email.email) + end + shared_examples 'a multiple recipients email' do it 'is sent to the given recipient' do - should deliver_to recipient.email + should deliver_to recipient.notification_email end end @@ -26,6 +31,7 @@ describe Notify do shared_examples 'an email starting a new thread' do |message_id_prefix| it 'has a discussion identifier' do should have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + should have_header 'X-GitLab-Project', /#{project.name}/ end end @@ -37,6 +43,7 @@ describe Notify do it 'has headers that reference an existing thread' do should have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ should have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + should have_header 'X-GitLab-Project', /#{project.name}/ end end @@ -441,7 +448,7 @@ describe Notify do end it 'is sent to the given recipient' do - should deliver_to recipient.email + should deliver_to recipient.notification_email end it 'contains the message from the note' do diff --git a/spec/models/asana_service_spec.rb b/spec/models/asana_service_spec.rb new file mode 100644 index 00000000000..6bebb76f8c7 --- /dev/null +++ b/spec/models/asana_service_spec.rb @@ -0,0 +1,60 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +require 'spec_helper' + +describe AsanaService, models: true do + describe 'Associations' do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe 'Validations' do + context 'active' do + before do + subject.active = true + end + + it { should validate_presence_of :api_key } + end + end + + describe 'Execute' do + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + @asana = AsanaService.new + @asana.stub( + project: project, + project_id: project.id, + service_hook: true, + api_key: 'verySecret', + restrict_to_branch: 'master' + ) + end + + it 'should call Asana service to created a story' do + Asana::Task.should_receive(:find).with('123456').once + + @asana.check_commit('related to #123456', 'pushed') + end + + it 'should call Asana service to created a story and close a task' do + Asana::Task.should_receive(:find).with('456789').twice + + @asana.check_commit('fix #456789', 'pushed') + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 4669a9fd87d..e2197420018 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -51,6 +51,7 @@ describe Project do it { should have_one(:forked_project_link).dependent(:destroy) } it { should have_one(:slack_service).dependent(:destroy) } it { should have_one(:pushover_service).dependent(:destroy) } + it { should have_one(:asana_service).dependent(:destroy) } end describe 'Mass assignment' do diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index c96f2b20529..1df34f56cf1 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -5,11 +5,12 @@ # id :integer not null, primary key # type :string(255) # title :string(255) -# project_id :integer not null +# project_id :integer # created_at :datetime # updated_at :datetime # active :boolean default(FALSE), not null # properties :text +# template :boolean default(FALSE) # require 'spec_helper' @@ -59,4 +60,29 @@ describe Service do end end end + + describe "Template" do + describe "for pushover service" do + let(:service_template) { + PushoverService.create(template: true, properties: {device: 'MyDevice', sound: 'mic', priority: 4, api_key: '123456789'}) + } + let(:project) { create(:project) } + + describe 'should be prefilled for projects pushover service' do + before do + service_template + project.build_missing_services + end + + it "should have all fields prefilled" do + service = project.pushover_service + expect(service.template).to eq(false) + expect(service.device).to eq('MyDevice') + expect(service.sound).to eq('mic') + expect(service.priority).to eq(4) + expect(service.api_key).to eq('123456789') + end + end + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 83341e516a5..629d51b960d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -474,7 +474,7 @@ describe User do @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha' @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega' end - + it "sorts users as recently_signed_in" do User.sort('recent_sign_in').first.should == @user end @@ -484,11 +484,11 @@ describe User do end it "sorts users as recently_created" do - User.sort('recently_created').first.should == @user + User.sort('created_desc').first.should == @user end it "sorts users as late_created" do - User.sort('late_created').first.should == @user1 + User.sort('created_asc').first.should == @user1 end it "sorts users by name when nil is passed" do diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb index 4957186f605..43d26d67efe 100644 --- a/spec/requests/api/group_members_spec.rb +++ b/spec/requests/api/group_members_spec.rb @@ -104,6 +104,69 @@ describe API::API, api: true do end end + describe 'PUT /groups/:id/members/:user_id' do + context 'when not a member of the group' do + it 'should return a 409 error if the user is not a group member' do + put( + api("/groups/#{group_no_members.id}/members/#{developer.id}", + owner), access_level: GroupMember::MASTER + ) + expect(response.status).to eq(404) + end + end + + context 'when a member of the group' do + it 'should return ok and update member access level' do + put( + api("/groups/#{group_with_members.id}/members/#{reporter.id}", + owner), + access_level: GroupMember::MASTER + ) + + expect(response.status).to eq(200) + + get api("/groups/#{group_with_members.id}/members", owner) + json_reporter = json_response.find do |e| + e['id'] == reporter.id + end + + expect(json_reporter['access_level']).to eq(GroupMember::MASTER) + end + + it 'should not allow guest to modify group members' do + put( + api("/groups/#{group_with_members.id}/members/#{developer.id}", + guest), + access_level: GroupMember::MASTER + ) + + expect(response.status).to eq(403) + + get api("/groups/#{group_with_members.id}/members", owner) + json_developer = json_response.find do |e| + e['id'] == developer.id + end + + expect(json_developer['access_level']).to eq(GroupMember::DEVELOPER) + end + + it 'should return a 400 error when access level is not given' do + put( + api("/groups/#{group_with_members.id}/members/#{master.id}", owner) + ) + expect(response.status).to eq(400) + end + + it 'should return a 422 error when access level is not known' do + put( + api("/groups/#{group_with_members.id}/members/#{master.id}", owner), + access_level: 1234 + ) + expect(response.status).to eq(422) + end + end + end + describe "DELETE /groups/:id/members/:user_id" do context "when not a member of the group" do it "should not delete guest's membership of group_with_members" do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 4faa1f9b964..1e8e9eb38d6 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -16,6 +16,27 @@ describe API::API, api: true do end end + describe "GET /internal/broadcast_message" do + context "broadcast message exists" do + let!(:broadcast_message) { create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow ) } + + it do + get api("/internal/broadcast_message"), secret_token: secret_token + + response.status.should == 200 + json_response["message"].should == broadcast_message.message + end + end + + context "broadcast message doesn't exist" do + it do + get api("/internal/broadcast_message"), secret_token: secret_token + + response.status.should == 404 + end + end + end + describe "GET /internal/discover" do it do get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) @@ -37,7 +58,7 @@ describe API::API, api: true do pull(key, project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_true + json_response["status"].should be_true end end @@ -46,7 +67,7 @@ describe API::API, api: true do push(key, project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_true + json_response["status"].should be_true end end end @@ -61,7 +82,7 @@ describe API::API, api: true do pull(key, project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_false + json_response["status"].should be_false end end @@ -70,7 +91,7 @@ describe API::API, api: true do push(key, project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_false + json_response["status"].should be_false end end end @@ -87,7 +108,7 @@ describe API::API, api: true do pull(key, personal_project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_false + json_response["status"].should be_false end end @@ -96,7 +117,7 @@ describe API::API, api: true do push(key, personal_project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_false + json_response["status"].should be_false end end end @@ -114,7 +135,7 @@ describe API::API, api: true do pull(key, project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_true + json_response["status"].should be_true end end @@ -123,7 +144,7 @@ describe API::API, api: true do push(key, project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_false + json_response["status"].should be_false end end end @@ -140,7 +161,7 @@ describe API::API, api: true do archive(key, project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_true + json_response["status"].should be_true end end @@ -149,7 +170,7 @@ describe API::API, api: true do archive(key, project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_false + json_response["status"].should be_false end end end @@ -159,7 +180,7 @@ describe API::API, api: true do pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists')) response.status.should == 200 - JSON.parse(response.body)["status"].should be_false + json_response["status"].should be_false end end @@ -168,7 +189,7 @@ describe API::API, api: true do pull(OpenStruct.new(id: 0), project) response.status.should == 200 - JSON.parse(response.body)["status"].should be_false + json_response["status"].should be_false end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 5795082f5cb..b5deb072cd1 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -26,30 +26,34 @@ describe API::API, api: true do response.status.should == 200 json_response.should be_an Array json_response.length.should == 3 - json_response.first['title'].should == merge_request.title + json_response.last['title'].should == merge_request.title end + it "should return an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests?state", user) response.status.should == 200 json_response.should be_an Array json_response.length.should == 3 - json_response.first['title'].should == merge_request.title + json_response.last['title'].should == merge_request.title end + it "should return an array of open merge_requests" do get api("/projects/#{project.id}/merge_requests?state=opened", user) response.status.should == 200 json_response.should be_an Array json_response.length.should == 1 - json_response.first['title'].should == merge_request.title + json_response.last['title'].should == merge_request.title end + it "should return an array of closed merge_requests" do get api("/projects/#{project.id}/merge_requests?state=closed", user) response.status.should == 200 json_response.should be_an Array json_response.length.should == 2 - json_response.first['title'].should == merge_request_closed.title - json_response.second['title'].should == merge_request_merged.title + json_response.second['title'].should == merge_request_closed.title + json_response.first['title'].should == merge_request_merged.title end + it "should return an array of merged merge_requests" do get api("/projects/#{project.id}/merge_requests?state=merged", user) response.status.should == 200 @@ -69,9 +73,10 @@ describe API::API, api: true do response.status.should == 200 json_response.should be_an Array json_response.length.should == 3 - json_response.first['id'].should == @mr_earlier.id - json_response.last['id'].should == @mr_later.id + json_response.last['id'].should == @mr_earlier.id + json_response.first['id'].should == @mr_later.id end + it "should return an array of merge_requests in descending order" do get api("/projects/#{project.id}/merge_requests?sort=desc", user) response.status.should == 200 @@ -80,21 +85,23 @@ describe API::API, api: true do json_response.first['id'].should == @mr_later.id json_response.last['id'].should == @mr_earlier.id end + it "should return an array of merge_requests ordered by updated_at" do get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user) response.status.should == 200 json_response.should be_an Array json_response.length.should == 3 - json_response.first['id'].should == @mr_earlier.id - json_response.last['id'].should == @mr_later.id + json_response.last['id'].should == @mr_earlier.id + json_response.first['id'].should == @mr_later.id end + it "should return an array of merge_requests ordered by created_at" do get api("/projects/#{project.id}/merge_requests?sort=created_at", user) response.status.should == 200 json_response.should be_an Array json_response.length.should == 3 - json_response.first['id'].should == @mr_earlier.id - json_response.last['id'].should == @mr_later.id + json_response.last['id'].should == @mr_earlier.id + json_response.first['id'].should == @mr_later.id end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index dc410107410..65c894ac0c2 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -119,57 +119,33 @@ describe API::API, api: true do describe 'POST /projects' do context 'maximum number of projects reached' do - before do - (1..user2.projects_limit).each do |project| - post api('/projects', user2), name: "foo#{project}" - end - end - - it 'should not create new project' do + it 'should not create new project and respond with 403' do + User.any_instance.stub(:projects_limit_left).and_return(0) expect { post api('/projects', user2), name: 'foo' }.to change {Project.count}.by(0) + response.status.should == 403 end end - it 'should create new project without path' do - expect { post api('/projects', user), name: 'foo' }.to change {Project.count}.by(1) - end - - it 'should not create new project without name' do - expect { post api('/projects', user) }.to_not change {Project.count} - end - - it 'should return a 400 error if name not given' do - post api('/projects', user) - response.status.should == 400 + it 'should create new project without path and return 201' do + expect { post api('/projects', user), name: 'foo' }. + to change { Project.count }.by(1) + response.status.should == 201 end it 'should create last project before reaching project limit' do - (1..user2.projects_limit-1).each { |p| post api('/projects', user2), name: "foo#{p}" } + User.any_instance.stub(:projects_limit_left).and_return(1) post api('/projects', user2), name: 'foo' response.status.should == 201 end - it 'should respond with 201 on success' do - post api('/projects', user), name: 'foo' - response.status.should == 201 - end - - it 'should respond with 400 if name is not given' do - post api('/projects', user) + it 'should not create new project without name and return 400' do + expect { post api('/projects', user) }.to_not change { Project.count } response.status.should == 400 end - it 'should return a 403 error if project limit reached' do - (1..user.projects_limit).each do |p| - post api('/projects', user), name: "foo#{p}" - end - post api('/projects', user), name: 'bar' - response.status.should == 403 - end - - it 'should assign attributes to project' do + it "should assign attributes to project" do project = attributes_for(:project, { path: 'camelCasePath', description: Faker::Lorem.sentence, @@ -232,21 +208,15 @@ describe API::API, api: true do before { project } before { admin } - it 'should create new project without path' do + it 'should create new project without path and return 201' do expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) - end - - it 'should not create new project without name' do - expect { post api("/projects/user/#{user.id}", admin) }.to_not change {Project.count} - end - - it 'should respond with 201 on success' do - post api("/projects/user/#{user.id}", admin), name: 'foo' response.status.should == 201 end - it 'should respond with 400 on failure' do - post api("/projects/user/#{user.id}", admin) + it 'should respond with 400 on failure and not project' do + expect { post api("/projects/user/#{user.id}", admin) }. + to_not change { Project.count } + response.status.should == 400 json_response['message']['name'].should == [ 'can\'t be blank', @@ -350,26 +320,28 @@ describe API::API, api: true do describe 'permissions' do context 'personal project' do - before do + it 'Sets project access and returns 200' do project.team << [user, :master] get api("/projects/#{project.id}", user) - end - it { response.status.should == 200 } - it { json_response['permissions']['project_access']['access_level'].should == Gitlab::Access::MASTER } - it { json_response['permissions']['group_access'].should be_nil } + expect(response.status).to eq(200) + expect(json_response['permissions']['project_access']['access_level']). + to eq(Gitlab::Access::MASTER) + expect(json_response['permissions']['group_access']).to be_nil + end end context 'group project' do - before do + it 'should set the owner and return 200' do project2 = create(:project, group: create(:group)) project2.group.add_owner(user) get api("/projects/#{project2.id}", user) - end - it { response.status.should == 200 } - it { json_response['permissions']['project_access'].should be_nil } - it { json_response['permissions']['group_access']['access_level'].should == Gitlab::Access::OWNER } + expect(response.status).to eq(200) + expect(json_response['permissions']['project_access']).to be_nil + expect(json_response['permissions']['group_access']['access_level']). + to eq(Gitlab::Access::OWNER) + end end end end @@ -432,22 +404,9 @@ describe API::API, api: true do json_response['title'].should == 'api test' end - it 'should return a 400 error if title is not given' do - post api("/projects/#{project.id}/snippets", user), - file_name: 'sample.rb', code: 'test' - response.status.should == 400 - end - - it 'should return a 400 error if file_name not given' do - post api("/projects/#{project.id}/snippets", user), - title: 'api test', code: 'test' - response.status.should == 400 - end - - it 'should return a 400 error if code not given' do - post api("/projects/#{project.id}/snippets", user), - title: 'api test', file_name: 'sample.rb' - response.status.should == 400 + it 'should return a 400 error if invalid snippet is given' do + post api("/projects/#{project.id}/snippets", user) + expect(status).to eq(400) end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index dec488c6d00..12dfcacec23 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -184,27 +184,11 @@ describe API::API, api: true do end describe "GET /users/sign_up" do - context 'enabled' do - before do - ApplicationSetting.any_instance.stub(signup_enabled?: true) - end - - it "should return sign up page if signup is enabled" do - get "/users/sign_up" - response.status.should == 200 - end - end - context 'disabled' do - before do - ApplicationSetting.any_instance.stub(signup_enabled?: false) - end - - it "should redirect to sign in page if signup is disabled" do - get "/users/sign_up" - response.status.should == 302 - response.should redirect_to(new_user_session_path) - end + it "should redirect to sign in page" do + get "/users/sign_up" + response.status.should == 302 + response.should redirect_to(new_user_session_path) end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index e36b266a1ff..b8f9d2bf20a 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -60,7 +60,7 @@ end # project GET /:id(.:format) projects#show # PUT /:id(.:format) projects#update # DELETE /:id(.:format) projects#destroy -# markdown_preview_project GET /:id/markdown_preview(.:format) projects#markdown_preview +# markdown_preview_project POST /:id/markdown_preview(.:format) projects#markdown_preview describe ProjectsController, 'routing' do it 'to #create' do post('/projects').should route_to('projects#create') @@ -91,7 +91,7 @@ describe ProjectsController, 'routing' do end it 'to #markdown_preview' do - get('/gitlab/gitlabhq/markdown_preview').should( + post('/gitlab/gitlabhq/markdown_preview').should( route_to('projects#markdown_preview', id: 'gitlab/gitlabhq') ) end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 36030577835..964b3a707e4 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -5,6 +5,7 @@ describe Issues::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } let(:issue) { create(:issue) } + let(:label) { create(:label) } before do project.team << [user, :master] @@ -18,7 +19,8 @@ describe Issues::UpdateService do title: 'New title', description: 'Also please fix', assignee_id: user2.id, - state_event: 'close' + state_event: 'close', + label_ids: [label.id] } @issue = Issues::UpdateService.new(project, user, opts).execute(issue) @@ -29,6 +31,8 @@ describe Issues::UpdateService do it { @issue.title.should == 'New title' } it { @issue.assignee.should == user2 } it { @issue.should be_closed } + it { @issue.labels.count.should == 1 } + it { @issue.labels.first.title.should == 'Bug' } it 'should send email to user2 about assign of new issue' do email = ActionMailer::Base.deliveries.last @@ -40,6 +44,11 @@ describe Issues::UpdateService do note = @issue.notes.last note.note.should include "Reassigned to \@#{user2.username}" end + + it 'should create system note about issue label edit' do + note = @issue.notes[1] + note.note.should include "Added ~#{label.id} label" + end end end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 0e60baae2c4..b27acb47711 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -5,6 +5,7 @@ describe MergeRequests::UpdateService do let(:user2) { create(:user) } let(:merge_request) { create(:merge_request, :simple) } let(:project) { merge_request.project } + let(:label) { create(:label) } before do project.team << [user, :master] @@ -18,7 +19,8 @@ describe MergeRequests::UpdateService do title: 'New title', description: 'Also please fix', assignee_id: user2.id, - state_event: 'close' + state_event: 'close', + label_ids: [label.id] } end @@ -35,6 +37,8 @@ describe MergeRequests::UpdateService do it { @merge_request.title.should == 'New title' } it { @merge_request.assignee.should == user2 } it { @merge_request.should be_closed } + it { @merge_request.labels.count.should == 1 } + it { @merge_request.labels.first.title.should == 'Bug' } it 'should execute hooks with update action' do expect(service).to have_received(:execute_hooks). @@ -51,6 +55,11 @@ describe MergeRequests::UpdateService do note = @merge_request.notes.last note.note.should include "Reassigned to \@#{user2.username}" end + + it 'should create system note about merge_request label edit' do + note = @merge_request.notes[1] + note.note.should include "Added ~#{label.id} label" + end end end end |