diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb new file mode 100644 index 0000000000000000000000000000000000000000..e64561982645ca4fc8ca0c7a757570a0b5359b5b --- /dev/null +++ b/app/models/commit_range.rb @@ -0,0 +1,106 @@ +# CommitRange makes it easier to work with commit ranges +# +# Examples: +# +# range = CommitRange.new('f3f85602...e86e1013') +# range.exclude_start? # => false +# range.reference_title # => "Commits f3f85602 through e86e1013" +# range.to_s # => "f3f85602...e86e1013" +# +# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae') +# range.exclude_start? # => true +# range.reference_title # => "Commits f3f85602^ through e86e1013" +# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"} +# range.to_s # => "f3f85602..e86e1013" +# +# # Assuming `project` is a Project with a repository containing both commits: +# range.project = project +# range.valid_commits? # => true +# +class CommitRange + include ActiveModel::Conversion + + attr_reader :sha_from, :notation, :sha_to + + # Optional Project model + attr_accessor :project + + # See `exclude_start?` + attr_reader :exclude_start + + # The beginning and ending SHA sums can be between 6 and 40 hex characters, + # and the range selection can be double- or triple-dot. + PATTERN = /\h{6,40}\.{2,3}\h{6,40}/ + + # Initialize a CommitRange + # + # range_string - The String commit range. + # project - An optional Project model. + # + # Raises ArgumentError if `range_string` does not match `PATTERN`. + def initialize(range_string, project = nil) + range_string.strip! + + unless range_string.match(/\A#{PATTERN}\z/) + raise ArgumentError, "invalid CommitRange string format: #{range_string}" + end + + @exclude_start = !range_string.include?('...') + @sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2) + + @project = project + end + + def inspect + %(#<#{self.class}:#{object_id} #{to_s}>) + end + + def to_s + "#{sha_from[0..7]}#{notation}#{sha_to[0..7]}" + end + + # Returns a String for use in a link's title attribute + def reference_title + "Commits #{suffixed_sha_from} through #{sha_to}" + end + + # Return a Hash of parameters for passing to a URL helper + # + # See `namespace_project_compare_url` + def to_param + { from: suffixed_sha_from, to: sha_to } + end + + def exclude_start? + exclude_start + end + + # Check if both the starting and ending commit IDs exist in a project's + # repository + # + # project - An optional Project to check (default: `project`) + def valid_commits?(project = project) + return nil unless project.present? + return false unless project.valid_repo? + + commit_from.present? && commit_to.present? + end + + def persisted? + true + end + + def commit_from + @commit_from ||= project.repository.commit(suffixed_sha_from) + end + + def commit_to + @commit_to ||= project.repository.commit(sha_to) + end + + private + + def suffixed_sha_from + sha_from + (exclude_start? ? '^' : '') + end +end diff --git a/doc/logs/logs.md b/doc/logs/logs.md index ec0109a426f427a5ea92621d69bd46899b700f59..ae139469b5dfd34919004bc2c775d20a566a73a2 100644 --- a/doc/logs/logs.md +++ b/doc/logs/logs.md @@ -5,7 +5,7 @@ In addition to system log files, GitLab Enterprise Edition comes with Audit Even System log files are typically plain text in a standard log file format. This guide talks about how to read and use these system log files. #### production.log -This file lives in `/var/log/gitlab/gitlab-rails/production.log` for omnibus package or in `/home/git/gitlab/logs/production.log` for installations from the source. +This file lives in `/var/log/gitlab/gitlab-rails/production.log` for omnibus package or in `/home/git/gitlab/log/production.log` for installations from the source. This file contains information about all performed requests. You can see url and type of request, IP address and what exactly parts of code were involved to service this particular request. Also you can see all SQL request that have been performed and how much time it took. This task is more useful for GitLab contributors and developers. Use part of this log file when you are going to report bug. diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb index baa97bee9bfd468187836254cb1df43e85564a6f..8764f7e474f4111730b227172c6b06f69c0aea29 100644 --- a/lib/gitlab/markdown/commit_range_reference_filter.rb +++ b/lib/gitlab/markdown/commit_range_reference_filter.rb @@ -32,11 +32,8 @@ module Gitlab # Pattern used to extract commit range references from text # - # The beginning and ending SHA1 sums can be between 6 and 40 hex - # characters, and the range selection can be double- or triple-dot. - # # This pattern supports cross-project references. - COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?\h{6,40}\.{2,3}\h{6,40})/ + COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?#{CommitRange::PATTERN})/ def call replace_text_nodes_matching(COMMIT_RANGE_PATTERN) do |content| @@ -53,52 +50,34 @@ module Gitlab # links have `gfm` and `gfm-commit_range` class names attached for # styling. def commit_range_link_filter(text) - self.class.references_in(text) do |match, commit_range, project_ref| + self.class.references_in(text) do |match, id, project_ref| project = self.project_from_ref(project_ref) - from_id, to_id = split_commit_range(commit_range) + range = CommitRange.new(id, project) + + if range.valid_commits? + push_result(:commit_range, range) - if valid_range?(project, from_id, to_id) - url = url_for_commit_range(project, from_id, to_id) + url = url_for_commit_range(project, range) - title = "Commits #{from_id} through #{to_id}" + title = range.reference_title klass = reference_class(:commit_range) project_ref += '@' if project_ref %(#{project_ref}#{commit_range}) + class="#{klass}">#{project_ref}#{range}) else match end end end - def split_commit_range(range) - from_id, to_id = range.split(/\.{2,3}/, 2) - from_id << "^" if range !~ /\.{3}/ - - [from_id, to_id] - end - - def commit(id) - unless @commit_map[id] - @commit_map[id] = project.commit(id) - end - - @commit_map[id] - end - - def valid_range?(project, from_id, to_id) - project && project.valid_repo? && commit(from_id) && commit(to_id) - end - - def url_for_commit_range(project, from_id, to_id) + def url_for_commit_range(project, range) h = Rails.application.routes.url_helpers h.namespace_project_compare_url(project.namespace, project, - from: from_id, to: to_id, - only_path: context[:only_path]) + range.to_param.merge(only_path: context[:only_path])) end end end diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb index 66598127f6e0ec78601433909d310711b000d954..b20b29f5d0ca07b7736242cf8659d926c9c16b00 100644 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ b/lib/gitlab/markdown/commit_reference_filter.rb @@ -48,6 +48,8 @@ module Gitlab project = self.project_from_ref(project_ref) if commit = commit_from_ref(project, commit_ref) + push_result(:commit, commit) + url = url_for_commit(project, commit) title = escape_once(commit.link_title) @@ -57,7 +59,7 @@ module Gitlab %(#{project_ref}#{commit_ref}) + class="#{klass}">#{project_ref}#{commit.short_id}) else match end diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb index c9267cc3e9d90c46bd41df347d5fa3f8967d70f4..4b360369d3768f5905c05fd41aa9b275e9706f6f 100644 --- a/lib/gitlab/markdown/issue_reference_filter.rb +++ b/lib/gitlab/markdown/issue_reference_filter.rb @@ -48,6 +48,9 @@ module Gitlab project = self.project_from_ref(project_ref) if project && project.issue_exists?(issue) + # FIXME (rspeicher): Law of Demeter + push_result(:issue, project.issues.where(iid: issue).first) + url = url_for_issue(issue, project, only_path: context[:only_path]) title = escape_once("Issue: #{title_for_issue(issue, project)}") diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index 4c21192c0d3cb4ba2cacd4228fc9084e10f97606..a357f28458d5269a0bf8ef6061f004137a69d798 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -52,11 +52,13 @@ module Gitlab params = label_params(id, name) if label = project.labels.find_by(params) - url = url_for_label(project, label) + push_result(:label, label) + url = url_for_label(project, label) klass = reference_class(:label) - %(#{render_colored_label(label)}) + %(#{render_colored_label(label)}) else match end diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index 40239523cdaa33188a4c436b36b84a7ab0d0b2f3..7c28fe112efcd0b3a8cb3bf9c811b7bb6e1ec50a 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -48,6 +48,8 @@ module Gitlab project = self.project_from_ref(project_ref) if project && merge_request = project.merge_requests.find_by(iid: id) + push_result(:merge_request, merge_request) + title = escape_once("Merge Request: #{merge_request.title}") klass = reference_class(:merge_request) diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index ef4aa408a7e57b8ddd1c7423ca1718538f148506..a4303d96befe2bbedc85564ac33334b568323e41 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -12,7 +12,15 @@ module Gitlab # :reference_class - Custom CSS class added to reference links. # :only_path - Generate path-only links. # + # Results: + # :references - A Hash of references that were found and replaced. class ReferenceFilter < HTML::Pipeline::Filter + def initialize(*args) + super + + result[:references] = Hash.new { |hash, type| hash[type] = [] } + end + def escape_once(html) ERB::Util.html_escape_once(html) end @@ -29,6 +37,16 @@ module Gitlab context[:project] end + # Add a reference to the pipeline's result Hash + # + # type - Singular Symbol reference type (e.g., :issue, :user, etc.) + # values - One or more Objects to add + def push_result(type, *values) + return if values.empty? + + result[:references][type].push(*values) + end + def reference_class(type) "gfm gfm-#{type} #{context[:reference_class]}".strip end diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb index ada67de992b575a6c543f497ffcf3bd53b7ac56b..64a0a2696f7587867fc4d06024695d64223d252e 100644 --- a/lib/gitlab/markdown/snippet_reference_filter.rb +++ b/lib/gitlab/markdown/snippet_reference_filter.rb @@ -48,6 +48,8 @@ module Gitlab project = self.project_from_ref(project_ref) if project && snippet = project.snippets.find_by(id: id) + push_result(:snippet, snippet) + title = escape_once("Snippet: #{snippet.title}") klass = reference_class(:snippet) diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb index 5fc8ed55fe26f114459f52e1ce95f3746b9a1190..28ec041b1d49735855f5380853474a393e6693af 100644 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ b/lib/gitlab/markdown/user_reference_filter.rb @@ -38,27 +38,11 @@ module Gitlab # Returns a String with `@user` references replaced with links. All links # have `gfm` and `gfm-project_member` class names attached for styling. def user_link_filter(text) - project = context[:project] - - self.class.references_in(text) do |match, user| - klass = reference_class(:project_member) - - if user == 'all' - url = link_to_all(project) - - %(@#{user}) - elsif namespace = Namespace.find_by(path: user) - if namespace.is_a?(Group) - if user_can_reference_group?(namespace) - url = group_url(user, only_path: context[:only_path]) - %(@#{user}) - else - match - end - else - url = user_url(user, only_path: context[:only_path]) - %(@#{user}) - end + self.class.references_in(text) do |match, username| + if username == 'all' + link_to_all + elsif namespace = Namespace.find_by(path: username) + link_to_namespace(namespace) || match else match end @@ -71,17 +55,46 @@ module Gitlab Rails.application.routes.url_helpers end - def group_url(*args) - urls.group_url(*args) + def link_class + reference_class(:project_member) + end + + def link_to_all + project = context[:project] + + # FIXME (rspeicher): Law of Demeter + push_result(:user, *project.team.members.flatten) + + url = urls.namespace_project_url(project.namespace, project, + only_path: context[:only_path]) + + %(@all) + end + + def link_to_namespace(namespace) + if namespace.is_a?(Group) + link_to_group(namespace.path, namespace) + else + link_to_user(namespace.path, namespace) + end end - def user_url(*args) - urls.user_url(*args) + def link_to_group(group, namespace) + return unless user_can_reference_group?(namespace) + + push_result(:user, *namespace.users) + + url = urls.group_url(group, only_path: context[:only_path]) + + %(@#{group}) end - def link_to_all(project) - urls.namespace_project_url(project.namespace, project, - only_path: context[:only_path]) + def link_to_user(user, namespace) + push_result(:user, namespace.owner) + + url = urls.user_url(user, only_path: context[:only_path]) + + %(@#{user}) end def user_can_reference_group?(group) diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 949dd5d26b10cdfef0cd58b2a0ed96a502b1a984..e35f848fa6e5f60701b4427a7d40ac97bd6d732f 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -8,151 +8,70 @@ module Gitlab @current_user = current_user end - def can?(user, action, subject) - Ability.abilities.allowed?(user, action, subject) - end - def analyze(text) - text = text.dup - - # Remove preformatted/code blocks so that references are not included - text.gsub!(/^```.*?^```/m, '') - text.gsub!(/[^`]`[^`]*?`[^`]/, '') - - @references = Hash.new { |hash, type| hash[type] = [] } - parse_references(text) + @_text = text.dup end - # Given a valid project, resolve the extracted identifiers of the requested type to - # model objects. - def users - references[:user].uniq.map do |project, identifier| - if identifier == "all" - project.team.members.flatten - elsif namespace = Namespace.find_by(path: identifier) - if namespace.is_a?(Group) - namespace.users if can?(current_user, :read_group, namespace) - else - namespace.owner - end - end - end.flatten.compact.uniq + result = pipeline_result(:user) + result.uniq end def labels - references[:label].uniq.map do |project, identifier| - project.labels.where(id: identifier).first - end.compact.uniq + result = pipeline_result(:label) + result.uniq end def issues - references[:issue].uniq.map do |project, identifier| - if project.default_issues_tracker? - project.issues.where(iid: identifier).first - end - end.compact.uniq + # TODO (rspeicher): What about external issues? + + result = pipeline_result(:issue) + result.uniq end def merge_requests - references[:merge_request].uniq.map do |project, identifier| - project.merge_requests.where(iid: identifier).first - end.compact.uniq + result = pipeline_result(:merge_request) + result.uniq end def snippets - references[:snippet].uniq.map do |project, identifier| - project.snippets.where(id: identifier).first - end.compact.uniq + result = pipeline_result(:snippet) + result.uniq end def commits - references[:commit].uniq.map do |project, identifier| - repo = project.repository - repo.commit(identifier) if repo - end.compact.uniq + result = pipeline_result(:commit) + result.uniq end def commit_ranges - references[:commit_range].uniq.map do |project, identifier| - repo = project.repository - if repo - from_id, to_id = identifier.split(/\.{2,3}/, 2) - [repo.commit(from_id), repo.commit(to_id)] - end - end.compact.uniq + result = pipeline_result(:commit_range) + result.uniq end private - NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR - PROJ_STR = "(?#{NAME_STR}/#{NAME_STR})" - - REFERENCE_PATTERN = %r{ - (?\W)? # Prefix - ( # Reference - @(?#{NAME_STR}) # User name - |~(?