From 020ea32e767b9ad033f9fedcaa902865a01fa944 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 2 Aug 2016 18:06:31 +0800 Subject: Implement pipeline hooks, extracted from !5525 Closes #20115 --- lib/api/entities.rb | 6 ++- lib/api/project_hooks.rb | 2 + lib/gitlab/data_builder/pipeline_data_builder.rb | 66 ++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 lib/gitlab/data_builder/pipeline_data_builder.rb (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3e21b7a0b8a..b6f6b11d97b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -48,7 +48,8 @@ module API class ProjectHook < Hook expose :project_id, :push_events - expose :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events + expose :issues_events, :merge_requests_events, :tag_push_events + expose :note_events, :build_events, :pipeline_events expose :enable_ssl_verification end @@ -342,7 +343,8 @@ module API class ProjectService < Grape::Entity expose :id, :title, :created_at, :updated_at, :active - expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events + expose :push_events, :issues_events, :merge_requests_events + expose :tag_push_events, :note_events, :build_events, :pipeline_events # Expose serialized properties expose :properties do |service, options| field_names = service.fields. diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 6bb70bc8bc3..3f63cd678e8 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -45,6 +45,7 @@ module API :tag_push_events, :note_events, :build_events, + :pipeline_events, :enable_ssl_verification ] @hook = user_project.hooks.new(attrs) @@ -78,6 +79,7 @@ module API :tag_push_events, :note_events, :build_events, + :pipeline_events, :enable_ssl_verification ] diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb new file mode 100644 index 00000000000..13417ba09eb --- /dev/null +++ b/lib/gitlab/data_builder/pipeline_data_builder.rb @@ -0,0 +1,66 @@ +module Gitlab + module DataBuilder + module PipelineDataBuilder + module_function + + def build(pipeline) + { + object_kind: 'pipeline', + object_attributes: hook_attrs(pipeline), + user: pipeline.user.try(:hook_attrs), + project: pipeline.project.hook_attrs(backward: false), + commit: pipeline.commit.try(:hook_attrs), + builds: pipeline.builds.map(&method(:build_hook_attrs)) + } + end + + def hook_attrs(pipeline) + first_pending_build = pipeline.builds.first_pending + config_processor = pipeline.config_processor + + { + id: pipeline.id, + ref: pipeline.ref, + tag: pipeline.tag, + sha: pipeline.sha, + before_sha: pipeline.before_sha, + status: pipeline.status, + stage: first_pending_build.try(:stage), + stages: config_processor.try(:stages), + created_at: pipeline.created_at, + finished_at: pipeline.finished_at, + duration: pipeline.duration + } + end + + def build_hook_attrs(build) + { + id: build.id, + stage: build.stage, + name: build.name, + status: build.status, + created_at: build.created_at, + started_at: build.started_at, + finished_at: build.finished_at, + when: build.when, + manual: build.manual?, + user: build.user.try(:hook_attrs), + runner: build.runner && runner_hook_attrs(build.runner), + artifacts_file: { + filename: build.artifacts_file.filename, + size: build.artifacts_size + } + } + end + + def runner_hook_attrs(runner) + { + id: runner.id, + description: runner.description, + active: runner.active?, + is_shared: runner.is_shared? + } + end + end + end +end -- cgit v1.2.1 From 984367f957c8f8d02fa82b08817e2f2f318c6bff Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 4 Aug 2016 23:44:27 +0800 Subject: Move those builders to their own namespace, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5620#note_13540099 --- lib/gitlab/build_data_builder.rb | 65 ------------------ lib/gitlab/data_builder/build_data_builder.rb | 67 +++++++++++++++++++ lib/gitlab/data_builder/note_data_builder.rb | 75 +++++++++++++++++++++ lib/gitlab/data_builder/push_data_builder.rb | 95 +++++++++++++++++++++++++++ lib/gitlab/note_data_builder.rb | 73 -------------------- lib/gitlab/push_data_builder.rb | 93 -------------------------- 6 files changed, 237 insertions(+), 231 deletions(-) delete mode 100644 lib/gitlab/build_data_builder.rb create mode 100644 lib/gitlab/data_builder/build_data_builder.rb create mode 100644 lib/gitlab/data_builder/note_data_builder.rb create mode 100644 lib/gitlab/data_builder/push_data_builder.rb delete mode 100644 lib/gitlab/note_data_builder.rb delete mode 100644 lib/gitlab/push_data_builder.rb (limited to 'lib') diff --git a/lib/gitlab/build_data_builder.rb b/lib/gitlab/build_data_builder.rb deleted file mode 100644 index 9f45aefda0f..00000000000 --- a/lib/gitlab/build_data_builder.rb +++ /dev/null @@ -1,65 +0,0 @@ -module Gitlab - class BuildDataBuilder - class << self - def build(build) - project = build.project - commit = build.pipeline - user = build.user - - data = { - object_kind: 'build', - - ref: build.ref, - tag: build.tag, - before_sha: build.before_sha, - sha: build.sha, - - # TODO: should this be not prefixed with build_? - # Leaving this way to have backward compatibility - build_id: build.id, - build_name: build.name, - build_stage: build.stage, - build_status: build.status, - build_started_at: build.started_at, - build_finished_at: build.finished_at, - build_duration: build.duration, - build_allow_failure: build.allow_failure, - - # TODO: do we still need it? - project_id: project.id, - project_name: project.name_with_namespace, - - user: { - id: user.try(:id), - name: user.try(:name), - email: user.try(:email), - }, - - commit: { - id: commit.id, - sha: commit.sha, - message: commit.git_commit_message, - author_name: commit.git_author_name, - author_email: commit.git_author_email, - status: commit.status, - duration: commit.duration, - started_at: commit.started_at, - finished_at: commit.finished_at, - }, - - repository: { - name: project.name, - url: project.url_to_repo, - description: project.description, - homepage: project.web_url, - git_http_url: project.http_url_to_repo, - git_ssh_url: project.ssh_url_to_repo, - visibility_level: project.visibility_level - }, - } - - data - end - end - end -end diff --git a/lib/gitlab/data_builder/build_data_builder.rb b/lib/gitlab/data_builder/build_data_builder.rb new file mode 100644 index 00000000000..5175645e238 --- /dev/null +++ b/lib/gitlab/data_builder/build_data_builder.rb @@ -0,0 +1,67 @@ +module Gitlab + module DataBuilder + module BuildDataBuilder + module_function + + def build(build) + project = build.project + commit = build.pipeline + user = build.user + + data = { + object_kind: 'build', + + ref: build.ref, + tag: build.tag, + before_sha: build.before_sha, + sha: build.sha, + + # TODO: should this be not prefixed with build_? + # Leaving this way to have backward compatibility + build_id: build.id, + build_name: build.name, + build_stage: build.stage, + build_status: build.status, + build_started_at: build.started_at, + build_finished_at: build.finished_at, + build_duration: build.duration, + build_allow_failure: build.allow_failure, + + # TODO: do we still need it? + project_id: project.id, + project_name: project.name_with_namespace, + + user: { + id: user.try(:id), + name: user.try(:name), + email: user.try(:email), + }, + + commit: { + id: commit.id, + sha: commit.sha, + message: commit.git_commit_message, + author_name: commit.git_author_name, + author_email: commit.git_author_email, + status: commit.status, + duration: commit.duration, + started_at: commit.started_at, + finished_at: commit.finished_at, + }, + + repository: { + name: project.name, + url: project.url_to_repo, + description: project.description, + homepage: project.web_url, + git_http_url: project.http_url_to_repo, + git_ssh_url: project.ssh_url_to_repo, + visibility_level: project.visibility_level + }, + } + + data + end + end + end +end diff --git a/lib/gitlab/data_builder/note_data_builder.rb b/lib/gitlab/data_builder/note_data_builder.rb new file mode 100644 index 00000000000..12ae1b99f9c --- /dev/null +++ b/lib/gitlab/data_builder/note_data_builder.rb @@ -0,0 +1,75 @@ +module Gitlab + module DataBuilder + module NoteDataBuilder + module_function + + # Produce a hash of post-receive data + # + # For all notes: + # + # data = { + # object_kind: "note", + # user: { + # name: String, + # username: String, + # avatar_url: String + # } + # project_id: Integer, + # repository: { + # name: String, + # url: String, + # description: String, + # homepage: String, + # } + # object_attributes: { + # + # } + # : { + # } + # note-specific data is a hash with one of the following keys and contains + # the hook data for that type. + # - commit + # - issue + # - merge_request + # - snippet + # + def build(note, user) + project = note.project + data = build_base_data(project, user, note) + + if note.for_commit? + data[:commit] = build_data_for_commit(project, user, note) + elsif note.for_issue? + data[:issue] = note.noteable.hook_attrs + elsif note.for_merge_request? + data[:merge_request] = note.noteable.hook_attrs + elsif note.for_snippet? + data[:snippet] = note.noteable.hook_attrs + end + + data + end + + def build_base_data(project, user, note) + base_data = { + object_kind: "note", + user: user.hook_attrs, + project_id: project.id, + project: project.hook_attrs, + object_attributes: note.hook_attrs, + # DEPRECATED + repository: project.hook_attrs.slice(:name, :url, :description, :homepage) + } + + base_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(note) + base_data + end + + def build_data_for_commit(project, user, note) + # commit_id is the SHA hash + commit = project.commit(note.commit_id) + commit.hook_attrs + end + end + end +end diff --git a/lib/gitlab/data_builder/push_data_builder.rb b/lib/gitlab/data_builder/push_data_builder.rb new file mode 100644 index 00000000000..f0cad51dd36 --- /dev/null +++ b/lib/gitlab/data_builder/push_data_builder.rb @@ -0,0 +1,95 @@ +module Gitlab + module DataBuilder + module PushDataBuilder + module_function + + # Produce a hash of post-receive data + # + # data = { + # before: String, + # after: String, + # ref: String, + # user_id: String, + # user_name: String, + # user_email: String + # project_id: String, + # repository: { + # name: String, + # url: String, + # description: String, + # homepage: String, + # }, + # commits: Array, + # total_commits_count: Fixnum + # } + # + def build(project, user, oldrev, newrev, ref, commits = [], message = nil) + commits = Array(commits) + + # Total commits count + commits_count = commits.size + + # Get latest 20 commits ASC + commits_limited = commits.last(20) + + # For performance purposes maximum 20 latest commits + # will be passed as post receive hook data. + commit_attrs = commits_limited.map do |commit| + commit.hook_attrs(with_changed_files: true) + end + + type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push' + + # Hash to be passed as post_receive_data + data = { + object_kind: type, + event_name: type, + before: oldrev, + after: newrev, + ref: ref, + checkout_sha: checkout_sha(project.repository, newrev, ref), + message: message, + user_id: user.id, + user_name: user.name, + user_email: user.email, + user_avatar: user.avatar_url, + project_id: project.id, + project: project.hook_attrs, + commits: commit_attrs, + total_commits_count: commits_count, + # DEPRECATED + repository: project.hook_attrs.slice(:name, :url, :description, :homepage, + :git_http_url, :git_ssh_url, :visibility_level) + } + + data + end + + # This method provide a sample data generated with + # existing project and commits to test webhooks + def build_sample(project, user) + commits = project.repository.commits(project.default_branch, limit: 3) + ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}" + build(project, user, commits.last.id, commits.first.id, ref, commits) + end + + def checkout_sha(repository, newrev, ref) + # Checkout sha is nil when we remove branch or tag + return if Gitlab::Git.blank_ref?(newrev) + + # Find sha for tag, except when it was deleted. + if Gitlab::Git.tag_ref?(ref) + tag_name = Gitlab::Git.ref_name(ref) + tag = repository.find_tag(tag_name) + + if tag + commit = repository.commit(tag.target) + commit.try(:sha) + end + else + newrev + end + end + end + end +end diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb deleted file mode 100644 index 8bdc89a7751..00000000000 --- a/lib/gitlab/note_data_builder.rb +++ /dev/null @@ -1,73 +0,0 @@ -module Gitlab - class NoteDataBuilder - class << self - # Produce a hash of post-receive data - # - # For all notes: - # - # data = { - # object_kind: "note", - # user: { - # name: String, - # username: String, - # avatar_url: String - # } - # project_id: Integer, - # repository: { - # name: String, - # url: String, - # description: String, - # homepage: String, - # } - # object_attributes: { - # - # } - # : { - # } - # note-specific data is a hash with one of the following keys and contains - # the hook data for that type. - # - commit - # - issue - # - merge_request - # - snippet - # - def build(note, user) - project = note.project - data = build_base_data(project, user, note) - - if note.for_commit? - data[:commit] = build_data_for_commit(project, user, note) - elsif note.for_issue? - data[:issue] = note.noteable.hook_attrs - elsif note.for_merge_request? - data[:merge_request] = note.noteable.hook_attrs - elsif note.for_snippet? - data[:snippet] = note.noteable.hook_attrs - end - - data - end - - def build_base_data(project, user, note) - base_data = { - object_kind: "note", - user: user.hook_attrs, - project_id: project.id, - project: project.hook_attrs, - object_attributes: note.hook_attrs, - # DEPRECATED - repository: project.hook_attrs.slice(:name, :url, :description, :homepage) - } - - base_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(note) - base_data - end - - def build_data_for_commit(project, user, note) - # commit_id is the SHA hash - commit = project.commit(note.commit_id) - commit.hook_attrs - end - end - end -end diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb deleted file mode 100644 index c8f12577112..00000000000 --- a/lib/gitlab/push_data_builder.rb +++ /dev/null @@ -1,93 +0,0 @@ -module Gitlab - class PushDataBuilder - class << self - # Produce a hash of post-receive data - # - # data = { - # before: String, - # after: String, - # ref: String, - # user_id: String, - # user_name: String, - # user_email: String - # project_id: String, - # repository: { - # name: String, - # url: String, - # description: String, - # homepage: String, - # }, - # commits: Array, - # total_commits_count: Fixnum - # } - # - def build(project, user, oldrev, newrev, ref, commits = [], message = nil) - commits = Array(commits) - - # Total commits count - commits_count = commits.size - - # Get latest 20 commits ASC - commits_limited = commits.last(20) - - # For performance purposes maximum 20 latest commits - # will be passed as post receive hook data. - commit_attrs = commits_limited.map do |commit| - commit.hook_attrs(with_changed_files: true) - end - - type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push' - - # Hash to be passed as post_receive_data - data = { - object_kind: type, - event_name: type, - before: oldrev, - after: newrev, - ref: ref, - checkout_sha: checkout_sha(project.repository, newrev, ref), - message: message, - user_id: user.id, - user_name: user.name, - user_email: user.email, - user_avatar: user.avatar_url, - project_id: project.id, - project: project.hook_attrs, - commits: commit_attrs, - total_commits_count: commits_count, - # DEPRECATED - repository: project.hook_attrs.slice(:name, :url, :description, :homepage, - :git_http_url, :git_ssh_url, :visibility_level) - } - - data - end - - # This method provide a sample data generated with - # existing project and commits to test webhooks - def build_sample(project, user) - commits = project.repository.commits(project.default_branch, limit: 3) - ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}" - build(project, user, commits.last.id, commits.first.id, ref, commits) - end - - def checkout_sha(repository, newrev, ref) - # Checkout sha is nil when we remove branch or tag - return if Gitlab::Git.blank_ref?(newrev) - - # Find sha for tag, except when it was deleted. - if Gitlab::Git.tag_ref?(ref) - tag_name = Gitlab::Git.ref_name(ref) - tag = repository.find_tag(tag_name) - - if tag - commit = repository.commit(tag.target) - commit.try(:sha) - end - else - newrev - end - end - end - end -end -- cgit v1.2.1 From f88d4523f3e9523494a96c149c28796df8023e8d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 5 Aug 2016 13:53:43 +0800 Subject: We still need to skip loading config_processor if skip_ci? --- lib/gitlab/data_builder/pipeline_data_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb index 13417ba09eb..a4c770b630f 100644 --- a/lib/gitlab/data_builder/pipeline_data_builder.rb +++ b/lib/gitlab/data_builder/pipeline_data_builder.rb @@ -16,7 +16,7 @@ module Gitlab def hook_attrs(pipeline) first_pending_build = pipeline.builds.first_pending - config_processor = pipeline.config_processor + config_processor = pipeline.config_processor unless pipeline.skip_ci? { id: pipeline.id, -- cgit v1.2.1 From efab1677a7a2959a28a09393a2b6519072005534 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 11 Aug 2016 11:03:26 +0200 Subject: Fix attribute inclusion in import/export config ignored in some cases --- lib/gitlab/import_export/json_hash_builder.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb index 008300bde45..b7db0e7a06a 100644 --- a/lib/gitlab/import_export/json_hash_builder.rb +++ b/lib/gitlab/import_export/json_hash_builder.rb @@ -57,10 +57,7 @@ module Gitlab # +value+ existing model to be included in the hash # +json_config_hash+ the original hash containing the root model def create_model_value(current_key, value, json_config_hash) - parsed_hash = { include: value } - parse_hash(value, parsed_hash) - - json_config_hash[current_key] = parsed_hash + json_config_hash[current_key] = parse_hash(value, { include: value }) end # Calls attributes finder to parse the hash and add any attributes to it @@ -69,8 +66,8 @@ module Gitlab # +parsed_hash+ the original hash def parse_hash(value, parsed_hash) @attributes_finder.parse(value) do |hash| - parsed_hash = { include: hash_or_merge(value, hash) } - end + { include: hash_or_merge(value, hash) } + end || parsed_hash end # Adds new model configuration to an existing hash with key +current_key+ -- cgit v1.2.1 From ffa75a497a23bf6f87de626fee08ff4538a12587 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 11 Aug 2016 17:23:07 +0200 Subject: Remove stage parameter from send payload --- lib/gitlab/data_builder/pipeline_data_builder.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb index a4c770b630f..3dc4d50fcde 100644 --- a/lib/gitlab/data_builder/pipeline_data_builder.rb +++ b/lib/gitlab/data_builder/pipeline_data_builder.rb @@ -15,9 +15,6 @@ module Gitlab end def hook_attrs(pipeline) - first_pending_build = pipeline.builds.first_pending - config_processor = pipeline.config_processor unless pipeline.skip_ci? - { id: pipeline.id, ref: pipeline.ref, @@ -25,8 +22,7 @@ module Gitlab sha: pipeline.sha, before_sha: pipeline.before_sha, status: pipeline.status, - stage: first_pending_build.try(:stage), - stages: config_processor.try(:stages), + stages: pipeline.stages, created_at: pipeline.created_at, finished_at: pipeline.finished_at, duration: pipeline.duration -- cgit v1.2.1 From c42f5f8b56a919473d9adceaf84c9ef77179c5cb Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 11 Aug 2016 21:42:34 +0200 Subject: refactor parse_hash based on feedback --- lib/gitlab/import_export/json_hash_builder.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb index b7db0e7a06a..0cc10f40087 100644 --- a/lib/gitlab/import_export/json_hash_builder.rb +++ b/lib/gitlab/import_export/json_hash_builder.rb @@ -57,17 +57,17 @@ module Gitlab # +value+ existing model to be included in the hash # +json_config_hash+ the original hash containing the root model def create_model_value(current_key, value, json_config_hash) - json_config_hash[current_key] = parse_hash(value, { include: value }) + json_config_hash[current_key] = parse_hash(value) || { include: value } end # Calls attributes finder to parse the hash and add any attributes to it # # +value+ existing model to be included in the hash # +parsed_hash+ the original hash - def parse_hash(value, parsed_hash) + def parse_hash(value) @attributes_finder.parse(value) do |hash| { include: hash_or_merge(value, hash) } - end || parsed_hash + end end # Adds new model configuration to an existing hash with key +current_key+ -- cgit v1.2.1 From 0a20897bbee538761352595e8f632c747b6d1b35 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 12 Aug 2016 15:33:28 +0800 Subject: Prefer extend self over module_function, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5620#note_13672004 https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5620#note_13810498 --- lib/gitlab/data_builder/build_data_builder.rb | 2 +- lib/gitlab/data_builder/note_data_builder.rb | 2 +- lib/gitlab/data_builder/pipeline_data_builder.rb | 2 +- lib/gitlab/data_builder/push_data_builder.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/data_builder/build_data_builder.rb b/lib/gitlab/data_builder/build_data_builder.rb index 5175645e238..1b42769bf5b 100644 --- a/lib/gitlab/data_builder/build_data_builder.rb +++ b/lib/gitlab/data_builder/build_data_builder.rb @@ -1,7 +1,7 @@ module Gitlab module DataBuilder module BuildDataBuilder - module_function + extend self def build(build) project = build.project diff --git a/lib/gitlab/data_builder/note_data_builder.rb b/lib/gitlab/data_builder/note_data_builder.rb index 12ae1b99f9c..78dc583abc7 100644 --- a/lib/gitlab/data_builder/note_data_builder.rb +++ b/lib/gitlab/data_builder/note_data_builder.rb @@ -1,7 +1,7 @@ module Gitlab module DataBuilder module NoteDataBuilder - module_function + extend self # Produce a hash of post-receive data # diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb index 3dc4d50fcde..1cba74c7065 100644 --- a/lib/gitlab/data_builder/pipeline_data_builder.rb +++ b/lib/gitlab/data_builder/pipeline_data_builder.rb @@ -1,7 +1,7 @@ module Gitlab module DataBuilder module PipelineDataBuilder - module_function + extend self def build(pipeline) { diff --git a/lib/gitlab/data_builder/push_data_builder.rb b/lib/gitlab/data_builder/push_data_builder.rb index f0cad51dd36..f0debe7b19f 100644 --- a/lib/gitlab/data_builder/push_data_builder.rb +++ b/lib/gitlab/data_builder/push_data_builder.rb @@ -1,7 +1,7 @@ module Gitlab module DataBuilder module PushDataBuilder - module_function + extend self # Produce a hash of post-receive data # -- cgit v1.2.1 From d5264e8804bca70e613c418a9d346f5787c6fc7a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 12 Aug 2016 16:09:29 +0800 Subject: Simplify the name for data builder, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5620#note_13671791 --- lib/gitlab/data_builder/build.rb | 67 +++++++++++++++++ lib/gitlab/data_builder/build_data_builder.rb | 67 ----------------- lib/gitlab/data_builder/note.rb | 75 +++++++++++++++++++ lib/gitlab/data_builder/note_data_builder.rb | 75 ------------------- lib/gitlab/data_builder/pipeline.rb | 62 ++++++++++++++++ lib/gitlab/data_builder/pipeline_data_builder.rb | 62 ---------------- lib/gitlab/data_builder/push.rb | 95 ++++++++++++++++++++++++ lib/gitlab/data_builder/push_data_builder.rb | 95 ------------------------ 8 files changed, 299 insertions(+), 299 deletions(-) create mode 100644 lib/gitlab/data_builder/build.rb delete mode 100644 lib/gitlab/data_builder/build_data_builder.rb create mode 100644 lib/gitlab/data_builder/note.rb delete mode 100644 lib/gitlab/data_builder/note_data_builder.rb create mode 100644 lib/gitlab/data_builder/pipeline.rb delete mode 100644 lib/gitlab/data_builder/pipeline_data_builder.rb create mode 100644 lib/gitlab/data_builder/push.rb delete mode 100644 lib/gitlab/data_builder/push_data_builder.rb (limited to 'lib') diff --git a/lib/gitlab/data_builder/build.rb b/lib/gitlab/data_builder/build.rb new file mode 100644 index 00000000000..6548e6475c6 --- /dev/null +++ b/lib/gitlab/data_builder/build.rb @@ -0,0 +1,67 @@ +module Gitlab + module DataBuilder + module Build + extend self + + def build(build) + project = build.project + commit = build.pipeline + user = build.user + + data = { + object_kind: 'build', + + ref: build.ref, + tag: build.tag, + before_sha: build.before_sha, + sha: build.sha, + + # TODO: should this be not prefixed with build_? + # Leaving this way to have backward compatibility + build_id: build.id, + build_name: build.name, + build_stage: build.stage, + build_status: build.status, + build_started_at: build.started_at, + build_finished_at: build.finished_at, + build_duration: build.duration, + build_allow_failure: build.allow_failure, + + # TODO: do we still need it? + project_id: project.id, + project_name: project.name_with_namespace, + + user: { + id: user.try(:id), + name: user.try(:name), + email: user.try(:email), + }, + + commit: { + id: commit.id, + sha: commit.sha, + message: commit.git_commit_message, + author_name: commit.git_author_name, + author_email: commit.git_author_email, + status: commit.status, + duration: commit.duration, + started_at: commit.started_at, + finished_at: commit.finished_at, + }, + + repository: { + name: project.name, + url: project.url_to_repo, + description: project.description, + homepage: project.web_url, + git_http_url: project.http_url_to_repo, + git_ssh_url: project.ssh_url_to_repo, + visibility_level: project.visibility_level + }, + } + + data + end + end + end +end diff --git a/lib/gitlab/data_builder/build_data_builder.rb b/lib/gitlab/data_builder/build_data_builder.rb deleted file mode 100644 index 1b42769bf5b..00000000000 --- a/lib/gitlab/data_builder/build_data_builder.rb +++ /dev/null @@ -1,67 +0,0 @@ -module Gitlab - module DataBuilder - module BuildDataBuilder - extend self - - def build(build) - project = build.project - commit = build.pipeline - user = build.user - - data = { - object_kind: 'build', - - ref: build.ref, - tag: build.tag, - before_sha: build.before_sha, - sha: build.sha, - - # TODO: should this be not prefixed with build_? - # Leaving this way to have backward compatibility - build_id: build.id, - build_name: build.name, - build_stage: build.stage, - build_status: build.status, - build_started_at: build.started_at, - build_finished_at: build.finished_at, - build_duration: build.duration, - build_allow_failure: build.allow_failure, - - # TODO: do we still need it? - project_id: project.id, - project_name: project.name_with_namespace, - - user: { - id: user.try(:id), - name: user.try(:name), - email: user.try(:email), - }, - - commit: { - id: commit.id, - sha: commit.sha, - message: commit.git_commit_message, - author_name: commit.git_author_name, - author_email: commit.git_author_email, - status: commit.status, - duration: commit.duration, - started_at: commit.started_at, - finished_at: commit.finished_at, - }, - - repository: { - name: project.name, - url: project.url_to_repo, - description: project.description, - homepage: project.web_url, - git_http_url: project.http_url_to_repo, - git_ssh_url: project.ssh_url_to_repo, - visibility_level: project.visibility_level - }, - } - - data - end - end - end -end diff --git a/lib/gitlab/data_builder/note.rb b/lib/gitlab/data_builder/note.rb new file mode 100644 index 00000000000..50fea1232af --- /dev/null +++ b/lib/gitlab/data_builder/note.rb @@ -0,0 +1,75 @@ +module Gitlab + module DataBuilder + module Note + extend self + + # Produce a hash of post-receive data + # + # For all notes: + # + # data = { + # object_kind: "note", + # user: { + # name: String, + # username: String, + # avatar_url: String + # } + # project_id: Integer, + # repository: { + # name: String, + # url: String, + # description: String, + # homepage: String, + # } + # object_attributes: { + # + # } + # : { + # } + # note-specific data is a hash with one of the following keys and contains + # the hook data for that type. + # - commit + # - issue + # - merge_request + # - snippet + # + def build(note, user) + project = note.project + data = build_base_data(project, user, note) + + if note.for_commit? + data[:commit] = build_data_for_commit(project, user, note) + elsif note.for_issue? + data[:issue] = note.noteable.hook_attrs + elsif note.for_merge_request? + data[:merge_request] = note.noteable.hook_attrs + elsif note.for_snippet? + data[:snippet] = note.noteable.hook_attrs + end + + data + end + + def build_base_data(project, user, note) + base_data = { + object_kind: "note", + user: user.hook_attrs, + project_id: project.id, + project: project.hook_attrs, + object_attributes: note.hook_attrs, + # DEPRECATED + repository: project.hook_attrs.slice(:name, :url, :description, :homepage) + } + + base_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(note) + base_data + end + + def build_data_for_commit(project, user, note) + # commit_id is the SHA hash + commit = project.commit(note.commit_id) + commit.hook_attrs + end + end + end +end diff --git a/lib/gitlab/data_builder/note_data_builder.rb b/lib/gitlab/data_builder/note_data_builder.rb deleted file mode 100644 index 78dc583abc7..00000000000 --- a/lib/gitlab/data_builder/note_data_builder.rb +++ /dev/null @@ -1,75 +0,0 @@ -module Gitlab - module DataBuilder - module NoteDataBuilder - extend self - - # Produce a hash of post-receive data - # - # For all notes: - # - # data = { - # object_kind: "note", - # user: { - # name: String, - # username: String, - # avatar_url: String - # } - # project_id: Integer, - # repository: { - # name: String, - # url: String, - # description: String, - # homepage: String, - # } - # object_attributes: { - # - # } - # : { - # } - # note-specific data is a hash with one of the following keys and contains - # the hook data for that type. - # - commit - # - issue - # - merge_request - # - snippet - # - def build(note, user) - project = note.project - data = build_base_data(project, user, note) - - if note.for_commit? - data[:commit] = build_data_for_commit(project, user, note) - elsif note.for_issue? - data[:issue] = note.noteable.hook_attrs - elsif note.for_merge_request? - data[:merge_request] = note.noteable.hook_attrs - elsif note.for_snippet? - data[:snippet] = note.noteable.hook_attrs - end - - data - end - - def build_base_data(project, user, note) - base_data = { - object_kind: "note", - user: user.hook_attrs, - project_id: project.id, - project: project.hook_attrs, - object_attributes: note.hook_attrs, - # DEPRECATED - repository: project.hook_attrs.slice(:name, :url, :description, :homepage) - } - - base_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(note) - base_data - end - - def build_data_for_commit(project, user, note) - # commit_id is the SHA hash - commit = project.commit(note.commit_id) - commit.hook_attrs - end - end - end -end diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb new file mode 100644 index 00000000000..06a783ebc1c --- /dev/null +++ b/lib/gitlab/data_builder/pipeline.rb @@ -0,0 +1,62 @@ +module Gitlab + module DataBuilder + module Pipeline + extend self + + def build(pipeline) + { + object_kind: 'pipeline', + object_attributes: hook_attrs(pipeline), + user: pipeline.user.try(:hook_attrs), + project: pipeline.project.hook_attrs(backward: false), + commit: pipeline.commit.try(:hook_attrs), + builds: pipeline.builds.map(&method(:build_hook_attrs)) + } + end + + def hook_attrs(pipeline) + { + id: pipeline.id, + ref: pipeline.ref, + tag: pipeline.tag, + sha: pipeline.sha, + before_sha: pipeline.before_sha, + status: pipeline.status, + stages: pipeline.stages, + created_at: pipeline.created_at, + finished_at: pipeline.finished_at, + duration: pipeline.duration + } + end + + def build_hook_attrs(build) + { + id: build.id, + stage: build.stage, + name: build.name, + status: build.status, + created_at: build.created_at, + started_at: build.started_at, + finished_at: build.finished_at, + when: build.when, + manual: build.manual?, + user: build.user.try(:hook_attrs), + runner: build.runner && runner_hook_attrs(build.runner), + artifacts_file: { + filename: build.artifacts_file.filename, + size: build.artifacts_size + } + } + end + + def runner_hook_attrs(runner) + { + id: runner.id, + description: runner.description, + active: runner.active?, + is_shared: runner.is_shared? + } + end + end + end +end diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb deleted file mode 100644 index 1cba74c7065..00000000000 --- a/lib/gitlab/data_builder/pipeline_data_builder.rb +++ /dev/null @@ -1,62 +0,0 @@ -module Gitlab - module DataBuilder - module PipelineDataBuilder - extend self - - def build(pipeline) - { - object_kind: 'pipeline', - object_attributes: hook_attrs(pipeline), - user: pipeline.user.try(:hook_attrs), - project: pipeline.project.hook_attrs(backward: false), - commit: pipeline.commit.try(:hook_attrs), - builds: pipeline.builds.map(&method(:build_hook_attrs)) - } - end - - def hook_attrs(pipeline) - { - id: pipeline.id, - ref: pipeline.ref, - tag: pipeline.tag, - sha: pipeline.sha, - before_sha: pipeline.before_sha, - status: pipeline.status, - stages: pipeline.stages, - created_at: pipeline.created_at, - finished_at: pipeline.finished_at, - duration: pipeline.duration - } - end - - def build_hook_attrs(build) - { - id: build.id, - stage: build.stage, - name: build.name, - status: build.status, - created_at: build.created_at, - started_at: build.started_at, - finished_at: build.finished_at, - when: build.when, - manual: build.manual?, - user: build.user.try(:hook_attrs), - runner: build.runner && runner_hook_attrs(build.runner), - artifacts_file: { - filename: build.artifacts_file.filename, - size: build.artifacts_size - } - } - end - - def runner_hook_attrs(runner) - { - id: runner.id, - description: runner.description, - active: runner.active?, - is_shared: runner.is_shared? - } - end - end - end -end diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb new file mode 100644 index 00000000000..4f81863da35 --- /dev/null +++ b/lib/gitlab/data_builder/push.rb @@ -0,0 +1,95 @@ +module Gitlab + module DataBuilder + module Push + extend self + + # Produce a hash of post-receive data + # + # data = { + # before: String, + # after: String, + # ref: String, + # user_id: String, + # user_name: String, + # user_email: String + # project_id: String, + # repository: { + # name: String, + # url: String, + # description: String, + # homepage: String, + # }, + # commits: Array, + # total_commits_count: Fixnum + # } + # + def build(project, user, oldrev, newrev, ref, commits = [], message = nil) + commits = Array(commits) + + # Total commits count + commits_count = commits.size + + # Get latest 20 commits ASC + commits_limited = commits.last(20) + + # For performance purposes maximum 20 latest commits + # will be passed as post receive hook data. + commit_attrs = commits_limited.map do |commit| + commit.hook_attrs(with_changed_files: true) + end + + type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push' + + # Hash to be passed as post_receive_data + data = { + object_kind: type, + event_name: type, + before: oldrev, + after: newrev, + ref: ref, + checkout_sha: checkout_sha(project.repository, newrev, ref), + message: message, + user_id: user.id, + user_name: user.name, + user_email: user.email, + user_avatar: user.avatar_url, + project_id: project.id, + project: project.hook_attrs, + commits: commit_attrs, + total_commits_count: commits_count, + # DEPRECATED + repository: project.hook_attrs.slice(:name, :url, :description, :homepage, + :git_http_url, :git_ssh_url, :visibility_level) + } + + data + end + + # This method provide a sample data generated with + # existing project and commits to test webhooks + def build_sample(project, user) + commits = project.repository.commits(project.default_branch, limit: 3) + ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}" + build(project, user, commits.last.id, commits.first.id, ref, commits) + end + + def checkout_sha(repository, newrev, ref) + # Checkout sha is nil when we remove branch or tag + return if Gitlab::Git.blank_ref?(newrev) + + # Find sha for tag, except when it was deleted. + if Gitlab::Git.tag_ref?(ref) + tag_name = Gitlab::Git.ref_name(ref) + tag = repository.find_tag(tag_name) + + if tag + commit = repository.commit(tag.target) + commit.try(:sha) + end + else + newrev + end + end + end + end +end diff --git a/lib/gitlab/data_builder/push_data_builder.rb b/lib/gitlab/data_builder/push_data_builder.rb deleted file mode 100644 index f0debe7b19f..00000000000 --- a/lib/gitlab/data_builder/push_data_builder.rb +++ /dev/null @@ -1,95 +0,0 @@ -module Gitlab - module DataBuilder - module PushDataBuilder - extend self - - # Produce a hash of post-receive data - # - # data = { - # before: String, - # after: String, - # ref: String, - # user_id: String, - # user_name: String, - # user_email: String - # project_id: String, - # repository: { - # name: String, - # url: String, - # description: String, - # homepage: String, - # }, - # commits: Array, - # total_commits_count: Fixnum - # } - # - def build(project, user, oldrev, newrev, ref, commits = [], message = nil) - commits = Array(commits) - - # Total commits count - commits_count = commits.size - - # Get latest 20 commits ASC - commits_limited = commits.last(20) - - # For performance purposes maximum 20 latest commits - # will be passed as post receive hook data. - commit_attrs = commits_limited.map do |commit| - commit.hook_attrs(with_changed_files: true) - end - - type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push' - - # Hash to be passed as post_receive_data - data = { - object_kind: type, - event_name: type, - before: oldrev, - after: newrev, - ref: ref, - checkout_sha: checkout_sha(project.repository, newrev, ref), - message: message, - user_id: user.id, - user_name: user.name, - user_email: user.email, - user_avatar: user.avatar_url, - project_id: project.id, - project: project.hook_attrs, - commits: commit_attrs, - total_commits_count: commits_count, - # DEPRECATED - repository: project.hook_attrs.slice(:name, :url, :description, :homepage, - :git_http_url, :git_ssh_url, :visibility_level) - } - - data - end - - # This method provide a sample data generated with - # existing project and commits to test webhooks - def build_sample(project, user) - commits = project.repository.commits(project.default_branch, limit: 3) - ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}" - build(project, user, commits.last.id, commits.first.id, ref, commits) - end - - def checkout_sha(repository, newrev, ref) - # Checkout sha is nil when we remove branch or tag - return if Gitlab::Git.blank_ref?(newrev) - - # Find sha for tag, except when it was deleted. - if Gitlab::Git.tag_ref?(ref) - tag_name = Gitlab::Git.ref_name(ref) - tag = repository.find_tag(tag_name) - - if tag - commit = repository.commit(tag.target) - commit.try(:sha) - end - else - newrev - end - end - end - end -end -- cgit v1.2.1 From df2ed097b730c8ba0b79cac8cc3dbfcb0cf587cb Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 25 Jul 2016 19:47:09 +0100 Subject: Add backend for merge conflicts reading --- lib/gitlab/conflict/file.rb | 91 +++++++++++++++++++++++++++++++++++++++++++ lib/gitlab/conflict/parser.rb | 59 ++++++++++++++++++++++++++++ lib/gitlab/diff/line.rb | 11 ++++++ 3 files changed, 161 insertions(+) create mode 100644 lib/gitlab/conflict/file.rb create mode 100644 lib/gitlab/conflict/parser.rb (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb new file mode 100644 index 00000000000..84d3e6f4e03 --- /dev/null +++ b/lib/gitlab/conflict/file.rb @@ -0,0 +1,91 @@ +module Gitlab + module Conflict + class File + CONTEXT_LINES = 3 + + attr_reader :merge_file, :their_path, :their_ref, :our_path, :our_ref, :repository + + def initialize(merge_file, conflict, their_ref, our_ref, repository) + @merge_file = merge_file + @their_path = conflict[:theirs][:path] + @our_path = conflict[:ours][:path] + @their_ref = their_ref + @our_ref = our_ref + @repository = repository + end + + # Array of Gitlab::Diff::Line objects + def lines + @lines ||= Gitlab::Conflict::Parser.new.parse(merge_file[:data], their_path, our_path) + end + + def highlighted_lines + return @highlighted_lines if @highlighted_lines + + their_highlight = Gitlab::Highlight.highlight_lines(repository, their_ref, their_path) + our_highlight = Gitlab::Highlight.highlight_lines(repository, our_ref, our_path) + + @highlighted_lines = lines.map do |line| + line = line.dup + if line.type == 'old' + line.rich_text = their_highlight[line.old_line - 1].delete("\n") + else + line.rich_text = our_highlight[line.new_line - 1].delete("\n") + end + line + end + end + + def sections + return @sections if @sections + + chunked_lines = highlighted_lines.chunk { |line| line.type.nil? } + match_line = nil + + @sections = chunked_lines.flat_map.with_index do |(no_conflict, lines), i| + section = nil + + if no_conflict + conflict_before = i > 0 + conflict_after = chunked_lines.peek + + if conflict_before && conflict_after + if lines.length > CONTEXT_LINES * 2 + tail_lines = lines.last(CONTEXT_LINES) + first_tail_line = tail_lines.first + match_line = Gitlab::Diff::Line.new('', + 'match', + first_tail_line.index, + first_tail_line.old_pos, + first_tail_line.new_pos) + + section = [ + { conflict: false, lines: lines.first(CONTEXT_LINES) }, + { conflict: false, lines: tail_lines.unshift(match_line) } + ] + end + elsif conflict_after + lines = lines.last(CONTEXT_LINES) + elsif conflict_before + lines = lines.first(CONTEXT_LINES) + end + end + + if match_line && !section + match_line.text = "@@ -#{match_line.old_pos},#{lines.last.old_pos} +#{match_line.new_pos},#{lines.last.new_pos} @@" + end + + section || { conflict: !no_conflict, lines: lines } + end + end + + def as_json(opts = nil) + { + old_path: their_path, + new_path: our_path, + sections: sections + } + end + end + end +end diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb new file mode 100644 index 00000000000..a233c268070 --- /dev/null +++ b/lib/gitlab/conflict/parser.rb @@ -0,0 +1,59 @@ +module Gitlab + module Conflict + class Parser + class UnexpectedDelimiter < StandardError + end + + class MissingEndDelimiter < StandardError + end + + def parse(text, their_path, our_path) + return [] if text.blank? + + line_obj_index = 0 + line_old = 1 + line_new = 1 + type = nil + lines = [] + conflict_start = "<<<<<<< #{our_path}" + conflict_middle = '=======' + conflict_end = ">>>>>>> #{their_path}" + + text.each_line.map do |line| + full_line = line.delete("\n") + + if full_line == conflict_start + raise UnexpectedDelimiter unless type.nil? + + type = 'new' + elsif full_line == conflict_middle + raise UnexpectedDelimiter unless type == 'new' + + type = 'old' + elsif full_line == conflict_end + raise UnexpectedDelimiter unless type == 'old' + + type = nil + elsif line[0] == '\\' + type = 'nonewline' + lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + else + lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + line_old += 1 if type != 'new' + line_new += 1 if type != 'old' + + line_obj_index += 1 + end + end + + raise MissingEndDelimiter unless type == nil + + lines + end + + def empty? + @lines.empty? + end + end + end +end diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index cf097e0d0de..5196051f90d 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -3,6 +3,7 @@ module Gitlab class Line attr_reader :type, :index, :old_pos, :new_pos attr_accessor :text + attr_accessor :rich_text def initialize(text, type, index, old_pos, new_pos) @text, @type, @index = text, type, index @@ -46,6 +47,16 @@ module Gitlab def meta? type == 'match' || type == 'nonewline' end + + def as_json(opts = nil) + { + type: type, + old_line: old_line, + new_line: new_line, + text: text, + rich_text: rich_text || text + } + end end end end -- cgit v1.2.1 From a1c79612172ce07c7b0de4c01fba8fa7369c71de Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 27 Jul 2016 12:42:18 +0100 Subject: Handle multiple merge conflict files in collection --- lib/gitlab/conflict/file.rb | 18 ++++++----- lib/gitlab/conflict/file_collection.rb | 59 ++++++++++++++++++++++++++++++++++ lib/gitlab/conflict/parser.rb | 8 ++--- 3 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 lib/gitlab/conflict/file_collection.rb (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 84d3e6f4e03..7f81c72431d 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -3,20 +3,22 @@ module Gitlab class File CONTEXT_LINES = 3 - attr_reader :merge_file, :their_path, :their_ref, :our_path, :our_ref, :repository + attr_reader :merge_file_result, :their_path, :their_ref, :our_path, :our_ref, :repository - def initialize(merge_file, conflict, their_ref, our_ref, repository) - @merge_file = merge_file + def initialize(merge_file_result, conflict, diff_refs:, repository:) + @merge_file_result = merge_file_result @their_path = conflict[:theirs][:path] @our_path = conflict[:ours][:path] - @their_ref = their_ref - @our_ref = our_ref + @their_ref = diff_refs.start_sha + @our_ref = diff_refs.head_sha @repository = repository end # Array of Gitlab::Diff::Line objects def lines - @lines ||= Gitlab::Conflict::Parser.new.parse(merge_file[:data], their_path, our_path) + @lines ||= Gitlab::Conflict::Parser.new.parse(merge_file_result[:data], + our_path: our_path, + their_path: their_path) end def highlighted_lines @@ -28,9 +30,9 @@ module Gitlab @highlighted_lines = lines.map do |line| line = line.dup if line.type == 'old' - line.rich_text = their_highlight[line.old_line - 1].delete("\n") + line.rich_text = their_highlight[line.old_line - 1] else - line.rich_text = our_highlight[line.new_line - 1].delete("\n") + line.rich_text = our_highlight[line.new_line - 1] end line end diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb new file mode 100644 index 00000000000..a3035a5c3e6 --- /dev/null +++ b/lib/gitlab/conflict/file_collection.rb @@ -0,0 +1,59 @@ +module Gitlab + module Conflict + class FileCollection + attr_reader :merge_request, :our_commit, :their_commit + + def initialize(merge_request) + @merge_request = merge_request + @our_commit = merge_request.diff_head_commit.raw.raw_commit + @their_commit = merge_request.target_branch_head.raw.raw_commit + end + + def repository + merge_request.project.repository + end + + def merge_index + @merge_index ||= repository.rugged.merge_commits(our_commit, their_commit) + end + + def files + @files ||= merge_index.conflicts.map do |conflict| + their_path = conflict[:theirs][:path] + our_path = conflict[:ours][:path] + + # TODO remove this + raise 'path mismatch!' unless their_path == our_path + + Gitlab::Conflict::File.new(merge_index.merge_file(our_path), + conflict, + diff_refs: merge_request.diff_refs, + repository: repository) + end + end + + def as_json(opts = nil) + { + target_branch: merge_request.target_branch, + source_branch: merge_request.source_branch, + commit_sha: merge_request.diff_head_sha, + commit_message: default_commit_message, + files: files + } + end + + def default_commit_message + conflict_filenames = merge_index.conflicts.map do |conflict| + "# #{conflict[:ours][:path]}" + end + + < Date: Wed, 27 Jul 2016 17:54:04 +0100 Subject: Allow resolving conflicts in MR controller --- lib/gitlab/conflict/file.rb | 44 +++++++++++++++++++++++++++++++++- lib/gitlab/conflict/file_collection.rb | 20 ++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 7f81c72431d..80f6f7feecf 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -1,6 +1,9 @@ module Gitlab module Conflict class File + class MissingResolution < StandardError + end + CONTEXT_LINES = 3 attr_reader :merge_file_result, :their_path, :their_ref, :our_path, :our_ref, :repository @@ -21,6 +24,39 @@ module Gitlab their_path: their_path) end + def resolve!(resolution, index:, rugged:) + new_file = resolve_lines(resolution).map(&:text).join("\n") + + oid = rugged.write(new_file, :blob) + our_mode = index.conflict_get(our_path)[:ours][:mode] + index.add(path: our_path, oid: oid, mode: our_mode) + index.conflict_remove(our_path) + end + + def resolve_lines(resolution) + current_section = nil + + lines.map do |line| + unless line.type + current_section = nil + next line + end + + current_section ||= resolution[line_code(line)] + + case current_section + when 'ours' + next unless line.type == 'new' + when 'theirs' + next unless line.type == 'old' + else + raise MissingResolution + end + + line + end.compact + end + def highlighted_lines return @highlighted_lines if @highlighted_lines @@ -77,10 +113,16 @@ module Gitlab match_line.text = "@@ -#{match_line.old_pos},#{lines.last.old_pos} +#{match_line.new_pos},#{lines.last.new_pos} @@" end - section || { conflict: !no_conflict, lines: lines } + section ||= { conflict: !no_conflict, lines: lines } + section[:id] = line_code(lines.first) unless no_conflict + section end end + def line_code(line) + Gitlab::Diff::LineCode.generate(our_path, line.new_pos, line.old_pos) + end + def as_json(opts = nil) { old_path: their_path, diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index a3035a5c3e6..a4a1505bb7d 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -17,6 +17,26 @@ module Gitlab @merge_index ||= repository.rugged.merge_commits(our_commit, their_commit) end + def resolve_conflicts!(resolutions, commit_message, user:) + rugged = repository.rugged + committer = repository.user_to_committer(user) + commit_message ||= default_commit_message + + files.each do |file| + file.resolve!(resolutions, index: merge_index, rugged: rugged) + end + + new_tree = merge_index.write_tree(rugged) + + Rugged::Commit.create(rugged, + author: committer, + committer: committer, + tree: new_tree, + message: commit_message, + parents: [our_commit, their_commit], + update_ref: Gitlab::Git::BRANCH_REF_PREFIX + merge_request.source_branch) + end + def files @files ||= merge_index.conflicts.map do |conflict| their_path = conflict[:theirs][:path] -- cgit v1.2.1 From 28ef06c52b3d76b2e5dc60b0bdcf407162035ee8 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 29 Jul 2016 11:05:33 +0100 Subject: Fix merge conflict reading for new diffs --- lib/gitlab/conflict/file_collection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index a4a1505bb7d..695dafa9e20 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -5,7 +5,7 @@ module Gitlab def initialize(merge_request) @merge_request = merge_request - @our_commit = merge_request.diff_head_commit.raw.raw_commit + @our_commit = merge_request.source_branch_head.raw.raw_commit @their_commit = merge_request.target_branch_head.raw.raw_commit end -- cgit v1.2.1 From 7af277f683cd5ee0d5e3ffbc1dd3ce1d61f17848 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 29 Jul 2016 13:29:14 +0100 Subject: Auto-highlight conflict when rich_text is called --- lib/gitlab/conflict/file.rb | 13 +++++-------- lib/gitlab/conflict/parser.rb | 6 +++--- lib/gitlab/diff/line.rb | 11 +++++++++-- 3 files changed, 17 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 80f6f7feecf..9ba8ae0367f 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -21,7 +21,8 @@ module Gitlab def lines @lines ||= Gitlab::Conflict::Parser.new.parse(merge_file_result[:data], our_path: our_path, - their_path: their_path) + their_path: their_path, + parent: self) end def resolve!(resolution, index:, rugged:) @@ -57,27 +58,23 @@ module Gitlab end.compact end - def highlighted_lines - return @highlighted_lines if @highlighted_lines - + def highlight_lines! their_highlight = Gitlab::Highlight.highlight_lines(repository, their_ref, their_path) our_highlight = Gitlab::Highlight.highlight_lines(repository, our_ref, our_path) - @highlighted_lines = lines.map do |line| - line = line.dup + lines.each do |line| if line.type == 'old' line.rich_text = their_highlight[line.old_line - 1] else line.rich_text = our_highlight[line.new_line - 1] end - line end end def sections return @sections if @sections - chunked_lines = highlighted_lines.chunk { |line| line.type.nil? } + chunked_lines = lines.chunk { |line| line.type.nil? } match_line = nil @sections = chunked_lines.flat_map.with_index do |(no_conflict, lines), i| diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index 9c541931680..0aa85202d56 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -7,7 +7,7 @@ module Gitlab class MissingEndDelimiter < StandardError end - def parse(text, our_path:, their_path:) + def parse(text, our_path:, their_path:, parent: nil) return [] if text.blank? line_obj_index = 0 @@ -36,9 +36,9 @@ module Gitlab type = nil elsif line[0] == '\\' type = 'nonewline' - lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new, parent: parent) else - lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new, parent: parent) line_old += 1 if type != 'new' line_new += 1 if type != 'old' diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index 5196051f90d..38400b48b5c 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -2,12 +2,13 @@ module Gitlab module Diff class Line attr_reader :type, :index, :old_pos, :new_pos + attr_writer :rich_text attr_accessor :text - attr_accessor :rich_text - def initialize(text, type, index, old_pos, new_pos) + def initialize(text, type, index, old_pos, new_pos, parent: nil) @text, @type, @index = text, type, index @old_pos, @new_pos = old_pos, new_pos + @parent = parent end def self.init_from_hash(hash) @@ -44,6 +45,12 @@ module Gitlab type == 'old' end + def rich_text + @parent.highlight_lines! if @parent && !@rich_text + + @rich_text + end + def meta? type == 'match' || type == 'nonewline' end -- cgit v1.2.1 From 97ceadeea7b3ad9fa69049932149157334beba11 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 29 Jul 2016 14:49:48 +0100 Subject: Fix MR conflict resolution commits --- lib/gitlab/conflict/file_collection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index 695dafa9e20..46e381d2661 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -33,7 +33,7 @@ module Gitlab committer: committer, tree: new_tree, message: commit_message, - parents: [our_commit, their_commit], + parents: [our_commit, their_commit].map(&:oid), update_ref: Gitlab::Git::BRANCH_REF_PREFIX + merge_request.source_branch) end -- cgit v1.2.1 From f2f844693ecd8a10d48530adf8d59a7c125d2752 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 29 Jul 2016 14:51:11 +0100 Subject: Handle conflict resolution errors in controller --- lib/gitlab/conflict/file.rb | 10 +++++----- lib/gitlab/conflict/parser.rb | 7 +++++-- 2 files changed, 10 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 9ba8ae0367f..598bf2a2612 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -35,23 +35,23 @@ module Gitlab end def resolve_lines(resolution) - current_section = nil + section_id = nil lines.map do |line| unless line.type - current_section = nil + section_id = nil next line end - current_section ||= resolution[line_code(line)] + section_id ||= line_code(line) - case current_section + case resolution[section_id] when 'ours' next unless line.type == 'new' when 'theirs' next unless line.type == 'old' else - raise MissingResolution + raise MissingResolution, "Missing resolution for section ID: #{section_id}" end line diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index 0aa85202d56..8ab7b6499aa 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -1,10 +1,13 @@ module Gitlab module Conflict class Parser - class UnexpectedDelimiter < StandardError + class ParserError < StandardError end - class MissingEndDelimiter < StandardError + class UnexpectedDelimiter < ParserError + end + + class MissingEndDelimiter < ParserError end def parse(text, our_path:, their_path:, parent: nil) -- cgit v1.2.1 From 18398152fa4e5237a7b682d955457b12a7d59b31 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 29 Jul 2016 15:17:06 +0100 Subject: Raise errors for large and binary files --- lib/gitlab/conflict/parser.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index 8ab7b6499aa..9f27cba353a 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -10,8 +10,12 @@ module Gitlab class MissingEndDelimiter < ParserError end + class UnmergeableFile < ParserError + end + def parse(text, our_path:, their_path:, parent: nil) - return [] if text.blank? + raise UnmergeableFile if text.blank? # Typically a binary file + raise UnmergeableFile if text.length > 102400 line_obj_index = 0 line_old = 1 -- cgit v1.2.1 From f0bbfe7a62f8005188a46b25184c7f32eb098d14 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 29 Jul 2016 16:37:51 +0100 Subject: Add match line headers --- lib/gitlab/conflict/file.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 598bf2a2612..bbabcf265f3 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -75,11 +75,17 @@ module Gitlab return @sections if @sections chunked_lines = lines.chunk { |line| line.type.nil? } + last_candidate_match_header = nil + match_line_header = nil match_line = nil @sections = chunked_lines.flat_map.with_index do |(no_conflict, lines), i| section = nil + lines.each do |line| + last_candidate_match_header = " #{line.text}" if line.text.match(/\A[A-Za-z$_]/) + end + if no_conflict conflict_before = i > 0 conflict_after = chunked_lines.peek @@ -88,6 +94,7 @@ module Gitlab if lines.length > CONTEXT_LINES * 2 tail_lines = lines.last(CONTEXT_LINES) first_tail_line = tail_lines.first + match_line_header = last_candidate_match_header match_line = Gitlab::Diff::Line.new('', 'match', first_tail_line.index, @@ -107,7 +114,7 @@ module Gitlab end if match_line && !section - match_line.text = "@@ -#{match_line.old_pos},#{lines.last.old_pos} +#{match_line.new_pos},#{lines.last.new_pos} @@" + match_line.text = "@@ -#{match_line.old_pos},#{lines.last.old_pos} +#{match_line.new_pos},#{lines.last.new_pos} @@#{match_line_header}" end section ||= { conflict: !no_conflict, lines: lines } -- cgit v1.2.1 From 4d8b0293aed69b6c379be3be63d5f05fd3a7106b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 1 Aug 2016 11:15:28 +0100 Subject: Remove unneeded raise --- lib/gitlab/conflict/file_collection.rb | 3 --- 1 file changed, 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index 46e381d2661..e621a76a7de 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -42,9 +42,6 @@ module Gitlab their_path = conflict[:theirs][:path] our_path = conflict[:ours][:path] - # TODO remove this - raise 'path mismatch!' unless their_path == our_path - Gitlab::Conflict::File.new(merge_index.merge_file(our_path), conflict, diff_refs: merge_request.diff_refs, -- cgit v1.2.1 From 10cf933f70657eb0c413259319a103e15abd9f5c Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 1 Aug 2016 14:50:08 +0100 Subject: Highlight files based on merged file --- lib/gitlab/conflict/file.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index bbabcf265f3..3d2b7272bad 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -59,8 +59,11 @@ module Gitlab end def highlight_lines! - their_highlight = Gitlab::Highlight.highlight_lines(repository, their_ref, their_path) - our_highlight = Gitlab::Highlight.highlight_lines(repository, our_ref, our_path) + their_file = lines.reject { |line| line.type == 'new' }.map(&:text).join("\n") + our_file = lines.reject { |line| line.type == 'old' }.map(&:text).join("\n") + + their_highlight = Gitlab::Highlight.highlight(their_path, their_file, repository: repository).lines.map(&:html_safe) + our_highlight = Gitlab::Highlight.highlight(our_path, our_file, repository: repository).lines.map(&:html_safe) lines.each do |line| if line.type == 'old' -- cgit v1.2.1 From f3cf40b8aaeb19479443821a767c354cd203c24e Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 1 Aug 2016 14:52:53 +0100 Subject: Handle case where one side deleted the file --- lib/gitlab/conflict/file_collection.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index e621a76a7de..5122a5b2111 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -1,6 +1,9 @@ module Gitlab module Conflict class FileCollection + class ConflictSideMissing < StandardError + end + attr_reader :merge_request, :our_commit, :their_commit def initialize(merge_request) @@ -39,10 +42,9 @@ module Gitlab def files @files ||= merge_index.conflicts.map do |conflict| - their_path = conflict[:theirs][:path] - our_path = conflict[:ours][:path] + raise ConflictSideMissing unless conflict[:theirs] && conflict[:ours] - Gitlab::Conflict::File.new(merge_index.merge_file(our_path), + Gitlab::Conflict::File.new(merge_index.merge_file(conflict[:ours][:path]), conflict, diff_refs: merge_request.diff_refs, repository: repository) -- cgit v1.2.1 From 6f3501fe252404b342984514b1b784ffa73edbd0 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 1 Aug 2016 15:41:49 +0100 Subject: Fix match line headers at start / end of file --- lib/gitlab/conflict/file.rb | 49 ++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 3d2b7272bad..2c7f6628c23 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -77,6 +77,10 @@ module Gitlab def sections return @sections if @sections + candidate_match_headers = lines.map do |line| + " #{line.text}" if line.text.match(/\A[A-Za-z$_]/) && line.type.nil? + end + chunked_lines = lines.chunk { |line| line.type.nil? } last_candidate_match_header = nil match_line_header = nil @@ -85,40 +89,41 @@ module Gitlab @sections = chunked_lines.flat_map.with_index do |(no_conflict, lines), i| section = nil - lines.each do |line| - last_candidate_match_header = " #{line.text}" if line.text.match(/\A[A-Za-z$_]/) - end - if no_conflict conflict_before = i > 0 conflict_after = chunked_lines.peek if conflict_before && conflict_after if lines.length > CONTEXT_LINES * 2 + head_lines = lines.first(CONTEXT_LINES) tail_lines = lines.last(CONTEXT_LINES) - first_tail_line = tail_lines.first - match_line_header = last_candidate_match_header - match_line = Gitlab::Diff::Line.new('', - 'match', - first_tail_line.index, - first_tail_line.old_pos, - first_tail_line.new_pos) + + update_match_line_text(match_line, head_lines.last, candidate_match_headers) + + match_line = create_match_line(tail_lines.first) + update_match_line_text(match_line, tail_lines.last, candidate_match_headers) section = [ - { conflict: false, lines: lines.first(CONTEXT_LINES) }, + { conflict: false, lines: head_lines }, { conflict: false, lines: tail_lines.unshift(match_line) } ] end elsif conflict_after - lines = lines.last(CONTEXT_LINES) + tail_lines = lines.last(CONTEXT_LINES) + + if lines.length > CONTEXT_LINES + match_line = create_match_line(tail_lines.first) + + tail_lines.unshift(match_line) + end + + lines = tail_lines elsif conflict_before lines = lines.first(CONTEXT_LINES) end end - if match_line && !section - match_line.text = "@@ -#{match_line.old_pos},#{lines.last.old_pos} +#{match_line.new_pos},#{lines.last.new_pos} @@#{match_line_header}" - end + update_match_line_text(match_line, lines.last, candidate_match_headers) unless section section ||= { conflict: !no_conflict, lines: lines } section[:id] = line_code(lines.first) unless no_conflict @@ -130,6 +135,18 @@ module Gitlab Gitlab::Diff::LineCode.generate(our_path, line.new_pos, line.old_pos) end + def create_match_line(line) + Gitlab::Diff::Line.new('', 'match', line.index, line.old_pos, line.new_pos) + end + + def update_match_line_text(match_line, line, headers) + return unless match_line + + header = headers.first(line.index).compact.last + + match_line.text = "@@ -#{match_line.old_pos},#{line.old_pos} +#{match_line.new_pos},#{line.new_pos} @@#{header}" + end + def as_json(opts = nil) { old_path: their_path, -- cgit v1.2.1 From 261d47bce9d7cc80b4c2068cb612411fe51530ee Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 2 Aug 2016 09:20:22 +0100 Subject: Fix specs - Add match line header to expected result for `File#sections`. - Lowercase CSS colours. - Remove unused `diff_refs` keyword argument. - Rename `parent` -> `parent_file`, to be more explicit. - Skip an iteration when highlighting. --- lib/gitlab/conflict/file.rb | 18 +++++++----------- lib/gitlab/conflict/file_collection.rb | 1 - lib/gitlab/conflict/parser.rb | 6 +++--- lib/gitlab/diff/line.rb | 6 +++--- 4 files changed, 13 insertions(+), 18 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 2c7f6628c23..7f10b8ea8fd 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -6,14 +6,12 @@ module Gitlab CONTEXT_LINES = 3 - attr_reader :merge_file_result, :their_path, :their_ref, :our_path, :our_ref, :repository + attr_reader :merge_file_result, :their_path, :our_path, :repository - def initialize(merge_file_result, conflict, diff_refs:, repository:) + def initialize(merge_file_result, conflict, repository:) @merge_file_result = merge_file_result @their_path = conflict[:theirs][:path] @our_path = conflict[:ours][:path] - @their_ref = diff_refs.start_sha - @our_ref = diff_refs.head_sha @repository = repository end @@ -22,7 +20,7 @@ module Gitlab @lines ||= Gitlab::Conflict::Parser.new.parse(merge_file_result[:data], our_path: our_path, their_path: their_path, - parent: self) + parent_file: self) end def resolve!(resolution, index:, rugged:) @@ -62,14 +60,14 @@ module Gitlab their_file = lines.reject { |line| line.type == 'new' }.map(&:text).join("\n") our_file = lines.reject { |line| line.type == 'old' }.map(&:text).join("\n") - their_highlight = Gitlab::Highlight.highlight(their_path, their_file, repository: repository).lines.map(&:html_safe) - our_highlight = Gitlab::Highlight.highlight(our_path, our_file, repository: repository).lines.map(&:html_safe) + their_highlight = Gitlab::Highlight.highlight(their_path, their_file, repository: repository).lines + our_highlight = Gitlab::Highlight.highlight(our_path, our_file, repository: repository).lines lines.each do |line| if line.type == 'old' - line.rich_text = their_highlight[line.old_line - 1] + line.rich_text = their_highlight[line.old_line - 1].html_safe else - line.rich_text = our_highlight[line.new_line - 1] + line.rich_text = our_highlight[line.new_line - 1].html_safe end end end @@ -82,8 +80,6 @@ module Gitlab end chunked_lines = lines.chunk { |line| line.type.nil? } - last_candidate_match_header = nil - match_line_header = nil match_line = nil @sections = chunked_lines.flat_map.with_index do |(no_conflict, lines), i| diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index 5122a5b2111..da9994a7405 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -46,7 +46,6 @@ module Gitlab Gitlab::Conflict::File.new(merge_index.merge_file(conflict[:ours][:path]), conflict, - diff_refs: merge_request.diff_refs, repository: repository) end end diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index 9f27cba353a..6eccded7872 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -13,7 +13,7 @@ module Gitlab class UnmergeableFile < ParserError end - def parse(text, our_path:, their_path:, parent: nil) + def parse(text, our_path:, their_path:, parent_file: nil) raise UnmergeableFile if text.blank? # Typically a binary file raise UnmergeableFile if text.length > 102400 @@ -43,9 +43,9 @@ module Gitlab type = nil elsif line[0] == '\\' type = 'nonewline' - lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new, parent: parent) + lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new, parent_file: parent_file) else - lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new, parent: parent) + lines << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new, parent_file: parent_file) line_old += 1 if type != 'new' line_new += 1 if type != 'old' diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index 38400b48b5c..80a146b4a5a 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -5,10 +5,10 @@ module Gitlab attr_writer :rich_text attr_accessor :text - def initialize(text, type, index, old_pos, new_pos, parent: nil) + def initialize(text, type, index, old_pos, new_pos, parent_file: nil) @text, @type, @index = text, type, index @old_pos, @new_pos = old_pos, new_pos - @parent = parent + @parent_file = parent_file end def self.init_from_hash(hash) @@ -46,7 +46,7 @@ module Gitlab end def rich_text - @parent.highlight_lines! if @parent && !@rich_text + @parent_file.highlight_lines! if @parent_file && !@rich_text @rich_text end -- cgit v1.2.1 From 3b84cfdc74e27c5f0b94187f5a15c95e3f292554 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 2 Aug 2016 13:56:50 +0100 Subject: Use same resolution format on FE and BE --- lib/gitlab/conflict/file.rb | 4 ++-- lib/gitlab/conflict/file_collection.rb | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 7f10b8ea8fd..b6fa9402d46 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -44,9 +44,9 @@ module Gitlab section_id ||= line_code(line) case resolution[section_id] - when 'ours' + when 'head' next unless line.type == 'new' - when 'theirs' + when 'origin' next unless line.type == 'old' else raise MissingResolution, "Missing resolution for section ID: #{section_id}" diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index da9994a7405..5df54fd8677 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -20,10 +20,11 @@ module Gitlab @merge_index ||= repository.rugged.merge_commits(our_commit, their_commit) end - def resolve_conflicts!(resolutions, commit_message, user:) + def resolve_conflicts!(params, user:) + resolutions = params[:sections] + commit_message = params[:commit_message] || default_commit_message rugged = repository.rugged committer = repository.user_to_committer(user) - commit_message ||= default_commit_message files.each do |file| file.resolve!(resolutions, index: merge_index, rugged: rugged) -- cgit v1.2.1 From e50e88b85c54097db22d571cfc76fcef143e1d01 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 3 Aug 2016 12:02:20 +0100 Subject: Add blob_path to conflict file JSON --- lib/gitlab/conflict/file.rb | 12 +++++++++--- lib/gitlab/conflict/file_collection.rb | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index b6fa9402d46..16727e752bd 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -1,18 +1,21 @@ module Gitlab module Conflict class File + include Gitlab::Routing.url_helpers + class MissingResolution < StandardError end CONTEXT_LINES = 3 - attr_reader :merge_file_result, :their_path, :our_path, :repository + attr_reader :merge_file_result, :their_path, :our_path, :merge_request, :repository - def initialize(merge_file_result, conflict, repository:) + def initialize(merge_file_result, conflict, merge_request:) @merge_file_result = merge_file_result @their_path = conflict[:theirs][:path] @our_path = conflict[:ours][:path] - @repository = repository + @merge_request = merge_request + @repository = merge_request.project.repository end # Array of Gitlab::Diff::Line objects @@ -147,6 +150,9 @@ module Gitlab { old_path: their_path, new_path: our_path, + blob_path: namespace_project_blob_path(merge_request.project.namespace, + merge_request.project, + ::File.join(merge_request.diff_refs.head_sha, our_path)), sections: sections } end diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index 5df54fd8677..04a75f9edc3 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -47,7 +47,7 @@ module Gitlab Gitlab::Conflict::File.new(merge_index.merge_file(conflict[:ours][:path]), conflict, - repository: repository) + merge_request: merge_request) end end -- cgit v1.2.1 From 52eb523c9b28a258489202dd35a2a65535d31cab Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 3 Aug 2016 12:10:42 +0100 Subject: Add blob_icon to conflict file JSON --- lib/gitlab/conflict/file.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 16727e752bd..1d63aec6903 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -2,18 +2,20 @@ module Gitlab module Conflict class File include Gitlab::Routing.url_helpers + include IconsHelper class MissingResolution < StandardError end CONTEXT_LINES = 3 - attr_reader :merge_file_result, :their_path, :our_path, :merge_request, :repository + attr_reader :merge_file_result, :their_path, :our_path, :our_mode, :merge_request, :repository def initialize(merge_file_result, conflict, merge_request:) @merge_file_result = merge_file_result @their_path = conflict[:theirs][:path] @our_path = conflict[:ours][:path] + @our_mode = conflict[:ours][:mode] @merge_request = merge_request @repository = merge_request.project.repository end @@ -30,7 +32,6 @@ module Gitlab new_file = resolve_lines(resolution).map(&:text).join("\n") oid = rugged.write(new_file, :blob) - our_mode = index.conflict_get(our_path)[:ours][:mode] index.add(path: our_path, oid: oid, mode: our_mode) index.conflict_remove(our_path) end @@ -150,6 +151,7 @@ module Gitlab { old_path: their_path, new_path: our_path, + blob_icon: file_type_icon_class('file', our_mode, our_path), blob_path: namespace_project_blob_path(merge_request.project.namespace, merge_request.project, ::File.join(merge_request.diff_refs.head_sha, our_path)), -- cgit v1.2.1 From 427e724698185169536d68e95873415038286849 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 4 Aug 2016 10:31:44 +0100 Subject: Don't allow resolving invalid conflicts An MR can only be resolved in the UI if: - It has conflicts. - It has valid diff_refs (in other words, it supports new diff notes). - It has no conflicts with one side missing. - It has no conflicts in binary files. - It has no conflicts in files too large to display. - It has no conflicts containing invalid conflict markers. --- lib/gitlab/conflict/file.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 1d63aec6903..cdbb63f626f 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -69,9 +69,9 @@ module Gitlab lines.each do |line| if line.type == 'old' - line.rich_text = their_highlight[line.old_line - 1].html_safe + line.rich_text = their_highlight[line.old_line - 1].try(:html_safe) else - line.rich_text = our_highlight[line.new_line - 1].html_safe + line.rich_text = our_highlight[line.new_line - 1].try(:html_safe) end end end -- cgit v1.2.1 From ac9229a31be8e672efdcc63b702a2039ae66ad46 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 4 Aug 2016 14:12:08 +0100 Subject: Clarify Conflict::File#sections method --- lib/gitlab/conflict/file.rb | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index cdbb63f626f..6284b0dd377 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -79,6 +79,9 @@ module Gitlab def sections return @sections if @sections + # Any line beginning with a letter, an underscore, or a dollar can be used in a + # match line header. Only context sections can contain match lines, as match lines + # have to exist in both versions of the file. candidate_match_headers = lines.map do |line| " #{line.text}" if line.text.match(/\A[A-Za-z$_]/) && line.type.nil? end @@ -89,19 +92,24 @@ module Gitlab @sections = chunked_lines.flat_map.with_index do |(no_conflict, lines), i| section = nil + # We need to reduce context sections to CONTEXT_LINES. Conflict sections are + # always shown in full. if no_conflict conflict_before = i > 0 conflict_after = chunked_lines.peek if conflict_before && conflict_after + # Create a gap in a long context section. if lines.length > CONTEXT_LINES * 2 head_lines = lines.first(CONTEXT_LINES) tail_lines = lines.last(CONTEXT_LINES) + # Ensure any existing match line has text for all lines up to the last + # line of its context. update_match_line_text(match_line, head_lines.last, candidate_match_headers) + # Insert a new match line after the created gap. match_line = create_match_line(tail_lines.first) - update_match_line_text(match_line, tail_lines.last, candidate_match_headers) section = [ { conflict: false, lines: head_lines }, @@ -111,7 +119,8 @@ module Gitlab elsif conflict_after tail_lines = lines.last(CONTEXT_LINES) - if lines.length > CONTEXT_LINES + # Create a gap and insert a match line at the start. + if lines.length > tail_lines.length match_line = create_match_line(tail_lines.first) tail_lines.unshift(match_line) @@ -119,10 +128,14 @@ module Gitlab lines = tail_lines elsif conflict_before + # We're at the end of the file (no conflicts after), so just remove extra + # trailing lines. lines = lines.first(CONTEXT_LINES) end end + # We want to update the match line's text every time unless we've already + # created a gap and its corresponding match line. update_match_line_text(match_line, lines.last, candidate_match_headers) unless section section ||= { conflict: !no_conflict, lines: lines } @@ -139,10 +152,13 @@ module Gitlab Gitlab::Diff::Line.new('', 'match', line.index, line.old_pos, line.new_pos) end + # Set the match line's text for the current line. A match line takes its start + # position and context header (where present) from itself, and its end position from + # the line passed in. def update_match_line_text(match_line, line, headers) return unless match_line - header = headers.first(line.index).compact.last + header = headers.first(match_line.index).compact.last match_line.text = "@@ -#{match_line.old_pos},#{line.old_pos} +#{match_line.new_pos},#{line.new_pos} @@#{header}" end -- cgit v1.2.1 From ba327e69ec4b2214f12f577cd86a37c65ea2f3e9 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 4 Aug 2016 14:20:04 +0100 Subject: Move resolving code to ResolveService --- lib/gitlab/conflict/file.rb | 8 -------- lib/gitlab/conflict/file_collection.rb | 21 --------------------- 2 files changed, 29 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 6284b0dd377..3560fae5b09 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -28,14 +28,6 @@ module Gitlab parent_file: self) end - def resolve!(resolution, index:, rugged:) - new_file = resolve_lines(resolution).map(&:text).join("\n") - - oid = rugged.write(new_file, :blob) - index.add(path: our_path, oid: oid, mode: our_mode) - index.conflict_remove(our_path) - end - def resolve_lines(resolution) section_id = nil diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index 04a75f9edc3..e8157fb9e91 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -20,27 +20,6 @@ module Gitlab @merge_index ||= repository.rugged.merge_commits(our_commit, their_commit) end - def resolve_conflicts!(params, user:) - resolutions = params[:sections] - commit_message = params[:commit_message] || default_commit_message - rugged = repository.rugged - committer = repository.user_to_committer(user) - - files.each do |file| - file.resolve!(resolutions, index: merge_index, rugged: rugged) - end - - new_tree = merge_index.write_tree(rugged) - - Rugged::Commit.create(rugged, - author: committer, - committer: committer, - tree: new_tree, - message: commit_message, - parents: [our_commit, their_commit].map(&:oid), - update_ref: Gitlab::Git::BRANCH_REF_PREFIX + merge_request.source_branch) - end - def files @files ||= merge_index.conflicts.map do |conflict| raise ConflictSideMissing unless conflict[:theirs] && conflict[:ours] -- cgit v1.2.1 From ce7eb4e49279ab56cac992b6743fe77cac578b48 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 5 Aug 2016 12:15:06 +0100 Subject: Add more tests for conflicts --- lib/gitlab/conflict/file_collection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index e8157fb9e91..bbd0427a2c8 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -46,7 +46,7 @@ module Gitlab end < Date: Fri, 5 Aug 2016 13:24:15 +0100 Subject: Find match line headers by backtracking This is more efficient for large files than performing a regex match on every single line. --- lib/gitlab/conflict/file.rb | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 3560fae5b09..926b65d46fc 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -18,6 +18,7 @@ module Gitlab @our_mode = conflict[:ours][:mode] @merge_request = merge_request @repository = merge_request.project.repository + @match_line_headers = {} end # Array of Gitlab::Diff::Line objects @@ -71,13 +72,6 @@ module Gitlab def sections return @sections if @sections - # Any line beginning with a letter, an underscore, or a dollar can be used in a - # match line header. Only context sections can contain match lines, as match lines - # have to exist in both versions of the file. - candidate_match_headers = lines.map do |line| - " #{line.text}" if line.text.match(/\A[A-Za-z$_]/) && line.type.nil? - end - chunked_lines = lines.chunk { |line| line.type.nil? } match_line = nil @@ -98,7 +92,7 @@ module Gitlab # Ensure any existing match line has text for all lines up to the last # line of its context. - update_match_line_text(match_line, head_lines.last, candidate_match_headers) + update_match_line_text(match_line, head_lines.last) # Insert a new match line after the created gap. match_line = create_match_line(tail_lines.first) @@ -128,7 +122,7 @@ module Gitlab # We want to update the match line's text every time unless we've already # created a gap and its corresponding match line. - update_match_line_text(match_line, lines.last, candidate_match_headers) unless section + update_match_line_text(match_line, lines.last) unless section section ||= { conflict: !no_conflict, lines: lines } section[:id] = line_code(lines.first) unless no_conflict @@ -144,13 +138,32 @@ module Gitlab Gitlab::Diff::Line.new('', 'match', line.index, line.old_pos, line.new_pos) end + # Any line beginning with a letter, an underscore, or a dollar can be used in a + # match line header. Only context sections can contain match lines, as match lines + # have to exist in both versions of the file. + def find_match_line_header(index) + return @match_line_headers[index] if @match_line_headers.key?(index) + + @match_line_headers[index] = begin + if index >= 0 + line = lines[index] + + if line.type.nil? && line.text.match(/\A[A-Za-z$_]/) + " #{line.text}" + else + find_match_line_header(index - 1) + end + end + end + end + # Set the match line's text for the current line. A match line takes its start # position and context header (where present) from itself, and its end position from # the line passed in. - def update_match_line_text(match_line, line, headers) + def update_match_line_text(match_line, line) return unless match_line - header = headers.first(match_line.index).compact.last + header = find_match_line_header(match_line.index - 1) match_line.text = "@@ -#{match_line.old_pos},#{line.old_pos} +#{match_line.new_pos},#{line.new_pos} @@#{header}" end -- cgit v1.2.1 From 254dbad8f7c2213f87087858ff31a62f0d41a189 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 10 Aug 2016 22:42:35 -0500 Subject: Fix bug where conflict view would have one too many context sections --- lib/gitlab/conflict/file.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 926b65d46fc..0a1fd27ced5 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -72,9 +72,11 @@ module Gitlab def sections return @sections if @sections - chunked_lines = lines.chunk { |line| line.type.nil? } + chunked_lines = lines.chunk { |line| line.type.nil? }.to_a match_line = nil + sections_count = chunked_lines.size + @sections = chunked_lines.flat_map.with_index do |(no_conflict, lines), i| section = nil @@ -82,7 +84,7 @@ module Gitlab # always shown in full. if no_conflict conflict_before = i > 0 - conflict_after = chunked_lines.peek + conflict_after = (sections_count - i) > 1 if conflict_before && conflict_after # Create a gap in a long context section. -- cgit v1.2.1 From 0eea8c885743575b0e93a98846b3663e9903aa66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 30 Jun 2016 17:34:19 +0200 Subject: Support slash commands in noteable description and notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some important things to note: - commands are removed from noteable.description / note.note - commands are translated to params so that they are treated as normal params in noteable Creation services - the logic is not in the models but in the Creation services, which is the right place for advanced logic that has nothing to do with what models should be responsible of! - UI/JS needs to be updated to handle notes which consist of commands only - the `/merge` command is not handled yet Other improvements: - Don't process commands in commit notes and display a flash is note is only commands - Add autocomplete for slash commands - Add description and params to slash command DSL methods - Ensure replying by email with a commands-only note works - Use :subscription_event instead of calling noteable.subscribe - Support :todo_event in IssuableBaseService Signed-off-by: Rémy Coutable --- lib/gitlab/email/handler/base_handler.rb | 1 + lib/gitlab/slash_commands/dsl.rb | 76 ++++++++++++++++++++++++++++++++ lib/gitlab/slash_commands/extractor.rb | 59 +++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 lib/gitlab/slash_commands/dsl.rb create mode 100644 lib/gitlab/slash_commands/extractor.rb (limited to 'lib') diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb index b7ed11cb638..7cccf465334 100644 --- a/lib/gitlab/email/handler/base_handler.rb +++ b/lib/gitlab/email/handler/base_handler.rb @@ -45,6 +45,7 @@ module Gitlab def verify_record!(record:, invalid_exception:, record_name:) return if record.persisted? + return if record.errors.key?(:commands_only) error_title = "The #{record_name} could not be created for the following reasons:" diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb new file mode 100644 index 00000000000..3ded4109f2e --- /dev/null +++ b/lib/gitlab/slash_commands/dsl.rb @@ -0,0 +1,76 @@ +module Gitlab + module SlashCommands + module Dsl + extend ActiveSupport::Concern + + included do + @command_definitions = [] + end + + module ClassMethods + def command_definitions + @command_definitions + end + + def command_names + command_definitions.flat_map do |command_definition| + [command_definition[:name], command_definition[:aliases]].flatten + end + end + + # Allows to give a description to the next slash command + def desc(text) + @description = text + end + + # Allows to define params for the next slash command + def params(*params) + @params = params + end + + # Registers a new command which is recognizeable + # from body of email or comment. + # Example: + # + # command :command_key do |arguments| + # # Awesome code block + # end + # + def command(*command_names, &block) + command_name, *aliases = command_names + proxy_method_name = "__#{command_name}__" + + # This proxy method is needed because calling `return` from inside a + # block/proc, causes a `return` from the enclosing method or lambda, + # otherwise a LocalJumpError error is raised. + define_method(proxy_method_name, &block) + + define_method(command_name) do |*args| + proxy_method = method(proxy_method_name) + + if proxy_method.arity == -1 || proxy_method.arity == args.size + instance_exec(*args, &proxy_method) + end + end + + private command_name + aliases.each do |alias_command| + alias_method alias_command, command_name + private alias_command + end + + command_definition = { + name: command_name, + aliases: aliases, + description: @description || '', + params: @params || [] + } + @command_definitions << command_definition + + @description = nil + @params = nil + end + end + end + end +end diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/slash_commands/extractor.rb new file mode 100644 index 00000000000..1a854b81aca --- /dev/null +++ b/lib/gitlab/slash_commands/extractor.rb @@ -0,0 +1,59 @@ +module Gitlab + module SlashCommands + # This class takes an array of commands that should be extracted from a + # given text. + # + # ``` + # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels]) + # ``` + class Extractor + attr_reader :command_names + + def initialize(command_names) + @command_names = command_names + end + + # Extracts commands from content and return an array of commands. + # The array looks like the following: + # [ + # ['command1'], + # ['command3', 'arg1 arg2'], + # ] + # The command and the arguments are stripped. + # The original command text is removed from the given `content`. + # + # Usage: + # ``` + # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels]) + # msg = %(hello\n/labels ~foo ~"bar baz"\nworld) + # commands = extractor.extract_commands! #=> [['labels', '~foo ~"bar baz"']] + # msg #=> "hello\nworld" + # ``` + def extract_commands!(content) + return [] unless content + + commands = [] + + content.gsub!(commands_regex) do + commands << [$1, $2].flatten.reject(&:blank?) + '' + end + + commands + end + + private + + # Builds a regular expression to match known commands. + # First match group captures the command name and + # second match group captures its arguments. + # + # It looks something like: + # + # /^\/(?close|reopen|...)(?:( |$))(?[^\/\n]*)(?:\n|$)/ + def commands_regex + /^\/(?#{command_names.join('|')})(?:( |$))(?[^\/\n]*)(?:\n|$)/ + end + end + end +end -- cgit v1.2.1 From e021604454f1093b7d762b28eae36e30083f0053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 9 Aug 2016 22:47:29 +0200 Subject: Don't extract slash commands inside blockcode, blockquote or HTML tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve slash command descriptions, support /due tomorrow Signed-off-by: Rémy Coutable --- lib/gitlab/slash_commands/extractor.rb | 51 ++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/slash_commands/extractor.rb index 1a854b81aca..ce0a2eba535 100644 --- a/lib/gitlab/slash_commands/extractor.rb +++ b/lib/gitlab/slash_commands/extractor.rb @@ -34,9 +34,14 @@ module Gitlab commands = [] + content.delete!("\r") content.gsub!(commands_regex) do - commands << [$1, $2].flatten.reject(&:blank?) - '' + if $~[:cmd] + commands << [$~[:cmd], $~[:args]].reject(&:blank?) + '' + else + $~[0] + end end commands @@ -52,7 +57,47 @@ module Gitlab # # /^\/(?close|reopen|...)(?:( |$))(?[^\/\n]*)(?:\n|$)/ def commands_regex - /^\/(?#{command_names.join('|')})(?:( |$))(?[^\/\n]*)(?:\n|$)/ + @commands_regex ||= %r{ + (? + # Code blocks: + # ``` + # Anything, including `/cmd args` which are ignored by this filter + # ``` + + ^``` + .+? + \n```$ + ) + | + (? + # HTML block: + # + # Anything, including `/cmd args` which are ignored by this filter + # + + ^<[^>]+?>\n + .+? + \n<\/[^>]+?>$ + ) + | + (? + # Quote block: + # >>> + # Anything, including `/cmd args` which are ignored by this filter + # >>> + + ^>>> + .+? + \n>>>$ + ) + | + (?: + # Command not in a blockquote, blockcode, or HTML tag: + # /close + + ^\/(?#{command_names.join('|')})(?:(\ |$))(?[^\/\n]*)(?:\n|$) + ) + }mx end end end -- cgit v1.2.1 From 23db6449542498636c145e83c71a4a466eb62746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 10 Aug 2016 14:12:09 +0200 Subject: Add support for no-op slash commands that appear in autocomplete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first one is /cc Signed-off-by: Rémy Coutable --- lib/gitlab/slash_commands/dsl.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb index 3ded4109f2e..edfe8405876 100644 --- a/lib/gitlab/slash_commands/dsl.rb +++ b/lib/gitlab/slash_commands/dsl.rb @@ -14,8 +14,10 @@ module Gitlab def command_names command_definitions.flat_map do |command_definition| - [command_definition[:name], command_definition[:aliases]].flatten - end + unless command_definition[:noop] + [command_definition[:name], command_definition[:aliases]].flatten + end + end.compact end # Allows to give a description to the next slash command @@ -28,6 +30,11 @@ module Gitlab @params = params end + # Allows to define if a command is a no-op, but should appear in autocomplete + def noop(noop) + @noop = noop + end + # Registers a new command which is recognizeable # from body of email or comment. # Example: @@ -63,7 +70,8 @@ module Gitlab name: command_name, aliases: aliases, description: @description || '', - params: @params || [] + params: @params || [], + noop: @noop || false } @command_definitions << command_definition -- cgit v1.2.1 From 65349c22129fcdf2ae0c7103094bbf50ae73db61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 10 Aug 2016 17:51:01 +0200 Subject: Make slash commands contextual MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Return only slash commands that make sense for the current noteable - Allow slash commands decription to be dynamic Other improvements: - Add permission checks in slash commands definition - Use IssuesFinder and MergeRequestsFinder - Use next if instead of a unless block, and use splat operator instead of flatten Signed-off-by: Rémy Coutable --- lib/gitlab/slash_commands/dsl.rb | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb index edfe8405876..20e1d071d06 100644 --- a/lib/gitlab/slash_commands/dsl.rb +++ b/lib/gitlab/slash_commands/dsl.rb @@ -8,15 +8,25 @@ module Gitlab end module ClassMethods - def command_definitions - @command_definitions - end + def command_definitions(opts = {}) + @command_definitions.map do |cmd_def| + next if cmd_def[:cond_lambda] && !cmd_def[:cond_lambda].call(opts) + + cmd_def = cmd_def.dup - def command_names - command_definitions.flat_map do |command_definition| - unless command_definition[:noop] - [command_definition[:name], command_definition[:aliases]].flatten + if cmd_def[:description].present? && cmd_def[:description].respond_to?(:call) + cmd_def[:description] = cmd_def[:description].call(opts) rescue '' end + + cmd_def + end.compact + end + + def command_names(opts = {}) + command_definitions(opts).flat_map do |command_definition| + next if command_definition[:noop] + + [command_definition[:name], *command_definition[:aliases]] end.compact end @@ -35,6 +45,11 @@ module Gitlab @noop = noop end + # Allows to define if a lambda to conditionally return an action + def condition(cond_lambda) + @cond_lambda = cond_lambda + end + # Registers a new command which is recognizeable # from body of email or comment. # Example: @@ -53,6 +68,10 @@ module Gitlab define_method(proxy_method_name, &block) define_method(command_name) do |*args| + unless @cond_lambda.nil? || @cond_lambda.call(project: project, current_user: current_user, noteable: noteable) + return + end + proxy_method = method(proxy_method_name) if proxy_method.arity == -1 || proxy_method.arity == args.size @@ -70,13 +89,16 @@ module Gitlab name: command_name, aliases: aliases, description: @description || '', - params: @params || [], - noop: @noop || false + params: @params || [] } + command_definition[:noop] = @noop unless @noop.nil? + command_definition[:cond_lambda] = @cond_lambda unless @cond_lambda.nil? @command_definitions << command_definition @description = nil @params = nil + @noop = nil + @cond_lambda = nil end end end -- cgit v1.2.1 From 42e30a5012bb3384ee6f275ff058d4c0841776cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 11 Aug 2016 18:51:37 +0200 Subject: Accept blocks for `.desc` and `.condition` slash commands DSL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, pass options as instance variables, making the DSL more user-friendly / natural. Signed-off-by: Rémy Coutable --- lib/gitlab/slash_commands/dsl.rb | 111 +++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 38 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb index 20e1d071d06..3affd6253e9 100644 --- a/lib/gitlab/slash_commands/dsl.rb +++ b/lib/gitlab/slash_commands/dsl.rb @@ -8,20 +8,27 @@ module Gitlab end module ClassMethods + # This method is used to generate the autocompletion menu + # It returns no-op slash commands (such as `/cc`) def command_definitions(opts = {}) @command_definitions.map do |cmd_def| - next if cmd_def[:cond_lambda] && !cmd_def[:cond_lambda].call(opts) + context = OpenStruct.new(opts) + next if cmd_def[:cond_block] && !context.instance_exec(&cmd_def[:cond_block]) cmd_def = cmd_def.dup if cmd_def[:description].present? && cmd_def[:description].respond_to?(:call) - cmd_def[:description] = cmd_def[:description].call(opts) rescue '' + cmd_def[:description] = context.instance_exec(&cmd_def[:description]) rescue '' end cmd_def end.compact end + # This method is used to generate a list of valid commands in the current + # context of `opts`. + # It excludes no-op slash commands (such as `/cc`). + # This list can then be given to `Gitlab::SlashCommands::Extractor`. def command_names(opts = {}) command_definitions(opts).flat_map do |command_definition| next if command_definition[:noop] @@ -30,59 +37,88 @@ module Gitlab end.compact end - # Allows to give a description to the next slash command - def desc(text) - @description = text + # Allows to give a description to the next slash command. + # This description is shown in the autocomplete menu. + # It accepts a block that will be evaluated with the context given to + # `.command_definitions` or `.command_names`. + # + # Example: + # + # desc do + # "This is a dynamic description for #{noteable.to_ability_name}" + # end + # command :command_key do |arguments| + # # Awesome code block + # end + def desc(text = '', &block) + @description = block_given? ? block : text end - # Allows to define params for the next slash command + # Allows to define params for the next slash command. + # These params are shown in the autocomplete menu. + # + # Example: + # + # params "~label ~label2" + # command :command_key do |arguments| + # # Awesome code block + # end def params(*params) @params = params end - # Allows to define if a command is a no-op, but should appear in autocomplete - def noop(noop) - @noop = noop - end - - # Allows to define if a lambda to conditionally return an action - def condition(cond_lambda) - @cond_lambda = cond_lambda - end - - # Registers a new command which is recognizeable - # from body of email or comment. + # Allows to define conditions that must be met in order for the command + # to be returned by `.command_names` & `.command_definitions`. + # It accepts a block that will be evaluated with the context given to + # `.command_definitions`, `.command_names`, and the actual command method. + # # Example: # + # condition do + # project.public? + # end # command :command_key do |arguments| # # Awesome code block # end + def condition(&block) + @cond_block = block + end + + # Registers a new command which is recognizeable from body of email or + # comment. + # It accepts aliases and takes a block. # + # Example: + # + # command :my_command, :alias_for_my_command do |arguments| + # # Awesome code block + # end def command(*command_names, &block) + opts = command_names.extract_options! command_name, *aliases = command_names proxy_method_name = "__#{command_name}__" - # This proxy method is needed because calling `return` from inside a - # block/proc, causes a `return` from the enclosing method or lambda, - # otherwise a LocalJumpError error is raised. - define_method(proxy_method_name, &block) + if block_given? + # This proxy method is needed because calling `return` from inside a + # block/proc, causes a `return` from the enclosing method or lambda, + # otherwise a LocalJumpError error is raised. + define_method(proxy_method_name, &block) - define_method(command_name) do |*args| - unless @cond_lambda.nil? || @cond_lambda.call(project: project, current_user: current_user, noteable: noteable) - return - end + define_method(command_name) do |*args| + return if @cond_block && !instance_exec(&@cond_block) - proxy_method = method(proxy_method_name) + proxy_method = method(proxy_method_name) - if proxy_method.arity == -1 || proxy_method.arity == args.size - instance_exec(*args, &proxy_method) + if proxy_method.arity == -1 || proxy_method.arity == args.size + instance_exec(*args, &proxy_method) + end end - end - private command_name - aliases.each do |alias_command| - alias_method alias_command, command_name - private alias_command + private command_name + aliases.each do |alias_command| + alias_method alias_command, command_name + private alias_command + end end command_definition = { @@ -91,14 +127,13 @@ module Gitlab description: @description || '', params: @params || [] } - command_definition[:noop] = @noop unless @noop.nil? - command_definition[:cond_lambda] = @cond_lambda unless @cond_lambda.nil? + command_definition[:noop] = opts[:noop] || false + command_definition[:cond_block] = @cond_block @command_definitions << command_definition @description = nil @params = nil - @noop = nil - @cond_lambda = nil + @cond_block = nil end end end -- cgit v1.2.1 From f393f2dde016edf63b5168eb63405f15d65803eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 12 Aug 2016 11:19:29 +0200 Subject: Simplify the slash commands DSL to store action blocks instead of creating methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Other improvements: - Ensure slash commands autocomplete doesn't break when noteable_type is not given - Slash commands: improve autocomplete behavior and /due command - We don't display slash commands for note edit forms. - Add tests for reply by email with slash commands - Be sure to execute slash commands after the note creation in Notes::CreateService Signed-off-by: Rémy Coutable --- lib/gitlab/slash_commands/dsl.rb | 88 ++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 43 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb index 3affd6253e9..ce659aff1da 100644 --- a/lib/gitlab/slash_commands/dsl.rb +++ b/lib/gitlab/slash_commands/dsl.rb @@ -4,20 +4,34 @@ module Gitlab extend ActiveSupport::Concern included do - @command_definitions = [] + cattr_accessor :definitions end - module ClassMethods - # This method is used to generate the autocompletion menu - # It returns no-op slash commands (such as `/cc`) + def execute_command(name, *args) + name = name.to_sym + cmd_def = self.class.definitions.find do |cmd_def| + self.class.command_name_and_aliases(cmd_def).include?(name) + end + return unless cmd_def && cmd_def[:action_block] + return if self.class.command_unavailable?(cmd_def, self) + + block_arity = cmd_def[:action_block].arity + if block_arity == -1 || block_arity == args.size + instance_exec(*args, &cmd_def[:action_block]) + end + end + + class_methods do + # This method is used to generate the autocompletion menu. + # It returns no-op slash commands (such as `/cc`). def command_definitions(opts = {}) - @command_definitions.map do |cmd_def| + self.definitions.map do |cmd_def| context = OpenStruct.new(opts) - next if cmd_def[:cond_block] && !context.instance_exec(&cmd_def[:cond_block]) + next if command_unavailable?(cmd_def, context) cmd_def = cmd_def.dup - if cmd_def[:description].present? && cmd_def[:description].respond_to?(:call) + if cmd_def[:description].respond_to?(:call) cmd_def[:description] = context.instance_exec(&cmd_def[:description]) rescue '' end @@ -30,13 +44,24 @@ module Gitlab # It excludes no-op slash commands (such as `/cc`). # This list can then be given to `Gitlab::SlashCommands::Extractor`. def command_names(opts = {}) - command_definitions(opts).flat_map do |command_definition| - next if command_definition[:noop] + self.definitions.flat_map do |cmd_def| + next if cmd_def[:opts].fetch(:noop, false) - [command_definition[:name], *command_definition[:aliases]] + context = OpenStruct.new(opts) + next if command_unavailable?(cmd_def, context) + + command_name_and_aliases(cmd_def) end.compact end + def command_unavailable?(cmd_def, context) + cmd_def[:condition_block] && !context.instance_exec(&cmd_def[:condition_block]) + end + + def command_name_and_aliases(cmd_def) + [cmd_def[:name], *cmd_def[:aliases]] + end + # Allows to give a description to the next slash command. # This description is shown in the autocomplete menu. # It accepts a block that will be evaluated with the context given to @@ -81,7 +106,7 @@ module Gitlab # # Awesome code block # end def condition(&block) - @cond_block = block + @condition_block = block end # Registers a new command which is recognizeable from body of email or @@ -95,45 +120,22 @@ module Gitlab # end def command(*command_names, &block) opts = command_names.extract_options! - command_name, *aliases = command_names - proxy_method_name = "__#{command_name}__" - - if block_given? - # This proxy method is needed because calling `return` from inside a - # block/proc, causes a `return` from the enclosing method or lambda, - # otherwise a LocalJumpError error is raised. - define_method(proxy_method_name, &block) - - define_method(command_name) do |*args| - return if @cond_block && !instance_exec(&@cond_block) - - proxy_method = method(proxy_method_name) - - if proxy_method.arity == -1 || proxy_method.arity == args.size - instance_exec(*args, &proxy_method) - end - end - - private command_name - aliases.each do |alias_command| - alias_method alias_command, command_name - private alias_command - end - end + name, *aliases = command_names - command_definition = { - name: command_name, + self.definitions ||= [] + self.definitions << { + name: name, aliases: aliases, description: @description || '', - params: @params || [] + params: @params || [], + condition_block: @condition_block, + action_block: block, + opts: opts } - command_definition[:noop] = opts[:noop] || false - command_definition[:cond_block] = @cond_block - @command_definitions << command_definition @description = nil @params = nil - @cond_block = nil + @condition_block = nil end end end -- cgit v1.2.1 From 5a07b760dff04660d9c7da84852c710b1fc2f786 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 12 Aug 2016 20:17:18 -0500 Subject: Refactor slash command definition --- lib/gitlab/slash_commands/command_definition.rb | 57 +++++++++++++++++ lib/gitlab/slash_commands/dsl.rb | 82 ++++++------------------- lib/gitlab/slash_commands/extractor.rb | 30 ++++++--- 3 files changed, 97 insertions(+), 72 deletions(-) create mode 100644 lib/gitlab/slash_commands/command_definition.rb (limited to 'lib') diff --git a/lib/gitlab/slash_commands/command_definition.rb b/lib/gitlab/slash_commands/command_definition.rb new file mode 100644 index 00000000000..5dec6c91869 --- /dev/null +++ b/lib/gitlab/slash_commands/command_definition.rb @@ -0,0 +1,57 @@ +module Gitlab + module SlashCommands + class CommandDefinition + attr_accessor :name, :aliases, :description, :params, :condition_block, :action_block + + def valid? + name.present? + end + + def all_names + [name, *aliases] + end + + def noop? + action_block.nil? + end + + def available?(opts) + return true unless condition_block + + context = OpenStruct.new(opts) + context.instance_exec(&condition_block) + end + + def to_description(opts) + return description unless description.respond_to?(:call) + + context = OpenStruct.new(opts) + context.instance_exec(&description) rescue '' + end + + def execute(context, opts, *args) + return if noop? || !available?(opts) + + block_arity = action_block.arity + return unless block_arity == -1 || block_arity == args.size + + context.instance_exec(*args, &action_block) + end + + def to_h(opts) + desc = description + if desc.respond_to?(:call) + context = OpenStruct.new(opts) + desc = context.instance_exec(&desc) rescue '' + end + + { + name: name, + aliases: aliases, + description: desc, + params: params + } + end + end + end +end diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb index ce659aff1da..58ba7027f84 100644 --- a/lib/gitlab/slash_commands/dsl.rb +++ b/lib/gitlab/slash_commands/dsl.rb @@ -4,64 +4,16 @@ module Gitlab extend ActiveSupport::Concern included do - cattr_accessor :definitions - end - - def execute_command(name, *args) - name = name.to_sym - cmd_def = self.class.definitions.find do |cmd_def| - self.class.command_name_and_aliases(cmd_def).include?(name) + cattr_accessor :command_definitions, instance_accessor: false do + [] end - return unless cmd_def && cmd_def[:action_block] - return if self.class.command_unavailable?(cmd_def, self) - block_arity = cmd_def[:action_block].arity - if block_arity == -1 || block_arity == args.size - instance_exec(*args, &cmd_def[:action_block]) + cattr_accessor :command_definitions_by_name, instance_accessor: false do + {} end end class_methods do - # This method is used to generate the autocompletion menu. - # It returns no-op slash commands (such as `/cc`). - def command_definitions(opts = {}) - self.definitions.map do |cmd_def| - context = OpenStruct.new(opts) - next if command_unavailable?(cmd_def, context) - - cmd_def = cmd_def.dup - - if cmd_def[:description].respond_to?(:call) - cmd_def[:description] = context.instance_exec(&cmd_def[:description]) rescue '' - end - - cmd_def - end.compact - end - - # This method is used to generate a list of valid commands in the current - # context of `opts`. - # It excludes no-op slash commands (such as `/cc`). - # This list can then be given to `Gitlab::SlashCommands::Extractor`. - def command_names(opts = {}) - self.definitions.flat_map do |cmd_def| - next if cmd_def[:opts].fetch(:noop, false) - - context = OpenStruct.new(opts) - next if command_unavailable?(cmd_def, context) - - command_name_and_aliases(cmd_def) - end.compact - end - - def command_unavailable?(cmd_def, context) - cmd_def[:condition_block] && !context.instance_exec(&cmd_def[:condition_block]) - end - - def command_name_and_aliases(cmd_def) - [cmd_def[:name], *cmd_def[:aliases]] - end - # Allows to give a description to the next slash command. # This description is shown in the autocomplete menu. # It accepts a block that will be evaluated with the context given to @@ -119,19 +71,23 @@ module Gitlab # # Awesome code block # end def command(*command_names, &block) - opts = command_names.extract_options! name, *aliases = command_names - self.definitions ||= [] - self.definitions << { - name: name, - aliases: aliases, - description: @description || '', - params: @params || [], - condition_block: @condition_block, - action_block: block, - opts: opts - } + definition = CommandDefinition.new + definition.name = name + definition.aliases = aliases + definition.description = @description || '' + definition.params = @params || [] + definition.condition_block = @condition_block + definition.action_block = block + + return unless definition.valid? + + self.command_definitions << definition + + definition.all_names.each do |name| + self.command_definitions_by_name[name] = definition + end @description = nil @params = nil diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/slash_commands/extractor.rb index ce0a2eba535..a6838cb5e7c 100644 --- a/lib/gitlab/slash_commands/extractor.rb +++ b/lib/gitlab/slash_commands/extractor.rb @@ -7,10 +7,10 @@ module Gitlab # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels]) # ``` class Extractor - attr_reader :command_names + attr_reader :command_definitions - def initialize(command_names) - @command_names = command_names + def initialize(command_definitions) + @command_definitions = command_definitions end # Extracts commands from content and return an array of commands. @@ -26,16 +26,18 @@ module Gitlab # ``` # extractor = Gitlab::SlashCommands::Extractor.new([:open, :assign, :labels]) # msg = %(hello\n/labels ~foo ~"bar baz"\nworld) - # commands = extractor.extract_commands! #=> [['labels', '~foo ~"bar baz"']] + # commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']] # msg #=> "hello\nworld" # ``` - def extract_commands!(content) + def extract_commands(content, opts) return [] unless content + content = content.dup + commands = [] content.delete!("\r") - content.gsub!(commands_regex) do + content.gsub!(commands_regex(opts)) do if $~[:cmd] commands << [$~[:cmd], $~[:args]].reject(&:blank?) '' @@ -44,11 +46,19 @@ module Gitlab end end - commands + [content.strip, commands] end private + def command_names(opts) + command_definitions.flat_map do |command| + next if command.noop? + + command.all_names + end.compact + end + # Builds a regular expression to match known commands. # First match group captures the command name and # second match group captures its arguments. @@ -56,7 +66,9 @@ module Gitlab # It looks something like: # # /^\/(?close|reopen|...)(?:( |$))(?[^\/\n]*)(?:\n|$)/ - def commands_regex + def commands_regex(opts) + names = command_names(opts).map(&:to_s) + @commands_regex ||= %r{ (? # Code blocks: @@ -95,7 +107,7 @@ module Gitlab # Command not in a blockquote, blockcode, or HTML tag: # /close - ^\/(?#{command_names.join('|')})(?:(\ |$))(?[^\/\n]*)(?:\n|$) + ^\/(?#{Regexp.union(names)})(?:$|\ (?[^\/\n]*)$) ) }mx end -- cgit v1.2.1 From 6f0b5800a92e5a0a9b94a36e013baa6361d638d5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 8 Aug 2016 13:37:16 +0200 Subject: Add empty test coverage badge class and specs --- lib/gitlab/badge/coverage.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lib/gitlab/badge/coverage.rb (limited to 'lib') diff --git a/lib/gitlab/badge/coverage.rb b/lib/gitlab/badge/coverage.rb new file mode 100644 index 00000000000..94af3a7ec34 --- /dev/null +++ b/lib/gitlab/badge/coverage.rb @@ -0,0 +1,17 @@ +module Gitlab + module Badge + ## + # Test coverage badge + # + class Coverage + def initialize(project, ref, job = nil) + @project = project + @ref = ref + @job = job + end + + def coverage + end + end + end +end -- cgit v1.2.1 From 9f0b46c05aef9d0352bfaa5e42e34143227de8ff Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 10 Aug 2016 14:12:31 +0200 Subject: Move badges to separate modules and add base class --- lib/gitlab/badge/base.rb | 21 +++++++++++++++++++++ lib/gitlab/badge/build.rb | 30 ------------------------------ lib/gitlab/badge/build/metadata.rb | 2 +- lib/gitlab/badge/build/status.rb | 32 ++++++++++++++++++++++++++++++++ lib/gitlab/badge/build/template.rb | 2 +- lib/gitlab/badge/coverage.rb | 17 ----------------- lib/gitlab/badge/coverage/report.rb | 19 +++++++++++++++++++ 7 files changed, 74 insertions(+), 49 deletions(-) create mode 100644 lib/gitlab/badge/base.rb delete mode 100644 lib/gitlab/badge/build.rb create mode 100644 lib/gitlab/badge/build/status.rb delete mode 100644 lib/gitlab/badge/coverage.rb create mode 100644 lib/gitlab/badge/coverage/report.rb (limited to 'lib') diff --git a/lib/gitlab/badge/base.rb b/lib/gitlab/badge/base.rb new file mode 100644 index 00000000000..229e7b5aa57 --- /dev/null +++ b/lib/gitlab/badge/base.rb @@ -0,0 +1,21 @@ +module Gitlab + module Badge + class Base + def key_text + raise NotImplementedError + end + + def value_text + raise NotImplementedError + end + + def metadata + raise NotImplementedError + end + + def template + raise NotImplementedError + end + end + end +end diff --git a/lib/gitlab/badge/build.rb b/lib/gitlab/badge/build.rb deleted file mode 100644 index 1de721a2269..00000000000 --- a/lib/gitlab/badge/build.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Gitlab - module Badge - ## - # Build badge - # - class Build - delegate :key_text, :value_text, to: :template - - def initialize(project, ref) - @project = project - @ref = ref - @sha = @project.commit(@ref).try(:sha) - end - - def status - @project.pipelines - .where(sha: @sha, ref: @ref) - .status || 'unknown' - end - - def metadata - @metadata ||= Build::Metadata.new(@project, @ref) - end - - def template - @template ||= Build::Template.new(status) - end - end - end -end diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/build/metadata.rb index 553ef8d7b16..fbe10b948c4 100644 --- a/lib/gitlab/badge/build/metadata.rb +++ b/lib/gitlab/badge/build/metadata.rb @@ -1,6 +1,6 @@ module Gitlab module Badge - class Build + module Build ## # Class that describes build badge metadata # diff --git a/lib/gitlab/badge/build/status.rb b/lib/gitlab/badge/build/status.rb new file mode 100644 index 00000000000..a72e284d513 --- /dev/null +++ b/lib/gitlab/badge/build/status.rb @@ -0,0 +1,32 @@ +module Gitlab + module Badge + module Build + ## + # Build status badge + # + class Status < Badge::Base + delegate :key_text, :value_text, to: :template + + def initialize(project, ref) + @project = project + @ref = ref + @sha = @project.commit(@ref).try(:sha) + end + + def status + @project.pipelines + .where(sha: @sha, ref: @ref) + .status || 'unknown' + end + + def metadata + @metadata ||= Build::Metadata.new(@project, @ref) + end + + def template + @template ||= Build::Template.new(status) + end + end + end + end +end diff --git a/lib/gitlab/badge/build/template.rb b/lib/gitlab/badge/build/template.rb index deba3b669b3..779569d0cd7 100644 --- a/lib/gitlab/badge/build/template.rb +++ b/lib/gitlab/badge/build/template.rb @@ -1,6 +1,6 @@ module Gitlab module Badge - class Build + module Build ## # Class that represents a build badge template. # diff --git a/lib/gitlab/badge/coverage.rb b/lib/gitlab/badge/coverage.rb deleted file mode 100644 index 94af3a7ec34..00000000000 --- a/lib/gitlab/badge/coverage.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Gitlab - module Badge - ## - # Test coverage badge - # - class Coverage - def initialize(project, ref, job = nil) - @project = project - @ref = ref - @job = job - end - - def coverage - end - end - end -end diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb new file mode 100644 index 00000000000..e6de15e085f --- /dev/null +++ b/lib/gitlab/badge/coverage/report.rb @@ -0,0 +1,19 @@ +module Gitlab + module Badge + module Coverage + ## + # Test coverage report badge + # + class Report < Badge::Base + def initialize(project, ref, job = nil) + @project = project + @ref = ref + @job = job + end + + def coverage + end + end + end + end +end -- cgit v1.2.1 From f3de46e6b0d4cc61e00c884753a8c9eec66f66c4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Aug 2016 11:16:14 +0200 Subject: Refactor badge template and metadata classes --- lib/gitlab/badge/base.rb | 4 ++-- lib/gitlab/badge/build/metadata.rb | 6 +++--- lib/gitlab/badge/build/status.rb | 11 ++++++++--- lib/gitlab/badge/build/template.rb | 12 ++++++------ 4 files changed, 19 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/badge/base.rb b/lib/gitlab/badge/base.rb index 229e7b5aa57..909fa24fa90 100644 --- a/lib/gitlab/badge/base.rb +++ b/lib/gitlab/badge/base.rb @@ -1,11 +1,11 @@ module Gitlab module Badge class Base - def key_text + def entity raise NotImplementedError end - def value_text + def status raise NotImplementedError end diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/build/metadata.rb index fbe10b948c4..52a10b19298 100644 --- a/lib/gitlab/badge/build/metadata.rb +++ b/lib/gitlab/badge/build/metadata.rb @@ -9,9 +9,9 @@ module Gitlab include ActionView::Helpers::AssetTagHelper include ActionView::Helpers::UrlHelper - def initialize(project, ref) - @project = project - @ref = ref + def initialize(badge) + @project = badge.project + @ref = badge.ref end def to_html diff --git a/lib/gitlab/badge/build/status.rb b/lib/gitlab/badge/build/status.rb index a72e284d513..50aa45e5406 100644 --- a/lib/gitlab/badge/build/status.rb +++ b/lib/gitlab/badge/build/status.rb @@ -5,14 +5,19 @@ module Gitlab # Build status badge # class Status < Badge::Base - delegate :key_text, :value_text, to: :template + attr_reader :project, :ref def initialize(project, ref) @project = project @ref = ref + @sha = @project.commit(@ref).try(:sha) end + def entity + 'build' + end + def status @project.pipelines .where(sha: @sha, ref: @ref) @@ -20,11 +25,11 @@ module Gitlab end def metadata - @metadata ||= Build::Metadata.new(@project, @ref) + @metadata ||= Build::Metadata.new(self) end def template - @template ||= Build::Template.new(status) + @template ||= Build::Template.new(self) end end end diff --git a/lib/gitlab/badge/build/template.rb b/lib/gitlab/badge/build/template.rb index 779569d0cd7..f52589ff736 100644 --- a/lib/gitlab/badge/build/template.rb +++ b/lib/gitlab/badge/build/template.rb @@ -17,16 +17,17 @@ module Gitlab unknown: '#9f9f9f' } - def initialize(status) - @status = status + def initialize(badge) + @entity = badge.entity + @status = badge.status end def key_text - 'build' + @entity.to_s end def value_text - @status + @status.to_s end def key_width @@ -42,8 +43,7 @@ module Gitlab end def value_color - STATUS_COLOR[@status.to_sym] || - STATUS_COLOR[:unknown] + STATUS_COLOR[@status.to_sym] || STATUS_COLOR[:unknown] end def key_text_anchor -- cgit v1.2.1 From f0ff1bfdcc43decd1888f7b8d4a9e8c4dd5540d9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Aug 2016 12:38:03 +0200 Subject: Implement the main class of test coverage badge --- lib/gitlab/badge/coverage/report.rb | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb index e6de15e085f..f06142003e3 100644 --- a/lib/gitlab/badge/coverage/report.rb +++ b/lib/gitlab/badge/coverage/report.rb @@ -5,13 +5,42 @@ module Gitlab # Test coverage report badge # class Report < Badge::Base + attr_reader :project, :ref, :job + def initialize(project, ref, job = nil) @project = project @ref = ref @job = job + + @pipeline = @project.pipelines + .where(ref: @ref) + .where(sha: @project.commit(@ref).try(:sha)) + .first end - def coverage + def entity + 'coverage' + end + + def status + @coverage ||= raw_coverage + return unless @coverage + + @coverage.to_i + end + + private + + def raw_coverage + return unless @pipeline + + if @job.blank? + @pipeline.coverage + else + @pipeline.builds + .find_by(name: @job) + .try(:coverage) + end end end end -- cgit v1.2.1 From 7b840c8483e2fe17a6c04474323cfb57c3c8a7d3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Aug 2016 13:58:57 +0200 Subject: Add coverage report badge metadata class --- lib/gitlab/badge/coverage/metadata.rb | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 lib/gitlab/badge/coverage/metadata.rb (limited to 'lib') diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb new file mode 100644 index 00000000000..ae9e5e84051 --- /dev/null +++ b/lib/gitlab/badge/coverage/metadata.rb @@ -0,0 +1,38 @@ +module Gitlab + module Badge + module Coverage + ## + # Class that describes coverage badge metadata + # + class Metadata + include Gitlab::Application.routes.url_helpers + include ActionView::Helpers::AssetTagHelper + include ActionView::Helpers::UrlHelper + + def initialize(badge) + @project = badge.project + @ref = badge.ref + @job = badge.job + end + + def to_html + link_to(image_tag(image_url, alt: 'coverage report'), link_url) + end + + def to_markdown + "[![coverage report](#{image_url})](#{link_url})" + end + + def image_url + coverage_namespace_project_badges_url(@project.namespace, + @project, @ref, + format: :svg) + end + + def link_url + namespace_project_commits_url(@project.namespace, @project, id: @ref) + end + end + end + end +end -- cgit v1.2.1 From cc244160c5e2ecda2818348cb6b909f1dcb96e44 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Aug 2016 14:08:27 +0200 Subject: Extract the abstract base class of badge metadata --- lib/gitlab/badge/build/metadata.rb | 14 +++----------- lib/gitlab/badge/coverage/metadata.rb | 14 +++----------- lib/gitlab/badge/metadata.rb | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 lib/gitlab/badge/metadata.rb (limited to 'lib') diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/build/metadata.rb index 52a10b19298..f87a7b7942e 100644 --- a/lib/gitlab/badge/build/metadata.rb +++ b/lib/gitlab/badge/build/metadata.rb @@ -4,22 +4,14 @@ module Gitlab ## # Class that describes build badge metadata # - class Metadata - include Gitlab::Application.routes.url_helpers - include ActionView::Helpers::AssetTagHelper - include ActionView::Helpers::UrlHelper - + class Metadata < Badge::Metadata def initialize(badge) @project = badge.project @ref = badge.ref end - def to_html - link_to(image_tag(image_url, alt: 'build status'), link_url) - end - - def to_markdown - "[![build status](#{image_url})](#{link_url})" + def title + 'build status' end def image_url diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb index ae9e5e84051..53588185622 100644 --- a/lib/gitlab/badge/coverage/metadata.rb +++ b/lib/gitlab/badge/coverage/metadata.rb @@ -4,23 +4,15 @@ module Gitlab ## # Class that describes coverage badge metadata # - class Metadata - include Gitlab::Application.routes.url_helpers - include ActionView::Helpers::AssetTagHelper - include ActionView::Helpers::UrlHelper - + class Metadata < Badge::Metadata def initialize(badge) @project = badge.project @ref = badge.ref @job = badge.job end - def to_html - link_to(image_tag(image_url, alt: 'coverage report'), link_url) - end - - def to_markdown - "[![coverage report](#{image_url})](#{link_url})" + def title + 'coverage report' end def image_url diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb new file mode 100644 index 00000000000..548f85b78bb --- /dev/null +++ b/lib/gitlab/badge/metadata.rb @@ -0,0 +1,36 @@ +module Gitlab + module Badge + ## + # Abstract class for badge metadata + # + class Metadata + include Gitlab::Application.routes.url_helpers + include ActionView::Helpers::AssetTagHelper + include ActionView::Helpers::UrlHelper + + def initialize(badge) + @badge = badge + end + + def to_html + link_to(image_tag(image_url, alt: title), link_url) + end + + def to_markdown + "[![#{title}](#{image_url})](#{link_url})" + end + + def title + raise NotImplementedError + end + + def image_url + raise NotImplementedError + end + + def link_url + raise NotImplementedError + end + end + end +end -- cgit v1.2.1 From 796efcc704558119c1e5cb298d45a4f592662cb7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Aug 2016 14:46:47 +0200 Subject: Add template class for coverage report badge --- lib/gitlab/badge/coverage/template.rb | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 lib/gitlab/badge/coverage/template.rb (limited to 'lib') diff --git a/lib/gitlab/badge/coverage/template.rb b/lib/gitlab/badge/coverage/template.rb new file mode 100644 index 00000000000..6f9a38b07dc --- /dev/null +++ b/lib/gitlab/badge/coverage/template.rb @@ -0,0 +1,69 @@ +module Gitlab + module Badge + module Coverage + ## + # Class that represents a coverage badge template. + # + # Template object will be passed to badge.svg.erb template. + # + class Template + STATUS_COLOR = { + good: '#4c1', + acceptable: '#b0c', + medium: '#dfb317', + low: '#e05d44', + unknown: '#9f9f9f' + } + + def initialize(badge) + @entity = badge.entity + @status = badge.status + end + + def key_text + @entity.to_s + end + + def value_text + @status ? "#{@status}%" : 'unknown' + end + + def key_width + 62 + end + + def value_width + @status ? 32 : 58 + end + + def key_color + '#555' + end + + def value_color + case @status + when nil then STATUS_COLOR[:unknown] + when 95..100 then STATUS_COLOR[:good] + when 90..95 then STATUS_COLOR[:acceptable] + when 75..90 then STATUS_COLOR[:medium] + when 0..75 then STATUS_COLOR[:low] + else + STATUS_COLOR[:unknown] + end + end + + def key_text_anchor + key_width / 2 + end + + def value_text_anchor + key_width + (value_width / 2) + end + + def width + key_width + value_width + end + end + end + end +end -- cgit v1.2.1 From dbb9d6a726285bdf59cc789aac584e712ea1280c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Aug 2016 15:01:14 +0200 Subject: Extract base abstract template for badges --- lib/gitlab/badge/build/template.rb | 18 +------------ lib/gitlab/badge/coverage/template.rb | 19 +------------- lib/gitlab/badge/template.rb | 49 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 lib/gitlab/badge/template.rb (limited to 'lib') diff --git a/lib/gitlab/badge/build/template.rb b/lib/gitlab/badge/build/template.rb index f52589ff736..2b95ddfcb53 100644 --- a/lib/gitlab/badge/build/template.rb +++ b/lib/gitlab/badge/build/template.rb @@ -6,7 +6,7 @@ module Gitlab # # Template object will be passed to badge.svg.erb template. # - class Template + class Template < Badge::Template STATUS_COLOR = { success: '#4c1', failed: '#e05d44', @@ -38,25 +38,9 @@ module Gitlab 54 end - def key_color - '#555' - end - def value_color STATUS_COLOR[@status.to_sym] || STATUS_COLOR[:unknown] end - - def key_text_anchor - key_width / 2 - end - - def value_text_anchor - key_width + (value_width / 2) - end - - def width - key_width + value_width - end end end end diff --git a/lib/gitlab/badge/coverage/template.rb b/lib/gitlab/badge/coverage/template.rb index 6f9a38b07dc..49a10d3e8e1 100644 --- a/lib/gitlab/badge/coverage/template.rb +++ b/lib/gitlab/badge/coverage/template.rb @@ -6,7 +6,7 @@ module Gitlab # # Template object will be passed to badge.svg.erb template. # - class Template + class Template < Badge::Template STATUS_COLOR = { good: '#4c1', acceptable: '#b0c', @@ -36,13 +36,8 @@ module Gitlab @status ? 32 : 58 end - def key_color - '#555' - end - def value_color case @status - when nil then STATUS_COLOR[:unknown] when 95..100 then STATUS_COLOR[:good] when 90..95 then STATUS_COLOR[:acceptable] when 75..90 then STATUS_COLOR[:medium] @@ -51,18 +46,6 @@ module Gitlab STATUS_COLOR[:unknown] end end - - def key_text_anchor - key_width / 2 - end - - def value_text_anchor - key_width + (value_width / 2) - end - - def width - key_width + value_width - end end end end diff --git a/lib/gitlab/badge/template.rb b/lib/gitlab/badge/template.rb new file mode 100644 index 00000000000..bfeb0052642 --- /dev/null +++ b/lib/gitlab/badge/template.rb @@ -0,0 +1,49 @@ +module Gitlab + module Badge + ## + # Abstract template class for badges + # + class Template + def initialize(badge) + @entity = badge.entity + @status = badge.status + end + + def key_text + raise NotImplementedError + end + + def value_text + raise NotImplementedError + end + + def key_width + raise NotImplementedError + end + + def value_width + raise NotImplementedError + end + + def value_color + raise NotImplementedError + end + + def key_color + '#555' + end + + def key_text_anchor + key_width / 2 + end + + def value_text_anchor + key_width + (value_width / 2) + end + + def width + key_width + value_width + end + end + end +end -- cgit v1.2.1 From 3e481f154f8e93a54cef8216c70ad5ab2d91f0f1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 11 Aug 2016 15:04:37 +0200 Subject: Add metadata and template methods for coverage badge --- lib/gitlab/badge/coverage/report.rb | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'lib') diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb index f06142003e3..3d56ea3e47a 100644 --- a/lib/gitlab/badge/coverage/report.rb +++ b/lib/gitlab/badge/coverage/report.rb @@ -29,6 +29,14 @@ module Gitlab @coverage.to_i end + def metadata + @metadata ||= Coverage::Metadata.new(self) + end + + def template + @template ||= Coverage::Template.new(self) + end + private def raw_coverage -- cgit v1.2.1 From b13e1d795a06499d0264b1103108057a7862826f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 12 Aug 2016 15:26:55 +0200 Subject: Add small corrections to test coverage report badge --- lib/gitlab/badge/coverage/template.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/badge/coverage/template.rb b/lib/gitlab/badge/coverage/template.rb index 49a10d3e8e1..06e0d084e9f 100644 --- a/lib/gitlab/badge/coverage/template.rb +++ b/lib/gitlab/badge/coverage/template.rb @@ -9,7 +9,7 @@ module Gitlab class Template < Badge::Template STATUS_COLOR = { good: '#4c1', - acceptable: '#b0c', + acceptable: '#a3c51c', medium: '#dfb317', low: '#e05d44', unknown: '#9f9f9f' @@ -33,7 +33,7 @@ module Gitlab end def value_width - @status ? 32 : 58 + @status ? 36 : 58 end def value_color -- cgit v1.2.1 From 722fc84e3d4785fb3a9db5f1c7d2aabad22e8e01 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 28 Jul 2016 19:02:56 -0500 Subject: Complete refactor of the `Spammable` concern and tests: - Merged `AkismetSubmittable` into `Spammable` - Clean up `SpamCheckService` - Added tests for `Spammable` - Added submit (ham or spam) options to `AkismetHelper` --- lib/api/issues.rb | 2 +- lib/gitlab/akismet_helper.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/api/issues.rb b/lib/api/issues.rb index c4d3134da6c..7bbfc137c2c 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -160,7 +160,7 @@ module API issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute - if issue.spam? + if issue.spam_detected? render_api_error!({ error: 'Spam detected' }, 400) end diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb index 207736b59db..19e73820321 100644 --- a/lib/gitlab/akismet_helper.rb +++ b/lib/gitlab/akismet_helper.rb @@ -43,5 +43,39 @@ module Gitlab false end end + + def ham!(details, text, user) + client = akismet_client + + params = { + type: 'comment', + text: text, + author: user.name, + author_email: user.email + } + + begin + client.submit_ham(details.ip_address, details.user_agent, params) + rescue => e + Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") + end + end + + def spam!(details, text, user) + client = akismet_client + + params = { + type: 'comment', + text: text, + author: user.name, + author_email: user.email + } + + begin + client.submit_spam(details.ip_address, details.user_agent, params) + rescue => e + Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") + end + end end end -- cgit v1.2.1 From 64ab2b3d9f10366249c03a6bcf5e8b1d20010d8f Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 29 Jul 2016 23:18:32 -0500 Subject: Refactored spam related code even further - Removed unnecessary column from `SpamLog` - Moved creation of SpamLogs out of its own service and into SpamCheckService - Simplified code in SpamCheckService. - Moved move spam related code into Spammable concern --- lib/api/issues.rb | 2 +- lib/gitlab/akismet_helper.rb | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 7bbfc137c2c..c4d3134da6c 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -160,7 +160,7 @@ module API issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute - if issue.spam_detected? + if issue.spam? render_api_error!({ error: 'Spam detected' }, 400) end diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb index 19e73820321..b74d8176cc7 100644 --- a/lib/gitlab/akismet_helper.rb +++ b/lib/gitlab/akismet_helper.rb @@ -17,10 +17,6 @@ module Gitlab env['HTTP_USER_AGENT'] end - def check_for_spam?(project) - akismet_enabled? && project.public? - end - def is_spam?(environment, user, text) client = akismet_client ip_address = client_ip(environment) @@ -44,7 +40,7 @@ module Gitlab end end - def ham!(details, text, user) + def ham!(ip_address, user_agent, text, user) client = akismet_client params = { @@ -55,7 +51,7 @@ module Gitlab } begin - client.submit_ham(details.ip_address, details.user_agent, params) + client.submit_ham(ip_address, user_agent, params) rescue => e Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") end -- cgit v1.2.1 From abf2dcd25c4a176801314872733ede91297d1ab0 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 1 Aug 2016 12:14:03 -0500 Subject: Allow `SpamLog` to be submitted as ham - Added `submitted_as_ham` to `SpamLog` to mark which logs have been submitted to Akismet. - Added routes and controller action. --- lib/gitlab/akismet_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb index b74d8176cc7..bd71a1aaa51 100644 --- a/lib/gitlab/akismet_helper.rb +++ b/lib/gitlab/akismet_helper.rb @@ -52,8 +52,10 @@ module Gitlab begin client.submit_ham(ip_address, user_agent, params) + true rescue => e Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") + false end end @@ -69,8 +71,10 @@ module Gitlab begin client.submit_spam(details.ip_address, details.user_agent, params) + true rescue => e Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") + false end end end -- cgit v1.2.1 From 43e756d4eafd79f4d2f366b646ebb94af78b5a4c Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 5 Aug 2016 17:10:08 -0500 Subject: Refactored AkismetHelper into AkismetService and cleaned up `Spammable` - Refactored SpamCheckService into SpamService --- lib/api/issues.rb | 2 -- lib/gitlab/akismet_helper.rb | 81 -------------------------------------------- 2 files changed, 83 deletions(-) delete mode 100644 lib/gitlab/akismet_helper.rb (limited to 'lib') diff --git a/lib/api/issues.rb b/lib/api/issues.rb index c4d3134da6c..077258faee1 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -3,8 +3,6 @@ module API class Issues < Grape::API before { authenticate! } - helpers ::Gitlab::AkismetHelper - helpers do def filter_issues_state(issues, state) case state diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb deleted file mode 100644 index bd71a1aaa51..00000000000 --- a/lib/gitlab/akismet_helper.rb +++ /dev/null @@ -1,81 +0,0 @@ -module Gitlab - module AkismetHelper - def akismet_enabled? - current_application_settings.akismet_enabled - end - - def akismet_client - @akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key, - Gitlab.config.gitlab.url) - end - - def client_ip(env) - env['action_dispatch.remote_ip'].to_s - end - - def user_agent(env) - env['HTTP_USER_AGENT'] - end - - def is_spam?(environment, user, text) - client = akismet_client - ip_address = client_ip(environment) - user_agent = user_agent(environment) - - params = { - type: 'comment', - text: text, - created_at: DateTime.now, - author: user.name, - author_email: user.email, - referrer: environment['HTTP_REFERER'], - } - - begin - is_spam, is_blatant = client.check(ip_address, user_agent, params) - is_spam || is_blatant - rescue => e - Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check") - false - end - end - - def ham!(ip_address, user_agent, text, user) - client = akismet_client - - params = { - type: 'comment', - text: text, - author: user.name, - author_email: user.email - } - - begin - client.submit_ham(ip_address, user_agent, params) - true - rescue => e - Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") - false - end - end - - def spam!(details, text, user) - client = akismet_client - - params = { - type: 'comment', - text: text, - author: user.name, - author_email: user.email - } - - begin - client.submit_spam(details.ip_address, details.user_agent, params) - true - rescue => e - Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") - false - end - end - end -end -- cgit v1.2.1 From e805a6470031d942f7de604fdf7acfc7cf4f0b1a Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 16 Aug 2016 10:39:13 +0530 Subject: Backport changes from gitlab-org/gitlab-ee!581 to CE. !581 has a lot of changes that would cause merge conflicts if not properly backported to CE. This commit/MR serves as a better foundation for gitlab-org/gitlab-ee!581. = Changes = 1. Move from `has_one {merge,push}_access_level` to `has_many`, with the `length` of the association limited to `1`. This is _effectively_ a `has_one` association, but should cause less conflicts with EE, which is set to `has_many`. This has a number of related changes in the views, specs, and factories. 2. Make `gon` variable loading more consistent (with EE!581) in the `ProtectedBranchesController`. Also use `::` to prefix the `ProtectedBranches` services, because this is required in EE. 3. Extract a `ProtectedBranchAccess` concern from the two access level models. This concern only has a single `humanize` method here, but will have more methods in EE. 4. Add `form_errors` to the protected branches creation form. This is not strictly required for EE compatibility, but was an oversight nonetheless. --- lib/api/entities.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index ae74d14a4bb..7bce427adf6 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -129,12 +129,12 @@ module API expose :developers_can_push do |repo_branch, options| project = options[:project] - project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.push_access_level.access_level == Gitlab::Access::DEVELOPER } + project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.push_access_levels.first.access_level == Gitlab::Access::DEVELOPER } end expose :developers_can_merge do |repo_branch, options| project = options[:project] - project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.merge_access_level.access_level == Gitlab::Access::DEVELOPER } + project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.merge_access_levels.first.access_level == Gitlab::Access::DEVELOPER } end end -- cgit v1.2.1 From 4ddbbcd11a6f03ae36efd4b9016974c34a1465ed Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 16 Aug 2016 12:00:56 +0530 Subject: Improve EE compatibility with protected branch access levels. 1. Change a few incorrect `access_level` to `access_levels.first` that were missed in e805a64. 2. `API::Entities` can iterate over all access levels instead of just the first one. This makes no difference to CE, and makes it more compatible with EE. --- lib/api/entities.rb | 6 ++++-- lib/gitlab/user_access.rb | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 7bce427adf6..ec455e67329 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -129,12 +129,14 @@ module API expose :developers_can_push do |repo_branch, options| project = options[:project] - project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.push_access_levels.first.access_level == Gitlab::Access::DEVELOPER } + access_levels = project.protected_branches.matching(repo_branch.name).map(&:push_access_levels).flatten + access_levels.any? { |access_level| access_level.access_level == Gitlab::Access::DEVELOPER } end expose :developers_can_merge do |repo_branch, options| project = options[:project] - project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.merge_access_levels.first.access_level == Gitlab::Access::DEVELOPER } + access_levels = project.protected_branches.matching(repo_branch.name).map(&:merge_access_levels).flatten + access_levels.any? { |access_level| access_level.access_level == Gitlab::Access::DEVELOPER } end end diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index c55a7fc4d3d..9858d2e7d83 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -32,7 +32,7 @@ module Gitlab if project.protected_branch?(ref) return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user) - access_levels = project.protected_branches.matching(ref).map(&:push_access_level) + access_levels = project.protected_branches.matching(ref).map(&:push_access_levels).flatten access_levels.any? { |access_level| access_level.check_access(user) } else user.can?(:push_code, project) @@ -43,7 +43,7 @@ module Gitlab return false unless user if project.protected_branch?(ref) - access_levels = project.protected_branches.matching(ref).map(&:merge_access_level) + access_levels = project.protected_branches.matching(ref).map(&:merge_access_levels).flatten access_levels.any? { |access_level| access_level.check_access(user) } else user.can?(:push_code, project) -- cgit v1.2.1 From dd3b738d5b3eb70217d7ac7f9fe441498d2e8e7e Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 16 Aug 2016 13:34:56 +0530 Subject: Fix failing tests relating to backporting ee!581. 1. `GitPushService` was still using `{merge,push}_access_level_attributes` instead of `{merge,push}_access_levels_attributes`. 2. The branches API creates access levels regardless of the state of the `developers_can_{push,merge}` parameters. This is in line with the UI, where Master access is the default for a new protected branch. 3. Use `after(:build)` to create access levels in the `protected_branches` factory, so that `factories_spec` passes. It only builds records, so we need to create access levels on `build` as well. --- lib/api/branches.rb | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/api/branches.rb b/lib/api/branches.rb index a77afe634f6..b615703df93 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -61,22 +61,27 @@ module API name: @branch.name } - unless developers_can_merge.nil? - protected_branch_params.merge!({ - merge_access_level_attributes: { - access_level: developers_can_merge ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER - } - }) + # If `developers_can_merge` is switched off, _all_ `DEVELOPER` + # merge_access_levels need to be deleted. + if developers_can_merge == false + protected_branch.merge_access_levels.where(access_level: Gitlab::Access::DEVELOPER).destroy_all end - unless developers_can_push.nil? - protected_branch_params.merge!({ - push_access_level_attributes: { - access_level: developers_can_push ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER - } - }) + # If `developers_can_push` is switched off, _all_ `DEVELOPER` + # push_access_levels need to be deleted. + if developers_can_push == false + protected_branch.push_access_levels.where(access_level: Gitlab::Access::DEVELOPER).destroy_all end + protected_branch_params.merge!( + merge_access_levels_attributes: [{ + access_level: developers_can_merge ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + }], + push_access_levels_attributes: [{ + access_level: developers_can_push ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + }] + ) + if protected_branch service = ProtectedBranches::UpdateService.new(user_project, current_user, protected_branch_params) service.execute(protected_branch) -- cgit v1.2.1 From c5a7a70d10cc59e940f85ce21bc25e392ab68978 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 10 Aug 2016 19:03:32 -0500 Subject: Allow Git over HTTP access using Personal Access Tokens --- lib/gitlab/auth.rb | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib') diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index db1704af75e..926774837d0 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -14,6 +14,8 @@ module Gitlab result.type = :gitlab_or_ldap elsif result.user = oauth_access_token_check(login, password) result.type = :oauth + elsif result.user = personal_access_token_check(login, password) + result.type = :personal_token end rate_limit!(ip, success: !!result.user || (result.type == :ci), login: login) @@ -82,6 +84,13 @@ module Gitlab token && token.accessible? && User.find_by(id: token.resource_owner_id) end end + + def personal_access_token_check(login, password) + if login && password + user = User.find_by_personal_access_token(password) + user if user && user.username == login + end + end end end end -- cgit v1.2.1 From 5f5d8a8e09bbd2fcdfd02c68145a8c1086fe5e7c Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 15 Aug 2016 15:47:29 -0500 Subject: Moved 2FA check to `auth.rb` and cleaned up the flow `authenticate_user` --- lib/gitlab/auth.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 926774837d0..538e001ec35 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -11,14 +11,20 @@ module Gitlab if valid_ci_request?(login, password, project) result.type = :ci elsif result.user = find_with_user_password(login, password) - result.type = :gitlab_or_ldap + if result.user.two_factor_enabled? + result.user = nil + result.type = :missing_personal_token + else + result.type = :gitlab_or_ldap + end elsif result.user = oauth_access_token_check(login, password) result.type = :oauth elsif result.user = personal_access_token_check(login, password) result.type = :personal_token end - rate_limit!(ip, success: !!result.user || (result.type == :ci), login: login) + success = result.user.present? || [:ci, :missing_personal_token].include?(result.type) + rate_limit!(ip, success: success, login: login) result end -- cgit v1.2.1 From 28726729452ef64270534806e75a9595ea1a659d Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 24 Jun 2016 16:43:46 -0300 Subject: Load issues and merge requests templates from repository --- lib/api/templates.rb | 26 ++++---- lib/gitlab/template/base_template.rb | 71 ++++++++++++++-------- .../template/finders/base_template_finder.rb | 35 +++++++++++ .../template/finders/global_template_finder.rb | 38 ++++++++++++ .../template/finders/repo_template_finder.rb | 59 ++++++++++++++++++ lib/gitlab/template/gitignore.rb | 22 ------- lib/gitlab/template/gitignore_template.rb | 26 ++++++++ lib/gitlab/template/gitlab_ci_yml.rb | 27 -------- lib/gitlab/template/gitlab_ci_yml_template.rb | 31 ++++++++++ lib/gitlab/template/issue_template.rb | 19 ++++++ lib/gitlab/template/merge_request_template.rb | 19 ++++++ 11 files changed, 288 insertions(+), 85 deletions(-) create mode 100644 lib/gitlab/template/finders/base_template_finder.rb create mode 100644 lib/gitlab/template/finders/global_template_finder.rb create mode 100644 lib/gitlab/template/finders/repo_template_finder.rb delete mode 100644 lib/gitlab/template/gitignore.rb create mode 100644 lib/gitlab/template/gitignore_template.rb delete mode 100644 lib/gitlab/template/gitlab_ci_yml.rb create mode 100644 lib/gitlab/template/gitlab_ci_yml_template.rb create mode 100644 lib/gitlab/template/issue_template.rb create mode 100644 lib/gitlab/template/merge_request_template.rb (limited to 'lib') diff --git a/lib/api/templates.rb b/lib/api/templates.rb index 18408797756..b9e718147e1 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -1,21 +1,28 @@ module API class Templates < Grape::API - TEMPLATE_TYPES = { - gitignores: Gitlab::Template::Gitignore, - gitlab_ci_ymls: Gitlab::Template::GitlabCiYml + GLOBAL_TEMPLATE_TYPES = { + gitignores: Gitlab::Template::GitignoreTemplate, + gitlab_ci_ymls: Gitlab::Template::GitlabCiYmlTemplate }.freeze - TEMPLATE_TYPES.each do |template, klass| + helpers do + def render_response(template_type, template) + not_found!(template_type.to_s.singularize) unless template + present template, with: Entities::Template + end + end + + GLOBAL_TEMPLATE_TYPES.each do |template_type, klass| # Get the list of the available template # # Example Request: # GET /gitignores # GET /gitlab_ci_ymls - get template.to_s do + get template_type.to_s do present klass.all, with: Entities::TemplatesList end - # Get the text for a specific template + # Get the text for a specific template present in local filesystem # # Parameters: # name (required) - The name of a template @@ -23,13 +30,10 @@ module API # Example Request: # GET /gitignores/Elixir # GET /gitlab_ci_ymls/Ruby - get "#{template}/:name" do + get "#{template_type}/:name" do required_attributes! [:name] - new_template = klass.find(params[:name]) - not_found!(template.to_s.singularize) unless new_template - - present new_template, with: Entities::Template + render_response(template_type, new_template) end end end diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb index 760ff3e614a..7ebec8e2cff 100644 --- a/lib/gitlab/template/base_template.rb +++ b/lib/gitlab/template/base_template.rb @@ -1,8 +1,9 @@ module Gitlab module Template class BaseTemplate - def initialize(path) + def initialize(path, project = nil) @path = path + @finder = self.class.finder(project) end def name @@ -10,23 +11,32 @@ module Gitlab end def content - File.read(@path) + @finder.read(@path) + end + + def to_json + { name: name, content: content } end class << self - def all - self.categories.keys.flat_map { |cat| by_category(cat) } + def all(project = nil) + if categories.any? + categories.keys.flat_map { |cat| by_category(cat, project) } + else + by_category("", project) + end end - def find(key) - file_name = "#{key}#{self.extension}" - - directory = select_directory(file_name) - directory ? new(File.join(category_directory(directory), file_name)) : nil + def find(key, project = nil) + path = self.finder(project).find(key) + path.present? ? new(path, project) : nil end + # Set categories as sub directories + # Example: { "category_name_1" => "directory_path_1", "category_name_2" => "directory_name_2" } + # Default is no category with all files in base dir of each class def categories - raise NotImplementedError + {} end def extension @@ -37,29 +47,40 @@ module Gitlab raise NotImplementedError end - def by_category(category) - templates_for_directory(category_directory(category)) + # Defines which strategy will be used to get templates files + # RepoTemplateFinder - Finds templates on project repository, templates are filtered perproject + # GlobalTemplateFinder - Finds templates on gitlab installation source, templates can be used in all projects + def finder(project = nil) + raise NotImplementedError end - def category_directory(category) - File.join(base_dir, categories[category]) + def by_category(category, project = nil) + directory = category_directory(category) + files = finder(project).list_files_for(directory) + + files.map { |f| new(f, project) } end - private + def category_directory(category) + return base_dir unless category.present? - def select_directory(file_name) - categories.keys.find do |category| - File.exist?(File.join(category_directory(category), file_name)) - end + File.join(base_dir, categories[category]) end - def templates_for_directory(dir) - dir << '/' unless dir.end_with?('/') - Dir.glob(File.join(dir, "*#{self.extension}")).select { |f| f =~ filter_regex }.map { |f| new(f) } - end + # If template is organized by category it returns { category_name: [{ name: template_name }, { name: template2_name }] } + # If no category is present returns [{ name: template_name }, { name: template2_name}] + def dropdown_names(project = nil) + return [] if project && !project.repository.exists? - def filter_regex - @filter_reges ||= /#{Regexp.escape(extension)}\z/ + if categories.any? + categories.keys.map do |category| + files = self.by_category(category, project) + [category, files.map { |t| { name: t.name } }] + end.to_h + else + files = self.all(project) + files.map { |t| { name: t.name } } + end end end end diff --git a/lib/gitlab/template/finders/base_template_finder.rb b/lib/gitlab/template/finders/base_template_finder.rb new file mode 100644 index 00000000000..473b05257c6 --- /dev/null +++ b/lib/gitlab/template/finders/base_template_finder.rb @@ -0,0 +1,35 @@ +module Gitlab + module Template + module Finders + class BaseTemplateFinder + def initialize(base_dir) + @base_dir = base_dir + end + + def list_files_for + raise NotImplementedError + end + + def read + raise NotImplementedError + end + + def find + raise NotImplementedError + end + + def category_directory(category) + return @base_dir unless category.present? + + @base_dir + @categories[category] + end + + class << self + def filter_regex(extension) + /#{Regexp.escape(extension)}\z/ + end + end + end + end + end +end diff --git a/lib/gitlab/template/finders/global_template_finder.rb b/lib/gitlab/template/finders/global_template_finder.rb new file mode 100644 index 00000000000..831da45191f --- /dev/null +++ b/lib/gitlab/template/finders/global_template_finder.rb @@ -0,0 +1,38 @@ +# Searches and reads file present on Gitlab installation directory +module Gitlab + module Template + module Finders + class GlobalTemplateFinder < BaseTemplateFinder + def initialize(base_dir, extension, categories = {}) + @categories = categories + @extension = extension + super(base_dir) + end + + def read(path) + File.read(path) + end + + def find(key) + file_name = "#{key}#{@extension}" + + directory = select_directory(file_name) + directory ? File.join(category_directory(directory), file_name) : nil + end + + def list_files_for(dir) + dir << '/' unless dir.end_with?('/') + Dir.glob(File.join(dir, "*#{@extension}")).select { |f| f =~ self.class.filter_regex(@extension) } + end + + private + + def select_directory(file_name) + @categories.keys.find do |category| + File.exist?(File.join(category_directory(category), file_name)) + end + end + end + end + end +end diff --git a/lib/gitlab/template/finders/repo_template_finder.rb b/lib/gitlab/template/finders/repo_template_finder.rb new file mode 100644 index 00000000000..22c39436cb2 --- /dev/null +++ b/lib/gitlab/template/finders/repo_template_finder.rb @@ -0,0 +1,59 @@ +# Searches and reads files present on each Gitlab project repository +module Gitlab + module Template + module Finders + class RepoTemplateFinder < BaseTemplateFinder + # Raised when file is not found + class FileNotFoundError < StandardError; end + + def initialize(project, base_dir, extension, categories = {}) + @categories = categories + @extension = extension + @repository = project.repository + @commit = @repository.head_commit if @repository.exists? + + super(base_dir) + end + + def read(path) + blob = @repository.blob_at(@commit.id, path) if @commit + raise FileNotFoundError if blob.nil? + blob.data + end + + def find(key) + file_name = "#{key}#{@extension}" + directory = select_directory(file_name) + raise FileNotFoundError if directory.nil? + + category_directory(directory) + file_name + end + + def list_files_for(dir) + return [] unless @commit + + dir << '/' unless dir.end_with?('/') + + entries = @repository.tree(:head, dir).entries + + names = entries.map(&:name) + names.select { |f| f =~ self.class.filter_regex(@extension) } + end + + private + + def select_directory(file_name) + return [] unless @commit + + # Insert root as directory + directories = ["", @categories.keys] + + directories.find do |category| + path = category_directory(category) + file_name + @repository.blob_at(@commit.id, path) + end + end + end + end + end +end diff --git a/lib/gitlab/template/gitignore.rb b/lib/gitlab/template/gitignore.rb deleted file mode 100644 index 964fbfd4de3..00000000000 --- a/lib/gitlab/template/gitignore.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Gitlab - module Template - class Gitignore < BaseTemplate - class << self - def extension - '.gitignore' - end - - def categories - { - "Languages" => '', - "Global" => 'Global' - } - end - - def base_dir - Rails.root.join('vendor/gitignore') - end - end - end - end -end diff --git a/lib/gitlab/template/gitignore_template.rb b/lib/gitlab/template/gitignore_template.rb new file mode 100644 index 00000000000..8d2a9d2305c --- /dev/null +++ b/lib/gitlab/template/gitignore_template.rb @@ -0,0 +1,26 @@ +module Gitlab + module Template + class GitignoreTemplate < BaseTemplate + class << self + def extension + '.gitignore' + end + + def categories + { + "Languages" => '', + "Global" => 'Global' + } + end + + def base_dir + Rails.root.join('vendor/gitignore') + end + + def finder(project = nil) + Gitlab::Template::Finders::GlobalTemplateFinder.new(self.base_dir, self.extension, self.categories) + end + end + end + end +end diff --git a/lib/gitlab/template/gitlab_ci_yml.rb b/lib/gitlab/template/gitlab_ci_yml.rb deleted file mode 100644 index 7f480fe33c0..00000000000 --- a/lib/gitlab/template/gitlab_ci_yml.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Gitlab - module Template - class GitlabCiYml < BaseTemplate - def content - explanation = "# This file is a template, and might need editing before it works on your project." - [explanation, super].join("\n") - end - - class << self - def extension - '.gitlab-ci.yml' - end - - def categories - { - "General" => '', - "Pages" => 'Pages' - } - end - - def base_dir - Rails.root.join('vendor/gitlab-ci-yml') - end - end - end - end -end diff --git a/lib/gitlab/template/gitlab_ci_yml_template.rb b/lib/gitlab/template/gitlab_ci_yml_template.rb new file mode 100644 index 00000000000..8d1a1ed54c9 --- /dev/null +++ b/lib/gitlab/template/gitlab_ci_yml_template.rb @@ -0,0 +1,31 @@ +module Gitlab + module Template + class GitlabCiYmlTemplate < BaseTemplate + def content + explanation = "# This file is a template, and might need editing before it works on your project." + [explanation, super].join("\n") + end + + class << self + def extension + '.gitlab-ci.yml' + end + + def categories + { + "General" => '', + "Pages" => 'Pages' + } + end + + def base_dir + Rails.root.join('vendor/gitlab-ci-yml') + end + + def finder(project = nil) + Gitlab::Template::Finders::GlobalTemplateFinder.new(self.base_dir, self.extension, self.categories) + end + end + end + end +end diff --git a/lib/gitlab/template/issue_template.rb b/lib/gitlab/template/issue_template.rb new file mode 100644 index 00000000000..c6fa8d3eafc --- /dev/null +++ b/lib/gitlab/template/issue_template.rb @@ -0,0 +1,19 @@ +module Gitlab + module Template + class IssueTemplate < BaseTemplate + class << self + def extension + '.md' + end + + def base_dir + '.gitlab/issue_templates/' + end + + def finder(project) + Gitlab::Template::Finders::RepoTemplateFinder.new(project, self.base_dir, self.extension, self.categories) + end + end + end + end +end diff --git a/lib/gitlab/template/merge_request_template.rb b/lib/gitlab/template/merge_request_template.rb new file mode 100644 index 00000000000..f826c02f3b5 --- /dev/null +++ b/lib/gitlab/template/merge_request_template.rb @@ -0,0 +1,19 @@ +module Gitlab + module Template + class MergeRequestTemplate < BaseTemplate + class << self + def extension + '.md' + end + + def base_dir + '.gitlab/merge_request_templates/' + end + + def finder(project) + Gitlab::Template::Finders::RepoTemplateFinder.new(project, self.base_dir, self.extension, self.categories) + end + end + end + end +end -- cgit v1.2.1 From 029b7d2e9266246feff2f165a10b16be1d7fe88e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sat, 13 Aug 2016 11:58:51 -0500 Subject: Fixed specs and fixes based on failing specs --- lib/gitlab/slash_commands/command_definition.rb | 11 ++--------- lib/gitlab/slash_commands/dsl.rb | 5 +---- lib/gitlab/slash_commands/extractor.rb | 12 +++++++++--- 3 files changed, 12 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/slash_commands/command_definition.rb b/lib/gitlab/slash_commands/command_definition.rb index 5dec6c91869..187c1c9489f 100644 --- a/lib/gitlab/slash_commands/command_definition.rb +++ b/lib/gitlab/slash_commands/command_definition.rb @@ -3,8 +3,8 @@ module Gitlab class CommandDefinition attr_accessor :name, :aliases, :description, :params, :condition_block, :action_block - def valid? - name.present? + def initialize(name) + @name = name end def all_names @@ -22,13 +22,6 @@ module Gitlab context.instance_exec(&condition_block) end - def to_description(opts) - return description unless description.respond_to?(:call) - - context = OpenStruct.new(opts) - context.instance_exec(&description) rescue '' - end - def execute(context, opts, *args) return if noop? || !available?(opts) diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb index 58ba7027f84..7b1a094a7e6 100644 --- a/lib/gitlab/slash_commands/dsl.rb +++ b/lib/gitlab/slash_commands/dsl.rb @@ -73,16 +73,13 @@ module Gitlab def command(*command_names, &block) name, *aliases = command_names - definition = CommandDefinition.new - definition.name = name + definition = CommandDefinition.new(name) definition.aliases = aliases definition.description = @description || '' definition.params = @params || [] definition.condition_block = @condition_block definition.action_block = block - return unless definition.valid? - self.command_definitions << definition definition.all_names.each do |name| diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/slash_commands/extractor.rb index a6838cb5e7c..02c4c8c492e 100644 --- a/lib/gitlab/slash_commands/extractor.rb +++ b/lib/gitlab/slash_commands/extractor.rb @@ -29,8 +29,8 @@ module Gitlab # commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']] # msg #=> "hello\nworld" # ``` - def extract_commands(content, opts) - return [] unless content + def extract_commands(content, opts = {}) + return [content, []] unless content content = content.dup @@ -107,7 +107,13 @@ module Gitlab # Command not in a blockquote, blockcode, or HTML tag: # /close - ^\/(?#{Regexp.union(names)})(?:$|\ (?[^\/\n]*)$) + ^\/ + (?#{Regexp.union(names)}) + (?: + [ ] + (?[^\/\n]*) + )? + (?:\n|$) ) }mx end -- cgit v1.2.1 From d345591fc80e2181acfa71e9eeec99875c523767 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 16 Aug 2016 16:18:48 +0200 Subject: Tracking of custom events GitLab Performance Monitoring is now able to track custom events not directly related to application performance. These events include the number of tags pushed, repositories created, builds registered, etc. The use of these events is to get a better overview of how a GitLab instance is used and how that may affect performance. For example, a large number of Git pushes may have a negative impact on the underlying storage engine. Events are stored in the "events" measurement and are not prefixed with "rails_" or "sidekiq_", this makes it easier to query events with the same name triggered from different parts of the application. All events being stored in the same measurement also makes it easier to downsample data. Currently the following events are tracked: * Creating repositories * Removing repositories * Changing the default branch of a repository * Pushing a new tag * Removing an existing tag * Pushing a commit (along with the branch being pushed to) * Pushing a new branch * Removing an existing branch * Importing a repository (along with the URL we're importing) * Forking a repository (along with the source/target path) * CI builds registered (and when no build could be found) * CI builds being updated * Rails and Sidekiq exceptions Fixes gitlab-org/gitlab-ce#13720 --- lib/ci/api/builds.rb | 8 ++++++++ lib/gitlab/metrics.rb | 9 +++++++++ lib/gitlab/metrics/metric.rb | 9 +++++++-- lib/gitlab/metrics/rack_middleware.rb | 4 ++++ lib/gitlab/metrics/sidekiq_middleware.rb | 4 ++++ lib/gitlab/metrics/transaction.rb | 21 +++++++++++++++++++-- 6 files changed, 51 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 260ac81f5fa..9f3b582a263 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -20,8 +20,13 @@ module Ci build = Ci::RegisterBuildService.new.execute(current_runner) if build + Gitlab::Metrics.add_event(:build_found, + project: build.project.path_with_namespace) + present build, with: Entities::BuildDetails else + Gitlab::Metrics.add_event(:build_not_found) + not_found! end end @@ -42,6 +47,9 @@ module Ci build.update_attributes(trace: params[:trace]) if params[:trace] + Gitlab::Metrics.add_event(:update_build, + project: build.project.path_with_namespace) + case params[:state].to_s when 'success' build.success diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 41fcd971c22..3d1ba33ec68 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -124,6 +124,15 @@ module Gitlab trans.action = action if trans end + # Tracks an event. + # + # See `Gitlab::Metrics::Transaction#add_event` for more details. + def self.add_event(*args) + trans = current_transaction + + trans.add_event(*args) if trans + end + # Returns the prefix to use for the name of a series. def self.series_prefix @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_' diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb index f23d67e1e38..bd0afe53c51 100644 --- a/lib/gitlab/metrics/metric.rb +++ b/lib/gitlab/metrics/metric.rb @@ -4,15 +4,20 @@ module Gitlab class Metric JITTER_RANGE = 0.000001..0.001 - attr_reader :series, :values, :tags + attr_reader :series, :values, :tags, :type # series - The name of the series (as a String) to store the metric in. # values - A Hash containing the values to store. # tags - A Hash containing extra tags to add to the metrics. - def initialize(series, values, tags = {}) + def initialize(series, values, tags = {}, type = :metric) @values = values @series = series @tags = tags + @type = type + end + + def event? + type == :event end # Returns a Hash in a format that can be directly written to InfluxDB. diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb index e61670f491c..b4493bf44d2 100644 --- a/lib/gitlab/metrics/rack_middleware.rb +++ b/lib/gitlab/metrics/rack_middleware.rb @@ -17,6 +17,10 @@ module Gitlab begin retval = trans.run { @app.call(env) } + rescue Exception => error # rubocop: disable Lint/RescueException + trans.add_event(:rails_exception) + + raise error # Even in the event of an error we want to submit any metrics we # might've gathered up to this point. ensure diff --git a/lib/gitlab/metrics/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb index a1240fd33ee..f9dd8e41912 100644 --- a/lib/gitlab/metrics/sidekiq_middleware.rb +++ b/lib/gitlab/metrics/sidekiq_middleware.rb @@ -11,6 +11,10 @@ module Gitlab # Old gitlad-shell messages don't provide enqueued_at/created_at attributes trans.set(:sidekiq_queue_duration, Time.now.to_f - (message['enqueued_at'] || message['created_at'] || 0)) trans.run { yield } + rescue Exception => error # rubocop: disable Lint/RescueException + trans.add_event(:sidekiq_exception) + + raise error ensure trans.finish end diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb index 968f3218950..7bc16181be6 100644 --- a/lib/gitlab/metrics/transaction.rb +++ b/lib/gitlab/metrics/transaction.rb @@ -4,7 +4,10 @@ module Gitlab class Transaction THREAD_KEY = :_gitlab_metrics_transaction - attr_reader :tags, :values, :methods + # The series to store events (e.g. Git pushes) in. + EVENT_SERIES = 'events' + + attr_reader :tags, :values, :method, :metrics attr_accessor :action @@ -55,6 +58,20 @@ module Gitlab @metrics << Metric.new("#{Metrics.series_prefix}#{series}", values, tags) end + # Tracks a business level event + # + # Business level events including events such as Git pushes, Emails being + # sent, etc. + # + # event_name - The name of the event (e.g. "git_push"). + # tags - A set of tags to attach to the event. + def add_event(event_name, tags = {}) + @metrics << Metric.new(EVENT_SERIES, + { count: 1 }, + { event: event_name }.merge(tags), + :event) + end + # Returns a MethodCall object for the given name. def method_call_for(name) unless method = @methods[name] @@ -101,7 +118,7 @@ module Gitlab submit_hashes = submit.map do |metric| hash = metric.to_hash - hash[:tags][:action] ||= @action if @action + hash[:tags][:action] ||= @action if @action && !metric.event? hash end -- cgit v1.2.1 From 88a0c984fc0bdbe0951b02c4e1d4b749dce88a24 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 17 Aug 2016 11:49:13 +0200 Subject: Fixed downtime check label colouring The colours were incorrect: offline was green and online was red, instead of the opposite. --- lib/gitlab/downtime_check/message.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/downtime_check/message.rb b/lib/gitlab/downtime_check/message.rb index 4446e921e0d..fd85f087c03 100644 --- a/lib/gitlab/downtime_check/message.rb +++ b/lib/gitlab/downtime_check/message.rb @@ -3,8 +3,8 @@ module Gitlab class Message attr_reader :path, :offline, :reason - OFFLINE = "\e[32moffline\e[0m" - ONLINE = "\e[31monline\e[0m" + OFFLINE = "\e[31moffline\e[0m" + ONLINE = "\e[32monline\e[0m" # path - The file path of the migration. # offline - When set to `true` the migration will require downtime. -- cgit v1.2.1 From fa0624fc6409d84373f3e06275e936c9e5171b79 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 17 Aug 2016 12:15:20 +0200 Subject: Better formatting for downtime check messages This removes excessive whitespace from the messages (e.g. leading whitespace) and ensures the message is more clearly visible. --- lib/gitlab/downtime_check/message.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/downtime_check/message.rb b/lib/gitlab/downtime_check/message.rb index fd85f087c03..40a4815a9a0 100644 --- a/lib/gitlab/downtime_check/message.rb +++ b/lib/gitlab/downtime_check/message.rb @@ -1,7 +1,7 @@ module Gitlab class DowntimeCheck class Message - attr_reader :path, :offline, :reason + attr_reader :path, :offline OFFLINE = "\e[31moffline\e[0m" ONLINE = "\e[32monline\e[0m" @@ -19,10 +19,21 @@ module Gitlab label = offline ? OFFLINE : ONLINE message = "[#{label}]: #{path}" - message += ": #{reason}" if reason + + if reason? + message += ":\n\n#{reason}\n\n" + end message end + + def reason? + @reason.present? + end + + def reason + @reason.strip.lines.map(&:strip).join("\n") + end end end end -- cgit v1.2.1 From 2f86860a6ded54bb48f03bae1de9a88113c75173 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 17 Aug 2016 17:21:18 -0500 Subject: Refactor `find_for_git_client` method to not use assignment in conditionals and syntax fixes. --- lib/gitlab/auth.rb | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 538e001ec35..e60ce21388e 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -10,17 +10,8 @@ module Gitlab if valid_ci_request?(login, password, project) result.type = :ci - elsif result.user = find_with_user_password(login, password) - if result.user.two_factor_enabled? - result.user = nil - result.type = :missing_personal_token - else - result.type = :gitlab_or_ldap - end - elsif result.user = oauth_access_token_check(login, password) - result.type = :oauth - elsif result.user = personal_access_token_check(login, password) - result.type = :personal_token + else + result.user, result.type = populate_result(login, password) end success = result.user.present? || [:ci, :missing_personal_token].include?(result.type) @@ -87,15 +78,36 @@ module Gitlab def oauth_access_token_check(login, password) if login == "oauth2" && password.present? token = Doorkeeper::AccessToken.by_token(password) - token && token.accessible? && User.find_by(id: token.resource_owner_id) + if token && token.accessible? + user = User.find_by(id: token.resource_owner_id) + return user, :oauth + end end end def personal_access_token_check(login, password) if login && password user = User.find_by_personal_access_token(password) - user if user && user.username == login + validation = User.by_login(login) + return user, :personal_token if user == validation + end + end + + def user_with_password_for_git(login, password) + user = find_with_user_password(login, password) + return user, :gitlab_or_ldap if user + end + + def populate_result(login, password) + user, type = + user_with_password_for_git(login, password) || oauth_access_token_check(login, password) || personal_access_token_check(login, password) + + if user && user.two_factor_enabled? && type == :gitlab_or_ldap + user = nil + type = :missing_personal_token end + + [user, type] end end end -- cgit v1.2.1 From 8b8a4626c601a13683599fd1a127e2c502af38a3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 16 Aug 2016 19:59:55 -0500 Subject: Fix specs and implement fixes based on failing specs --- lib/gitlab/slash_commands/command_definition.rb | 8 +++++++- lib/gitlab/slash_commands/dsl.rb | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/slash_commands/command_definition.rb b/lib/gitlab/slash_commands/command_definition.rb index 187c1c9489f..641c92e77da 100644 --- a/lib/gitlab/slash_commands/command_definition.rb +++ b/lib/gitlab/slash_commands/command_definition.rb @@ -3,8 +3,14 @@ module Gitlab class CommandDefinition attr_accessor :name, :aliases, :description, :params, :condition_block, :action_block - def initialize(name) + def initialize(name, attributes = {}) @name = name + + @aliases = attributes[:aliases] || [] + @description = attributes[:description] || '' + @params = attributes[:params] || [] + @condition_block = attributes[:condition_block] + @action_block = attributes[:action_block] end def all_names diff --git a/lib/gitlab/slash_commands/dsl.rb b/lib/gitlab/slash_commands/dsl.rb index 7b1a094a7e6..50b0937d267 100644 --- a/lib/gitlab/slash_commands/dsl.rb +++ b/lib/gitlab/slash_commands/dsl.rb @@ -17,7 +17,7 @@ module Gitlab # Allows to give a description to the next slash command. # This description is shown in the autocomplete menu. # It accepts a block that will be evaluated with the context given to - # `.command_definitions` or `.command_names`. + # `CommandDefintion#to_h`. # # Example: # @@ -47,7 +47,7 @@ module Gitlab # Allows to define conditions that must be met in order for the command # to be returned by `.command_names` & `.command_definitions`. # It accepts a block that will be evaluated with the context given to - # `.command_definitions`, `.command_names`, and the actual command method. + # `CommandDefintion#to_h`. # # Example: # @@ -73,12 +73,14 @@ module Gitlab def command(*command_names, &block) name, *aliases = command_names - definition = CommandDefinition.new(name) - definition.aliases = aliases - definition.description = @description || '' - definition.params = @params || [] - definition.condition_block = @condition_block - definition.action_block = block + definition = CommandDefinition.new( + name, + aliases: aliases, + description: @description, + params: @params, + condition_block: @condition_block, + action_block: block + ) self.command_definitions << definition -- cgit v1.2.1 From 3e7eeefc939f2ce5234e36684c00b8d1c7e1c7dc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 17 Aug 2016 18:58:44 -0500 Subject: Address feedback --- lib/gitlab/slash_commands/command_definition.rb | 16 ++++++++-------- lib/gitlab/slash_commands/extractor.rb | 17 ++++++++--------- 2 files changed, 16 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/slash_commands/command_definition.rb b/lib/gitlab/slash_commands/command_definition.rb index 641c92e77da..2ff8f4eddf0 100644 --- a/lib/gitlab/slash_commands/command_definition.rb +++ b/lib/gitlab/slash_commands/command_definition.rb @@ -6,11 +6,11 @@ module Gitlab def initialize(name, attributes = {}) @name = name - @aliases = attributes[:aliases] || [] - @description = attributes[:description] || '' - @params = attributes[:params] || [] - @condition_block = attributes[:condition_block] - @action_block = attributes[:action_block] + @aliases = attributes[:aliases] || [] + @description = attributes[:description] || '' + @params = attributes[:params] || [] + @condition_block = attributes[:condition_block] + @action_block = attributes[:action_block] end def all_names @@ -28,13 +28,13 @@ module Gitlab context.instance_exec(&condition_block) end - def execute(context, opts, *args) + def execute(context, opts, args) return if noop? || !available?(opts) block_arity = action_block.arity - return unless block_arity == -1 || block_arity == args.size + return unless (args.present? && block_arity == 1) || (args.blank? && block_arity <= 0) - context.instance_exec(*args, &action_block) + context.instance_exec(args, &action_block) end def to_h(opts) diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/slash_commands/extractor.rb index 02c4c8c492e..c790b825347 100644 --- a/lib/gitlab/slash_commands/extractor.rb +++ b/lib/gitlab/slash_commands/extractor.rb @@ -50,15 +50,6 @@ module Gitlab end private - - def command_names(opts) - command_definitions.flat_map do |command| - next if command.noop? - - command.all_names - end.compact - end - # Builds a regular expression to match known commands. # First match group captures the command name and # second match group captures its arguments. @@ -117,6 +108,14 @@ module Gitlab ) }mx end + + def command_names(opts) + command_definitions.flat_map do |command| + next if command.noop? + + command.all_names + end.compact + end end end end -- cgit v1.2.1 From de7b8e51b8b7b5bfaa72ee62a4eb08497cf69b4b Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 17 Aug 2016 10:09:42 +0200 Subject: Add endpoints for pipelines --- lib/api/api.rb | 1 + lib/api/entities.rb | 8 ++++++ lib/api/pipelines.rb | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 lib/api/pipelines.rb (limited to 'lib') diff --git a/lib/api/api.rb b/lib/api/api.rb index d43af3f24e9..d9261c97908 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -56,6 +56,7 @@ module API mount ::API::Milestones mount ::API::Namespaces mount ::API::Notes + mount ::API::Pipelines mount ::API::ProjectHooks mount ::API::ProjectSnippets mount ::API::Projects diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 055716ab1e3..09c9aca1e22 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -502,6 +502,14 @@ module API expose :key, :value end + class Pipeline < Grape::Entity + expose :status, :ref, :sha, :before_sha, :tag, :yaml_errors + + expose :user, with: Entities::UserBasic + expose :created_at, :updated_at, :started_at, :finished_at, :committed_at + expose :duration + end + class Environment < Grape::Entity expose :id, :name, :external_url end diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb new file mode 100644 index 00000000000..2aae75c471d --- /dev/null +++ b/lib/api/pipelines.rb @@ -0,0 +1,74 @@ +module API + class Pipelines < Grape::API + before { authenticate! } + + params do + requires :id, type: String, desc: 'The project ID' + end + resource :projects do + desc 'Get all Pipelines of the project' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Pipeline + end + params do + optional :page, type: Integer, desc: 'Page number of the current request' + optional :per_page, type: Integer, desc: 'Number of items per page' + end + get ':id/pipelines' do + authorize! :read_pipeline, user_project + + present paginate(user_project.pipelines), with: Entities::Pipeline + end + + desc 'Gets a specific pipeline for the project' do + detail 'This feature was introduced in GitLab 8.11' + success Entities::Pipeline + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + end + get ':id/pipelines/:pipeline_id' do + authorize! :read_pipeline, user_project + + present pipeline, with: Entities::Pipeline + end + + desc 'Retry failed builds in the pipeline' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Pipeline + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + end + post ':id/pipelines/:pipeline_id/retry' do + authorize! :update_pipeline, user_project + + pipeline.retry_failed(current_user) + + present pipeline, with: Entities::Pipeline + end + + desc 'Cancel all builds in the pipeline' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Pipeline + end + params do + requires :pipeline_id, type: Integer, desc: 'The pipeline ID' + end + post ':id/pipelines/:pipeline_id/cancel' do + authorize! :update_pipeline, user_project + + pipeline.cancel_running + + status 200 + present pipeline.reload, with: Entities::Pipeline + end + end + + helpers do + def pipeline + @pipeline ||= user_project.pipelines.find(params[:pipeline_id]) + end + end + end +end -- cgit v1.2.1 From df5661b6f49d8a18db7fae6bda71f9dc733f5279 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 17 Aug 2016 15:23:58 +0200 Subject: Add docs on API for pipelines, plus minor fixes --- lib/api/entities.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 09c9aca1e22..b575af53850 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -503,7 +503,7 @@ module API end class Pipeline < Grape::Entity - expose :status, :ref, :sha, :before_sha, :tag, :yaml_errors + expose :id, :status, :ref, :sha, :before_sha, :tag, :yaml_errors expose :user, with: Entities::UserBasic expose :created_at, :updated_at, :started_at, :finished_at, :committed_at -- cgit v1.2.1 From fffe5c2b577b39be2254525d4320cf396b7ff58b Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 15 Aug 2016 15:58:22 +0200 Subject: Add Play endpoints on Builds --- lib/api/builds.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'lib') diff --git a/lib/api/builds.rb b/lib/api/builds.rb index be5a3484ec8..2bd3b65acdc 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -189,6 +189,29 @@ module API present build, with: Entities::Build, user_can_download_artifacts: can?(current_user, :read_build, user_project) end + + desc 'Trigger a manual build' do + success Entities::Build + detail 'This feature was added in GitLab 8.11' + end + params do + requires :build_id, type: Integer, desc: 'The ID of a Build' + end + post ":id/builds/:build_id/play" do + authorize_read_builds! + + build = get_build!(params[:build_id]) + + if build.playable? + build.play(current_user) + + status 200 + present build, with: Entities::Build, + user_can_download_artifacts: can?(current_user, :read_build, user_project) + else + bad_request!("Unplayable Build") + end + end end helpers do -- cgit v1.2.1 From 47d6f286eb565ae582def5a28af2cb450b2ee479 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 16 Aug 2016 08:45:23 +0200 Subject: Add deployment endpoints --- lib/api/api.rb | 1 + lib/api/deployments.rb | 40 ++++++++++++++++++++++++++++++++++++++++ lib/api/entities.rb | 12 ++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 lib/api/deployments.rb (limited to 'lib') diff --git a/lib/api/api.rb b/lib/api/api.rb index d9261c97908..6b8bfbbdae6 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -43,6 +43,7 @@ module API mount ::API::CommitStatuses mount ::API::Commits mount ::API::DeployKeys + mount ::API::Deployments mount ::API::Environments mount ::API::Files mount ::API::Groups diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb new file mode 100644 index 00000000000..f782bcaf7e9 --- /dev/null +++ b/lib/api/deployments.rb @@ -0,0 +1,40 @@ +module API + # Deployments RESTfull API endpoints + class Deployments < Grape::API + before { authenticate! } + + params do + requires :id, type: String, desc: 'The project ID' + end + resource :projects do + desc 'Get all deployments of the project' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Deployment + end + params do + optional :page, type: Integer, desc: 'Page number of the current request' + optional :per_page, type: Integer, desc: 'Number of items per page' + end + get ':id/deployments' do + authorize! :read_deployment, user_project + + present paginate(user_project.deployments), with: Entities::Deployment + end + + desc 'Gets a specific deployment' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Deployment + end + params do + requires :deployment_id, type: Integer, desc: 'The deployment ID' + end + get ':id/deployments/:deployment_id' do + authorize! :read_deployment, user_project + + deployment = user_project.deployments.find(params[:deployment_id]) + + present deployment, with: Entities::Deployment + end + end + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index b575af53850..9ae68947379 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -514,6 +514,18 @@ module API expose :id, :name, :external_url end + class EnvironmentBasic < Grape::Entity + expose :id, :name, :external_url + end + + class Deployment < Grape::Entity + expose :id, :iid, :ref, :sha, :created_at + expose :project, using: Entities::Project + expose :user, using: Entities::UserBasic + expose :environment, using: Entities::EnvironmentBasic + expose :deployable, using: Entities::Build + end + class RepoLicense < Grape::Entity expose :key, :name, :nickname expose :featured, as: :popular -- cgit v1.2.1 From 39c71a19c468736a94f642173a54bf72b2dc1689 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 16 Aug 2016 08:51:27 +0200 Subject: Expose project for environments --- lib/api/entities.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9ae68947379..a10951b9ea0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -512,6 +512,7 @@ module API class Environment < Grape::Entity expose :id, :name, :external_url + expose :project, using: Entities::Project end class EnvironmentBasic < Grape::Entity -- cgit v1.2.1 From ba01e519e24bf716c138a0b46e371c60de4aa935 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 18 Aug 2016 11:42:37 +0200 Subject: Incorporate feedback --- lib/api/builds.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 2bd3b65acdc..52bdbcae5a8 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -202,15 +202,13 @@ module API build = get_build!(params[:build_id]) - if build.playable? - build.play(current_user) + bad_request!("Unplayable Build") unless build.playable? - status 200 - present build, with: Entities::Build, - user_can_download_artifacts: can?(current_user, :read_build, user_project) - else - bad_request!("Unplayable Build") - end + build.play(current_user) + + status 200 + present build, with: Entities::Build, + user_can_download_artifacts: can?(current_user, :read_build, user_project) end end -- cgit v1.2.1 From 2038e035c73f80292fa1bf9757803d8ab5e6ecab Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 18 Aug 2016 21:10:20 +0200 Subject: Do not expose projects on deployments --- lib/api/entities.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a10951b9ea0..67420772335 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -521,10 +521,9 @@ module API class Deployment < Grape::Entity expose :id, :iid, :ref, :sha, :created_at - expose :project, using: Entities::Project - expose :user, using: Entities::UserBasic - expose :environment, using: Entities::EnvironmentBasic - expose :deployable, using: Entities::Build + expose :user, using: Entities::UserBasic + expose :environment, using: Entities::EnvironmentBasic + expose :deployable, using: Entities::Build end class RepoLicense < Grape::Entity -- cgit v1.2.1 From 2703330a19e813351e9c33241a59d6b7f54741df Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 18 Aug 2016 14:21:52 -0500 Subject: Fix behavior around commands with optional arguments --- lib/gitlab/slash_commands/command_definition.rb | 11 ++++++----- lib/gitlab/slash_commands/extractor.rb | 13 +++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/slash_commands/command_definition.rb b/lib/gitlab/slash_commands/command_definition.rb index 2ff8f4eddf0..60d35be2599 100644 --- a/lib/gitlab/slash_commands/command_definition.rb +++ b/lib/gitlab/slash_commands/command_definition.rb @@ -28,13 +28,14 @@ module Gitlab context.instance_exec(&condition_block) end - def execute(context, opts, args) + def execute(context, opts, arg) return if noop? || !available?(opts) - block_arity = action_block.arity - return unless (args.present? && block_arity == 1) || (args.blank? && block_arity <= 0) - - context.instance_exec(args, &action_block) + if arg.present? + context.instance_exec(arg, &action_block) + elsif action_block.arity == 0 + context.instance_exec(&action_block) + end end def to_h(opts) diff --git a/lib/gitlab/slash_commands/extractor.rb b/lib/gitlab/slash_commands/extractor.rb index c790b825347..a672e5e4855 100644 --- a/lib/gitlab/slash_commands/extractor.rb +++ b/lib/gitlab/slash_commands/extractor.rb @@ -39,7 +39,7 @@ module Gitlab content.delete!("\r") content.gsub!(commands_regex(opts)) do if $~[:cmd] - commands << [$~[:cmd], $~[:args]].reject(&:blank?) + commands << [$~[:cmd], $~[:arg]].reject(&:blank?) '' else $~[0] @@ -50,13 +50,14 @@ module Gitlab end private + # Builds a regular expression to match known commands. # First match group captures the command name and # second match group captures its arguments. # # It looks something like: # - # /^\/(?close|reopen|...)(?:( |$))(?[^\/\n]*)(?:\n|$)/ + # /^\/(?close|reopen|...)(?:( |$))(?[^\/\n]*)(?:\n|$)/ def commands_regex(opts) names = command_names(opts).map(&:to_s) @@ -64,7 +65,7 @@ module Gitlab (? # Code blocks: # ``` - # Anything, including `/cmd args` which are ignored by this filter + # Anything, including `/cmd arg` which are ignored by this filter # ``` ^``` @@ -75,7 +76,7 @@ module Gitlab (? # HTML block: # - # Anything, including `/cmd args` which are ignored by this filter + # Anything, including `/cmd arg` which are ignored by this filter # ^<[^>]+?>\n @@ -86,7 +87,7 @@ module Gitlab (? # Quote block: # >>> - # Anything, including `/cmd args` which are ignored by this filter + # Anything, including `/cmd arg` which are ignored by this filter # >>> ^>>> @@ -102,7 +103,7 @@ module Gitlab (?#{Regexp.union(names)}) (?: [ ] - (?[^\/\n]*) + (?[^\/\n]*) )? (?:\n|$) ) -- cgit v1.2.1 From e2f9c87600e34a415d43c981e0182094b123771f Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 12 Aug 2016 16:16:12 -0500 Subject: Added checks for 2FA to the API `/sessions` endpoint and the Resource Owner Password Credentials flow. --- lib/api/helpers.rb | 4 ++++ lib/api/session.rb | 1 + 2 files changed, 5 insertions(+) (limited to 'lib') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index d0469d6602d..bbd647684a4 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -275,6 +275,10 @@ module API end end + def render_2fa_error! + render_api_error!('401 You have 2FA enabled. Please use a personal access token to access the API', 401) + end + def render_api_error!(message, status) error!({ 'message' => message }, status) end diff --git a/lib/api/session.rb b/lib/api/session.rb index 56c202f1294..b26be3be22e 100644 --- a/lib/api/session.rb +++ b/lib/api/session.rb @@ -14,6 +14,7 @@ module API user = Gitlab::Auth.find_with_user_password(params[:email] || params[:login], params[:password]) return unauthorized! unless user + return render_2fa_error! if user.two_factor_enabled? present user, with: Entities::UserLogin end end -- cgit v1.2.1 From c29780086201b331091be3ba5df0653381cf0c2c Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 17 Aug 2016 11:56:50 -0500 Subject: Removed unnecessary service for user retrieval and improved API error message. --- lib/api/helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index bbd647684a4..3e906f6f929 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -276,7 +276,7 @@ module API end def render_2fa_error! - render_api_error!('401 You have 2FA enabled. Please use a personal access token to access the API', 401) + render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) end def render_api_error!(message, status) -- cgit v1.2.1 From a4137411c62d093a55dc171665dc90325182bb04 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 17 Aug 2016 17:39:20 -0500 Subject: Small refactor and syntax fixes. --- lib/api/helpers.rb | 4 ---- lib/api/session.rb | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 3e906f6f929..d0469d6602d 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -275,10 +275,6 @@ module API end end - def render_2fa_error! - render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) - end - def render_api_error!(message, status) error!({ 'message' => message }, status) end diff --git a/lib/api/session.rb b/lib/api/session.rb index b26be3be22e..55ec66a6d67 100644 --- a/lib/api/session.rb +++ b/lib/api/session.rb @@ -14,7 +14,7 @@ module API user = Gitlab::Auth.find_with_user_password(params[:email] || params[:login], params[:password]) return unauthorized! unless user - return render_2fa_error! if user.two_factor_enabled? + return render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) if user.two_factor_enabled? present user, with: Entities::UserLogin end end -- cgit v1.2.1 From de5f2380293f9c8ccbb9a1c83a309589f42b77b8 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 17 Aug 2016 17:59:25 -0500 Subject: Refactor `find_for_git_client` and its related methods. --- lib/gitlab/auth.rb | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index e60ce21388e..91f0270818a 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -11,7 +11,7 @@ module Gitlab if valid_ci_request?(login, password, project) result.type = :ci else - result.user, result.type = populate_result(login, password) + result = populate_result(login, password) end success = result.user.present? || [:ci, :missing_personal_token].include?(result.type) @@ -75,12 +75,34 @@ module Gitlab end end + def populate_result(login, password) + result = + user_with_password_for_git(login, password) || + oauth_access_token_check(login, password) || + personal_access_token_check(login, password) + + if result + result.type = nil unless result.user + + if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap + result.type = :missing_personal_token + end + end + + result || Result.new + end + + def user_with_password_for_git(login, password) + user = find_with_user_password(login, password) + Result.new(user, :gitlab_or_ldap) if user + end + def oauth_access_token_check(login, password) if login == "oauth2" && password.present? token = Doorkeeper::AccessToken.by_token(password) if token && token.accessible? user = User.find_by(id: token.resource_owner_id) - return user, :oauth + Result.new(user, :oauth) end end end @@ -89,26 +111,9 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - return user, :personal_token if user == validation + Result.new(user, :personal_token) if user == validation end end - - def user_with_password_for_git(login, password) - user = find_with_user_password(login, password) - return user, :gitlab_or_ldap if user - end - - def populate_result(login, password) - user, type = - user_with_password_for_git(login, password) || oauth_access_token_check(login, password) || personal_access_token_check(login, password) - - if user && user.two_factor_enabled? && type == :gitlab_or_ldap - user = nil - type = :missing_personal_token - end - - [user, type] - end end end end -- cgit v1.2.1