diff options
103 files changed, 1526 insertions, 448 deletions
diff --git a/CHANGELOG b/CHANGELOG index 4623a1f1a44..d577a2087b8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,11 +1,14 @@ Please view this file on the master branch, on stable branches it's out of date. v 7.10.0 (unreleased) + - Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu) + - Add ability to configure Reply-To address in gitlab.yml (Stan Hu) - Fix broken side-by-side diff view on merge request page (Stan Hu) - Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu) - Allow HTML tags in Markdown input - Fix code unfold not working on Compare commits page (Stan Hu) - Fix dots in Wiki slugs causing errors (Stan Hu) + - Make maximum attachment size configurable via Application Settings (Stan Hu) - Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg) - Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu) - Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu) @@ -48,13 +51,22 @@ v 7.10.0 (unreleased) - Prevent note form from being cleared when submitting failed. - Improve file icons rendering on tree (Sullivan Sénéchal) - API: Add pagination to project events + - Get issue links in notification mail to work again. + - Don't show commit comment button when user is not signed in. + - Fix admin user projects lists. + - Don't leak private group existence by redirecting from namespace controller to group controller. + - Ability to skip some items from backup (database, respositories or uploads) + - Fix "Hello @username." references not working by no longer allowing usernames to end in period. + - Archive repositories in background worker. + - Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace. + - Project labels are now available over the API under the "tag_list" field (Cristian Medina) + - Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz) + - Fix and improve help rendering (Sullivan Sénéchal) - Fix final line in EmailsOnPush email diff being rendered as error. -v 7.9.0 - - Send EmailsOnPush email when branch or tag is created or deleted. - - Faster merge request processing for large repository - - Prevent doubling AJAX request with each commit visit via Turbolink - - Prevent unnecessary doubling of js events on import pages and user calendar + +v 7.9.2 + - Contains no changes v 7.9.1 - Include missing events and fix save functionality in admin service template settings form (Stan Hu) @@ -144,6 +156,10 @@ v 7.9.0 - Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther) - Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup) - Add canceled status for CI + - Send EmailsOnPush email when branch or tag is created or deleted. + - Faster merge request processing for large repository + - Prevent doubling AJAX request with each commit visit via Turbolink + - Prevent unnecessary doubling of js events on import pages and user calendar v 7.8.4 - Fix issue_tracker_id substitution in custom issue trackers @@ -1,2 +1,2 @@ web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} -worker: bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default +worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 821712f7512..330ebac6f75 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -100,6 +100,8 @@ class Dispatcher when 'users:show' new User() new Activities() + when 'admin:users:show' + new ProjectsList() switch path.first() when 'admin' diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index 06e9f0001ae..fca2a290e2d 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -10,6 +10,7 @@ class @DropzoneInput iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>" btnAlert = "<button type=\"button\"" + alertAttr + ">×</button>" project_uploads_path = window.project_uploads_path or null + max_file_size = gon.max_file_size or 10 form_textarea = $(form).find("textarea.markdown-area") form_textarea.wrap "<div class=\"div-dropzone\"></div>" @@ -76,7 +77,7 @@ class @DropzoneInput dictDefaultMessage: "" clickable: true paramName: "file" - maxFilesize: 10 + maxFilesize: max_file_size uploadMultiple: false headers: "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") @@ -108,9 +109,10 @@ class @DropzoneInput return error: (temp, errorMessage) -> - checkIfMsgExists = $(".error-alert").children().length + errorAlert = $(form).find('.error-alert') + checkIfMsgExists = errorAlert.children().length if checkIfMsgExists is 0 - $(".error-alert").append divAlert + errorAlert.append divAlert $(".div-dropzone-alert").append btnAlert + errorMessage return @@ -221,9 +223,10 @@ class @DropzoneInput "display": "none" showError = (message) -> - checkIfMsgExists = $(".error-alert").children().length + errorAlert = $(form).find('.error-alert') + checkIfMsgExists = errorAlert.children().length if checkIfMsgExists is 0 - $(".error-alert").append divAlert + errorAlert.append divAlert $(".div-dropzone-alert").append btnAlert + message closeAlertMessage = -> @@ -237,4 +240,4 @@ class @DropzoneInput formatLink: (link) -> text = "[#{link.alt}](#{link.url})" text = "!#{text}" if link.is_image - text
\ No newline at end of file + text diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index bf71c144eaf..4e2e6550eb2 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -9,12 +9,8 @@ class @Issue if $("a.btn-close").length $("li.task-list-item input:checkbox").prop("disabled", false) - $(".task-list-item input:checkbox").on( - "click" - null - "issue" - updateTaskState - ) + $('.task-list-item input:checkbox').off('change') + $('.task-list-item input:checkbox').change('issue', updateTaskState) $('.issue-details').waitForImages -> $('.issuable-affix').affix offset: @@ -22,3 +18,7 @@ class @Issue @top = ($('.issuable-affix').offset().top - 70) bottom: -> @bottom = $('.footer').outerHeight(true) + $('.issuable-affix').on 'affix.bs.affix', -> + $(@).width($(@).outerWidth()) + .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> + $(@).width('') diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 6127d2bb480..fc75f143836 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -26,6 +26,10 @@ class @MergeRequest @top = ($('.issuable-affix').offset().top - 70) bottom: -> @bottom = $('.footer').outerHeight(true) + $('.issuable-affix').on 'affix.bs.affix', -> + $(@).width($(@).outerWidth()) + .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> + $(@).width('') # Local jQuery finder $: (selector) -> @@ -81,12 +85,8 @@ class @MergeRequest this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_widget.failed').show() - $(".task-list-item input:checkbox").on( - "click" - null - "merge_request" - updateTaskState - ) + $('.task-list-item input:checkbox').off('change') + $('.task-list-item input:checkbox').change('merge_request', updateTaskState) activateTab: (action) -> this.$('.merge-request-tabs li').removeClass 'active' @@ -164,4 +164,3 @@ class @MergeRequest else setTimeout(merge_request.mergeInProgress, 3000) dataType: 'json' - diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index db393e08819..7c3021989a8 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -246,7 +246,7 @@ li.note { .milestone { &.milestone-closed { - background: #eee; + background: #f9f9f9; } .progress { margin-bottom: 0; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 9a5685877f8..b5fda196bf0 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -38,6 +38,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :twitter_sharing_enabled, :sign_in_text, :home_page_url, + :max_attachment_size, restricted_visibility_levels: [] ) end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2809f90c0d5..80e983b5314 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -203,6 +203,7 @@ class ApplicationController < ActionController::Base gon.api_version = API::API.version gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s + gon.max_file_size = current_application_settings.max_attachment_size; if current_user gon.current_user_id = current_user.id diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index c4d620d87b1..fbd9e67e6df 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -3,17 +3,35 @@ class HelpController < ApplicationController end def show - @category = params[:category] - @file = params[:file] + @filepath = params[:filepath] + @format = params[:format] - if File.exists?(Rails.root.join('doc', @category, @file + '.md')) - render 'show' + respond_to do |format| + format.md { render_doc } + format.all { send_file_data } + end + end + + def shortcuts + end + + private + + def render_doc + if File.exists?(Rails.root.join('doc', @filepath + '.md')) + render 'show.html.haml' else not_found! end end - def shortcuts + def send_file_data + path = Rails.root.join('doc', "#{@filepath}.#{@format}") + if File.exists?(path) + send_file(path, disposition: 'inline') + else + head :not_found + end end def ui diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index edb8bd4160b..93a7ace3530 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -8,7 +8,7 @@ class Import::BaseController < ApplicationController namespace.add_owner(current_user) rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid namespace = Namespace.find_by_path_or_name(@target_namespace) - unless namespace.owner == current_user + unless current_user.can?(:create_projects, namespace) @already_been_taken = true return false end diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 83ebc5fddca..bb8d7e0235c 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -36,8 +36,11 @@ class Import::BitbucketController < Import::BaseController def create @repo_id = params[:repo_id] || "" repo = client.project(@repo_id.gsub("___", "/")) - @target_namespace = params[:new_namespace].presence || repo["owner"] @project_name = repo["slug"] + + repo_owner = repo["owner"] + repo_owner = current_user.username if repo_owner == client.user["user"]["username"] + @target_namespace = params[:new_namespace].presence || repo_owner namespace = get_or_create_namespace || (render and return) diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index 8650b6464dc..87b41454c77 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -31,8 +31,11 @@ class Import::GithubController < Import::BaseController def create @repo_id = params[:repo_id].to_i repo = client.repo(@repo_id) - @target_namespace = params[:new_namespace].presence || repo.owner.login @project_name = repo.name + + repo_owner = repo.owner.login + repo_owner = current_user.username if repo_owner == client.user.login + @target_namespace = params[:new_namespace].presence || repo_owner namespace = get_or_create_namespace || (render and return) diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index e979dad4b11..bddbfded812 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -28,8 +28,11 @@ class Import::GitlabController < Import::BaseController def create @repo_id = params[:repo_id].to_i repo = client.project(@repo_id) - @target_namespace = params[:new_namespace].presence || repo["namespace"]["path"] @project_name = repo["name"] + + repo_owner = repo["namespace"]["path"] + repo_owner = current_user.username if repo_owner == client.user["username"] + @target_namespace = params[:new_namespace].presence || repo_owner namespace = get_or_create_namespace || (render and return) diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb index b7a9d8c1291..386d103ee5a 100644 --- a/app/controllers/namespaces_controller.rb +++ b/app/controllers/namespaces_controller.rb @@ -4,14 +4,22 @@ class NamespacesController < ApplicationController def show namespace = Namespace.find_by(path: params[:id]) - unless namespace - return render_404 + if namespace + if namespace.is_a?(Group) + group = namespace + else + user = namespace.owner + end end - if namespace.type == "Group" - redirect_to group_path(namespace) + if user + redirect_to user_path(user) + elsif group && can?(current_user, :read_group, group) + redirect_to group_path(group) + elsif current_user.nil? + authenticate_user! else - redirect_to user_path(namespace.owner) + render_404 end end end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index cbb888b25e8..96defb0c721 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -11,18 +11,18 @@ class Projects::RepositoriesController < Projects::ApplicationController end def archive - unless can?(current_user, :download_code, @project) - render_404 and return + begin + file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute + rescue + return head :not_found end - file_path = ArchiveRepositoryService.new.execute(@project, params[:ref], params[:format]) - if file_path # Send file to user response.headers["Content-Length"] = File.open(file_path).size.to_s send_file file_path else - render_404 + redirect_to request.fullpath end end end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 3386fac8657..9703c8d9e9c 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -29,6 +29,10 @@ module GitlabRoutingHelper namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args) end + def milestone_path(entity, *args) + namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args) + end + def project_url(project, *args) namespace_project_url(project.namespace, project, *args) end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 187e21832f0..a9030729b48 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -40,7 +40,7 @@ module IconsHelper def file_type_icon_class(type, mode, name) if type == 'folder' icon_class = 'folder' - elsif mode == 0120000 + elsif mode == '120000' icon_class = 'share' else # Guess which icon to choose based on file extension. diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index a4bd4d30215..ad4a7612724 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -13,22 +13,34 @@ module IssuesHelper OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned') end - def url_for_project_issues(project = @project) + def url_for_project_issues(project = @project, options = {}) return '' if project.nil? - project.issues_tracker.project_url + if options[:only_path] + project.issues_tracker.project_path + else + project.issues_tracker.project_url + end end - def url_for_new_issue(project = @project) + def url_for_new_issue(project = @project, options = {}) return '' if project.nil? - project.issues_tracker.new_issue_url + if options[:only_path] + project.issues_tracker.new_issue_path + else + project.issues_tracker.new_issue_url + end end - def url_for_issue(issue_iid, project = @project) + def url_for_issue(issue_iid, project = @project, options = {}) return '' if project.nil? - project.issues_tracker.issue_url(issue_iid) + if options[:only_path] + project.issues_tracker.issue_path(issue_iid) + else + project.issues_tracker.issue_url(issue_iid) + end end def title_for_issue(issue_iid, project = @project) diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 8fcdd3bc853..0c186ab5866 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -19,7 +19,7 @@ class Notify < ActionMailer::Base default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root default from: Proc.new { default_sender_address.format } - default reply_to: "noreply@#{Gitlab.config.gitlab.host}" + default reply_to: Gitlab.config.gitlab.email_reply_to # Just send email with 2 seconds delay def self.delay diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 1c87db613ae..6e98c4c2f02 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -16,6 +16,7 @@ # default_branch_protection :integer default(2) # twitter_sharing_enabled :boolean default(TRUE) # restricted_visibility_levels :text +# max_attachment_size :integer default(10) # class ApplicationSetting < ActiveRecord::Base @@ -49,7 +50,8 @@ class ApplicationSetting < ActiveRecord::Base twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'], gravatar_enabled: Settings.gravatar['enabled'], sign_in_text: Settings.extra['sign_in_text'], - restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'] + restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], + max_attachment_size: Settings.gitlab['max_attachment_size'] ) end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 74900d4675d..d96e07034ec 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -52,7 +52,7 @@ module Mentionable if identifier == "all" users.push(*project.team.members.flatten) elsif namespace = Namespace.find_by(path: identifier) - if namespace.type == "Group" + if namespace.is_a?(Group) users.push(*namespace.users) else users << namespace.owner diff --git a/app/models/event.rb b/app/models/event.rb index 57f6d5cd4e0..c9a88ffa8e0 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -183,7 +183,11 @@ class Event < ActiveRecord::Base elsif commented? "commented on" elsif created_project? - "created" + if project.import? + "imported" + else + "created" + end else "opened" end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 35280889a86..dd74165f887 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -24,8 +24,8 @@ class Namespace < ActiveRecord::Base validates :name, presence: true, uniqueness: true, length: { within: 0..255 }, - format: { with: Gitlab::Regex.name_regex, - message: Gitlab::Regex.name_regex_message } + format: { with: Gitlab::Regex.namespace_name_regex, + message: Gitlab::Regex.namespace_name_regex_message } validates :description, length: { within: 0..255 } validates :path, @@ -33,8 +33,8 @@ class Namespace < ActiveRecord::Base presence: true, length: { within: 1..255 }, exclusion: { in: Gitlab::Blacklist.path }, - format: { with: Gitlab::Regex.path_regex, - message: Gitlab::Regex.path_regex_message } + format: { with: Gitlab::Regex.namespace_regex, + message: Gitlab::Regex.namespace_regex_message } delegate :name, to: :owner, allow_nil: true, prefix: true @@ -44,21 +44,36 @@ class Namespace < ActiveRecord::Base scope :root, -> { where('type IS NULL') } - def self.by_path(path) - where('lower(path) = :value', value: path.downcase).first - end + class << self + def by_path(path) + where('lower(path) = :value', value: path.downcase).first + end - # Case insensetive search for namespace by path or name - def self.find_by_path_or_name(path) - find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase) - end + # Case insensetive search for namespace by path or name + def find_by_path_or_name(path) + find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase) + end - def self.search(query) - where("name LIKE :query OR path LIKE :query", query: "%#{query}%") - end + def search(query) + where("name LIKE :query OR path LIKE :query", query: "%#{query}%") + end + + def clean_path(path) + path.gsub!(/@.*\z/, "") + path.gsub!(/\.git\z/, "") + path.gsub!(/\A-/, "") + path.gsub!(/.\z/, "") + path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + + counter = 0 + base = path + while Namespace.by_path(path).present? + counter += 1 + path = "#{base}#{counter}" + end - def self.global_id - 'GLN' + path + end end def to_param diff --git a/app/models/note.rb b/app/models/note.rb index e86160e7cd9..fdab4517df3 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -22,6 +22,7 @@ require 'file_size_validator' class Note < ActiveRecord::Base include Mentionable + include Gitlab::CurrentSettings default_value_for :system, false @@ -36,7 +37,8 @@ class Note < ActiveRecord::Base validates :note, :project, presence: true validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true - validates :attachment, file_size: { maximum: 10.megabytes.to_i } + # Attachments are deprecated and are handled by Markdown uploader + validates :attachment, file_size: { maximum: :max_attachment_size } validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } @@ -321,6 +323,10 @@ class Note < ActiveRecord::Base end end + def max_attachment_size + current_application_settings.max_attachment_size.megabytes.to_i + end + def commit_author @commit_author ||= project.team.users.find_by(email: noteable.author_email) || @@ -451,7 +457,7 @@ class Note < ActiveRecord::Base prev_match_line = line else prev_lines << line - + break if generate_line_code(line) == self.line_code prev_lines.shift if prev_lines.length >= max_number_of_lines diff --git a/app/models/project.rb b/app/models/project.rb index c50b8a12621..79572f255db 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -124,12 +124,12 @@ class Project < ActiveRecord::Base presence: true, length: { within: 0..255 }, format: { with: Gitlab::Regex.project_name_regex, - message: Gitlab::Regex.project_regex_message } + message: Gitlab::Regex.project_name_regex_message } validates :path, presence: true, length: { within: 0..255 }, - format: { with: Gitlab::Regex.path_regex, - message: Gitlab::Regex.path_regex_message } + format: { with: Gitlab::Regex.project_path_regex, + message: Gitlab::Regex.project_path_regex_message } validates :issues_enabled, :merge_requests_enabled, :wiki_enabled, inclusion: { in: [true, false] } validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 84346350a6c..5f0553f3b0b 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -20,8 +20,13 @@ class GitlabIssueTrackerService < IssueTrackerService include Rails.application.routes.url_helpers - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + default_url_options[:host] = Gitlab.config.gitlab.host + default_url_options[:protocol] = Gitlab.config.gitlab.protocol + default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port? + default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root + + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url def default? true @@ -32,20 +37,26 @@ class GitlabIssueTrackerService < IssueTrackerService end def project_url - "#{gitlab_url}#{namespace_project_issues_path(project.namespace, project)}" + namespace_project_issues_url(project.namespace, project) end def new_issue_url - "#{gitlab_url}#{new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project)}" + new_namespace_project_issue_url(namespace_id: project.namespace, project_id: project) end def issue_url(iid) - "#{gitlab_url}#{namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid)}" + namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: iid) end - private + def project_path + namespace_project_issues_path(project.namespace, project) + end + + def new_issue_path + new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project) + end - def gitlab_url - Gitlab.config.gitlab.relative_url_root.chomp("/") if Gitlab.config.gitlab.relative_url_root + def issue_path(iid) + namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid) end end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 8e90c44d103..c8ab9d63b74 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -34,6 +34,18 @@ class IssueTrackerService < Service self.issues_url.gsub(':id', iid.to_s) end + def project_path + project_url + end + + def new_issue_path + new_issue_url + end + + def issue_path(iid) + issue_url(iid) + end + def fields [ { type: 'text', name: 'description', placeholder: description }, diff --git a/app/models/repository.rb b/app/models/repository.rb index 77765cae1a0..72769498872 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -267,6 +267,9 @@ class Repository # Remove archives older than 2 hours def clean_old_archives repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path + + return unless File.directory?(repository_downloads_path) + Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 3fb2ec1d66c..b35e72c4bdb 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -33,8 +33,8 @@ class Snippet < ActiveRecord::Base validates :file_name, presence: true, length: { within: 0..255 }, - format: { with: Gitlab::Regex.path_regex, - message: Gitlab::Regex.path_regex_message } + format: { with: Gitlab::Regex.file_name_regex, + message: Gitlab::Regex.file_name_regex_message } validates :content, presence: true validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } diff --git a/app/models/user.rb b/app/models/user.rb index 979150b4d68..515f29ea0ba 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -129,8 +129,8 @@ class User < ActiveRecord::Base presence: true, uniqueness: { case_sensitive: false }, exclusion: { in: Gitlab::Blacklist.path }, - format: { with: Gitlab::Regex.username_regex, - message: Gitlab::Regex.username_regex_message } + format: { with: Gitlab::Regex.namespace_regex, + message: Gitlab::Regex.namespace_regex_message } validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true validate :namespace_uniq, if: ->(user) { user.username_changed? } @@ -229,22 +229,6 @@ class User < ActiveRecord::Base def build_user(attrs = {}) User.new(attrs) end - - def clean_username(username) - username.gsub!(/@.*\z/, "") - username.gsub!(/\.git\z/, "") - username.gsub!(/\A-/, "") - username.gsub!(/[^a-zA-Z0-9_\-\.]/, "") - - counter = 0 - base = username - while User.by_login(username).present? || Namespace.by_path(username).present? - counter += 1 - username = "#{base}#{counter}" - end - - username - end end # diff --git a/app/services/archive_repository_service.rb b/app/services/archive_repository_service.rb index 8823f6fdc67..e1b41527d8d 100644 --- a/app/services/archive_repository_service.rb +++ b/app/services/archive_repository_service.rb @@ -1,14 +1,62 @@ class ArchiveRepositoryService - def execute(project, ref, format) - storage_path = Gitlab.config.gitlab.repository_downloads_path + attr_reader :project, :ref, :format - unless File.directory?(storage_path) - FileUtils.mkdir_p(storage_path) + def initialize(project, ref, format) + format ||= 'tar.gz' + @project, @ref, @format = project, ref, format.downcase + end + + def execute(options = {}) + project.repository.clean_old_archives + + raise "No archive file path" unless file_path + + return file_path if archived? + + unless archiving? + RepositoryArchiveWorker.perform_async(project.id, ref, format) end - format ||= 'tar.gz' - repository = project.repository - repository.clean_old_archives - repository.archive_repo(ref, storage_path, format.downcase) + archived = wait_until_archived(options[:timeout] || 5.0) + + file_path if archived + end + + private + + def storage_path + Gitlab.config.gitlab.repository_downloads_path + end + + def file_path + @file_path ||= project.repository.archive_file_path(ref, storage_path, format) + end + + def pid_file_path + @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format) + end + + def archived? + File.exist?(file_path) + end + + def archiving? + File.exist?(pid_file_path) + end + + def wait_until_archived(timeout = 5.0) + return archived? if timeout == 0.0 + + t1 = Time.now + + begin + sleep 0.1 + + success = archived? + + t2 = Time.now + end until success || t2 - t1 >= timeout + + success end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index eeafefc25af..23833aa78ec 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -12,10 +12,10 @@ module Files file_name = File.basename(path) file_path = path - unless file_name =~ Gitlab::Regex.path_regex + unless file_name =~ Gitlab::Regex.file_name_regex return error( 'Your changes could not be committed, because the file name ' + - Gitlab::Regex.path_regex_message + Gitlab::Regex.file_name_regex_message ) end diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb index a186c97628f..992a7a7a1dc 100644 --- a/app/services/projects/upload_service.rb +++ b/app/services/projects/upload_service.rb @@ -5,7 +5,7 @@ module Projects end def execute - return nil unless @file + return nil unless @file and @file.size <= max_attachment_size uploader = FileUploader.new(@project) uploader.store!(@file) @@ -18,5 +18,11 @@ module Projects 'is_image' => uploader.image? } end + + private + + def max_attachment_size + current_application_settings.max_attachment_size.megabytes.to_i + end end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index edfcccfcf4c..4f3565c67eb 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -60,5 +60,10 @@ .col-sm-10 = f.text_area :sign_in_text, class: 'form-control', rows: 4 .help-block Markdown enabled + .form-group + = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :max_attachment_size, class: 'form-control' + .form-actions = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 8db2b2a709c..4ef8e878a7f 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -41,4 +41,4 @@ .panel.panel-default - %iframe{src: sidekiq_path, width: '100%', height: 900, style: "border: none"} + %iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"} diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 05372f4124f..b0b23132560 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -42,11 +42,11 @@ %li %span.light http: %strong - = link_to @project.http_url_to_repo + = link_to @project.http_url_to_repo, project_path(@project) %li %span.light ssh: %strong - = link_to @project.ssh_url_to_repo + = link_to @project.ssh_url_to_repo, project_path(@project) - if @project.repository.exists? %li %span.light fs: diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml new file mode 100644 index 00000000000..21e730bb7ff --- /dev/null +++ b/app/views/dashboard/milestones/_milestone.html.haml @@ -0,0 +1,20 @@ +%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } + %h4 + = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) + .row + .col-sm-6 + = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do + = pluralize milestone.issue_count, 'Issue' + + = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do + = pluralize milestone.merge_requests_count, 'Merge Request' + + %span.light #{milestone.percent_complete}% complete + + .col-sm-6 + = milestone_progress_bar(milestone) + %div + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = milestone.project.name_with_namespace diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index caf3b685864..9944c0df815 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -16,23 +16,5 @@ .nothing-here-block No milestones to show - else - @dashboard_milestones.each do |milestone| - %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - %h4 - = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) - %div - %div - = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do - = pluralize milestone.issue_count, 'Issue' - - = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do - = pluralize milestone.merge_requests_count, 'Merge Request' - - %span.light #{milestone.percent_complete}% complete - = milestone_progress_bar(milestone) - %div - %br - - milestone.milestones.each do |milestone| - = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) do - %span.label.label-default - = milestone.project.name_with_namespace + = render 'milestone', milestone: milestone = paginate @dashboard_milestones, theme: "gitlab" diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml index 3c7153d235f..552525f4a07 100644 --- a/app/views/events/event/_created_project.html.haml +++ b/app/views/events/event/_created_project.html.haml @@ -18,10 +18,10 @@ %a.twitter-share-button{ | href: "https://twitter.com/share", | "data-url" => event.project.web_url, | - "data-text" => "I just created a new project in GitLab! GitLab is version control on your server.", | + "data-text" => "I just #{event.project.imported? ? "imported" : "created"} a new project in GitLab! GitLab is version control on your server.", | "data-size" => "medium", | "data-related" => "gitlab", | "data-hashtags" => "gitlab", | "data-count" => "none"} Tweet - %script{src: "//platform.twitter.com/widgets.js"}
\ No newline at end of file + %script{src: "//platform.twitter.com/widgets.js"} diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml new file mode 100644 index 00000000000..94fc43a581e --- /dev/null +++ b/app/views/groups/milestones/_milestone.html.haml @@ -0,0 +1,25 @@ +%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } + .pull-right + - if can?(current_user, :manage_group, @group) + - if milestone.closed? + = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" + - else + = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" + %h4 + = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) + .row + .col-sm-6 + = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = pluralize milestone.issue_count, 'Issue' + + = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = pluralize milestone.merge_requests_count, 'Merge Request' + + %span.light #{milestone.percent_complete}% complete + .col-sm-6 + = milestone_progress_bar(milestone) + %div + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = milestone.project.name diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 57dc235f5bb..008d5a6bd22 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -18,29 +18,5 @@ .nothing-here-block No milestones to show - else - @group_milestones.each do |milestone| - %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - .pull-right - - if can?(current_user, :manage_group, @group) - - if milestone.closed? - = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" - - else - = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" - %h4 - = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) - %div - %div - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do - = pluralize milestone.issue_count, 'Issue' - - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do - = pluralize milestone.merge_requests_count, 'Merge Request' - - %span.light #{milestone.percent_complete}% complete - = milestone_progress_bar(milestone) - %div - %br - - milestone.milestones.each do |milestone| - = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) do - %span.label.label-default - = milestone.project.name + = render 'milestone', milestone: milestone = paginate @group_milestones, theme: "gitlab" diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index eca34dbff06..f22aa92caf7 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,2 +1,2 @@ .documentation.wiki - = markdown File.read(Rails.root.join('doc', @category, @file + '.md')).gsub("$your_email", current_user.email) + = markdown File.read(Rails.root.join('doc', @filepath + '.md')).gsub("$your_email", current_user.email) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 52681865d64..6c13f30f627 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -53,7 +53,7 @@ - if project_nav_tab? :issues = nav_link(controller: :issues) do - = link_to url_for_project_issues, title: 'Issues', class: 'shortcuts-issues' do + = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do %i.fa.fa-exclamation-circle %span Issues diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml index 778a78acf56..e796353cec4 100644 --- a/app/views/notify/_note_message.html.haml +++ b/app/views/notify/_note_message.html.haml @@ -1,2 +1,2 @@ %div - = replace_image_links_with_base64(markdown(@note.note), @note.project) + = replace_image_links_with_base64(markdown(@note.note, reference_only_path: false), @note.project) diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml index 03cbee94608..d4d413b5b44 100644 --- a/app/views/notify/new_issue_email.html.haml +++ b/app/views/notify/new_issue_email.html.haml @@ -1,5 +1,5 @@ -if @issue.description - = replace_image_links_with_base64(markdown(@issue.description), @issue.project) + = replace_image_links_with_base64(markdown(@issue.description, reference_only_path: false), @issue.project) - if @issue.assignee_id.present? %p diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index 729a7bb505d..60e33227e01 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -6,4 +6,4 @@ Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name} -if @merge_request.description - = replace_image_links_with_base64(markdown(@merge_request.description), @merge_request.project) + = replace_image_links_with_base64(markdown(@merge_request.description, reference_only_path: false), @merge_request.project) diff --git a/app/views/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml index f4f4c2662cf..3adb3087289 100644 --- a/app/views/projects/_dropdown.html.haml +++ b/app/views/projects/_dropdown.html.haml @@ -5,7 +5,7 @@ %ul.dropdown-menu - if @project.issues_enabled && can?(current_user, :write_issue, @project) %li - = link_to url_for_new_issue, title: "New Issue" do + = link_to url_for_new_issue(@project, only_path: true), title: "New Issue" do New issue - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) %li diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 7039c85bb2c..62360158ff9 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -11,16 +11,14 @@ %span.cred (Expired) %small = milestone.expires_at - - if milestone.is_empty? - %span.muted Empty - - else - %div - %div - = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do - = pluralize milestone.issues.count, 'Issue' - - = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do - = pluralize milestone.merge_requests.count, 'Merge Request' - - %span.light #{milestone.percent_complete}% complete + .row + .col-sm-6 + = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do + = pluralize milestone.issues.count, 'Issue' + + = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do + = pluralize milestone.merge_requests.count, 'Merge Request' + + %span.light #{milestone.percent_complete}% complete + .col-sm-6 = milestone_progress_bar(milestone) diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index be96c302143..2ada6cb6700 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -12,7 +12,7 @@ .comment-hints.clearfix .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. - + .error-alert .note-form-actions .buttons diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml index b7383d5594e..297fa537394 100644 --- a/app/views/users/_projects.html.haml +++ b/app/views/users/_projects.html.haml @@ -1,13 +1,13 @@ -- if @contributed_projects.present? +- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present? .panel.panel-default.contributed-projects .panel-heading Projects contributed to = render 'shared/projects_list', - projects: @contributed_projects.sort_by(&:star_count).reverse, + projects: contributed_projects.sort_by(&:star_count).reverse, projects_limit: 5, stars: true, avatar: false -- if @projects.present? +- if local_assigns.has_key?(:projects) && projects.present? .panel.panel-default .panel-heading Personal projects = render 'shared/projects_list', - projects: @projects.sort_by(&:star_count).reverse, + projects: projects.sort_by(&:star_count).reverse, projects_limit: 10, stars: true, avatar: false diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 5e1d65e2ed8..9dd8cb0738c 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -44,7 +44,7 @@ = spinner %aside.col-md-4 = render 'profile', user: @user - = render 'projects' + = render 'projects', projects: @projects, contributed_projects: @contributed_projects :coffeescript $(".user-calendar").load("#{user_calendar_path}") diff --git a/app/workers/repository_archive_worker.rb b/app/workers/repository_archive_worker.rb new file mode 100644 index 00000000000..021c1139568 --- /dev/null +++ b/app/workers/repository_archive_worker.rb @@ -0,0 +1,43 @@ +class RepositoryArchiveWorker + include Sidekiq::Worker + + sidekiq_options queue: :archive_repo + + attr_accessor :project, :ref, :format + + def perform(project_id, ref, format) + @project = Project.find(project_id) + @ref, @format = ref, format.downcase + + repository = project.repository + + repository.clean_old_archives + + return unless file_path + return if archived? || archiving? + + repository.archive_repo(ref, storage_path, format) + end + + private + + def storage_path + Gitlab.config.gitlab.repository_downloads_path + end + + def file_path + @file_path ||= project.repository.archive_file_path(ref, storage_path, format) + end + + def pid_file_path + @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format) + end + + def archived? + File.exist?(file_path) + end + + def archiving? + File.exist?(pid_file_path) + end +end diff --git a/bin/background_jobs b/bin/background_jobs index 59a51c5c868..a041a4b0433 100755 --- a/bin/background_jobs +++ b/bin/background_jobs @@ -37,7 +37,7 @@ start_no_deamonize() start_sidekiq() { - bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 + bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 } load_ok() diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 760a589d6e2..3d91b67e748 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -49,6 +49,7 @@ production: &base # Email address used in the "From" field in mails sent by GitLab email_from: example@example.com email_display_name: GitLab + email_reply_to: noreply@example.com # Email server smtp settings are in config/initializers/smtp_settings.rb.sample diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 15c1ae9466f..de79595d031 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -103,6 +103,7 @@ Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" Settings.gitlab['email_display_name'] ||= "GitLab" +Settings.gitlab['email_reply_to'] ||= "noreply@#{Settings.gitlab.host}" Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' Settings.gitlab['user_home'] ||= begin @@ -119,6 +120,7 @@ Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab['webhook_timeout'] ||= 10 +Settings.gitlab['max_attachment_size'] ||= 10 Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil? Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil? Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil? diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 8f8bef42bef..6978ad93024 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -6,3 +6,4 @@ Mime::Type.register_alias "text/plain", :diff Mime::Type.register_alias "text/plain", :patch +Mime::Type.register_alias 'text/html', :md diff --git a/config/routes.rb b/config/routes.rb index 388858d2670..c1b85b025b5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -39,9 +39,9 @@ Gitlab::Application.routes.draw do # Help get 'help' => 'help#index' - get 'help/:category/:file' => 'help#show', as: :help_page get 'help/shortcuts' get 'help/ui' => 'help#ui' + get 'help/:filepath' => 'help#show', as: :help_page, constraints: { filepath: /[^\.]+/ } # # Global snippets diff --git a/db/migrate/20150324133047_remove_periods_at_ends_of_usernames.rb b/db/migrate/20150324133047_remove_periods_at_ends_of_usernames.rb new file mode 100644 index 00000000000..7ce53c2a0d6 --- /dev/null +++ b/db/migrate/20150324133047_remove_periods_at_ends_of_usernames.rb @@ -0,0 +1,76 @@ +class RemovePeriodsAtEndsOfUsernames < ActiveRecord::Migration + include Gitlab::ShellAdapter + + class Namespace < ActiveRecord::Base + class << self + def by_path(path) + where('lower(path) = :value', value: path.downcase).first + end + + def clean_path(path) + path = path.dup + path.gsub!(/@.*\z/, "") + path.gsub!(/\.git\z/, "") + path.gsub!(/\A-/, "") + path.gsub!(/.\z/, "") + path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + + counter = 0 + base = path + while Namespace.by_path(path).present? + counter += 1 + path = "#{base}#{counter}" + end + + path + end + end + end + + def up + changed_paths = {} + + select_all("SELECT id, username FROM users WHERE username LIKE '%.'").each do |user| + username_was = user["username"] + username = Namespace.clean_path(username_was) + changed_paths[username_was] = username + + username = quote_string(username) + execute "UPDATE users SET username = '#{username}' WHERE id = #{user["id"]}" + execute "UPDATE namespaces SET path = '#{username}', name = '#{username}' WHERE type IS NULL AND owner_id = #{user["id"]}" + end + + select_all("SELECT id, path FROM namespaces WHERE type = 'Group' AND path LIKE '%.'").each do |group| + path_was = group["path"] + path = Namespace.clean_path(path_was) + changed_paths[path_was] = path + + path = quote_string(path) + execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{group["id"]}" + end + + changed_paths.each do |path_was, path| + if gitlab_shell.mv_namespace(path_was, path) + # If repositories moved successfully we need to remove old satellites + # and send update instructions to users. + # However we cannot allow rollback since we moved namespace dir + # So we basically we mute exceptions in next actions + begin + gitlab_shell.rm_satellites(path_was) + # We cannot send update instructions since models and mailers + # can't safely be used from migrations as they may be written for + # later versions of the database. + # send_update_instructions + rescue + # Returning false does not rollback after_* transaction but gives + # us information about failing some of tasks + false + end + else + # if we cannot move namespace directory we should rollback + # db changes in order to prevent out of sync between db and fs + raise Exception.new('namespace directory cannot be moved') + end + end + end +end diff --git a/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb b/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb new file mode 100644 index 00000000000..1d161674a9a --- /dev/null +++ b/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddMaxAttachmentSizeToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :max_attachment_size, :integer, default: 10, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 4a445ae5832..14e32a7946e 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: 20150324155957) do +ActiveRecord::Schema.define(version: 20150328132231) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -28,6 +28,7 @@ ActiveRecord::Schema.define(version: 20150324155957) do t.integer "default_branch_protection", default: 2 t.boolean "twitter_sharing_enabled", default: true t.text "restricted_visibility_levels" + t.integer "max_attachment_size", default: 10, null: false end create_table "broadcast_messages", force: true do |t| diff --git a/doc/api/projects.md b/doc/api/projects.md index 7fe244477db..55d525fef66 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -44,6 +44,10 @@ Parameters: "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", "web_url": "http://example.com/diaspora/diaspora-client", + "tag_list": [ + "example", + "disapora client" + ], "owner": { "id": 3, "name": "Diaspora", @@ -80,6 +84,10 @@ Parameters: "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", "http_url_to_repo": "http://example.com/brightbox/puppet.git", "web_url": "http://example.com/brightbox/puppet", + "tag_list": [ + "example", + "puppet" + ], "owner": { "id": 4, "name": "Brightbox", @@ -163,6 +171,10 @@ Parameters: "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", + "tag_list": [ + "example", + "disapora project" + ], "owner": { "id": 3, "name": "Diaspora", diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 99cdfff0ac6..2e41fad89e7 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -17,6 +17,13 @@ sudo gitlab-rake gitlab:backup:create sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` +Also you can choose what should be backed up by adding environment variable SKIP. Available options: db, +uploads (attachments), repositories. Use a comma to specify several options at the same time. + +``` +sudo gitlab-rake gitlab:backup:create SKIP=db,uploads +``` + Example output: ``` diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index b9031f6f32b..af01c7058ea 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -42,6 +42,7 @@ Feature: Project Issues Given I visit issue page "Release 0.4" And I leave a comment like "XML attached" Then I should see comment "XML attached" + And I should see an error alert section within the comment form @javascript Scenario: I search issue diff --git a/features/steps/dashboard/help.rb b/features/steps/dashboard/help.rb index ef433c57c6e..fa52e391f05 100644 --- a/features/steps/dashboard/help.rb +++ b/features/steps/dashboard/help.rb @@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps end step 'I visit the "Rake Tasks" help page' do - visit help_page_path("raketasks", "maintenance") + visit help_page_path('raketasks/maintenance', format: 'md') end step 'I should see "Rake Tasks" page markdown rendered' do diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index e8ca3f7c176..e2834d51a27 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -204,6 +204,12 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end end + step 'I should see an error alert section within the comment form' do + within(".js-main-target-form") do + find(".error-alert") + end + end + step 'The code block should be unchanged' do page.should have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```") end diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 557555aee58..caf6c73ee06 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -74,7 +74,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I fill the new file name with an illegal name' do - fill_in :file_name, with: '.git' + fill_in :file_name, with: 'Spaces Not Allowed' end step 'I fill the commit message' do diff --git a/lib/api/branches.rb b/lib/api/branches.rb index edfdf842f85..592100a7045 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -1,5 +1,4 @@ require 'mime/types' -require 'uri' module API # Projects API @@ -101,10 +100,11 @@ module API # branch (required) - The name of the branch # Example Request: # DELETE /projects/:id/repository/branches/:branch - delete ":id/repository/branches/:branch" do + delete ":id/repository/branches/:branch", + requirements: { branch: /.*/ } do authorize_push_project result = DeleteBranchService.new(user_project, current_user). - execute(URI.unescape(params[:branch])) + execute(params[:branch]) if result[:status] == :success { diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 489be210784..51cb934616b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -46,7 +46,7 @@ module API end class Project < Grape::Entity - expose :id, :description, :default_branch + expose :id, :description, :default_branch, :tag_list expose :public?, as: :public expose :archived?, as: :archived expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index b259914a01c..1fbf3dca3c6 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -133,10 +133,11 @@ module API authorize! :download_code, user_project begin - file_path = ArchiveRepositoryService.new.execute( - user_project, - params[:sha], - params[:format]) + file_path = ArchiveRepositoryService.new( + user_project, + params[:sha], + params[:format] + ).execute rescue not_found!('File') end @@ -149,7 +150,7 @@ module API env['api.format'] = :binary present data else - not_found!('File') + redirect request.fullpath end end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index afd05897509..b69aebf9fe1 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -1,7 +1,5 @@ module Backup class Manager - BACKUP_CONTENTS = %w{repositories/ db/ uploads/ backup_information.yml} - def pack # saving additional informations s = {} @@ -9,6 +7,7 @@ module Backup s[:backup_created_at] = Time.now s[:gitlab_version] = Gitlab::VERSION s[:tar_version] = tar_version + s[:skipped] = ENV["SKIP"] tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar" Dir.chdir(Gitlab.config.backup.path) do @@ -17,12 +16,12 @@ module Backup file << s.to_yaml.gsub(/^---\n/,'') end - FileUtils.chmod_R(0700, %w{db uploads repositories}) + FileUtils.chmod(0700, folders_to_backup) # create archive $progress.print "Creating backup archive: #{tar_file} ... " orig_umask = File.umask(0077) - if Kernel.system('tar', '-cf', tar_file, *BACKUP_CONTENTS) + if Kernel.system('tar', '-cf', tar_file, *backup_contents) $progress.puts "done".green else puts "creating archive #{tar_file} failed".red @@ -46,6 +45,7 @@ module Backup connection = ::Fog::Storage.new(connection_settings) directory = connection.directories.get(remote_directory) + if directory.files.create(key: tar_file, body: File.open(tar_file), public: false) $progress.puts "done".green else @@ -56,7 +56,10 @@ module Backup def cleanup $progress.print "Deleting tmp directories ... " - BACKUP_CONTENTS.each do |dir| + + backup_contents.each do |dir| + next unless File.exist?(File.join(Gitlab.config.backup.path, dir)) + if FileUtils.rm_rf(File.join(Gitlab.config.backup.path, dir)) $progress.puts "done".green else @@ -73,6 +76,7 @@ module Backup if keep_time > 0 removed = 0 + Dir.chdir(Gitlab.config.backup.path) do file_list = Dir.glob('*_gitlab_backup.tar') file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ } @@ -84,6 +88,7 @@ module Backup end end end + $progress.puts "done. (#{removed} removed)".green else $progress.puts "skipping".yellow @@ -96,6 +101,7 @@ module Backup # check for existing backups in the backup dir file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i } puts "no backups found" if file_list.count == 0 + if file_list.count > 1 && ENV["BACKUP"].nil? puts "Found more than one backup, please specify which one you want to restore:" puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup" @@ -110,6 +116,7 @@ module Backup end $progress.print "Unpacking backup ... " + unless Kernel.system(*%W(tar -xf #{tar_file})) puts "unpacking backup failed".red exit 1 @@ -117,7 +124,6 @@ module Backup $progress.puts "done".green end - settings = YAML.load_file("backup_information.yml") ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0 # restoring mismatching backups can lead to unexpected problems @@ -136,5 +142,29 @@ module Backup tar_version, _ = Gitlab::Popen.popen(%W(tar --version)) tar_version.force_encoding('locale').split("\n").first end + + def skipped?(item) + settings[:skipped] && settings[:skipped].include?(item) + end + + private + + def backup_contents + folders_to_backup + ["backup_information.yml"] + end + + def folders_to_backup + folders = %w{repositories db uploads} + + if ENV["SKIP"] + return folders.reject{ |folder| ENV["SKIP"].include?(folder) } + end + + folders + end + + def settings + @settings ||= YAML.load_file("backup_information.yml") + end end end diff --git a/lib/file_size_validator.rb b/lib/file_size_validator.rb index 42970c1be59..2eae55e534b 100644 --- a/lib/file_size_validator.rb +++ b/lib/file_size_validator.rb @@ -25,8 +25,8 @@ class FileSizeValidator < ActiveModel::EachValidator keys.each do |key| value = options[key] - unless value.is_a?(Integer) && value >= 0 - raise ArgumentError, ":#{key} must be a nonnegative Integer" + unless (value.is_a?(Integer) && value >= 0) || value.is_a?(Symbol) + raise ArgumentError, ":#{key} must be a nonnegative Integer or symbol" end end end @@ -39,6 +39,14 @@ class FileSizeValidator < ActiveModel::EachValidator CHECKS.each do |key, validity_check| next unless check_value = options[key] + check_value = + case check_value + when Integer + check_value + when Symbol + record.send(check_value) + end + value ||= [] if key == :maximum value_size = value.size diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index db33af2c2da..54420e62c90 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -10,29 +10,16 @@ module Gitlab end def execute - @project = Project.new( + ::Projects::CreateService.new(current_user, name: repo["name"], path: repo["slug"], description: repo["description"], - namespace: namespace, - creator: current_user, + namespace_id: namespace.id, visibility_level: repo["is_private"] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, import_type: "bitbucket", import_source: "#{repo["owner"]}/#{repo["slug"]}", import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git" - ) - - if @project.save! - @project.reload - - if @project.import_failed? - @project.import_retry - else - @project.import_start - end - end - - @project + ).execute end end end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 0ebebfa09c4..d8f696d247b 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -20,7 +20,8 @@ module Gitlab signin_enabled: Settings.gitlab['signin_enabled'], gravatar_enabled: Settings.gravatar['enabled'], sign_in_text: Settings.extra['sign_in_text'], - restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'] + restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], + max_attachment_size: Settings.gitlab['max_attachment_size'] ) end end diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index 9439ca6cbf4..2723eec933e 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -10,29 +10,16 @@ module Gitlab end def execute - @project = Project.new( + ::Projects::CreateService.new(current_user, name: repo.name, path: repo.name, description: repo.description, - namespace: namespace, - creator: current_user, + namespace_id: namespace.id, visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, import_type: "github", import_source: repo.full_name, import_url: repo.clone_url.sub("https://", "https://#{current_user.github_access_token}@") - ) - - if @project.save! - @project.reload - - if @project.import_failed? - @project.import_retry - else - @project.import_start - end - end - - @project + ).execute end end end diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb index f48ede9d067..9c00896c913 100644 --- a/lib/gitlab/gitlab_import/client.rb +++ b/lib/gitlab/gitlab_import/client.rb @@ -28,6 +28,10 @@ module Gitlab client.auth_code.get_token(code, redirect_uri: redirect_uri).token end + def user + api.get("/api/v3/user").parsed + end + def issues(project_identifier) lazy_page_iterator(PER_PAGE) do |page| api.get("/api/v3/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb index 6424d56f8f1..f0d7141bf56 100644 --- a/lib/gitlab/gitlab_import/project_creator.rb +++ b/lib/gitlab/gitlab_import/project_creator.rb @@ -10,29 +10,16 @@ module Gitlab end def execute - @project = Project.new( + ::Projects::CreateService.new(current_user, name: repo["name"], path: repo["path"], description: repo["description"], - namespace: namespace, - creator: current_user, + namespace_id: namespace.id, visibility_level: repo["visibility_level"], import_type: "gitlab", import_source: repo["path_with_namespace"], import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{current_user.gitlab_access_token}@") - ) - - if @project.save! - @project.reload - - if @project.import_failed? - @project.import_retry - else - @project.import_start - end - end - - @project + ).execute end end end diff --git a/lib/gitlab/gitorious_import/project_creator.rb b/lib/gitlab/gitorious_import/project_creator.rb index 3cbebe53997..cc9a91c91f4 100644 --- a/lib/gitlab/gitorious_import/project_creator.rb +++ b/lib/gitlab/gitorious_import/project_creator.rb @@ -10,29 +10,16 @@ module Gitlab end def execute - @project = Project.new( + ::Projects::CreateService.new(current_user, name: repo.name, path: repo.path, description: repo.description, - namespace: namespace, - creator: current_user, + namespace_id: namespace.id, visibility_level: Gitlab::VisibilityLevel::PUBLIC, import_type: "gitorious", import_source: repo.full_name, import_url: repo.import_url - ) - - if @project.save! - @project.reload - - if @project.import_failed? - @project.import_retry - else - @project.import_start - end - end - - @project + ).execute end end end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 41bb8d08924..f1e2ae74a3a 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -32,12 +32,12 @@ module Gitlab module Markdown include IssuesHelper - attr_reader :html_options + attr_reader :options, :html_options # Public: Parse the provided text with GitLab-Flavored Markdown # # text - the source text - # project - extra options for the reference links as given to link_to + # project - the project # html_options - extra options for the reference links as given to link_to def gfm(text, project = @project, html_options = {}) gfm_with_options(text, {}, project, html_options) @@ -46,9 +46,10 @@ module Gitlab # Public: Parse the provided text with GitLab-Flavored Markdown # # text - the source text - # options - parse_tasks: true - render tasks - # - xhtml: true - output XHTML instead of HTML - # project - extra options for the reference links as given to link_to + # options - parse_tasks - render tasks + # - xhtml - output XHTML instead of HTML + # - reference_only_path - Use relative path for reference links + # project - the project # html_options - extra options for the reference links as given to link_to def gfm_with_options(text, options = {}, project = @project, html_options = {}) return text if text.nil? @@ -58,6 +59,13 @@ module Gitlab # for gsub calls to work as we need them to. text = text.dup.to_str + options.reverse_merge!( + parse_tasks: false, + xhtml: false, + reference_only_path: true + ) + + @options = options @html_options = html_options # Extract pre blocks so they are not altered @@ -113,12 +121,13 @@ module Gitlab markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline result = markdown_pipeline.call(text, markdown_context) - saveoptions = 0 + + save_options = 0 if options[:xhtml] - saveoptions |= Nokogiri::XML::Node::SaveOptions::AS_XHTML + save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML end - text = result[:output].to_html(save_with: saveoptions) + text = result[:output].to_html(save_with: save_options) if options[:parse_tasks] text = parse_tasks(text) @@ -152,7 +161,7 @@ module Gitlab text end - NAME_STR = '[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*' + NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})" REFERENCE_PATTERN = %r{ @@ -229,33 +238,37 @@ module Gitlab end def reference_user(identifier, project = @project, _ = nil) - options = html_options.merge( + link_options = html_options.merge( class: "gfm gfm-project_member #{html_options[:class]}" ) if identifier == "all" - link_to("@all", namespace_project_url(project.namespace, project), options) + link_to( + "@all", + namespace_project_url(project.namespace, project, only_path: options[:reference_only_path]), + link_options + ) elsif namespace = Namespace.find_by(path: identifier) url = - if namespace.type == "Group" - group_url(identifier) - else - user_url(identifier) + if namespace.is_a?(Group) + group_url(identifier, only_path: options[:reference_only_path]) + else + user_url(identifier, only_path: options[:reference_only_path]) end - - link_to("@#{identifier}", url, options) + + link_to("@#{identifier}", url, link_options) end end def reference_label(identifier, project = @project, _ = nil) if label = project.labels.find_by(id: identifier) - options = html_options.merge( + link_options = html_options.merge( class: "gfm gfm-label #{html_options[:class]}" ) link_to( render_colored_label(label), namespace_project_issues_path(project.namespace, project, label_name: label.name), - options + link_options ) end end @@ -263,14 +276,14 @@ module Gitlab def reference_issue(identifier, project = @project, prefix_text = nil) if project.default_issues_tracker? if project.issue_exists? identifier - url = url_for_issue(identifier, project) + url = url_for_issue(identifier, project, only_path: options[:reference_only_path]) title = title_for_issue(identifier, project) - options = html_options.merge( + link_options = html_options.merge( title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}" ) - link_to("#{prefix_text}##{identifier}", url, options) + link_to("#{prefix_text}##{identifier}", url, link_options) end else if project.external_issue_tracker.present? @@ -280,44 +293,46 @@ module Gitlab end end - def reference_merge_request(identifier, project = @project, - prefix_text = nil) + def reference_merge_request(identifier, project = @project, prefix_text = nil) if merge_request = project.merge_requests.find_by(iid: identifier) - options = html_options.merge( + link_options = html_options.merge( title: "Merge Request: #{merge_request.title}", class: "gfm gfm-merge_request #{html_options[:class]}" ) url = namespace_project_merge_request_url(project.namespace, project, - merge_request) - link_to("#{prefix_text}!#{identifier}", url, options) + merge_request, + only_path: options[:reference_only_path]) + link_to("#{prefix_text}!#{identifier}", url, link_options) end end def reference_snippet(identifier, project = @project, _ = nil) if snippet = project.snippets.find_by(id: identifier) - options = html_options.merge( + link_options = html_options.merge( title: "Snippet: #{snippet.title}", class: "gfm gfm-snippet #{html_options[:class]}" ) link_to( "$#{identifier}", - namespace_project_snippet_url(project.namespace, project, snippet), - options + namespace_project_snippet_url(project.namespace, project, snippet, + only_path: options[:reference_only_path]), + link_options ) end end def reference_commit(identifier, project = @project, prefix_text = nil) if project.valid_repo? && commit = project.repository.commit(identifier) - options = html_options.merge( + link_options = html_options.merge( title: commit.link_title, class: "gfm gfm-commit #{html_options[:class]}" ) prefix_text = "#{prefix_text}@" if prefix_text link_to( "#{prefix_text}#{identifier}", - namespace_project_commit_url(project.namespace, project, commit), - options + namespace_project_commit_url( project.namespace, project, commit, + only_path: options[:reference_only_path]), + link_options ) end end @@ -328,11 +343,11 @@ module Gitlab inclusive = identifier !~ /\.{3}/ from_id << "^" if inclusive - if project.valid_repo? && - from = project.repository.commit(from_id) && + if project.valid_repo? && + from = project.repository.commit(from_id) && to = project.repository.commit(to_id) - options = html_options.merge( + link_options = html_options.merge( title: "Commits #{from_id} through #{to_id}", class: "gfm gfm-commit_range #{html_options[:class]}" ) @@ -340,22 +355,23 @@ module Gitlab link_to( "#{prefix_text}#{identifier}", - namespace_project_compare_url(project.namespace, project, from: from_id, to: to_id), - options + namespace_project_compare_url(project.namespace, project, + from: from_id, to: to_id, + only_path: options[:reference_only_path]), + link_options ) end end - def reference_external_issue(identifier, project = @project, - prefix_text = nil) - url = url_for_issue(identifier, project) + def reference_external_issue(identifier, project = @project, prefix_text = nil) + url = url_for_issue(identifier, project, only_path: options[:reference_only_path]) title = project.external_issue_tracker.title - options = html_options.merge( + link_options = html_options.merge( title: "Issue in #{title}", class: "gfm gfm-issue #{html_options[:class]}" ) - link_to("#{prefix_text}##{identifier}", url, options) + link_to("#{prefix_text}##{identifier}", url, link_options) end # Turn list items that start with "[ ]" into HTML checkbox inputs. diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index c023d275703..2f5c217d764 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -86,7 +86,7 @@ module Gitlab def user_attributes { name: auth_hash.name, - username: ::User.clean_username(auth_hash.username), + username: ::Namespace.clean_path(auth_hash.username), email: auth_hash.email, password: auth_hash.password, password_confirmation: auth_hash.password, diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index cf6e260f257..0571574aa4f 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -2,49 +2,66 @@ module Gitlab module Regex extend self - def username_regex - default_regex + NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])'.freeze + + def namespace_regex + @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze + end + + def namespace_regex_message + "can contain only letters, digits, '_', '-' and '.'. " \ + "Cannot start with '-' or end in '.'." \ + end + + + def namespace_name_regex + @namespace_name_regex ||= /\A[a-zA-Z0-9_\-\. ]*\z/.freeze end - def username_regex_message - default_regex_message + def namespace_name_regex_message + "can contain only letters, digits, '_', '-', '.' and space." end + def project_name_regex - /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\. ]*\z/ + @project_name_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\. ]*\z/.freeze end - def project_regex_message - "can contain only letters, digits, '_', '-' and '.' and space. " \ + def project_name_regex_message + "can contain only letters, digits, '_', '-', '.' and space. " \ "It must start with letter, digit or '_'." end - def name_regex - /\A[a-zA-Z0-9_\-\. ]*\z/ + + def project_path_regex + @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/.freeze end - def name_regex_message - "can contain only letters, digits, '_', '-' and '.' and space." + def project_path_regex_message + "can contain only letters, digits, '_', '-' and '.'. " \ + "Cannot start with '-' or end in '.git'" \ end - def path_regex - default_regex + + def file_name_regex + @file_name_regex ||= /\A[a-zA-Z0-9_\-\.]*\z/.freeze end - def path_regex_message - default_regex_message + def file_name_regex_message + "can contain only letters, digits, '_', '-' and '.'. " end + def archive_formats_regex - #|zip|tar| tar.gz | tar.bz2 | - /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/ + # |zip|tar| tar.gz | tar.bz2 | + @archive_formats_regex ||= /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/.freeze end def git_reference_regex # Valid git ref regex, see: # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html - %r{ + @git_reference_regex ||= %r{ (?! (?# doesn't begins with) \/| (?# rule #6) @@ -60,18 +77,7 @@ module Gitlab (?# doesn't end with) (?<!\.lock) (?# rule #1) (?<![\/.]) (?# rule #6-7) - }x - end - - protected - - def default_regex_message - "can contain only letters, digits, '_', '-' and '.'. " \ - "Cannot start with '-' or end in '.git'" \ - end - - def default_regex - /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/ + }x.freeze end end end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 0230fbb010b..84445b3bf2f 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -27,9 +27,9 @@ namespace :gitlab do backup = Backup::Manager.new backup.unpack - Rake::Task["gitlab:backup:db:restore"].invoke - Rake::Task["gitlab:backup:repo:restore"].invoke - Rake::Task["gitlab:backup:uploads:restore"].invoke + Rake::Task["gitlab:backup:db:restore"].invoke unless backup.skipped?("db") + Rake::Task["gitlab:backup:repo:restore"].invoke unless backup.skipped?("repositories") + Rake::Task["gitlab:backup:uploads:restore"].invoke unless backup.skipped?("uploads") Rake::Task["gitlab:shell:setup"].invoke backup.cleanup @@ -38,8 +38,13 @@ namespace :gitlab do namespace :repo do task create: :environment do $progress.puts "Dumping repositories ...".blue - Backup::Repository.new.dump - $progress.puts "done".green + + if ENV["SKIP"] && ENV["SKIP"].include?("repositories") + $progress.puts "[SKIPPED]".cyan + else + Backup::Repository.new.dump + $progress.puts "done".green + end end task restore: :environment do @@ -52,8 +57,13 @@ namespace :gitlab do namespace :db do task create: :environment do $progress.puts "Dumping database ... ".blue - Backup::Database.new.dump - $progress.puts "done".green + + if ENV["SKIP"] && ENV["SKIP"].include?("db") + $progress.puts "[SKIPPED]".cyan + else + Backup::Database.new.dump + $progress.puts "done".green + end end task restore: :environment do @@ -66,8 +76,13 @@ namespace :gitlab do namespace :uploads do task create: :environment do $progress.puts "Dumping uploads ... ".blue - Backup::Uploads.new.dump - $progress.puts "done".green + + if ENV["SKIP"] && ENV["SKIP"].include?("uploads") + $progress.puts "[SKIPPED]".cyan + else + Backup::Uploads.new.dump + $progress.puts "done".green + end end task restore: :environment do diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb index 5dd4124061c..c31563e6d77 100644 --- a/spec/controllers/import/bitbucket_controller_spec.rb +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -55,24 +55,109 @@ describe Import::BitbucketController do end describe "POST create" do - before do - @repo = { - slug: 'vim', - owner: "john" + let(:bitbucket_username) { user.username } + + let(:bitbucket_user) { + { + user: { + username: bitbucket_username + } }.with_indifferent_access - end + } - it "takes already existing namespace" do - namespace = create(:namespace, name: "john", owner: user) - expect(Gitlab::BitbucketImport::KeyAdder). - to receive(:new).with(@repo, user). - and_return(double(execute: true)) - expect(Gitlab::BitbucketImport::ProjectCreator). - to receive(:new).with(@repo, namespace, user). + let(:bitbucket_repo) { + { + slug: "vim", + owner: bitbucket_username + }.with_indifferent_access + } + + before do + allow(Gitlab::BitbucketImport::KeyAdder). + to receive(:new).with(bitbucket_repo, user). and_return(double(execute: true)) - controller.stub_chain(:client, :project).and_return(@repo) - post :create, format: :js + controller.stub_chain(:client, :user).and_return(bitbucket_user) + controller.stub_chain(:client, :project).and_return(bitbucket_repo) + end + + context "when the repository owner is the Bitbucket user" do + context "when the Bitbucket user and GitLab user's usernames match" do + it "takes the current user's namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the Bitbucket user and GitLab user's usernames don't match" do + let(:bitbucket_username) { "someone_else" } + + it "takes the current user's namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + end + + context "when the repository owner is not the Bitbucket user" do + let(:other_username) { "someone_else" } + + before do + bitbucket_repo["owner"] = other_username + end + + context "when a namespace with the Bitbucket user's username already exists" do + let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) } + + context "when the namespace is owned by the GitLab user" do + it "takes the existing namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, existing_namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the namespace is not owned by the GitLab user" do + before do + existing_namespace.owner = create(:user) + existing_namespace.save + end + + it "doesn't create a project" do + expect(Gitlab::BitbucketImport::ProjectCreator). + not_to receive(:new) + + post :create, format: :js + end + end + end + + context "when a namespace with the Bitbucket user's username doesn't exist" do + it "creates the namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) + + post :create, format: :js + + expect(Namespace.where(name: other_username).first).not_to be_nil + end + + it "takes the new namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, an_instance_of(Group), user). + and_return(double(execute: true)) + + post :create, format: :js + end + end end end end diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index 5b967bfcc0c..3d3846b2e3a 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -56,18 +56,98 @@ describe Import::GithubController do end describe "POST create" do + let(:github_username) { user.username } + + let(:github_user) { + OpenStruct.new(login: github_username) + } + + let(:github_repo) { + OpenStruct.new(name: 'vim', full_name: "#{github_username}/vim", owner: OpenStruct.new(login: github_username)) + } + before do - @repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim', owner: OpenStruct.new(login: "john")) + controller.stub_chain(:client, :user).and_return(github_user) + controller.stub_chain(:client, :repo).and_return(github_repo) + end + + context "when the repository owner is the GitHub user" do + context "when the GitHub user and GitLab user's usernames match" do + it "takes the current user's namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the GitHub user and GitLab user's usernames don't match" do + let(:github_username) { "someone_else" } + + it "takes the current user's namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end end - it "takes already existing namespace" do - namespace = create(:namespace, name: "john", owner: user) - expect(Gitlab::GithubImport::ProjectCreator). - to receive(:new).with(@repo, namespace, user). - and_return(double(execute: true)) - controller.stub_chain(:client, :repo).and_return(@repo) + context "when the repository owner is not the GitHub user" do + let(:other_username) { "someone_else" } + + before do + github_repo.owner = OpenStruct.new(login: other_username) + end + + context "when a namespace with the GitHub user's username already exists" do + let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) } + + context "when the namespace is owned by the GitLab user" do + it "takes the existing namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, existing_namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the namespace is not owned by the GitLab user" do + before do + existing_namespace.owner = create(:user) + existing_namespace.save + end + + it "doesn't create a project" do + expect(Gitlab::GithubImport::ProjectCreator). + not_to receive(:new) + + post :create, format: :js + end + end + end + + context "when a namespace with the GitHub user's username doesn't exist" do + it "creates the namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) + + post :create, format: :js + + expect(Namespace.where(name: other_username).first).not_to be_nil + end + + it "takes the new namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, an_instance_of(Group), user). + and_return(double(execute: true)) - post :create, format: :js + post :create, format: :js + end + end end end end diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb index b6b86b1bcee..112e51d431e 100644 --- a/spec/controllers/import/gitlab_controller_spec.rb +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -48,23 +48,105 @@ describe Import::GitlabController do end describe "POST create" do - before do - @repo = { + let(:gitlab_username) { user.username } + + let(:gitlab_user) { + { + username: gitlab_username + }.with_indifferent_access + } + + let(:gitlab_repo) { + { path: 'vim', - path_with_namespace: 'asd/vim', - owner: {name: "john"}, - namespace: {path: "john"} + path_with_namespace: "#{gitlab_username}/vim", + owner: { name: gitlab_username }, + namespace: { path: gitlab_username } }.with_indifferent_access + } + + before do + controller.stub_chain(:client, :user).and_return(gitlab_user) + controller.stub_chain(:client, :project).and_return(gitlab_repo) + end + + context "when the repository owner is the GitLab.com user" do + context "when the GitLab.com user and GitLab server user's usernames match" do + it "takes the current user's namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the GitLab.com user and GitLab server user's usernames don't match" do + let(:gitlab_username) { "someone_else" } + + it "takes the current user's namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end end - it "takes already existing namespace" do - namespace = create(:namespace, name: "john", owner: user) - expect(Gitlab::GitlabImport::ProjectCreator). - to receive(:new).with(@repo, namespace, user). - and_return(double(execute: true)) - controller.stub_chain(:client, :project).and_return(@repo) + context "when the repository owner is not the GitLab.com user" do + let(:other_username) { "someone_else" } + + before do + gitlab_repo["namespace"]["path"] = other_username + end + + context "when a namespace with the GitLab.com user's username already exists" do + let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) } + + context "when the namespace is owned by the GitLab server user" do + it "takes the existing namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, existing_namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the namespace is not owned by the GitLab server user" do + before do + existing_namespace.owner = create(:user) + existing_namespace.save + end + + it "doesn't create a project" do + expect(Gitlab::GitlabImport::ProjectCreator). + not_to receive(:new) + + post :create, format: :js + end + end + end + + context "when a namespace with the GitLab.com user's username doesn't exist" do + it "creates the namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) + + post :create, format: :js + + expect(Namespace.where(name: other_username).first).not_to be_nil + end + + it "takes the new namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, an_instance_of(Group), user). + and_return(double(execute: true)) - post :create, format: :js + post :create, format: :js + end + end end end end diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb new file mode 100644 index 00000000000..9c8619722cd --- /dev/null +++ b/spec/controllers/namespaces_controller_spec.rb @@ -0,0 +1,121 @@ +require 'spec_helper' + +describe NamespacesController do + let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + + describe "GET show" do + context "when the namespace belongs to a user" do + let!(:other_user) { create(:user) } + + it "redirects to the user's page" do + get :show, id: other_user.username + + expect(response).to redirect_to(user_path(other_user)) + end + end + + context "when the namespace belongs to a group" do + let!(:group) { create(:group) } + let!(:project) { create(:project, namespace: group) } + + context "when the group has public projects" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context "when not signed in" do + it "redirects to the group's page" do + get :show, id: group.path + + expect(response).to redirect_to(group_path(group)) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + it "redirects to the group's page" do + get :show, id: group.path + + expect(response).to redirect_to(group_path(group)) + end + end + end + + context "when the project doesn't have public projects" do + context "when not signed in" do + it "redirects to the sign in page" do + get :show, id: group.path + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + it "redirects to the sign in page" do + get :show, id: group.path + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "redirects to the group's page" do + get :show, id: group.path + + expect(response).to redirect_to(group_path(group)) + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + get :show, id: group.path + + expect(response.status).to eq(404) + end + end + end + end + end + + context "when the namespace doesn't exist" do + context "when signed in" do + before do + sign_in(user) + end + + it "responds with status 404" do + get :show, id: "doesntexist" + + expect(response.status).to eq(404) + end + end + + context "when not signed in" do + it "redirects to the sign in page" do + get :show, id: "doesntexist" + + expect(response).to redirect_to(new_user_session_path) + end + end + end + end +end diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb new file mode 100644 index 00000000000..91856ed0cc0 --- /dev/null +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -0,0 +1,65 @@ +require "spec_helper" + +describe Projects::RepositoriesController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + describe "GET archive" do + before do + sign_in(user) + project.team << [user, :developer] + + allow(ArchiveRepositoryService).to receive(:new).and_return(service) + end + + let(:service) { ArchiveRepositoryService.new(project, "master", "zip") } + + it "executes ArchiveRepositoryService" do + expect(ArchiveRepositoryService).to receive(:new).with(project, "master", "zip") + expect(service).to receive(:execute) + + get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + end + + context "when the service raises an error" do + + before do + allow(service).to receive(:execute).and_raise("Archive failed") + end + + it "renders Not Found" do + get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + + expect(response.status).to eq(404) + end + end + + context "when the service doesn't return a path" do + + before do + allow(service).to receive(:execute).and_return(nil) + end + + it "reloads the page" do + get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + + expect(response).to redirect_to(archive_namespace_project_repository_path(project.namespace, project, ref: "master", format: "zip")) + end + end + + context "when the service returns a path" do + + let(:path) { Rails.root.join("spec/fixtures/dk.png").to_s } + + before do + allow(service).to receive(:execute).and_return(path) + end + + it "sends the file" do + get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + + expect(response.body).to eq(File.binread(path)) + end + end + end +end diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index 41088ce8271..28423eb8caa 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -6,7 +6,7 @@ describe 'Help Pages', feature: true do login_as :user end it 'replace the variable $your_email with the email of the user' do - visit help_page_path(category: 'ssh', file: 'README.md') + visit help_page_path(filepath: 'ssh/README', format: 'md') expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"") end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index c631acc591d..0d06c6ffb82 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -651,7 +651,7 @@ describe GitlabMarkdownHelper do end it "should leave ref-like href of 'manual' links untouched" do - expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a class=\"gfm gfm-merge_request \" href=\"#{namespace_project_merge_request_url(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n") + expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("<p>why not <a href=\"http://example.tld/#!#{merge_request.iid}\">inspect </a><a class=\"gfm gfm-merge_request \" href=\"#{namespace_project_merge_request_path(project.namespace, project, merge_request)}\" title=\"Merge Request: #{merge_request.title}\">!#{merge_request.iid}</a><a href=\"http://example.tld/#!#{merge_request.iid}\"></a></p>\n") end it "should leave ref-like src of images untouched" do diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb index 0b1cf07b7b0..c052981fe73 100644 --- a/spec/helpers/icons_helper_spec.rb +++ b/spec/helpers/icons_helper_spec.rb @@ -7,7 +7,7 @@ describe IconsHelper do end it 'returns share class' do - expect(file_type_icon_class('file', 0120000, 'link')).to eq 'share' + expect(file_type_icon_class('file', '120000', 'link')).to eq 'share' end it 'returns file-pdf-o class with .pdf' do diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb new file mode 100644 index 00000000000..5c89c854714 --- /dev/null +++ b/spec/lib/file_size_validator_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe 'Gitlab::FileSizeValidatorSpec' do + let(:validator) { FileSizeValidator.new(options) } + let(:attachment) { AttachmentUploader.new } + let(:note) { create(:note) } + + describe 'options uses an integer' do + let(:options) { { maximum: 10, attributes: { attachment: attachment } } } + + it 'attachment exceeds maximum limit' do + allow(attachment).to receive(:size) { 100 } + validator.validate_each(note, :attachment, attachment) + expect(note.errors).to have_key(:attachment) + end + + it 'attachment under maximum limit' do + allow(attachment).to receive(:size) { 1 } + validator.validate_each(note, :attachment, attachment) + expect(note.errors).not_to have_key(:attachment) + end + end + + describe 'options uses a symbol' do + let(:options) { { maximum: :test, + attributes: { attachment: attachment } } } + before do + allow(note).to receive(:test) { 10 } + end + + it 'attachment exceeds maximum limit' do + allow(attachment).to receive(:size) { 100 } + validator.validate_each(note, :attachment, attachment) + expect(note.errors).to have_key(:attachment) + end + + it 'attachment under maximum limit' do + allow(attachment).to receive(:size) { 1 } + validator.validate_each(note, :attachment, attachment) + expect(note.errors).not_to have_key(:attachment) + end + end +end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 1db9f15b790..727884c41c5 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -1,14 +1,14 @@ require 'spec_helper' describe Gitlab::Regex do - describe 'path regex' do - it { expect('gitlab-ce').to match(Gitlab::Regex.path_regex) } - it { expect('gitlab_git').to match(Gitlab::Regex.path_regex) } - it { expect('_underscore.js').to match(Gitlab::Regex.path_regex) } - it { expect('100px.com').to match(Gitlab::Regex.path_regex) } - it { expect('?gitlab').not_to match(Gitlab::Regex.path_regex) } - it { expect('git lab').not_to match(Gitlab::Regex.path_regex) } - it { expect('gitlab.git').not_to match(Gitlab::Regex.path_regex) } + describe 'project path regex' do + it { expect('gitlab-ce').to match(Gitlab::Regex.project_path_regex) } + it { expect('gitlab_git').to match(Gitlab::Regex.project_path_regex) } + it { expect('_underscore.js').to match(Gitlab::Regex.project_path_regex) } + it { expect('100px.com').to match(Gitlab::Regex.project_path_regex) } + it { expect('?gitlab').not_to match(Gitlab::Regex.project_path_regex) } + it { expect('git lab').not_to match(Gitlab::Regex.project_path_regex) } + it { expect('gitlab.git').not_to match(Gitlab::Regex.project_path_regex) } end describe 'project name regex' do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index ba42f9e5c70..7c4235ab379 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -7,9 +7,12 @@ describe Notify do let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } let(:gitlab_sender) { Gitlab.config.gitlab.email_from } + let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } let(:recipient) { create(:user, email: 'recipient@example.com') } let(:project) { create(:project) } + around(:each) { ActionMailer::Base.deliveries.clear } + before(:each) do email = recipient.emails.create(email: "notifications@example.com") recipient.update_attribute(:notification_email, email.email) @@ -27,6 +30,11 @@ describe Notify do expect(sender.display_name).to eq(gitlab_sender_display_name) expect(sender.address).to eq(gitlab_sender) end + + it 'has a Reply-To address' do + reply_to = subject.header[:reply_to].addresses + expect(reply_to).to eq([gitlab_sender_reply_to]) + end end shared_examples 'an email starting a new thread' do |message_id_prefix| diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index ed6845c82cc..e87432fdf62 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -33,8 +33,6 @@ describe Namespace do it { is_expected.to respond_to(:to_param) } end - it { expect(Namespace.global_id).to eq('GLN') } - describe :to_param do it { expect(namespace.to_param).to eq(namespace.path) } end @@ -85,4 +83,14 @@ describe Namespace do it { expect(Namespace.find_by_path_or_name('WOW')).to eq(@namespace) } it { expect(Namespace.find_by_path_or_name('unknown')).to eq(nil) } end + + describe ".clean_path" do + + let!(:user) { create(:user, username: "johngitlab-etc") } + let!(:namespace) { create(:namespace, path: "JohnGitLab-etc1") } + + it "cleans the path and makes sure it's available" do + expect(Namespace.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2") + end + end end diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 959044dc727..f94bef5c365 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -31,6 +31,7 @@ describe GitlabIssueTrackerService do context 'with absolute urls' do before do + GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root" @service = project.create_gitlab_issue_tracker_service(active: true) end @@ -39,15 +40,15 @@ describe GitlabIssueTrackerService do end it 'should give the correct path' do - expect(@service.project_url).to eq("/#{project.path_with_namespace}/issues") - expect(@service.new_issue_url).to eq("/#{project.path_with_namespace}/issues/new") - expect(@service.issue_url(432)).to eq("/#{project.path_with_namespace}/issues/432") + expect(@service.project_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues") + expect(@service.new_issue_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/new") + expect(@service.issue_url(432)).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/432") end end - context 'with enabled relative urls' do + context 'with relative urls' do before do - Settings.gitlab.stub(:relative_url_root).and_return("/gitlab/root") + GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root" @service = project.create_gitlab_issue_tracker_service(active: true) end @@ -56,9 +57,9 @@ describe GitlabIssueTrackerService do end it 'should give the correct path' do - expect(@service.project_url).to eq("/gitlab/root/#{project.path_with_namespace}/issues") - expect(@service.new_issue_url).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new") - expect(@service.issue_url(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432") + expect(@service.project_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues") + expect(@service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new") + expect(@service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432") end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 10e90cae143..24384e8bf22 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -307,16 +307,6 @@ describe User do end end - describe ".clean_username" do - - let!(:user) { create(:user, username: "johngitlab-etc") } - let!(:namespace) { create(:namespace, path: "JohnGitLab-etc1") } - - it "cleans a username and makes sure it's available" do - expect(User.clean_username("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2") - end - end - describe 'all_ssh_keys' do it { is_expected.to have_many(:keys).dependent(:destroy) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index f28dfea3ccf..cc387378d3a 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -57,7 +57,14 @@ describe API::API, api: true do expect(json_response.first['name']).to eq(project.name) expect(json_response.first['owner']['username']).to eq(user.username) end - + + it 'should include the project labels as the tag_list' do + get api('/projects', user) + response.status.should == 200 + json_response.should be_an Array + json_response.first.keys.should include('tag_list') + end + context 'and using search' do it 'should return searched project' do get api('/projects', user), { search: project.name } @@ -247,12 +254,12 @@ describe API::API, api: true do expect(json_response['message']['name']).to eq([ 'can\'t be blank', 'is too short (minimum is 0 characters)', - Gitlab::Regex.project_regex_message + Gitlab::Regex.project_name_regex_message ]) expect(json_response['message']['path']).to eq([ 'can\'t be blank', 'is too short (minimum is 0 characters)', - Gitlab::Regex.send(:default_regex_message) + Gitlab::Regex.send(:project_path_regex_message) ]) end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 081400cdedd..e6d5545f812 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -140,7 +140,7 @@ describe API::API, api: true do expect(json_response['message']['projects_limit']). to eq(['must be greater than or equal to 0']) expect(json_response['message']['username']). - to eq([Gitlab::Regex.send(:default_regex_message)]) + to eq([Gitlab::Regex.send(:namespace_regex_message)]) end it "shouldn't available for non admin users" do @@ -266,7 +266,7 @@ describe API::API, api: true do expect(json_response['message']['projects_limit']). to eq(['must be greater than or equal to 0']) expect(json_response['message']['username']). - to eq([Gitlab::Regex.send(:default_regex_message)]) + to eq([Gitlab::Regex.send(:namespace_regex_message)]) end context "with existing user" do diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index d4915b51952..f5db548f97c 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -73,41 +73,41 @@ end # help_markdown GET /help/markdown(.:format) help#markdown # help_ssh GET /help/ssh(.:format) help#ssh # help_raketasks GET /help/raketasks(.:format) help#raketasks -describe HelpController, "routing" do - it "to #index" do - expect(get("/help")).to route_to('help#index') +describe HelpController, 'routing' do + it 'to #index' do + expect(get('/help')).to route_to('help#index') end - it "to #permissions" do - expect(get("/help/permissions/permissions")).to route_to('help#show', category: "permissions", file: "permissions") + it 'to #permissions' do + expect(get('/help/permissions/permissions')).to route_to('help#show', filepath: 'permissions/permissions') end - it "to #workflow" do - expect(get("/help/workflow/README")).to route_to('help#show', category: "workflow", file: "README") + it 'to #workflow' do + expect(get('/help/workflow/README')).to route_to('help#show', filepath: 'workflow/README') end - it "to #api" do - expect(get("/help/api/README")).to route_to('help#show', category: "api", file: "README") + it 'to #api' do + expect(get('/help/api/README')).to route_to('help#show', filepath: 'api/README') end - it "to #web_hooks" do - expect(get("/help/web_hooks/web_hooks")).to route_to('help#show', category: "web_hooks", file: "web_hooks") + it 'to #web_hooks' do + expect(get('/help/web_hooks/web_hooks')).to route_to('help#show', filepath: 'web_hooks/web_hooks') end - it "to #system_hooks" do - expect(get("/help/system_hooks/system_hooks")).to route_to('help#show', category: "system_hooks", file: "system_hooks") + it 'to #system_hooks' do + expect(get('/help/system_hooks/system_hooks')).to route_to('help#show', filepath: 'system_hooks/system_hooks') end - it "to #markdown" do - expect(get("/help/markdown/markdown")).to route_to('help#show',category: "markdown", file: "markdown") + it 'to #markdown' do + expect(get('/help/markdown/markdown')).to route_to('help#show',filepath: 'markdown/markdown') end - it "to #ssh" do - expect(get("/help/ssh/README")).to route_to('help#show', category: "ssh", file: "README") + it 'to #ssh' do + expect(get('/help/ssh/README')).to route_to('help#show', filepath: 'ssh/README') end - it "to #raketasks" do - expect(get("/help/raketasks/README")).to route_to('help#show', category: "raketasks", file: "README") + it 'to #raketasks' do + expect(get('/help/raketasks/README')).to route_to('help#show', filepath: 'raketasks/README') end end diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb new file mode 100644 index 00000000000..f168a913976 --- /dev/null +++ b/spec/services/archive_repository_service_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +describe ArchiveRepositoryService do + let(:project) { create(:project) } + subject { ArchiveRepositoryService.new(project, "master", "zip") } + + describe "#execute" do + it "cleans old archives" do + expect(project.repository).to receive(:clean_old_archives) + + subject.execute(timeout: 0.0) + end + + context "when the repository doesn't have an archive file path" do + before do + allow(project.repository).to receive(:archive_file_path).and_return(nil) + end + + it "raises an error" do + expect { + subject.execute(timeout: 0.0) + }.to raise_error + end + end + + context "when the repository has an archive file path" do + let(:file_path) { "/archive.zip" } + let(:pid_file_path) { "/archive.zip.pid" } + + before do + allow(project.repository).to receive(:archive_file_path).and_return(file_path) + allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path) + end + + context "when the archive file already exists" do + before do + allow(File).to receive(:exist?).with(file_path).and_return(true) + end + + it "returns the file path" do + expect(subject.execute(timeout: 0.0)).to eq(file_path) + end + end + + context "when the archive file doesn't exist yet" do + before do + allow(File).to receive(:exist?).with(file_path).and_return(false) + allow(File).to receive(:exist?).with(pid_file_path).and_return(true) + end + + context "when the archive pid file doesn't exist yet" do + before do + allow(File).to receive(:exist?).with(pid_file_path).and_return(false) + end + + it "queues the RepositoryArchiveWorker" do + expect(RepositoryArchiveWorker).to receive(:perform_async) + + subject.execute(timeout: 0.0) + end + end + + context "when the archive pid file already exists" do + it "doesn't queue the RepositoryArchiveWorker" do + expect(RepositoryArchiveWorker).not_to receive(:perform_async) + + subject.execute(timeout: 0.0) + end + end + + context "when the archive file exists after a little while" do + before do + Thread.new do + sleep 0.1 + allow(File).to receive(:exist?).with(file_path).and_return(true) + end + end + + it "returns the file path" do + expect(subject.execute(timeout: 0.2)).to eq(file_path) + end + end + + context "when the archive file doesn't exist after the timeout" do + it "returns nil" do + expect(subject.execute(timeout: 0.0)).to eq(nil) + end + end + end + end + end +end + diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb index fc34b456482..e5c47015a03 100644 --- a/spec/services/projects/upload_service_spec.rb +++ b/spec/services/projects/upload_service_spec.rb @@ -67,6 +67,16 @@ describe Projects::UploadService do it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file['url']).to match('doc_sample.txt') } end + + context 'for too large a file' do + before do + txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') + allow(txt).to receive(:size) { 1000.megabytes.to_i } + @link_to_file = upload_file(@project.repository, txt) + end + + it { expect(@link_to_file).to eq(nil) } + end end def upload_file(repository, file) diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 8a411b7720a..a59f74c2121 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -87,7 +87,7 @@ describe 'gitlab:app namespace rake task' do expect(tar_contents).to match('db/') expect(tar_contents).to match('uploads/') expect(tar_contents).to match('repositories/') - expect(tar_contents).not_to match(/^.{4,9}[rwx]/) + expect(tar_contents).not_to match(/^.{4,9}[rwx].* (db|uploads|repositories)\/$/) end it 'should delete temp directories' do @@ -98,4 +98,55 @@ describe 'gitlab:app namespace rake task' do expect(temp_dirs).to be_empty end end # backup_create task + + describe "Skipping items" do + def tars_glob + Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) + end + + before :all do + @origin_cd = Dir.pwd + + Rake::Task["gitlab:backup:db:create"].reenable + Rake::Task["gitlab:backup:repo:create"].reenable + Rake::Task["gitlab:backup:uploads:create"].reenable + + # Record the existing backup tars so we don't touch them + existing_tars = tars_glob + + # Redirect STDOUT and run the rake task + orig_stdout = $stdout + $stdout = StringIO.new + ENV["SKIP"] = "repositories" + run_rake_task('gitlab:backup:create') + $stdout = orig_stdout + + @backup_tar = (tars_glob - existing_tars).first + end + + after :all do + FileUtils.rm(@backup_tar) + Dir.chdir @origin_cd + end + + it "does not contain skipped item" do + tar_contents, exit_status = Gitlab::Popen.popen( + %W{tar -tvf #{@backup_tar} db uploads repositories} + ) + + expect(tar_contents).to match('db/') + expect(tar_contents).to match('uploads/') + expect(tar_contents).not_to match('repositories/') + end + + it 'does not invoke repositories restore' do + Rake::Task["gitlab:shell:setup"].stub invoke: true + allow($stdout).to receive :write + + expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke + expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke + expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke + expect { run_rake_task('gitlab:backup:restore') }.to_not raise_error + end + end end # gitlab:app namespace diff --git a/spec/workers/repository_archive_worker_spec.rb b/spec/workers/repository_archive_worker_spec.rb new file mode 100644 index 00000000000..c2362058cfd --- /dev/null +++ b/spec/workers/repository_archive_worker_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +describe RepositoryArchiveWorker do + let(:project) { create(:project) } + subject { RepositoryArchiveWorker.new } + + before do + allow(Project).to receive(:find).and_return(project) + end + + describe "#perform" do + it "cleans old archives" do + expect(project.repository).to receive(:clean_old_archives) + + subject.perform(project.id, "master", "zip") + end + + context "when the repository doesn't have an archive file path" do + before do + allow(project.repository).to receive(:archive_file_path).and_return(nil) + end + + it "doesn't archive the repo" do + expect(project.repository).not_to receive(:archive_repo) + + subject.perform(project.id, "master", "zip") + end + end + + context "when the repository has an archive file path" do + let(:file_path) { "/archive.zip" } + let(:pid_file_path) { "/archive.zip.pid" } + + before do + allow(project.repository).to receive(:archive_file_path).and_return(file_path) + allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path) + end + + context "when the archive file already exists" do + before do + allow(File).to receive(:exist?).with(file_path).and_return(true) + end + + it "doesn't archive the repo" do + expect(project.repository).not_to receive(:archive_repo) + + subject.perform(project.id, "master", "zip") + end + end + + context "when the archive file doesn't exist yet" do + before do + allow(File).to receive(:exist?).with(file_path).and_return(false) + allow(File).to receive(:exist?).with(pid_file_path).and_return(true) + end + + context "when the archive pid file doesn't exist yet" do + before do + allow(File).to receive(:exist?).with(pid_file_path).and_return(false) + end + + it "archives the repo" do + expect(project.repository).to receive(:archive_repo) + + subject.perform(project.id, "master", "zip") + end + end + + context "when the archive pid file already exists" do + it "doesn't archive the repo" do + expect(project.repository).not_to receive(:archive_repo) + + subject.perform(project.id, "master", "zip") + end + end + end + end + end +end + |
