diff options
136 files changed, 1676 insertions, 764 deletions
diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000000..ac2cdeba013 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.1.3 diff --git a/CHANGELOG b/CHANGELOG index 7e6e0f5f64b..c98d21f9865 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,12 @@ v 7.4.0 - Add select field type for services options (Sullivan Senechal) - Add cross-project references to the Markdown parser (Vinnie Okada) - Add task lists to issue and merge request descriptions (Vinnie Okada) + - Snippets can be public, internal or private + - Improve danger zone: ask project path to confirm data-loss action + - Raise exception on forgery + - Show build coverage in Merge Requests (requires GitLab CI v5.1) + - New milestone and label links on issue edit form + - Improved repository graphs v 7.3.2 - Fix creating new file via web editor diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d1e8270f46..ed49080d57a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Closing policy for issues and merge requests @@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language ## Issue tracker -To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/). +To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://about.gitlab.com/subscription/) and [consulting services](http://about.gitlab.com/consultancy/) are available from [GitLab.com](http://about.gitlab.com/). The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue. diff --git a/Gemfile.lock b/Gemfile.lock index babb23ed606..0e82f14ca9d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -168,7 +168,7 @@ GEM multi_json gitlab-grack (2.0.0.pre) rack (~> 1.5.1) - gitlab-grit (2.6.11) + gitlab-grit (2.6.12) charlock_holmes (~> 0.6) diff-lcs (~> 1.1) mime-types (~> 1.15) @@ -241,9 +241,11 @@ GEM html-pipeline (1.11.0) activesupport (>= 2) nokogiri (~> 1.4) - html-pipeline-gitlab (0.1.0) - gitlab_emoji (~> 0.0.1.1) + html-pipeline-gitlab (0.1.5) + actionpack (~> 4) + gitlab_emoji (~> 0.0.1) html-pipeline (~> 1.11.0) + sanitize (~> 2.1) http_parser.rb (0.5.3) httparty (0.13.0) json (~> 1.8) diff --git a/PROCESS.md b/PROCESS.md index c3a787662f7..1dd28d6b670 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -18,7 +18,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co - Responds to merge requests the issue team mentions them in and monitors for new merge requests - Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.) - Mark merge requests 'ready-for-merge' when they meet the contribution guidelines -- Mention developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/) +- Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/) - Closes merge requests with no feedback from the reporter for two weeks ## Priorities of the issue team @@ -30,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co ## Mentioning people -The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. +The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://about.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. ## Workflow labels @@ -79,7 +79,7 @@ Thanks for the issue report but we only support issues for the latest stable ver ### Support requests and configuration questions -Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. +Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://about.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. ### Code format diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index a1a4dc8e24f..ff0d0bb32b9 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -177,6 +177,13 @@ $ -> $(@).closest(".diff-file").find(".notes_holder").toggle() e.preventDefault() + $(document).on "click", '.js-confirm-danger', (e) -> + e.preventDefault() + btn = $(e.target) + text = btn.data("confirm-danger-message") + form = btn.closest("form") + new ConfirmDangerModal(form, text) + (($) -> # Disable an element and add the 'disabled' Bootstrap class $.fn.extend disable: -> diff --git a/app/assets/javascripts/behaviors/taskable.js.coffee b/app/assets/javascripts/behaviors/taskable.js.coffee new file mode 100644 index 00000000000..ddce71c1886 --- /dev/null +++ b/app/assets/javascripts/behaviors/taskable.js.coffee @@ -0,0 +1,21 @@ +window.updateTaskState = (taskableType) -> + objType = taskableType.data + isChecked = $(this).prop("checked") + if $(this).is(":checked") + stateEvent = "task_check" + else + stateEvent = "task_uncheck" + + taskableUrl = $("form.edit-" + objType).first().attr("action") + taskableNum = taskableUrl.match(/\d+$/) + taskNum = 0 + $("li.task-list-item input:checkbox").each( (index, e) => + if e == this + taskNum = index + 1 + ) + + $.ajax + type: "PATCH" + url: taskableUrl + data: objType + "[state_event]=" + stateEvent + + "&" + objType + "[task_num]=" + taskNum diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee new file mode 100644 index 00000000000..1687b7d961c --- /dev/null +++ b/app/assets/javascripts/confirm_danger_modal.js.coffee @@ -0,0 +1,20 @@ +class ConfirmDangerModal + constructor: (form, text) -> + @form = form + $('.js-confirm-text').text(text || '') + $('.js-confirm-danger-input').val('') + $('#modal-confirm-danger').modal('show') + project_path = $('.js-confirm-danger-match').text() + submit = $('.js-confirm-danger-submit') + submit.disable() + + $('.js-confirm-danger-input').on 'input', -> + if rstrip($(@).val()) is project_path + submit.enable() + else + submit.disable() + + $('.js-confirm-danger-submit').on 'click', => + @form.submit() + +@ConfirmDangerModal = ConfirmDangerModal diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index f2b531fb2b1..0e2a2fa792a 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -9,25 +9,11 @@ class Issue if $("a.btn-close").length $("li.task-list-item input:checkbox").prop("disabled", false) - $(".task-list-item input:checkbox").on "click", -> - is_checked = $(this).prop("checked") - if $(this).is(":checked") - state_event = "task_check" - else - state_event = "task_uncheck" - - mr_url = $("form.edit-issue").first().attr("action") - mr_num = mr_url.match(/\d+$/) - task_num = 0 - $("li.task-list-item input:checkbox").each( (index, e) => - if e == this - task_num = index + 1 - ) - - $.ajax - type: "PATCH" - url: mr_url - data: "issue[state_event]=" + state_event + - "&issue[task_num]=" + task_num + $(".task-list-item input:checkbox").on( + "click" + null + "issue" + updateTaskState + ) @Issue = Issue diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index c0460a7ec4e..9f99ff403f8 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -17,7 +17,7 @@ class MergeRequest disableButtonIfEmptyField '#commit_message', '.accept_merge_request' - if $("a.close-mr-link").length + if $("a.btn-close").length $("li.task-list-item input:checkbox").prop("disabled", false) # Local jQuery finder @@ -74,26 +74,12 @@ class MergeRequest this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_widget.failed').show() - this.$(".task-list-item input:checkbox").on "click", -> - is_checked = $(this).prop("checked") - if $(this).is(":checked") - state_event = "task_check" - else - state_event = "task_uncheck" - - mr_url = $("form.edit-merge_request").first().attr("action") - mr_num = mr_url.match(/\d+$/) - task_num = 0 - $("li.task-list-item input:checkbox").each( (index, e) => - if e == this - task_num = index + 1 - ) - - $.ajax - type: "PATCH" - url: mr_url - data: "merge_request[state_event]=" + state_event + - "&merge_request[task_num]=" + task_num + $(".task-list-item input:checkbox").on( + "click" + null + "merge_request" + updateTaskState + ) activateTab: (action) -> this.$('.merge-request-tabs li').removeClass 'active' diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index 0486955d6e1..94149594e24 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -10,8 +10,7 @@ .issue-box { color: #555; margin:20px 0; - background: #f9f9f9; - border-top-left-radius: 5px; + background: $box_bg; @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); &.issue-box-closed { @@ -112,7 +111,6 @@ float: left; font-weight: bold; padding: 10px 15px; - border-top-left-radius: 5px; } .creator { diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss index 45044c5acb6..9c5e76ab8e2 100644 --- a/app/assets/stylesheets/gl_bootstrap.scss +++ b/app/assets/stylesheets/gl_bootstrap.scss @@ -233,8 +233,8 @@ $list-group-active-bg: $bg_primary; } .form-actions { - margin-bottom: 0; - background: #FFF; + margin: -15px; + margin-top: 18px; } } @@ -262,53 +262,33 @@ $list-group-active-bg: $bg_primary; } .panel-danger { - border-color: $border_danger; + @include panel-colored; .panel-heading { - color: #ffffff; - background-color: $bg_danger; + color: $border_danger; border-color: $border_danger; - a { - color: #FFF; - text-decoration: underline; - } } } .panel-success { - border-color: $border_success; + @include panel-colored; .panel-heading { - color: #ffffff; - background-color: $bg_success; + color: $border_success; border-color: $border_success; - a { - color: #FFF; - text-decoration: underline; - } } } .panel-primary { - border-color: $border_primary; + @include panel-colored; .panel-heading { - color: #ffffff; - background-color: $bg_primary; + color: $border_primary; border-color: $border_primary; - a { - color: #FFF; - text-decoration: underline; - } } } .panel-warning { - border-color: $border_warning; + @include panel-colored; .panel-heading { - color: #ffffff; - background-color: $bg_warning; + color: $border_warning; border-color: $border_warning; - a { - color: #FFF; - text-decoration: underline; - } } } diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/main/mixins.scss index 93faf5ced65..7f607fc4e8b 100644 --- a/app/assets/stylesheets/main/mixins.scss +++ b/app/assets/stylesheets/main/mixins.scss @@ -132,3 +132,14 @@ white-space: nowrap; max-width: $max_width; } + +@mixin panel-colored { + border: none; + background: $box_bg; + @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); + + .panel-heading { + font-weight: bold; + background-color: $box_bg; + } +} diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss index 72d84226fe7..c71984a5665 100644 --- a/app/assets/stylesheets/main/variables.scss +++ b/app/assets/stylesheets/main/variables.scss @@ -3,6 +3,7 @@ */ $style_color: #474D57; $hover: #FFECDB; +$box_bg: #F9F9F9; /* * Link colors diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss index 46e3884b302..c8d0cac2926 100644 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ b/app/assets/stylesheets/sections/merge_requests.scss @@ -104,7 +104,7 @@ } .mr-state-widget { - background: #f9f9f9; + background: $box_bg; margin-bottom: 20px; @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index f63df27eebd..baad9095b70 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController def index @users = User.filter(params[:filter]) @users = @users.search(params[:name]) if params[:name].present? + @users = @users.sort(@sort = params[:sort]) @users = @users.alphabetically.page(params[:page]) end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 3ed6a69c2d8..fa5685938f0 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController error.to_s.humanize if error end + # We only find ourselves here + # if the authentication to LDAP was successful. def ldap - # We only find ourselves here - # if the authentication to LDAP was successful. - @user = Gitlab::LDAP::User.find_or_create(oauth) - @user.remember_me = true if @user.persisted? + @user = Gitlab::LDAP::User.new(oauth) + @user.save if @user.changed? # will also save new users + gl_user = @user.gl_user + gl_user.remember_me = true if @user.persisted? # Do additional LDAP checks for the user filter and EE features - if Gitlab::LDAP::Access.allowed?(@user) - sign_in_and_redirect(@user) + if Gitlab::LDAP::Access.allowed?(gl_user) + sign_in_and_redirect(gl_user) else flash[:alert] = "Access denied for your LDAP account." redirect_to new_user_session_path @@ -46,24 +48,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController current_user.save redirect_to profile_path else - @user = Gitlab::OAuth::User.find(oauth) + @user = Gitlab::OAuth::User.new(oauth) - # Create user if does not exist - # and allow_single_sign_on is true - if Gitlab.config.omniauth['allow_single_sign_on'] && !@user - @user, errors = Gitlab::OAuth::User.create(oauth) + if Gitlab.config.omniauth['allow_single_sign_on'] && @user.new? + @user.save end - if @user && !errors - sign_in_and_redirect(@user) + if @user.valid? + sign_in_and_redirect(@user.gl_user) else - if errors - error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") - redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return - else - flash[:notice] = "There's no such user!" - end - redirect_to new_user_session_path + error_message = @user.gl_user.errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") + redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return end end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 038645aa497..b7f09eb271d 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -17,7 +17,7 @@ class Projects::CommitsController < Projects::ApplicationController group(:commit_id).count respond_to do |format| - format.html # index.html.erb + format.html format.json { pager_json("projects/commits/_commits", @commits.size) } format.atom { render layout: false } end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index cba058fe214..9d5dd8a95cc 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController respond_to :html def index - @snippets = @project.snippets.fresh.non_expired + @snippets = SnippetsFinder.new.execute(current_user, { + filter: :by_project, + project: @project + }) end def new @@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController end def snippet_params - params.require(:project_snippet).permit(:title, :content, :file_name, :private) + params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level) end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 5904dbbceda..30fb4c5552d 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -9,12 +9,14 @@ class SnippetsController < ApplicationController before_filter :set_title + skip_before_filter :authenticate_user!, only: [:index, :user_index] + respond_to :html - layout 'navless' + layout :determine_layout def index - @snippets = Snippet.are_internal.fresh.non_expired.page(params[:page]).per(20) + @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(20) end def user_index @@ -22,22 +24,11 @@ class SnippetsController < ApplicationController render_404 and return unless @user - @snippets = @user.snippets.fresh.non_expired - - if @user == current_user - @snippets = case params[:scope] - when 'are_internal' then - @snippets.are_internal - when 'are_private' then - @snippets.are_private - else - @snippets - end - else - @snippets = @snippets.are_internal - end - - @snippets = @snippets.page(params[:page]).per(20) + @snippets = SnippetsFinder.new.execute(current_user, { + filter: :by_user, + user: @user, + scope: params[:scope]}). + page(params[:page]).per(20) if @user == current_user render 'current_user_index' @@ -95,7 +86,14 @@ class SnippetsController < ApplicationController protected def snippet - @snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id]) + @snippet ||= if current_user + PersonalSnippet.where("author_id = ? OR visibility_level IN (?)", + current_user.id, + [Snippet::PUBLIC, Snippet::INTERNAL]). + find(params[:id]) + else + PersonalSnippet.are_public.find(params[:id]) + end end def authorize_modify_snippet! @@ -111,6 +109,10 @@ class SnippetsController < ApplicationController end def snippet_params - params.require(:personal_snippet).permit(:title, :content, :file_name, :private) + params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level) + end + + def determine_layout + current_user ? 'navless' : 'public_users' end end diff --git a/app/finders/README.md b/app/finders/README.md index 47823c51efb..1f46518d230 100644 --- a/app/finders/README.md +++ b/app/finders/README.md @@ -1,7 +1,7 @@ # Finders -This type of classes responsible for collectiong items based on different conditions. -To prevent lookup methods in models like this: +This type of classes responsible for collection items based on different conditions. +To prevent lookup methods in models like this: ```ruby class Project @@ -13,10 +13,10 @@ end issues = project.issues_for_user_filtered_by(user, params) ``` -Better use this: +Better use this: ```ruby issues = IssuesFinder.new.execute(project, user, filter) ``` -It will help keep models thiner +It will help keep models thiner. diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb new file mode 100644 index 00000000000..b29ab6cf40b --- /dev/null +++ b/app/finders/snippets_finder.rb @@ -0,0 +1,61 @@ +class SnippetsFinder + def execute(current_user, params = {}) + filter = params[:filter] + + case filter + when :all then + snippets(current_user).fresh.non_expired + when :by_user then + by_user(current_user, params[:user], params[:scope]) + when :by_project + by_project(current_user, params[:project]) + end + end + + private + + def snippets(current_user) + if current_user + Snippet.public_and_internal + else + # Not authenticated + # + # Return only: + # public snippets + Snippet.are_public + end + end + + def by_user(current_user, user, scope) + snippets = user.snippets.fresh.non_expired + + if user == current_user + case scope + when 'are_internal' then + snippets.are_internal + when 'are_private' then + snippets.are_private + when 'are_public' then + snippets.are_public + else + snippets + end + else + snippets.public_and_internal + end + end + + def by_project(current_user, project) + snippets = project.snippets.fresh.non_expired + + if current_user + if project.team.member?(current_user.id) + snippets + else + snippets.public_and_internal + end + else + snippets.are_public + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e8a9c2efadf..021bd0a494c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -263,4 +263,12 @@ module ApplicationHelper def escaped_autolink(text) auto_link ERB::Util.html_escape(text), link: :urls end + + def promo_host + 'about.gitlab.com' + end + + def promo_url + 'https://' + promo_host + end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index cab2984a4c4..0e0532b65b2 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -120,4 +120,8 @@ module CommitsHelper class: 'commit-short-id') end end + + def truncate_sha(sha) + Commit.truncate_sha(sha) + end end diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index c4e33e3308f..acc0eeb76b3 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -37,40 +37,31 @@ module DashboardHelper end def assigned_entities_count(current_user, entity, scope = nil) - items = current_user.send("assigned_" + entity.pluralize).opened - - if scope.kind_of?(Group) - items = items.of_group(scope) - elsif scope.kind_of?(Project) - items = items.of_projects(scope) - end - - items.count + items = current_user.send('assigned_' + entity.pluralize) + get_count(items, scope) end def authored_entities_count(current_user, entity, scope = nil) - items = current_user.send(entity.pluralize).opened - - if scope.kind_of?(Group) - items = items.of_group(scope) - elsif scope.kind_of?(Project) - items = items.of_projects(scope) - end - - items.count + items = current_user.send(entity.pluralize) + get_count(items, scope) end def authorized_entities_count(current_user, entity, scope = nil) - items = entity.classify.constantize.opened + items = entity.classify.constantize + get_count(items, scope, true, current_user) + end + + protected + def get_count(items, scope, get_authorized = false, current_user = nil) + items = items.opened if scope.kind_of?(Group) items = items.of_group(scope) elsif scope.kind_of?(Project) items = items.of_projects(scope) - else + elsif get_authorized items = items.of_projects(current_user.authorized_projects) end - items.count end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 6aeab7bb8ce..71f97fbb8c8 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -136,9 +136,8 @@ module EventsHelper end def event_note(text) - text = first_line_in_markdown(text) - text = truncate(text, length: 150) - sanitize(markdown(text), tags: %w(a img b pre p)) + text = first_line_in_markdown(text, 150) + sanitize(text, tags: %w(a img b pre code p)) end def event_commit_title(message) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 0365681a128..7d3cb749829 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -51,12 +51,14 @@ module GitlabMarkdownHelper @markdown.render(text).html_safe end - def first_line_in_markdown(text) - line = text.split("\n").detect do |i| - i.present? && markdown(i).present? - end - line += '...' unless line.nil? - line + # Return the first line of +text+, up to +max_chars+, after parsing the line + # as Markdown. HTML tags in the parsed output are not counted toward the + # +max_chars+ limit. If the length limit falls within a tag's contents, then + # the tag contents are truncated without removing the closing tag. + def first_line_in_markdown(text, max_chars = nil) + md = markdown(text).strip + + truncate_visible(md, max_chars || md.length) if md.present? end def render_wiki_content(wiki_page) @@ -204,4 +206,52 @@ module GitlabMarkdownHelper def correct_ref @ref ? @ref : "master" end + + private + + # Return +text+, truncated to +max_chars+ characters, excluding any HTML + # tags. + def truncate_visible(text, max_chars) + doc = Nokogiri::HTML.fragment(text) + content_length = 0 + truncated = false + + doc.traverse do |node| + if node.text? || node.content.empty? + if truncated + node.remove + next + end + + # Handle line breaks within a node + if node.content.strip.lines.length > 1 + node.content = "#{node.content.lines.first.chomp}..." + truncated = true + end + + num_remaining = max_chars - content_length + if node.content.length > num_remaining + node.content = node.content.truncate(num_remaining) + truncated = true + end + content_length += node.content.length + end + + truncated = truncate_if_block(node, truncated) + end + + doc.to_html + end + + # Used by #truncate_visible. If +node+ is the first block element, and the + # text hasn't already been truncated, then append "..." to the node contents + # and return true. Otherwise return false. + def truncate_if_block(node, truncated) + if node.element? && node.description.block? && !truncated + node.content = "#{node.content}..." if node.next_sibling + true + else + truncated + end + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 2188b30a9af..883c1f63af6 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -56,6 +56,10 @@ module ProjectsHelper "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?" end + def transfer_project_message(project) + "You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?" + end + def project_nav_tabs @nav_tabs ||= get_project_nav_tabs(@project, current_user) end diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 8b83b8ff640..deb9c8b4d49 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -28,6 +28,23 @@ module VisibilityLevelHelper end end + def snippet_visibility_level_description(level) + capture_haml do + haml_tag :span do + case level + when Gitlab::VisibilityLevel::PRIVATE + haml_concat "The snippet is visible only for me" + when Gitlab::VisibilityLevel::INTERNAL + haml_concat "The snippet is visible for any logged in user." + when Gitlab::VisibilityLevel::PUBLIC + haml_concat "The snippet can be accessed" + haml_concat "without any" + haml_concat "authentication." + end + end + end + end + def visibility_level_icon(level) case level when Gitlab::VisibilityLevel::PRIVATE diff --git a/app/models/commit.rb b/app/models/commit.rb index a1343b65c72..212229649fc 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -19,13 +19,24 @@ class Commit class << self def decorate(commits) - commits.map { |c| self.new(c) } + commits.map do |commit| + if commit.kind_of?(Commit) + commit + else + self.new(commit) + end + end end # Calculate number of lines to render for diffs def diff_line_count(diffs) diffs.reduce(0) { |sum, d| sum + d.diff.lines.count } end + + # Truncate sha to 8 characters + def truncate_sha(sha) + sha[0..7] + end end attr_accessor :raw @@ -111,7 +122,7 @@ class Commit # Mentionable override. def gfm_reference - "commit #{sha[0..5]}" + "commit #{id}" end def method_missing(m, *args, &block) @@ -124,6 +135,11 @@ class Commit super end + # Truncate sha to 8 characters + def short_id + @raw.short_id(7) + end + def parents @parents ||= Commit.decorate(super) end diff --git a/app/models/event.rb b/app/models/event.rb index 9e296c00281..c0b126713a6 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -266,7 +266,7 @@ class Event < ActiveRecord::Base end def note_short_commit_id - note_commit_id[0..8] + Commit.truncate_sha(note_commit_id) end def note_commit? diff --git a/app/models/member.rb b/app/models/member.rb index 7dc13c18bf3..671ef466baa 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -1,3 +1,18 @@ +# == Schema Information +# +# Table name: members +# +# id :integer not null, primary key +# access_level :integer not null +# source_id :integer not null +# source_type :string(255) not null +# user_id :integer not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + class Member < ActiveRecord::Base include Notifiable include Gitlab::Access diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index e72393c4278..b7f296b13fb 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -1,3 +1,18 @@ +# == Schema Information +# +# Table name: members +# +# id :integer not null, primary key +# access_level :integer not null +# source_id :integer not null +# source_type :string(255) not null +# user_id :integer not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + class GroupMember < Member SOURCE_TYPE = 'Namespace' diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 71525f91961..30c09f768d7 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -1,3 +1,18 @@ +# == Schema Information +# +# Table name: members +# +# id :integer not null, primary key +# access_level :integer not null +# source_id :integer not null +# source_type :string(255) not null +# user_id :integer not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + class ProjectMember < Member SOURCE_TYPE = 'Project' diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 409e82ed1ef..a71122d5e07 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -55,7 +55,7 @@ class MergeRequestDiff < ActiveRecord::Base end def last_commit_short_sha - @last_commit_short_sha ||= last_commit.sha[0..10] + @last_commit_short_sha ||= last_commit.short_id end private diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb index a3c0d201ee5..9cee3b70cb3 100644 --- a/app/models/personal_snippet.rb +++ b/app/models/personal_snippet.rb @@ -2,17 +2,17 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # class PersonalSnippet < Snippet diff --git a/app/models/project.rb b/app/models/project.rb index 44d63d37bee..90d2649ba23 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -64,6 +64,7 @@ class Project < ActiveRecord::Base has_one :assembla_service, dependent: :destroy has_one :gemnasium_service, dependent: :destroy has_one :slack_service, dependent: :destroy + has_one :buildbox_service, dependent: :destroy has_one :pushover_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link @@ -312,7 +313,7 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox) end def gitlab_ci? diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 3421a0330aa..0b90a14f39c 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class AssemblaService < Service diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb new file mode 100644 index 00000000000..b0f8e28c97f --- /dev/null +++ b/app/models/project_services/buildbox_service.rb @@ -0,0 +1,121 @@ +# == 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 +# + +class BuildboxService < CiService + prop_accessor :project_url, :token + + validates :project_url, presence: true, if: :activated? + validates :token, presence: true, if: :activated? + + after_save :compose_service_hook, if: :activated? + + def webhook_url + "#{buildbox_endpoint('webhook')}/deliver/#{webhook_token}" + end + + def compose_service_hook + hook = service_hook || build_service_hook + hook.url = webhook_url + hook.save + end + + def execute(data) + service_hook.execute(data) + end + + def commit_status(sha) + response = HTTParty.get(commit_status_path(sha), verify: false) + + if response.code == 200 && response['status'] + response['status'] + else + :error + end + end + + def commit_status_path(sha) + "#{buildbox_endpoint('gitlab')}/status/#{status_token}.json?commit=#{sha}" + end + + def build_page(sha) + "#{project_url}/builds?commit=#{sha}" + end + + def builds_path + "#{project_url}/builds?branch=#{project.default_branch}" + end + + def status_img_path + "#{buildbox_endpoint('badge')}/#{status_token}.svg" + end + + def title + 'Buildbox' + end + + def description + 'Continuous integration and deployments' + end + + def to_param + 'buildbox' + end + + def fields + [ + { type: 'text', + name: 'token', + placeholder: 'Buildbox project GitLab token' }, + + { type: 'text', + name: 'project_url', + placeholder: 'https://buildbox.io/example/project' } + ] + end + + private + + def webhook_token + token_parts.first + end + + def status_token + token_parts.second + end + + def token_parts + if token.present? + token.split(':') + else + [] + end + end + + def buildbox_endpoint(subdomain = nil) + endpoint = 'https://buildbox.io' + + if subdomain.present? + uri = Addressable::URI.parse(endpoint) + new_endpoint = "#{uri.scheme || 'http'}://#{subdomain}.#{uri.host}" + + if uri.port.present? + "#{new_endpoint}:#{uri.port}" + else + new_endpoint + end + else + endpoint + end + end +end diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 2d8950db491..0736ddab99b 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class CampfireService < Service diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index 829f495abc6..b1d5e49ede3 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # # Base class for CI services diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index 5c4537cfca5..b9071b98295 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class EmailsOnPushService < Service diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 4d11b00c192..0020b4482e5 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require "flowdock-git-hook" diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 7b6c87e4cec..6d2fc06a5d0 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # 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 001b11c5966..a897c4ab76b 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -2,14 +2,14 @@ # # 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 -# property :text +# 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 # class GitlabCiService < CiService diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 3a1ba168e6a..4078938cdbb 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class HipchatService < Service diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index 3aa928b92a0..09e114f9cca 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class PivotaltrackerService < Service diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index dfa1e9c9820..95f3ddcef45 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # class SlackService < Service diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index 14c88046423..9e2c1b0e18e 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -2,17 +2,17 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # class ProjectSnippet < Snippet diff --git a/app/models/project_team.rb b/app/models/project_team.rb index e065554d3b8..657ee23ae23 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -133,6 +133,10 @@ class ProjectTeam max_tm_access(user.id) == Gitlab::Access::MASTER end + def member?(user_id) + !!find_tm(user_id) + end + def max_tm_access(user_id) access = [] access << project.project_members.find_by(user_id: user_id).try(:access_field) diff --git a/app/models/repository.rb b/app/models/repository.rb index 339e485e6d2..93994123a90 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -30,6 +30,8 @@ class Repository commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Commit.new(commit) if commit commit + rescue Rugged::OdbError => ex + nil end def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) diff --git a/app/models/service.rb b/app/models/service.rb index 1f3a6520473..c489c1e96e1 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -2,14 +2,15 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# # To add new service you should build a class inherited from Service # and implement a set of methods diff --git a/app/models/snippet.rb b/app/models/snippet.rb index addde2d106a..a47fbca3260 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -2,23 +2,24 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # class Snippet < ActiveRecord::Base include Linguist::BlobHelper + include Gitlab::VisibilityLevel - default_value_for :private, true + default_value_for :visibility_level, Snippet::PRIVATE belongs_to :author, class_name: "User" @@ -30,10 +31,13 @@ class Snippet < ActiveRecord::Base validates :title, presence: true, length: { within: 0..255 } validates :file_name, presence: true, length: { within: 0..255 } validates :content, presence: true + validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } # Scopes - scope :are_internal, -> { where(private: false) } - scope :are_private, -> { where(private: true) } + scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) } + scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) } + scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) } + scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) } scope :fresh, -> { order("created_at DESC") } scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } @@ -66,6 +70,10 @@ class Snippet < ActiveRecord::Base expires_at && expires_at < Time.current end + def visibility_level_field + visibility_level + end + class << self def search(query) where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%") @@ -76,7 +84,7 @@ class Snippet < ActiveRecord::Base end def accessible_to(user) - where('private = ? OR author_id = ?', false, user) + where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user) end end end diff --git a/app/models/user.rb b/app/models/user.rb index c90f2462426..c6baa7ee704 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -196,6 +196,16 @@ class User < ActiveRecord::Base end end + 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") + end + end + def find_for_commit(email, name) # Prefer email match over name match User.where(email: email).first || diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb new file mode 100644 index 00000000000..e3371ec3c1b --- /dev/null +++ b/app/services/issuable_base_service.rb @@ -0,0 +1,13 @@ +class IssuableBaseService < BaseService + private + + def create_assignee_note(issuable) + Note.create_assignee_change_note( + issuable, issuable.project, current_user, issuable.assignee) + end + + def create_milestone_note(issuable) + Note.create_milestone_change_note( + issuable, issuable.project, current_user, issuable.milestone) + end +end diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 71b9ffc3489..41948f226a6 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -1,21 +1,13 @@ module Issues - class BaseService < ::BaseService + class BaseService < ::IssuableBaseService private - def create_assignee_note(issue) - Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee) - end - def execute_hooks(issue, action = 'open') issue_data = issue.to_hook_data issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id) issue_data[:object_attributes].merge!(url: issue_url, action: action) issue.project.execute_hooks(issue_data, :issue_hooks) end - - def create_milestone_note(issue) - Note.create_milestone_change_note(issue, issue.project, current_user, issue.milestone) - end end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 5b2746ffecf..0ee9635ed99 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -33,12 +33,5 @@ module Issues issue end - - private - - def update_task(issue, params, checked) - issue.update_nth_task(params[:task_num].to_i, checked) - params.except!(:task_num) - end end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 2907f3587da..694994001b0 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -1,11 +1,5 @@ module MergeRequests - class BaseService < ::BaseService - - private - - def create_assignee_note(merge_request) - Note.create_assignee_change_note(merge_request, merge_request.project, current_user, merge_request.assignee) - end + class BaseService < ::IssuableBaseService def create_note(merge_request) Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil) @@ -16,9 +10,5 @@ module MergeRequests merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) end end - - def create_milestone_note(merge_request) - Note.create_milestone_change_note(merge_request, merge_request.project, current_user, merge_request.milestone) - end end end diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 5c2664e14fe..92c619738a2 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -32,6 +32,26 @@ .panel-heading Users (#{@users.total_count}) .panel-head-actions + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = @sort.humanize + - else + 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 + Recently created + = link_to admin_users_path(sort: 'late_created') do + Late created = link_to 'New User', new_admin_user_path, class: "btn btn-new" %ul.well-list - @users.each do |user| diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index 0e03e116e7d..f0c34def145 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -1,5 +1,5 @@ %li.commit .commit-row-title - = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: '' + = link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit_short_id", alt: '' = gfm event_commit_title(commit[:message]), project diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index 17228c430ca..2b63519edac 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -2,7 +2,7 @@ - event.commits.first(15).each do |commit| %p %strong= commit[:author][:name] - = link_to "(##{commit[:id][0...8]})", project_commit_path(event.project, id: commit[:id]) + = link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id]) %i at = commit[:timestamp].to_time.to_s(:short) diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 1bca64c7d50..b912b5e092f 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -22,4 +22,4 @@ - if event.commits_count > 2 %span ... and #{event.commits_count - 2} more commits. = link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do - %strong Compare → #{event.commit_from[0..7]}...#{event.commit_to[0..7]} + %strong Compare → #{truncate_sha(event.commit_from)}...#{truncate_sha(event.commit_to)} diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 903e093e5fc..7b8193abfdf 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -14,7 +14,7 @@ %br Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. %br - Read more about GitLab at #{link_to "www.gitlab.com", "https://www.gitlab.com/", target: "_blank"}. + Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}. %hr @@ -34,7 +34,7 @@ %ul.well-list %li See our website for - = link_to "getting help", "https://www.gitlab.com/getting-help/" + = link_to 'getting help', promo_url + '/getting-help/' %li Use the = link_to 'search bar', '#', onclick: 'Shortcuts.focusSearch(event)' diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index e5cde488c3c..bdf02c6285d 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -15,7 +15,7 @@ %tr %td.blame-commit %span.commit - = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" + = link_to commit.short_id, project_commit_path(@project, commit), class: "commit_short_id" = commit_author_link(commit, avatar: true, size: 16) diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 0b6b6af4f90..e149f017f84 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -35,7 +35,7 @@ .commit-info-row %span.cgray= pluralize(@commit.parents.count, "parent") - @commit.parents.each do |parent| - = link_to parent.id[0...10], project_commit_path(@project, parent) + = link_to parent.short_id, project_commit_path(@project, parent) - if @branches.any? .commit-info-row diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 68852ba973f..1eb17f760dc 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -1,6 +1,6 @@ %li.commit.js-toggle-container .commit-row-title - = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" + = link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id" %span.str-truncated = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml index b36369b4285..574599aa2d2 100644 --- a/app/views/projects/commits/_inline_commit.html.haml +++ b/app/views/projects/commits/_inline_commit.html.haml @@ -1,6 +1,6 @@ %li.commit.inline-commit .commit-row-title - = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" + = link_to commit.short_id, project_commit_path(project, commit), class: "commit_short_id" %span.str-truncated = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 5ee5641b069..f48f4bb2953 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -86,101 +86,92 @@ - .danger-settings.js-toggle-container - .centered-light-block - %h3 - %i.fa.fa-exclamation-triangle - Dangerous settings - - %p Project settings below may result in data loss! - = link_to '#', class: 'btn js-toggle-button' do - Show them to me - %i.fa.fa-chevron-down - - .js-toggle-content.hide - - if can? current_user, :archive_project, @project - .panel.panel-default.panel.panel-warning + .danger-settings + - if can? current_user, :archive_project, @project + - if @project.archived? + .panel.panel-success .panel-heading - - if @project.archived? - Unarchive project - - else - Archive project + Unarchive project .panel-body - - if @project.archived? - %p - Unarchiving the project will mark its repository as active. - %br - The project can be committed to. - %br - %strong Once active this project shows up in the search and on the dashboard. - = link_to 'Unarchive', unarchive_project_path(@project), - data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." }, - method: :post, class: "btn btn-remove" - - else - %p - Archiving the project will mark its repository as read-only. - %br - It is hidden from the dashboard and doesn't show up in searches. - %br - %strong Archived projects cannot be committed to! - = link_to 'Archive', archive_project_path(@project), - data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, - method: :post, class: "btn btn-warning" + %p + Unarchiving the project will mark its repository as active. + %br + The project can be committed to. + %br + %strong Once active this project shows up in the search and on the dashboard. + = link_to 'Unarchive', unarchive_project_path(@project), + data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." }, + method: :post, class: "btn btn-success" - else - .nothing-here-block Only the project owner can archive a project - - .panel.panel-default.panel.panel-warning - .panel-heading Rename repository + .panel.panel-warning + .panel-heading + Archive project + .panel-body + %p + Archiving the project will mark its repository as read-only. + %br + It is hidden from the dashboard and doesn't show up in searches. + %br + %strong Archived projects cannot be committed to! + = link_to 'Archive', archive_project_path(@project), + data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, + method: :post, class: "btn btn-warning" + - else + .nothing-here-block Only the project owner can archive a project + + .panel.panel-default.panel.panel-warning + .panel-heading Rename repository + .errors-holder + .panel-body + = form_for(@project, html: { class: 'form-horizontal' }) do |f| + .form-group + = f.label :path, class: 'control-label' do + %span Path + .col-sm-9 + .form-group + .input-group + = f.text_field :path, class: 'form-control' + %span.input-group-addon .git + %ul + %li Be careful. Renaming a project's repository can have unintended side effects. + %li You will need to update your local repositories to point to the new location. + .form-actions + = f.submit 'Rename', class: "btn btn-warning" + + - if can?(current_user, :change_namespace, @project) + .panel.panel-default.panel.panel-danger + .panel-heading Transfer project .errors-holder .panel-body - = form_for(@project, html: { class: 'form-horizontal' }) do |f| + = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f| .form-group - = f.label :path, class: 'control-label' do - %span Path - .col-sm-9 + = f.label :namespace_id, class: 'control-label' do + %span Namespace + .col-sm-10 .form-group - .input-group - = f.text_field :path, class: 'form-control' - %span.input-group-addon .git + = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' } %ul - %li Be careful. Renaming a project's repository can have unintended side effects. + %li Be careful. Changing the project's namespace can have unintended side effects. + %li You can only transfer the project to namespaces you manage. %li You will need to update your local repositories to point to the new location. .form-actions - = f.submit 'Rename', class: "btn btn-warning" - - - if can?(current_user, :change_namespace, @project) - .panel.panel-default.panel.panel-danger - .panel-heading Transfer project - .errors-holder - .panel-body - = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f| - .form-group - = f.label :namespace_id, class: 'control-label' do - %span Namespace - .col-sm-10 - .form-group - = f.select :namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace' }, { class: 'select2' } - %ul - %li Be careful. Changing the project's namespace can have unintended side effects. - %li You can only transfer the project to namespaces you manage. - %li You will need to update your local repositories to point to the new location. - .form-actions - = f.submit 'Transfer', class: "btn btn-remove" - - else - .nothing-here-block Only the project owner can transfer a project + = f.submit 'Transfer', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) } + - else + .nothing-here-block Only the project owner can transfer a project - - if can?(current_user, :remove_project, @project) - .panel.panel-default.panel.panel-danger - .panel-heading Remove project - .panel-body + - if can?(current_user, :remove_project, @project) + .panel.panel-default.panel.panel-danger + .panel-heading Remove project + .panel-body + = form_tag(project_path(@project), method: :delete, html: { class: 'form-horizontal'}) do %p Removing the project will delete its repository and all related resources including issues, merge requests etc. %br %strong Removed projects cannot be restored! - = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove" - - else - .nothing-here-block Only project owner can remove a project + = link_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) } + - else + .nothing-here-block Only project owner can remove a project .save-project-loader.hide .center @@ -188,3 +179,6 @@ %i.fa.fa-spinner.fa-spin Saving project. %p Please wait a moment, this page will automatically refresh when ready. + + += render 'shared/confirm_modal', phrase: @project.path diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index e1849b3f8b8..71eb0d5c866 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -62,7 +62,8 @@ = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue" .participants - %cite.cgray #{@issue.participants.count} participants + %cite.cgray + = pluralize(@issue.participants.count, 'participant') - @issue.participants.each do |participant| = link_to_member(@project, participant, name: false, size: 24) diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 49a3ef4c8a7..227a2f9a061 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -1,13 +1,13 @@ %h3.page-title Protected branches -%p.light This ability keeps stable branches secure and forces developers to use code reviews +%p.light Keep stable branches secure and force developers to use Merge Requests %hr .bs-callout.bs-callout-info %p Protected branches are designed to %ul %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} - %li prevents anyone from force pushing to the branch - %li prevents anyone from deleting the branch + %li prevent anyone from force pushing to the branch + %li prevent anyone from deleting the branch %p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"} - if can? current_user, :admin_project, @project diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 09664ed51eb..9b06ebe95a4 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -62,11 +62,14 @@ - else #{link_to @project.owner_name, @project.owner} - - - if @project.gitlab_ci? - %hr - = link_to @project.gitlab_ci_service.builds_path do - = image_tag @project.gitlab_ci_service.status_img_path, alt: "build status" + - @project.ci_services.each do |ci_service| + - if ci_service.active? && ci_service.respond_to?(:builds_path) + - if ci_service.respond_to?(:status_img_path) + = link_to ci_service.builds_path do + = image_tag ci_service.status_img_path, alt: "build status" + - else + %span.light CI provided by + = link_to ci_service.title, ci_service.builds_path - if readme .tab-pane#tab-readme diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml index a8ec9df2c8f..46e9be4af83 100644 --- a/app/views/projects/tree/_submodule_item.html.haml +++ b/app/views/projects/tree/_submodule_item.html.haml @@ -7,8 +7,8 @@ @ %span.monospace - if commit.nil? - #{submodule_item.id[0..10]} + #{truncate_sha(submodule_item.id)} - else - = link_to "#{submodule_item.id[0..10]}", commit + = link_to "#{truncate_sha(submodule_item.id)}", commit %td %td.hidden-xs diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index d3a66c48c9b..ef4b8f74714 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -17,7 +17,7 @@ %tr %td = link_to project_wiki_path(@project, @page, version_id: commit.id) do - = commit.id[0..10] + = truncate_sha(commit.id) %td = commit.author.name %td diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml index f2327cd69cc..a44a4542df5 100644 --- a/app/views/search/results/_note.html.haml +++ b/app/views/search/results/_note.html.haml @@ -10,7 +10,7 @@ = project.name_with_namespace · = link_to project_commit_path(project, note.commit_id, anchor: dom_id(note)) do - Commit #{note.commit_id[0..8]} + Commit #{truncate_sha(note.commit_id)} - else = link_to project do = project.name_with_namespace diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml new file mode 100644 index 00000000000..30ba361c860 --- /dev/null +++ b/app/views/shared/_confirm_modal.html.haml @@ -0,0 +1,22 @@ +#modal-confirm-danger.modal.hide{tabindex: -1} + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h4 Confirmation required + + .modal-body + %p.cred.lead.js-confirm-text + + %p + This action can lead to data loss. + To prevent accidental actions we ask you to confirm your intention. + %br + Please type + %code.js-confirm-danger-match #{phrase} + to proceed or close this modal to cancel + + .form-group + = text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input' + .form-group + = submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit" diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml index 5675e43b05f..3400c345c4c 100644 --- a/app/views/shared/_promo.html.haml +++ b/app/views/shared/_promo.html.haml @@ -1,5 +1,5 @@ .gitlab-promo - = link_to "Homepage", "https://www.gitlab.com/" - = link_to "Blog", "https://www.gitlab.com/blog/" + = link_to 'Homepage', promo_url + = link_to "Blog", promo_url + '/blog/' = link_to "@gitlabhq", "https://twitter.com/gitlabhq" = link_to "Requests", "http://feedback.gitlab.com/" diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index f4d74045f77..f729f129e45 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -10,22 +10,8 @@ = f.label :title, class: 'control-label' .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true - - unless @snippet.respond_to?(:project) - .form-group - = f.label "Access", class: 'control-label' - .col-sm-10 - = f.label :private_true, class: 'radio-label' do - = f.radio_button :private, true - %span - %strong Private - (only you can see this snippet) - %br - = f.label :private_false, class: 'radio-label' do - = f.radio_button :private, false - %span - %strong Internal - (GitLab users can see this snippet) - + = render "shared/snippets/visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true + .form-group .file-editor = f.label :file_name, "File", class: 'control-label' diff --git a/app/views/shared/snippets/_visibility_level.html.haml b/app/views/shared/snippets/_visibility_level.html.haml new file mode 100644 index 00000000000..9acff18e450 --- /dev/null +++ b/app/views/shared/snippets/_visibility_level.html.haml @@ -0,0 +1,27 @@ +.form-group.project-visibility-level-holder + = f.label :visibility_level, class: 'control-label' do + Visibility Level + = link_to "(?)", help_page_path("public_access", "public_access") + .col-sm-10 + - if can_change_visibility_level + - Gitlab::VisibilityLevel.values.each do |level| + .radio + - restricted = restricted_visibility_levels.include?(level) + = f.radio_button :visibility_level, level, disabled: restricted + = label "#{dom_class(@snippet)}_visibility_level", level do + = visibility_level_icon(level) + .option-title + = visibility_level_label(level) + .option-descr + = snippet_visibility_level_description(level) + - unless restricted_visibility_levels.empty? + .col-sm-10 + %span.info + Some visibility level settings have been restricted by the administrator. + - else + .col-sm-10 + %span.info + = visibility_level_icon(visibility_level) + %strong + = visibility_level_label(visibility_level) + .light= visibility_level_description(visibility_level) diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml index 14b5b072ec2..b2b7ea4df0e 100644 --- a/app/views/snippets/current_user_index.html.haml +++ b/app/views/snippets/current_user_index.html.haml @@ -28,6 +28,11 @@ Internal %span.pull-right = @user.snippets.are_internal.count + = nav_tab :scope, 'are_public' do + = link_to user_snippets_path(@user, scope: 'are_public') do + Public + %span.pull-right + = @user.snippets.are_public.count .col-md-9.my-snippets = render 'snippets' diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index cea2517a8e1..0d71c41e2e7 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -2,10 +2,12 @@ Public snippets .pull-right - = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do - Add new snippet - = link_to user_snippets_path(current_user), class: "btn btn-grouped" do - My snippets + + - if current_user + = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do + Add new snippet + = link_to user_snippets_path(current_user), class: "btn btn-grouped" do + My snippets %p.light Public snippets created by you and other users are listed here diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml index 1cb53ec6a25..67f3a68aa22 100644 --- a/app/views/snippets/user_index.html.haml +++ b/app/views/snippets/user_index.html.haml @@ -4,8 +4,9 @@ %span \/ Snippets - = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do - Add new snippet + - if current_user + = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do + Add new snippet %hr diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb new file mode 100644 index 00000000000..7f125acb5d1 --- /dev/null +++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb @@ -0,0 +1,21 @@ +class AddVisibilityLevelToSnippet < ActiveRecord::Migration + def up + add_column :snippets, :visibility_level, :integer, :default => 0, :null => false + + Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + + add_index :snippets, :visibility_level + + remove_column :snippets, :private + end + + def down + add_column :snippets, :private, :boolean, :default => false, :null => false + + Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false) + Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true) + + remove_column :snippets, :visibility_level + end +end diff --git a/db/schema.rb b/db/schema.rb index 84fd1256677..8ddebc5132a 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: 20141006143943) do +ActiveRecord::Schema.define(version: 20141007100818) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -293,20 +293,21 @@ ActiveRecord::Schema.define(version: 20141006143943) do create_table "snippets", force: true do |t| t.string "title" t.text "content" - t.integer "author_id", null: false + t.integer "author_id", null: false t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" t.string "file_name" t.datetime "expires_at" - t.boolean "private", default: true, null: false t.string "type" + t.integer "visibility_level", default: 0, null: false end add_index "snippets", ["author_id"], name: "index_snippets_on_author_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 + add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree create_table "taggings", force: true do |t| t.integer "tag_id" diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 4624d9f60b6..c4813d22eaa 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -2,7 +2,7 @@ ## Software delivery -There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://www.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development. +There are two editions of GitLab: [Enterprise Edition](https://about.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://about.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development. EE releases are available not long after CE releases. To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme. diff --git a/doc/install/installation.md b/doc/install/installation.md index af6e182cfa0..0d1a8da4d1b 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -6,13 +6,13 @@ Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitla  -If the highest number stable branch is unclear please check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version. +If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version. ## Important Notes This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880). -This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://www.gitlab.com/downloads/). +This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/). This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation). diff --git a/doc/release/security.md b/doc/release/security.md index da442de6ee1..79d23c02ea4 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -8,7 +8,7 @@ Do a security release when there is a critical issue that needs to be addresses ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to <support@gitlab.com>, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Release Procedure @@ -21,7 +21,7 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c 1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only) 1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq) 1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number -1. Add the security researcher to the [Security Researcher Acknowledgments list](http://www.gitlab.com/vulnerability-acknowledgements/) +1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/) 1. Thank the security researcher in an email for their cooperation 1. Update the blog post and the CHANGELOG when we receive the CVE number diff --git a/doc/update/6.x-or-7.x-to-7.3.md b/doc/update/6.x-or-7.x-to-7.3.md index 171fcb4033a..fe3530ef9c1 100644 --- a/doc/update/6.x-or-7.x-to-7.3.md +++ b/doc/update/6.x-or-7.x-to-7.3.md @@ -64,12 +64,12 @@ sudo gem install bundler --no-ri --no-rdoc ```bash cd /home/git/gitlab sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically ``` For GitLab Community Edition: ```bash -sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically sudo -u git -H git checkout 7-3-stable ``` @@ -78,7 +78,6 @@ OR For GitLab Enterprise Edition: ```bash -sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically sudo -u git -H git checkout 7-3-stable-ee ``` diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md index 329b763322a..44f3f8f1a38 100644 --- a/doc/update/7.2-to-7.3.md +++ b/doc/update/7.2-to-7.3.md @@ -18,12 +18,12 @@ sudo service gitlab stop ```bash cd /home/git/gitlab sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically ``` For GitLab Community Edition: ```bash -sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically sudo -u git -H git checkout 7-3-stable ``` @@ -32,7 +32,6 @@ OR For GitLab Enterprise Edition: ```bash -sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically sudo -u git -H git checkout 7-3-stable-ee ``` diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md new file mode 100644 index 00000000000..2e1b993aeb8 --- /dev/null +++ b/doc/update/7.3-to-7.4.md @@ -0,0 +1,68 @@ +# From 7.3 to 7.4 + +## GitLab 7.4 has not been released yet! + +This document currently just serves as a place to keep track of updates that will be needed for the 7.4 update. + +## Update config files + +* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql) + +## Optional optimizations for GitLab setups with MySQL databases + +Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. + +``` +# Secure your MySQL installation (added in GitLab 6.2) +sudo mysql_secure_installation + +# Login to MySQL +mysql -u root -p + +# do not type the 'mysql>', this is part of the prompt + +# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8) +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all outputed SQL statements + +# Find MySQL users +mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%'; + +# If git user exists and gitlab user does not exist +# you are done with the database cleanup tasks +mysql> \q + +# If both users exist skip to Delete gitlab user + +# Create new user for GitLab (changed in GitLab 6.4) +# change $password in the command below to a real password you pick +mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; + +# Grant the git user necessary permissions on the database +mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Delete the old gitlab user +mysql> DELETE FROM mysql.user WHERE user='gitlab'; + +# Quit the database session +mysql> \q + +# Try connecting to the new database with the new user +sudo -u git -H mysql -u git -p -D gitlabhq_production + +# Type the password you replaced $password with earlier + +# You should now see a 'mysql>' prompt + +# Quit the database session +mysql> \q + +# Update database configuration details +# See config/database.yml.mysql for latest recommended configuration details +# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8) +# Set production -> pool: 10 (updated in GitLab 5.3) +# Set production -> username: git +# Set production -> password: the password your replaced $password with earlier +sudo -u git -H editor /home/git/gitlab/config/database.yml +``` diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature index b28e16f0d6a..5de07e90e28 100644 --- a/features/admin/active_tab.feature +++ b/features/admin/active_tab.feature @@ -1,5 +1,5 @@ @admin -Feature: Admin active tab +Feature: Admin Active Tab Background: Given I sign in as an admin diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index e989569ccd4..4db8551559b 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -144,7 +144,7 @@ Feature: Project Issues Scenario: Issues list should display task status Given project "Shop" has "Tasks-open" open issue with task markdown When I visit project "Shop" issues page - Then I should see the task status for issue "Tasks-open" + Then I should see the task status for the Taskable # Toggling task items diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index e1e0edd0545..d20358a7dc6 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -168,11 +168,10 @@ Feature: Project Merge Requests # Task status in issues list - @now Scenario: Merge requests list should display task status Given project "Shop" has "MR-task-open" open MR with task markdown When I visit project "Shop" merge requests page - Then I should see the task status for merge request "MR-task-open" + Then I should see the task status for the Taskable # Toggling task items diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 8ff2f583b38..aca255b9444 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -1,4 +1,4 @@ -Feature: Project Source Browse files +Feature: Project Source Browse Files Background: Given I sign in as a user And I own project "Shop" diff --git a/features/snippets/discover.feature b/features/snippets/discover.feature index 5094062c8c3..1a7e132ea25 100644 --- a/features/snippets/discover.feature +++ b/features/snippets/discover.feature @@ -4,8 +4,10 @@ Feature: Snippets Discover Given I sign in as a user And I have public "Personal snippet one" snippet And I have private "Personal snippet private" snippet + And I have internal "Personal snippet internal" snippet Scenario: I should see snippets Given I visit snippets page Then I should see "Personal snippet one" in snippets + And I should see "Personal snippet internal" in snippets And I should not see "Personal snippet private" in snippets diff --git a/features/snippets/user.feature b/features/snippets/user.feature index ae34e8e7ffa..5b5dadb7b39 100644 --- a/features/snippets/user.feature +++ b/features/snippets/user.feature @@ -4,20 +4,31 @@ Feature: Snippets User Given I sign in as a user And I have public "Personal snippet one" snippet And I have private "Personal snippet private" snippet + And I have internal "Personal snippet internal" snippet Scenario: I should see all my snippets Given I visit my snippets page Then I should see "Personal snippet one" in snippets And I should see "Personal snippet private" in snippets + And I should see "Personal snippet internal" in snippets Scenario: I can see only my private snippets Given I visit my snippets page And I click "Private" filter Then I should not see "Personal snippet one" in snippets + And I should not see "Personal snippet internal" in snippets And I should see "Personal snippet private" in snippets Scenario: I can see only my public snippets Given I visit my snippets page - And I click "Internal" filter + And I click "Public" filter Then I should see "Personal snippet one" in snippets And I should not see "Personal snippet private" in snippets + And I should not see "Personal snippet internal" in snippets + + Scenario: I can see only my internal snippets + Given I visit my snippets page + And I click "Internal" filter + Then I should see "Personal snippet internal" in snippets + And I should not see "Personal snippet private" in snippets + And I should not see "Personal snippet one" in snippets diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index c054e0e8282..935f313e298 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -8,7 +8,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps commit = @project.repository.commit page.should have_content(@project.name) page.should have_content(commit.message[0..20]) - page.should have_content(commit.id.to_s[0..5]) + page.should have_content(commit.short_id) end step 'I click atom feed link' do diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index 26c3c7c14bc..640603562dd 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -154,29 +154,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'project "Shop" has "Tasks-open" open issue with task markdown' do - desc_text = <<EOT.gsub(/^ {6}/, '') - * [ ] Task 1 - * [x] Task 2 -EOT - create(:issue, - title: 'Tasks-open', - project: project, - author: project.users.first, - description: desc_text - ) + create_taskable(:issue, 'Tasks-open') end step 'project "Shop" has "Tasks-closed" closed issue with task markdown' do - desc_text = <<EOT.gsub(/^ {6}/, '') - * [ ] Task 1 - * [x] Task 2 -EOT - create(:closed_issue, - title: 'Tasks-closed', - project: project, - author: project.users.first, - description: desc_text - ) + create_taskable(:closed_issue, 'Tasks-closed') end step 'empty project "Empty Project"' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 0fec2604915..fae0cec53a6 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -98,17 +98,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'project "Shop" has "MR-task-open" open MR with task markdown' do - desc_text = <<EOT.gsub(/^ {6}/, '') - * [ ] Task 1 - * [x] Task 2 -EOT - create(:merge_request, - title: 'MR-task-open', - source_project: project, - target_project: project, - author: project.users.first, - description: desc_text - ) + create_taskable(:merge_request, 'MR-task-open') end step 'I switch to the diff tab' do @@ -121,7 +111,7 @@ EOT step 'I click on the commit in the merge request' do within '.mr-commits' do - click_link sample_commit.id[0..8] + click_link Commit.truncate_sha(sample_commit.id) end end diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb index 1d9058cf256..8bf138065b0 100644 --- a/features/steps/shared/markdown.rb +++ b/features/steps/shared/markdown.rb @@ -6,6 +6,27 @@ module SharedMarkdown find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/ end + def create_taskable(type, title) + desc_text = <<EOT.gsub(/^ {6}/, '') + * [ ] Task 1 + * [x] Task 2 +EOT + + case type + when :issue, :closed_issue + options = { project: project } + when :merge_request + options = { source_project: project, target_project: project } + end + + create( + type, + options.merge(title: title, + author: project.users.first, + description: desc_text) + ) + end + step 'Header "Description header" should have correct id and link' do header_should_have_correct_id_and_link(1, 'Description header', 'description-header') end @@ -16,13 +37,7 @@ module SharedMarkdown ) end - step 'I should see the task status for issue "Tasks-open"' do - expect(find(:css, 'span.task-status').text).to eq( - '2 tasks (1 done, 1 unfinished)' - ) - end - - step 'I should see the task status for merge request "MR-task-open"' do + step 'I should see the task status for the Taskable' do expect(find(:css, 'span.task-status').text).to eq( '2 tasks (1 done, 1 unfinished)' ) diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb index 5a27e8750cf..432f32defce 100644 --- a/features/steps/shared/snippet.rb +++ b/features/steps/shared/snippet.rb @@ -6,7 +6,7 @@ module SharedSnippet title: "Personal snippet one", content: "Test content", file_name: "snippet.rb", - private: false, + visibility_level: Snippet::PUBLIC, author: current_user) end @@ -15,9 +15,19 @@ module SharedSnippet title: "Personal snippet private", content: "Provate content", file_name: "private_snippet.rb", - private: true, + visibility_level: Snippet::PRIVATE, author: current_user) end + + step 'I have internal "Personal snippet internal" snippet' do + create(:personal_snippet, + title: "Personal snippet internal", + content: "Provate content", + file_name: "internal_snippet.rb", + visibility_level: Snippet::INTERNAL, + author: current_user) + end + step 'I have a public many lined snippet' do create(:personal_snippet, title: 'Many lined snippet', @@ -38,7 +48,7 @@ module SharedSnippet |line fourteen END file_name: 'many_lined_snippet.rb', - private: true, + visibility_level: Snippet::PUBLIC, author: current_user) end end diff --git a/features/steps/snippets/discover.rb b/features/steps/snippets/discover.rb index 42bccafcc84..2667c1e3d44 100644 --- a/features/steps/snippets/discover.rb +++ b/features/steps/snippets/discover.rb @@ -7,6 +7,10 @@ class Spinach::Features::SnippetsDiscover < Spinach::FeatureSteps page.should have_content "Personal snippet one" end + step 'I should see "Personal snippet internal" in snippets' do + page.should have_content "Personal snippet internal" + end + step 'I should not see "Personal snippet private" in snippets' do page.should_not have_content "Personal snippet private" end diff --git a/features/steps/snippets/user.rb b/features/steps/snippets/user.rb index c41bc436142..866f637ab6c 100644 --- a/features/steps/snippets/user.rb +++ b/features/steps/snippets/user.rb @@ -15,6 +15,10 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps page.should have_content "Personal snippet private" end + step 'I should see "Personal snippet internal" in snippets' do + page.should have_content "Personal snippet internal" + end + step 'I should not see "Personal snippet one" in snippets' do page.should_not have_content "Personal snippet one" end @@ -23,6 +27,10 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps page.should_not have_content "Personal snippet private" end + step 'I should not see "Personal snippet internal" in snippets' do + page.should_not have_content "Personal snippet internal" + end + step 'I click "Internal" filter' do within('.nav-stacked') do click_link "Internal" @@ -35,6 +43,12 @@ class Spinach::Features::SnippetsUser < Spinach::FeatureSteps end end + step 'I click "Public" filter' do + within('.nav-stacked') do + click_link "Public" + end + end + def snippet @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 5f484f63418..9ac659f50fd 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -14,13 +14,20 @@ module API # post "/allowed" do status 200 + project_path = params[:project] # Check for *.wiki repositories. # Strip out the .wiki from the pathname before finding the # project. This applies the correct project permissions to # the wiki repository as well. - project_path = params[:project] - project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/ + access = + if project_path =~ /\.wiki\Z/ + project_path.sub!(/\.wiki\Z/, '') + Gitlab::GitAccessWiki.new + else + Gitlab::GitAccess.new + end + project = Project.find_with_namespace(project_path) return false unless project @@ -32,7 +39,7 @@ module API return false unless actor - Gitlab::GitAccess.new.allowed?( + access.allowed?( actor, params[:action], project, diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 6247dd59867..b768a99a0e8 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -49,25 +49,7 @@ module Gitlab # Iterate over all changes to find if user allowed all of them to be applied changes.each do |change| - oldrev, newrev, ref = change.split(' ') - - action = if project.protected_branch?(branch_name(ref)) - # we dont allow force push to protected branch - if forced_push?(project, oldrev, newrev) - :force_push_code_to_protected_branches - # and we dont allow remove of protected branch - elsif newrev =~ /0000000/ - :remove_protected_branches - else - :push_code_to_protected_branches - end - elsif project.repository && project.repository.tag_names.include?(tag_name(ref)) - # Prevent any changes to existing git tag unless user has permissions - :admin_project - else - :push_code - end - unless user.can?(action, project) + unless change_allowed?(user, project, change) # If user does not have access to make at least one change - cancel all push return false end @@ -77,6 +59,29 @@ module Gitlab true end + def change_allowed?(user, project, change) + oldrev, newrev, ref = change.split(' ') + + action = if project.protected_branch?(branch_name(ref)) + # we dont allow force push to protected branch + if forced_push?(project, oldrev, newrev) + :force_push_code_to_protected_branches + # and we dont allow remove of protected branch + elsif newrev =~ /0000000/ + :remove_protected_branches + else + :push_code_to_protected_branches + end + elsif project.repository && project.repository.tag_names.include?(tag_name(ref)) + # Prevent any changes to existing git tag unless user has permissions + :admin_project + else + :push_code + end + + user.can?(action, project) + end + def forced_push?(project, oldrev, newrev) return false if project.empty_repo? diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb new file mode 100644 index 00000000000..9f0eb3be20f --- /dev/null +++ b/lib/gitlab/git_access_wiki.rb @@ -0,0 +1,7 @@ +module Gitlab + class GitAccessWiki < GitAccess + def change_allowed?(user, project, change) + user.can?(:write_wiki, project) + end + end +end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index 25b5a702f9a..006ef170726 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -10,22 +10,6 @@ module Gitlab module LDAP class User < Gitlab::OAuth::User class << self - def find_or_create(auth_hash) - self.auth_hash = auth_hash - find(auth_hash) || find_and_connect_by_email(auth_hash) || create(auth_hash) - end - - def find_and_connect_by_email(auth_hash) - self.auth_hash = auth_hash - user = model.find_by(email: self.auth_hash.email) - - if user - user.update_attributes(extern_uid: auth_hash.uid, provider: auth_hash.provider) - Gitlab::AppLogger.info("(LDAP) Updating legacy LDAP user #{self.auth_hash.email} with extern_uid => #{auth_hash.uid}") - return user - end - end - def authenticate(login, password) # Check user against LDAP backend if user is not authenticated # Only check with valid login and password to prevent anonymous bind results @@ -44,10 +28,18 @@ module Gitlab @adapter ||= OmniAuth::LDAP::Adaptor.new(ldap_conf) end - protected + def user_filter(login) + filter = Net::LDAP::Filter.eq(adapter.uid, login) + # Apply LDAP user filter if present + if ldap_conf['user_filter'].present? + user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter']) + filter = Net::LDAP::Filter.join(filter, user_filter) + end + filter + end - def find_by_uid_and_provider - find_by_uid(auth_hash.uid) + def ldap_conf + Gitlab.config.ldap end def find_by_uid(uid) @@ -58,24 +50,39 @@ module Gitlab def provider 'ldap' end + end - def raise_error(message) - raise OmniAuth::Error, "(LDAP) " + message - end + def initialize(auth_hash) + super + update_user_attributes + end - def ldap_conf - Gitlab.config.ldap - end + # instance methods + def gl_user + @gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user + end - def user_filter(login) - filter = Net::LDAP::Filter.eq(adapter.uid, login) - # Apply LDAP user filter if present - if ldap_conf['user_filter'].present? - user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter']) - filter = Net::LDAP::Filter.join(filter, user_filter) - end - filter - end + def find_by_uid_and_provider + # LDAP distinguished name is case-insensitive + model. + where(provider: auth_hash.provider). + where('lower(extern_uid) = ?', auth_hash.uid.downcase).last + end + + def find_by_email + model.find_by(email: auth_hash.email) + end + + def update_user_attributes + gl_user.attributes = { + extern_uid: auth_hash.uid, + provider: auth_hash.provider, + email: auth_hash.email + } + end + + def changed? + gl_user.changed? end def needs_blocking? diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 17512a51658..ddcce7557a0 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -70,14 +70,22 @@ module Gitlab insert_piece($1) end - # Context passed to the markdoqwn pipeline + # Used markdown pipelines in GitLab: + # GitlabEmojiFilter - performs emoji replacement. + # + # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters + filters = [ + HTML::Pipeline::Gitlab::GitlabEmojiFilter + ] + markdown_context = { - asset_root: File.join(root_url, - Gitlab::Application.config.assets.prefix) + asset_root: Gitlab.config.gitlab.url, + asset_host: Gitlab::Application.config.asset_host } - result = HTML::Pipeline::Gitlab::MarkdownPipeline.call(text, - markdown_context) + markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline + + result = markdown_pipeline.call(text, markdown_context) text = result[:output].to_html(save_with: 0) allowed_attributes = ActionView::Base.sanitized_allowed_attributes diff --git a/lib/gitlab/oauth/auth_hash.rb b/lib/gitlab/oauth/auth_hash.rb index 0198f61f427..ce52beec78e 100644 --- a/lib/gitlab/oauth/auth_hash.rb +++ b/lib/gitlab/oauth/auth_hash.rb @@ -21,7 +21,7 @@ module Gitlab end def name - (info.name || full_name).to_s.force_encoding('utf-8') + (info.try(:name) || full_name).to_s.force_encoding('utf-8') end def full_name diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index b768eda185f..699258baee4 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -6,55 +6,52 @@ module Gitlab module OAuth class User - class << self - attr_reader :auth_hash + attr_accessor :auth_hash, :gl_user - def find(auth_hash) - self.auth_hash = auth_hash - find_by_uid_and_provider - end - - def create(auth_hash) - user = new(auth_hash) - user.save_and_trigger_callbacks - end + def initialize(auth_hash) + self.auth_hash = auth_hash + end - def model - ::User - end + def persisted? + gl_user.persisted? + end - def auth_hash=(auth_hash) - @auth_hash = AuthHash.new(auth_hash) - end + def new? + !gl_user.persisted? + end - protected - def find_by_uid_and_provider - model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last - end + def valid? + gl_user.valid? end - # Instance methods - attr_accessor :auth_hash, :user + def save + gl_user.save! + log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" + gl_user.block if needs_blocking? - def initialize(auth_hash) - self.auth_hash = auth_hash - self.user = self.class.model.new(user_attributes) - user.skip_confirmation! + gl_user + rescue ActiveRecord::RecordInvalid => e + log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}" + return self, e.record.errors + end + + def gl_user + @user ||= find_by_uid_and_provider || build_new_user end + protected def auth_hash=(auth_hash) @auth_hash = AuthHash.new(auth_hash) end - def save_and_trigger_callbacks - user.save! - log.info "(OAuth) Creating user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" - user.block if needs_blocking? + def find_by_uid_and_provider + model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last + end - user - rescue ActiveRecord::RecordInvalid => e - log.info "(OAuth) Email #{e.record.errors[:email]}. Username #{e.record.errors[:username]}" - return nil, e.record.errors + def build_new_user + model.new(user_attributes).tap do |user| + user.skip_confirmation! + end end def user_attributes @@ -80,6 +77,10 @@ module Gitlab def needs_blocking? Gitlab.config.omniauth['block_auto_created_users'] end + + def model + ::User + end end end end diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index c3378d6a18f..54d740908d5 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -10,6 +10,17 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML super options end + # If project has issue number 39, apostrophe will be linked in + # regular text to the issue as Redcarpet will convert apostrophe to + # #39; + # We replace apostrophe with right single quote before Redcarpet + # does the processing and put the apostrophe back in postprocessing. + # This only influences regular text, code blocks are untouched. + def normal_text(text) + return text unless text.present? + text.gsub("'", "’") + end + def block_code(code, language) # New lines are placed to fix an rendering issue # with code wrapped inside <h1> tag for next case: @@ -44,6 +55,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML end def postprocess(full_document) + full_document.gsub!("’", "'") unless @template.instance_variable_get("@project_wiki") || @project.nil? full_document = h.create_relative_links(full_document) end diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 5f1afe6575c..d3fb467ef27 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -19,7 +19,7 @@ ## - installing an old version of Nginx with the chunkin module [2] compiled in, or ## - using a newer version of Nginx. ## -## At the time of writing we do not know if either of these theoretical solutions works. +## At the time of writing we do not know if either of these theoretical solutions works. ## As a workaround users can use Git over SSH to push large files. ## ## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 @@ -42,7 +42,7 @@ server { listen *:80 default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice - + ## Redirects all traffic to the HTTPS host root /nowhere; ## root doesn't have to be a valid path since we are redirecting rewrite ^ https://$server_name$request_uri? permanent; @@ -65,14 +65,15 @@ server { ssl_certificate /etc/nginx/ssl/gitlab.crt; ssl_certificate_key /etc/nginx/ssl/gitlab.key; - ssl_ciphers 'AES256+EECDH:AES256+EDH'; + # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs + ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4'; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_session_cache builtin:1000 shared:SSL:10m; ssl_prefer_server_ciphers on; - ## [WARNING] The following header states that the browser should only communicate + ## [WARNING] The following header states that the browser should only communicate ## with your server over a secure connection for the next 24 months. add_header Strict-Transport-Security max-age=63072000; add_header X-Frame-Options SAMEORIGIN; diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index cbfa736c84c..b6ed874e11a 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -27,7 +27,10 @@ namespace :gitlab do group_name = nil if group_name == '.' # Skip if group or user - next if namespaces.include?(name) + if namespaces.include?(name) + puts "Skipping #{project.name} due to namespace conflict with group or user".yellow + next + end puts "Processing #{repo_path}".yellow diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 524c4d5fa21..98ba5a47ee5 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -10,8 +10,12 @@ describe "Projects", feature: true do visit edit_project_path(@project) end - it "should be correct path" do - expect { click_link "Remove project" }.to change {Project.count}.by(-1) + it "should be correct path", js: true do + expect { + click_link "Remove project" + fill_in 'confirm_name_input', with: @project.path + click_button 'Confirm' + }.to change {Project.count}.by(-1) end end end diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb new file mode 100644 index 00000000000..5af76968183 --- /dev/null +++ b/spec/finders/snippets_finder_spec.rb @@ -0,0 +1,94 @@ +require 'spec_helper' + +describe SnippetsFinder do + let(:user) { create :user } + let(:user1) { create :user } + let(:group) { create :group } + + let(:project1) { create(:empty_project, :public, group: group) } + let(:project2) { create(:empty_project, :private, group: group) } + + + context ':all filter' do + before do + @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE) + @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL) + @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC) + end + + it "returns all private and internal snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :all) + snippets.should include(@snippet2, @snippet3) + snippets.should_not include(@snippet1) + end + + it "returns all public snippets" do + snippets = SnippetsFinder.new.execute(nil, filter: :all) + snippets.should include(@snippet3) + snippets.should_not include(@snippet1, @snippet2) + end + end + + context ':by_user filter' do + before do + @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user) + @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user) + @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user) + end + + it "returns all public and internal snippets" do + snippets = SnippetsFinder.new.execute(user1, filter: :by_user, user: user) + snippets.should include(@snippet2, @snippet3) + snippets.should_not include(@snippet1) + end + + it "returns internal snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_internal") + snippets.should include(@snippet2) + snippets.should_not include(@snippet1, @snippet3) + end + + it "returns private snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_private") + snippets.should include(@snippet1) + snippets.should_not include(@snippet2, @snippet3) + end + + it "returns public snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_public") + snippets.should include(@snippet3) + snippets.should_not include(@snippet1, @snippet2) + end + + it "returns all snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user) + snippets.should include(@snippet1, @snippet2, @snippet3) + end + end + + context 'by_project filter' do + before do + @snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1) + @snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1) + @snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1) + end + + it "returns public snippets for unauthorized user" do + snippets = SnippetsFinder.new.execute(nil, filter: :by_project, project: project1) + snippets.should include(@snippet3) + snippets.should_not include(@snippet1, @snippet2) + end + + it "returns public and internal snippets for none project members" do + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) + snippets.should include(@snippet2, @snippet3) + snippets.should_not include(@snippet1) + end + + it "returns all snippets for project members" do + project1.team << [user, :developer] + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) + snippets.should include(@snippet1, @snippet2, @snippet3) + end + end +end diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb new file mode 100644 index 00000000000..4de54d291f2 --- /dev/null +++ b/spec/helpers/events_helper_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe EventsHelper do + include ApplicationHelper + include GitlabMarkdownHelper + + it 'should display one line of plain text without alteration' do + input = 'A short, plain note' + expect(event_note(input)).to match(input) + expect(event_note(input)).not_to match(/\.\.\.\z/) + end + + it 'should display inline code' do + input = 'A note with `inline code`' + expected = 'A note with <code>inline code</code>' + + expect(event_note(input)).to match(expected) + end + + it 'should truncate a note with multiple paragraphs' do + input = "Paragraph 1\n\nParagraph 2" + expected = 'Paragraph 1...' + + expect(event_note(input)).to match(expected) + end + + it 'should display the first line of a code block' do + input = "```\nCode block\nwith two lines\n```" + expected = '<pre><code class="">Code block...</code></pre>' + + expect(event_note(input)).to match(expected) + end + + it 'should truncate a single long line of text' do + text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars + input = "#{text}#{text}#{text}#{text}" # 200 chars + expected = "#{text}#{text}".sub(/.{3}/, '...') + + expect(event_note(input)).to match(expected) + end + + it 'should preserve a link href when link text is truncated' do + text = 'The quick brown fox jumped over the lazy dog' # 44 chars + input = "#{text}#{text}#{text} " # 133 chars + link_url = 'http://example.com/foo/bar/baz' # 30 chars + input << link_url + expected_link_text = 'http://example...</a>' + + expect(event_note(input)).to match(link_url) + expect(event_note(input)).to match(expected_link_text) + end +end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 15033f07432..61751a82369 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -60,7 +60,7 @@ describe GitlabMarkdownHelper do end it "should link using a short id" do - actual = "Backported from #{commit.short_id(6)}" + actual = "Backported from #{commit.short_id}" gfm(actual).should match(expected) end @@ -530,6 +530,24 @@ describe GitlabMarkdownHelper do markdown(actual).should match(%r{<li>light by <a.+>@#{member.user.username}</a></li>}) end + it "should not link the apostrophe to issue 39" do + project.team << [user, :master] + project.issues.stub(:where).with(iid: '39').and_return([issue]) + + actual = "Yes, it is @#{member.user.username}'s task." + expected = /Yes, it is <a.+>@#{member.user.username}<\/a>'s task/ + markdown(actual).should match(expected) + end + + it "should not link the apostrophe to issue 39 in code blocks" do + project.team << [user, :master] + project.issues.stub(:where).with(iid: '39').and_return([issue]) + + actual = "Yes, `it is @#{member.user.username}'s task.`" + expected = /Yes, <code>it is @gfm\'s task.<\/code>/ + markdown(actual).should match(expected) + end + it "should handle references in <em>" do actual = "Apply _!#{merge_request.iid}_ ASAP" @@ -576,9 +594,21 @@ describe GitlabMarkdownHelper do end it "should generate absolute urls for emoji" do - markdown(":smile:").should include("src=\"#{url_helper('emoji/smile')}") + markdown(":smile:").should include("src=\"http://localhost/assets/emoji/smile.png") + end + + it "should generate absolute urls for emoji if relative url is present" do + Gitlab.config.gitlab.stub(:url).and_return('http://localhost/gitlab/root') + markdown(":smile:").should include("src=\"http://localhost/gitlab/root/assets/emoji/smile.png") end + it "should generate absolute urls for emoji if asset_host is present" do + Gitlab::Application.config.stub(:asset_host).and_return("https://cdn.example.com") + ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com") + markdown(":smile:").should include("src=\"https://cdn.example.com/assets/emoji/smile.png") + end + + it "should handle relative urls for a file in master" do actual = "[GitLab API doc](doc/api/README.md)\n" expected = "<p><a href=\"/#{project.path_with_namespace}/blob/#{@ref}/doc/api/README.md\">GitLab API doc</a></p>\n" diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb new file mode 100644 index 00000000000..ed5785b31e6 --- /dev/null +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Gitlab::GitAccessWiki do + let(:access) { Gitlab::GitAccessWiki.new } + let(:project) { create(:project) } + let(:user) { create(:user) } + + describe 'push_allowed?' do + before do + create(:protected_branch, name: 'master', project: project) + project.team << [user, :developer] + end + + subject { access.push_allowed?(user, project, changes) } + + it { should be_true } + end + + def changes + ['6f6d7e7ed 570e7b2ab refs/heads/master'] + end +end diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb index d232cb20759..a1aec0bb96f 100644 --- a/spec/lib/gitlab/ldap/user_spec.rb +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -1,30 +1,28 @@ require 'spec_helper' describe Gitlab::LDAP::User do - let(:gl_user) { Gitlab::LDAP::User } + let(:gl_user) { Gitlab::LDAP::User.new(auth_hash) } let(:info) do - double( + { name: 'John', email: 'john@example.com', nickname: 'john' - ) + } + end + let(:auth_hash) do + double(uid: 'my-uid', provider: 'ldap', info: double(info)) end - before { Gitlab.config.stub(omniauth: {}) } describe :find_or_create do - let(:auth) do - double(info: info, provider: 'ldap', uid: 'my-uid') - end - it "finds the user if already existing" do existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldap') - expect{ gl_user.find_or_create(auth) }.to_not change{ User.count } + expect{ gl_user.save }.to_not change{ User.count } end it "connects to existing non-ldap user if the email matches" do existing_user = create(:user, email: 'john@example.com') - expect{ gl_user.find_or_create(auth) }.to_not change{ User.count } + expect{ gl_user.save }.to_not change{ User.count } existing_user.reload expect(existing_user.extern_uid).to eql 'my-uid' @@ -32,7 +30,7 @@ describe Gitlab::LDAP::User do end it "creates a new user if not found" do - expect{ gl_user.find_or_create(auth) }.to change{ User.count }.by(1) + expect{ gl_user.save }.to change{ User.count }.by(1) end end diff --git a/spec/lib/gitlab/oauth/auth_hash_spec.rb b/spec/lib/gitlab/oauth/auth_hash_spec.rb new file mode 100644 index 00000000000..5eb77b492b2 --- /dev/null +++ b/spec/lib/gitlab/oauth/auth_hash_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Gitlab::OAuth::AuthHash do + let(:auth_hash) do + Gitlab::OAuth::AuthHash.new(double({ + provider: 'twitter', + uid: uid, + info: double(info_hash) + })) + end + let(:uid) { 'my-uid' } + let(:email) { 'my-email@example.com' } + let(:nickname) { 'my-nickname' } + let(:info_hash) { + { + email: email, + nickname: nickname, + name: 'John', + first_name: "John", + last_name: "Who" + } + } + + context "defaults" do + it { expect(auth_hash.provider).to eql 'twitter' } + it { expect(auth_hash.uid).to eql uid } + it { expect(auth_hash.email).to eql email } + it { expect(auth_hash.username).to eql nickname } + it { expect(auth_hash.name).to eql "John" } + it { expect(auth_hash.password).to_not be_empty } + end + + context "email not provided" do + before { info_hash.delete(:email) } + it "generates a temp email" do + expect( auth_hash.email).to start_with('temp-email-for-oauth') + end + end + + context "username not provided" do + before { info_hash.delete(:nickname) } + + it "takes the first part of the email as username" do + expect( auth_hash.username ).to eql "my-email" + end + end + + context "name not provided" do + before { info_hash.delete(:name) } + + it "concats first and lastname as the name" do + expect( auth_hash.name ).to eql "John Who" + end + end +end
\ No newline at end of file diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb index c241e198609..e4e96fd9f49 100644 --- a/spec/lib/gitlab/oauth/user_spec.rb +++ b/spec/lib/gitlab/oauth/user_spec.rb @@ -1,83 +1,55 @@ require 'spec_helper' describe Gitlab::OAuth::User do - let(:gl_auth) { Gitlab::OAuth::User } - let(:info) do - double( + let(:oauth_user) { Gitlab::OAuth::User.new(auth_hash) } + let(:gl_user) { oauth_user.gl_user } + let(:uid) { 'my-uid' } + let(:provider) { 'my-provider' } + let(:auth_hash) { double(uid: uid, provider: provider, info: double(info_hash)) } + let(:info_hash) do + { nickname: 'john', name: 'John', email: 'john@mail.com' - ) + } end - before do - Gitlab.config.stub(omniauth: {}) - end - - describe :find do + describe :persisted? do let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') } it "finds an existing user based on uid and provider (facebook)" do auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider') - assert gl_auth.find(auth) + expect( oauth_user.persisted? ).to be_true end - it "finds an existing user based on nested uid and provider" do - auth = double(info: info, uid: 'my-uid', provider: 'my-provider') - assert gl_auth.find(auth) + it "returns false if use is not found in database" do + auth_hash.stub(uid: 'non-existing') + expect( oauth_user.persisted? ).to be_false end end - describe :create do - it "should create user from LDAP" do - auth = double(info: info, uid: 'my-uid', provider: 'ldap') - user = gl_auth.create(auth) - - user.should be_valid - user.extern_uid.should == auth.uid - user.provider.should == 'ldap' - end - - it "should create user from Omniauth" do - auth = double(info: info, uid: 'my-uid', provider: 'twitter') - user = gl_auth.create(auth) + describe :save do + context "LDAP" do + let(:provider) { 'ldap' } + it "creates a user from LDAP" do + oauth_user.save - user.should be_valid - user.extern_uid.should == auth.uid - user.provider.should == 'twitter' + expect(gl_user).to be_valid + expect(gl_user.extern_uid).to eql uid + expect(gl_user.provider).to eql 'ldap' + end end - it "should apply defaults to user" do - auth = double(info: info, uid: 'my-uid', provider: 'ldap') - user = gl_auth.create(auth) - - user.should be_valid - user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit - user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group - end - - it "Set a temp email address if not provided (like twitter does)" do - info = double( - uid: 'my-uid', - nickname: 'john', - name: 'John' - ) - auth = double(info: info, uid: 'my-uid', provider: 'my-provider') - - user = gl_auth.create(auth) - expect(user.email).to_not be_empty - end + context "twitter" do + let(:provider) { 'twitter' } - it 'generates a username if non provided (google)' do - info = double( - uid: 'my-uid', - name: 'John', - email: 'john@example.com' - ) - auth = double(info: info, uid: 'my-uid', provider: 'my-provider') + it "creates a user from Omniauth" do + oauth_user.save - user = gl_auth.create(auth) - expect(user.username).to eql 'john' + expect(gl_user).to be_valid + expect(gl_user.extern_uid).to eql uid + expect(gl_user.provider).to eql 'twitter' + end end end end diff --git a/spec/models/assembla_service_spec.rb b/spec/models/assembla_service_spec.rb index 0ef475b87c3..4300090eb13 100644 --- a/spec/models/assembla_service_spec.rb +++ b/spec/models/assembla_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/buildbox_service_spec.rb b/spec/models/buildbox_service_spec.rb new file mode 100644 index 00000000000..1d9ca51be16 --- /dev/null +++ b/spec/models/buildbox_service_spec.rb @@ -0,0 +1,73 @@ +# == 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 BuildboxService do + describe 'Associations' do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe 'commits methods' do + before do + @project = Project.new + @project.stub( + default_branch: 'default-brancho' + ) + + @service = BuildboxService.new + @service.stub( + project: @project, + service_hook: true, + project_url: 'https://buildbox.io/account-name/example-project', + token: 'secret-sauce-webhook-token:secret-sauce-status-token' + ) + end + + describe :webhook_url do + it 'returns the webhook url' do + @service.webhook_url.should == + 'https://webhook.buildbox.io/deliver/secret-sauce-webhook-token' + end + end + + describe :commit_status_path do + it 'returns the correct status page' do + @service.commit_status_path('2ab7834c').should == + 'https://gitlab.buildbox.io/status/secret-sauce-status-token.json?commit=2ab7834c' + end + end + + describe :build_page do + it 'returns the correct build page' do + @service.build_page('2ab7834c').should == + 'https://buildbox.io/account-name/example-project/builds?commit=2ab7834c' + end + end + + describe :builds_page do + it 'returns the correct path to the builds page' do + @service.builds_path.should == + 'https://buildbox.io/account-name/example-project/builds?branch=default-brancho' + end + end + + describe :status_img_path do + it 'returns the correct path to the status image' do + @service.status_img_path.should == 'https://badge.buildbox.io/secret-sauce-status-token.svg' + end + end + end +end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 6f201adc4e8..a6ec44da4be 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -75,7 +75,7 @@ eos it_behaves_like 'a mentionable' do let(:subject) { commit } let(:mauthor) { create :user, email: commit.author_email } - let(:backref_text) { "commit #{subject.sha[0..5]}" } + let(:backref_text) { "commit #{subject.id}" } let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } } # Include the subject in the repository stub. diff --git a/spec/models/flowdock_service_spec.rb b/spec/models/flowdock_service_spec.rb index 710b8cba502..5540f0fa988 100644 --- a/spec/models/flowdock_service_spec.rb +++ b/spec/models/flowdock_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/gemnasium_service_spec.rb b/spec/models/gemnasium_service_spec.rb index 5de645cdf33..60ffa6f8b05 100644 --- a/spec/models/gemnasium_service_spec.rb +++ b/spec/models/gemnasium_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb index e4cd8bb90c3..ebc377047be 100644 --- a/spec/models/gitlab_ci_service_spec.rb +++ b/spec/models/gitlab_ci_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/group_member_spec.rb b/spec/models/group_member_spec.rb index 6acbc9bb4ae..38657de6793 100644 --- a/spec/models/group_member_spec.rb +++ b/spec/models/group_member_spec.rb @@ -1,14 +1,16 @@ # == Schema Information # -# Table name: group_members +# Table name: members # # id :integer not null, primary key # access_level :integer not null -# group_id :integer not null +# source_id :integer not null +# source_type :string(255) not null # user_id :integer not null +# notification_level :integer not null +# type :string(255) # created_at :datetime # updated_at :datetime -# notification_level :integer default(3), not null # require 'spec_helper' diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index eeecd714a28..2d839e9611b 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -228,7 +228,7 @@ describe Note do it { should be_valid } its(:noteable) { should == issue } - its(:note) { should == "_mentioned in commit #{commit.sha[0..5]}_" } + its(:note) { should == "_mentioned in commit #{commit.sha}_" } end context 'merge request from an issue' do @@ -267,7 +267,7 @@ describe Note do its(:noteable_type) { should == "Commit" } its(:noteable_id) { should be_nil } its(:commit_id) { should == commit.id } - its(:note) { should == "_mentioned in commit #{parent_commit.id[0...6]}_" } + its(:note) { should == "_mentioned in commit #{parent_commit.id}_" } end end diff --git a/spec/models/project_member_spec.rb b/spec/models/project_member_spec.rb index 0178d065e57..9b5f89b6d7d 100644 --- a/spec/models/project_member_spec.rb +++ b/spec/models/project_member_spec.rb @@ -1,14 +1,16 @@ # == Schema Information # -# Table name: project_members +# Table name: members # # id :integer not null, primary key +# access_level :integer not null +# source_id :integer not null +# source_type :string(255) not null # user_id :integer not null -# project_id :integer not null +# notification_level :integer not null +# type :string(255) # created_at :datetime # updated_at :datetime -# project_access :integer default(0), not null -# notification_level :integer default(3), not null # require 'spec_helper' diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb index e4df934460b..a6e1d9eef50 100644 --- a/spec/models/project_snippet_spec.rb +++ b/spec/models/project_snippet_spec.rb @@ -2,17 +2,17 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # require 'spec_helper' diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 34c1a686c96..bbf50b654f4 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -27,6 +27,8 @@ describe ProjectTeam do it { project.team.master?(guest).should be_false } it { project.team.master?(reporter).should be_false } it { project.team.master?(nonmember).should be_false } + it { project.team.member?(nonmember).should be_false } + it { project.team.member?(guest).should be_true } end end @@ -60,6 +62,8 @@ describe ProjectTeam do it { project.team.master?(guest).should be_true } it { project.team.master?(reporter).should be_false } it { project.team.master?(nonmember).should be_false } + it { project.team.member?(nonmember).should be_false } + it { project.team.member?(guest).should be_true } end end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 480aeabf67f..c96f2b20529 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb index 3e555193b32..95df38d9400 100644 --- a/spec/models/slack_service_spec.rb +++ b/spec/models/slack_service_spec.rb @@ -2,14 +2,14 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text # require 'spec_helper' diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index d179e9516e2..1ef2c512c1f 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -2,17 +2,17 @@ # # Table name: snippets # -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# id :integer not null, primary key +# title :string(255) +# content :text +# author_id :integer not null +# project_id :integer +# created_at :datetime +# updated_at :datetime +# file_name :string(255) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # require 'spec_helper' diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0250014bc21..8c79bf5f3c2 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -429,4 +429,32 @@ describe User do expect(user.starred?(project)).to be_false end end + + describe "#sort" do + before do + User.delete_all + @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 + + it "sorts users as late_signed_in" do + User.sort('oldest_sign_in').first.should == @user1 + end + + it "sorts users as recently_created" do + User.sort('recently_created').first.should == @user + end + + it "sorts users as late_created" do + User.sort('late_created').first.should == @user1 + end + + it "sorts users by name when nil is passed" do + User.sort(nil).first.should == @user + end + end end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 692834c9f29..ebd74206699 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -30,15 +30,15 @@ def common_mentionable_setup "!#{mentioned_mr.iid}, " + "#{ext_proj.path_with_namespace}##{ext_issue.iid}, " + "#{ext_proj.path_with_namespace}!#{ext_mr.iid}, " + - "#{ext_proj.path_with_namespace}@#{ext_commit.id[0..5]}, " + - "#{mentioned_commit.sha[0..5]} and itself as #{backref_text}" + "#{ext_proj.path_with_namespace}@#{ext_commit.short_id}, " + + "#{mentioned_commit.sha[0..10]} and itself as #{backref_text}" end before do # Wire the project's repository to return the mentioned commit, and +nil+ for any # unrecognized commits. - commitmap = { '123456' => mentioned_commit } - extra_commits.each { |c| commitmap[c.sha[0..5]] = c } + commitmap = { '1234567890a' => mentioned_commit } + extra_commits.each { |c| commitmap[c.short_id] = c } mproject.repository.stub(:commit) { |sha| commitmap[sha] } set_mentionable_text.call(ref_string) end @@ -54,7 +54,6 @@ shared_examples 'a mentionable' do it "extracts references from its reference property" do # De-duplicate and omit itself refs = subject.references(mproject) - refs.should have(6).items refs.should include(mentioned_issue) refs.should include(mentioned_mr) @@ -90,7 +89,7 @@ shared_examples 'an editable mentionable' do it 'creates new cross-reference notes when the mentionable text is edited' do new_text = "still mentions ##{mentioned_issue.iid}, " + - "#{mentioned_commit.sha[0..5]}, " + + "#{mentioned_commit.sha[0..10]}, " + "#{ext_issue.iid}, " + "new refs: ##{other_issue.iid}, " + "#{ext_proj.path_with_namespace}##{other_ext_issue.iid}" |